纯shader实现移动的箭头(three.js实战15)
Posted 点燃火柴
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了纯shader实现移动的箭头(three.js实战15)相关的知识,希望对你有一定的参考价值。
纯shader实现移动的箭头
1. demo效果
2. 实现要点
2.1 绘制箭头
首先绘制矩形,然后将两个矩形合并生成一个箭头,然后循环生成五个箭头作为底色,单独绘制一个白色箭头作为当前高亮元素,使白色箭头不断切换到底色中的位置,模拟流动效果
float band(float t,float start,float end,float blur){
float step1 = smoothstep(start-blur,start+blur,t);
float step2 = 1.- smoothstep(end-blur,end+blur,t);
return step1*step2;
}
//绘制矩形
float rect(vec2 uv,float left,float right,float bottom,float top,float blur){
float band1 = band(uv.x,left,right,blur);
float band2 = band(uv.y,bottom,top,blur);
return band1*band2;
}
//绘制单个箭头
vec3 oneArrow(vec2 st,vec3 lineColor,float width,float blur){
vec3 color = vec3(0.0);
vec2 uv = st;
float diff = 0.01;
st = rotate2d( -PI/4.0) * st;
st.y += sin(PI/4.0)*width + diff;
float pct = rect(st,-0.3,0.0,-width,width,blur);
color += lineColor*pct;
uv = rotate2d( PI/4.0) * uv;
uv.y -= sin(PI/4.0)*width + diff;
pct = rect(uv,-0.3,0.0,-width,width,blur);
color = max(color,lineColor*pct);
return color;
}
//绘制底色箭头5个
vec3 drawArrows(vec2 st){
vec3 color = vec3(0.0);
vec3 lineColor = vec3(0.17,0.97,1.0);
st.x += 0.4;
for(float i = 0.0;i < 5.0;i++){
st.x -= 0.2;
color += oneArrow(st,lineColor,0.04,0.004);
}
return color;
}
//绘制白色流动箭头
vec3 drawLightArrows(vec2 st,float speed){
vec3 color = vec3(0.0);
vec3 lineColor = vec3(1.0);
st.x += 0.2;
// 依据时间取模5,5个数据一个循环
float flag = floor(mod(u_time*speed,5.0));
//坐标右移[1~5]个单位
st.x -= flag*0.2;
color += oneArrow(st,lineColor,0.04,0.009);
return color;
}
2.2 绘制不同方向流动箭头
已经制作了向右流动的箭头,使用within函数将屏幕坐标分为四块,每一块重新映射为-1到1,在使用旋转函数分别绘制其他三个方向的流动箭头即可
//绘制不同方向流动箭头:使用type匹配,默认-向右;2.0-向上;3.0-向下;4.0-向左
vec3 flowingArrows(vec2 st,float type,float speed){
if(type==2.0){
st = rotate2d( PI/2.0) * st;
}
if(type==3.0){
st = rotate2d( -PI/2.0) * st;
}
if(type==4.0){
st = rotate2d( PI) * st;
}
vec3 color = vec3(0.0);
color += drawArrows(st);
color = max(color,drawLightArrows(st,speed));
return color;
}
// 将a-b范围的t重新映射到c-d范围
float remap(float a, float b, float c, float d, float t)
{
return sat(((t-a)/(b-a))*(d-c) + c);
}
//将leftBottom和rightTop框定的范围重新映射到-1,1
vec2 within(vec2 uv,vec2 leftBottom,vec2 rightTop){
uv.x = remap(leftBottom.x, rightTop.x, -1.0, 1.0, uv.x);
uv.y = remap(leftBottom.y, rightTop.y, -1.0, 1.0, uv.y);
return uv;
}
void main( void ) {
//转换为窗口坐标[0,1],坐标原点在屏幕左下角
//vec2 st = gl_FragCoord.xy/u_resolution.y;
//窗口坐标调整为[-1,1],坐标原点在屏幕中心
vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;
vec2 uv = st;
vec3 color = vec3(1.0);
uv = within(st,vec2(-1.0,-1.0),vec2(0.0,0.0));
color *=flowingArrows(uv,4.0,3.0);
uv = within(st,vec2(-1.0,-0.0),vec2(0.0,1.0));
color = max(color,flowingArrows(uv,2.0,1.0));
uv = within(st,vec2(-0.0,-0.0),vec2(1.0,1.0));
color = max(color,flowingArrows(uv,3.0,5.0));
uv = within(st,vec2(0.0,-1.0),vec2(1.0,0.0));
color = max(color,flowingArrows(uv,1.0,8.0));
gl_FragColor = vec4(color,1.0);
}
3. demo代码
<body>
<div id="container"></div>
<script type="text/javascript" src="../three/build/three.js"></script>
<script>
var container;
var camera, scene, planeMesh, renderer;
var clock = new THREE.Clock(); // 创建THREE.Clock对象
var uniforms = {
u_resolution: {
type: "v2",
value: new THREE.Vector2()
},
radius: {
type: "f",
value: 0.5
},
u_time: {
type: "f",
value: 0.5
}
};
var vertexShader = `
attribute vec3 position;
void main() {
gl_Position = vec4( position, 1.0 );
}
`
var fragmentShader = `
#define PI 3.1415926535897932384626433832795
#define sat(x) clamp(x, -1., 1.)
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform float u_time;
//二维旋转矩阵
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),sin(_angle),cos(_angle));
}
float band(float t,float start,float end,float blur){
float step1 = smoothstep(start-blur,start+blur,t);
float step2 = 1.- smoothstep(end-blur,end+blur,t);
return step1*step2;
}
//绘制矩形
float rect(vec2 uv,float left,float right,float bottom,float top,float blur){
float band1 = band(uv.x,left,right,blur);
float band2 = band(uv.y,bottom,top,blur);
return band1*band2;
}
//绘制单个箭头
vec3 oneArrow(vec2 st,vec3 lineColor,float width,float blur){
vec3 color = vec3(0.0);
vec2 uv = st;
float diff = 0.01;
st = rotate2d( -PI/4.0) * st;
st.y += sin(PI/4.0)*width + diff;
float pct = rect(st,-0.3,0.0,-width,width,blur);
color += lineColor*pct;
uv = rotate2d( PI/4.0) * uv;
uv.y -= sin(PI/4.0)*width + diff;
pct = rect(uv,-0.3,0.0,-width,width,blur);
color = max(color,lineColor*pct);
return color;
}
//绘制底色箭头5个
vec3 drawArrows(vec2 st){
vec3 color = vec3(0.0);
vec3 lineColor = vec3(0.17,0.97,1.0);
st.x += 0.4;
for(float i = 0.0;i < 5.0;i++){
st.x -= 0.2;
color += oneArrow(st,lineColor,0.04,0.004);
}
return color;
}
//绘制白色流动箭头
vec3 drawLightArrows(vec2 st,float speed){
vec3 color = vec3(0.0);
vec3 lineColor = vec3(1.0);
st.x += 0.2;
// 依据时间取模5,5个数据一个循环
float flag = floor(mod(u_time*speed,5.0));
//坐标右移[1~5]个单位
st.x -= flag*0.2;
color += oneArrow(st,lineColor,0.04,0.009);
return color;
}
//绘制不同方向流动箭头:使用type匹配,默认-向右;2.0-向上;3.0-向下;4.0-向左
vec3 flowingArrows(vec2 st,float type,float speed){
if(type==2.0){
st = rotate2d( PI/2.0) * st;
}
if(type==3.0){
st = rotate2d( -PI/2.0) * st;
}
if(type==4.0){
st = rotate2d( PI) * st;
}
vec3 color = vec3(0.0);
color += drawArrows(st);
color = max(color,drawLightArrows(st,speed));
return color;
}
// 将a-b范围的t重新映射到c-d范围
float remap(float a, float b, float c, float d, float t)
{
return sat(((t-a)/(b-a))*(d-c) + c);
}
//将leftBottom和rightTop框定的范围重新映射到-1,1
vec2 within(vec2 uv,vec2 leftBottom,vec2 rightTop){
uv.x = remap(leftBottom.x, rightTop.x, -1.0, 1.0, uv.x);
uv.y = remap(leftBottom.y, rightTop.y, -1.0, 1.0, uv.y);
return uv;
}
void main( void ) {
//转换为窗口坐标[0,1],坐标原点在屏幕左下角
//vec2 st = gl_FragCoord.xy/u_resolution.y;
//窗口坐标调整为[-1,1],坐标原点在屏幕中心
vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;
vec2 uv = st;
vec3 color = vec3(1.0);
uv = within(st,vec2(-1.0,-1.0),vec2(0.0,0.0));
color *=flowingArrows(uv,4.0,3.0);
uv = within(st,vec2(-1.0,-0.0),vec2(0.0,1.0));
color = max(color,flowingArrows(uv,2.0,1.0));
uv = within(st,vec2(-0.0,-0.0),vec2(1.0,1.0));
color = max(color,flowingArrows(uv,3.0,5.0));
uv = within(st,vec2(0.0,-1.0),vec2(1.0,0.0));
color = max(color,flowingArrows(uv,1.0,8.0));
gl_FragColor = vec4(color,1.0);
}
`
init();
animate();
function init() {
container = document.getElementById('container');
camera = new THREE.Camera();
scene = new THREE.Scene();
var geometry = new THREE.PlaneBufferGeometry(2, 2);
var material = new THREE.RawShaderMaterial({
uniforms: uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader
});
planeMesh = new THREE.Mesh(geometry, material);
scene.add(planeMesh);
renderer = new THREE.WebGLRenderer();
renderer.setSize(1000, 800); //设置窗口大小800px*800px
container.appendChild(renderer.domElement);
uniforms.u_resolution.value.x = renderer.domElement.width;
uniforms.u_resolution.value.y = renderer.domElement.height;
}
function animate() {
requestAnimationFrame(animate);
const elapsed = clock.getElapsedTime();
planeMesh.material.uniforms.u_time.value = clock.getElapsedTime();
renderer.render(scene, camera);
}
</script>
</body>
以上是关于纯shader实现移动的箭头(three.js实战15)的主要内容,如果未能解决你的问题,请参考以下文章
纯shader实现动态警告可视化组件(three.js实战16)
既有方向又会动的线,包你学会制作按箭头方向流动的线(three.js实战3)