THREE shader实现高性能的动画(一)— 重复贴图箭头动画

5,176 阅读4分钟

一、首先了解一下什么是shader

shader:GPU流水线上一些可以高度编程的阶段,是GPU上运行的代码。

二、shader有哪些分类

  1. 顶点着色器vertexShader:定义顶点的位置,大小。每个顶点都会执行一次。
  2. 片元着色器fragmentShader:定义画出来的物体的材质(颜色,反光度等)。每个像素片元都会执行一次代码。

三、用到的shader内置函数

  1. texture2D(sampler2D, coord): texture lookup
  2. fract(x): 获取小数部分

四、实现思路

需求——实现一个围栏形状的箭头动画。

根据轨迹生成bufferGeometry。

(这里没有用THREE内置的一些几何体,而是自己设定bufferGeometry的position,uv关系值,可能没有相关经验的同学看起来有点难懂)

首先我们了解一下webgl绘制一个正方形平面需要多少个顶点?**因为webgl绘制最小单位是三角形,一个正方形需要两个三角形组成所以顶点需要6个(在没有定义indices的情况下,如果定义了只需要4个顶点)**

假设我们绘制的是一个长方形的封闭围栏。 那底部的路径数组应该是一个首尾一致的长度为5的三维坐标点集points。

let positions = []
let uvs = []
for (let i = 0, j = positions.length, t = uvs.length; i < points.length - 1; i++) {
 let vUvyMax = 1
 let left = points[i]
 let right = points[i + 1]
 positions[j++] = left.x
 positions[j++] = left.y
 positions[j++] = 0
 uvs[t++] = 0
 uvs[t++] = 0

 positions[j++] = right.x
 positions[j++] = right.y
 positions[j++] = 0
 uvs[t++] = 1
 uvs[t++] = 0

 positions[j++] = left.x
 positions[j++] = left.y
 positions[j++] = height
 uvs[t++] = 0
 uvs[t++] = vUvyMax

 positions[j++] = left.x
 positions[j++] = left.y
 positions[j++] = height
 uvs[t++] = 0
 uvs[t++] = vUvyMax

 positions[j++] = right.x
 positions[j++] = right.y
 positions[j++] = 0
 uvs[t++] = 1
 uvs[t++] = 0

 positions[j++] = right.x
 positions[j++] = right.y
 positions[j++] = height
 uvs[t++] = 1
 uvs[t++] = vUvyMax
}
let geometry = new THREE.BufferGeometry()
geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2))

这样我们就得到一个包含所有顶点的uv区间在0-1的一个buffer自定义围栏几何体了。

定义材质

  • 首先定义uniforms

    1. 动画需要的time参数。
    2. 为了防止贴图失真的计算出来的贴图重复数量repeatX参数。 length / (aspect * height) // length: 底部路径长度, aspect: 箭头贴图长宽比(可以预加载图片获取到),height: 围栏高度
    3. 箭头贴图map参数new THREE.TextureLoader().load('arrow_url')
    4. 动画的速度speed参数。
  • 定义顶点着色器

    varying vec2 vUv;
    ${THREE.ShaderChunk['fog_pars_vertex']}
    void main() {
      vUv = uv;
      vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
      gl_Position = projectionMatrix * mvPosition;
      ${THREE.ShaderChunk['fog_vertex']}
    }

不需要特殊处理,只需要保存一下uv,传递到fragmentShader中,转换一下position就好,此处额外添加了一个对于雾化的支持

  • 定义片元着色器 正常的贴图方式。
 vec4 fragColor = texture2D(map, vUv);

不过我们需要重复的保持宽高比例的贴图

我们先看这行代码的实现原理 => 我们以贴图中心点为例子,贴图中心点相当于整张贴图的坐标按照百分比的形式描述就是(0.5,0.5),系统就会将该像素绘制于uv值为(0.5, 0.5)处。所以按照上面那行代码会把贴图变形的拉伸贴在物体上。

所以我们给uv.x * repeatX。 这样相当于x方向的uv范围改成了 0-repeatX

 vec4 fragColor = texture2D(map, vec2(vUv.x * repeatX, vUv.y));

这是我们就在几何体上面绘制了一个没有形变的贴图,但是vUv.x为2-repeatX的范围内都是空白的,因为贴图上并没有这个范围内的像素。

然后就是最黑科技的一步了。我们只取新uv的小数点部分,相当于uv.x对1取余。

 vec4 fragColor = texture2D(map, vec2(fract(vUv.x * repeatX), vUv.y));
 vec4 fragColor = texture2D(map, vec2(mod(vUv.x * repeatX, 1.), vUv.y));

上面两句代码其实作用一样,这样我们就获取到了一个箭头平铺的贴图。 最后一步,我们需要让这个箭头动起来,还记得我们的time参数么。

vec4 fragColor = texture2D(map, vec2(fract(vUv.x * repeatX + time), vUv.y));

我们只要每帧去递增time的值就可以让我们的箭头动起来了。 仔细一想好像动画控制不够自由。 我们只要给这个time*上一个动画的速度就可以了。

vec4 fragColor = texture2D(map, vec2(fract(vUv.x * repeatX + time*speed), vUv.y));

想动的快动的快,想叛逆反过来动就反过来动,何等的潇洒

创建几何体

let wall = new THREE.Mesh(geometry, material)

多么简单,一个高性能的箭头围墙动画就做好了,贴图还是可以自己定义的,想改成什么改成什么

个人一个字一个字辛苦的码出来的,望转载请注明出处,谢谢!