【译】WebGL With Three.js: Shaders

452 阅读4分钟

学习链接

WebGL With Three.js: Shaders

关于Shaders

Shaders是用GLSL(Graphics Layer Scripting Language - 着色器语言)写的,执行Shader的是GPU,我们可以把一部分CPU的工作交给GPU来提升性能

Shaders有两种,一个是vertex shanders(顶点着色器),一个是fragment shaders(片元着色器)

  • 顶点着色器是用于修改物体的结构的(移动顶点)
  • 片元着色器是用来为每一个像素点进行着色的!

1. Vertex Shader顶点着色器

写一个最简单,这个顶点着色器用于修改几何中的矢量位置,用于移动平面

<script id="cubeVertexShader" type="x-shader/x-vertex">

    uniform float time;
    varying vec2 vUv;
 
    void main() {
        vUv = uv;
        vec3 newPosition = position + normal * vec3(sin(time * 0.2) * 3.0);
        gl_Position = projectionMatrix * modelViewMatrix  * vec4(newPosition, 1.0);
    }
    
</script>

这样的script标签,浏览器是不能识别的,所以不会执行,(我们通过threejs来执行它)。。

第一行unifrom float time,uniform变量可以在顶点着色器和片元着色器中共同使用(不可修改),这个time变量在运行时,以毫秒为单位

第二行varying vec2 vUv,varying变量是顶点着色器和片元着色器的接口,这里的vUv保存的是UV映射,存储每个顶点的位置关系,我们可以使用vUv在片元着色器中

这里有void main()函数所有的着色器都必须有这个函数,在函数中可以把uv映射进行转换(转换顶点位置),最后,我们使用gl_Position来实际改变每个顶点的位置!这个改变位置的通用公式是projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);,使用乘积,如果没有这一步,GPU将不会识别我们对UV进行的操作(不会修改UV位置)

2. Fragment Shader片元着色器

画面的效果呈现,主要靠的就是片元着色器,这里写一个简单的例子。

<script id="cubeFragmentShader" type="x-shader/x-fragment">
        uniform float time;
        varying vec2 vUv;
 
        void main() {
            vec2 position = -1.0 + 2.0 * vUv;
 
            float red = abs(sin(position.x * position.y + time / 5.0));
            float green = abs(sin(position.x * position.y + time / 4.0));
            float blue = abs(sin(position.x * position.y + time / 3.0 ));
            gl_FragColor = vec4(red, green, blue, 1.0);
        }
</script>

这里我们又有两个变量 - uniform float time; varying vec2 vUv; 需要记住的是,所有要用到变量,都要写在每个着色器中(片元和顶点)

在main函数中,我们通过uv和时间来计算颜色(片元着色器是操作片元的,片元由顶点组成,所以varying变量是片元着色器的插值 - 这里没看懂😂。。。),这里要注意的一点是,颜色值必须是正的!

最后,我们用gl_FragColor来设置片元的颜色!

这里我们只是设置好了顶点着色器和片元着色器,还需要在Threejs中进行设置(结合ShaderMaterial材质!)

3. THREE.ShaderMaterial

ShaderMaterial可以使用shaders,首先定义一个uniforms变量(只是个对象结构的变量),这个texture没出来,注释掉了😅。。。

var uniforms = {
  time: { type: "f", value: 0 },
  resolution: { type: "v2", value: new THREE.Vector2() },
  // texture: { type: "t", value: new THREE.TextureLoader().load('https://ts1.cn.mm.bing.net/th/id/R-C.c5d772339679965c1ee67cf4c409e1bc?rik=%2fUQK1YwX6fX%2fMQ&pid=ImgRaw&r=0')}
};

定义ShaderMaterial材质!使用了uniforms变量,还有两个定义的着色器!!!

  • 为什么要定义这个uniforms变量?
  • 用于修改uv(顶点位置的!),这里定义了time,修改time也是同样效果(我们在着色器中定义的uv就用到了time!)
const material2 = new THREE.ShaderMaterial({
  uniforms: uniforms,
  vertexShader: document.getElementById("cubeVertexShader").innerHTML,
  fragmentShader: document.getElementById("cubeFragmentShader").innerHTML
});

写到这,它的颜色还是没有改变,要动态改变它的颜色,需要在render函数中,更改变量,我们可以修改time变量,在shaders中,每一秒每个片元都是在绘制的!

uniforms.time.value += delta * 10;

现在我们的shader就可以应用了!嗯,,这里闪的太快了。。。。

关于性能

如果我们要创建像这样的纹理效果,比如用Canvas,这会占用大量的CPU资源,但是所有的shaders都是用GPU来执行的,分离图形和非图形的计算方式是提示app性能的关键

其他学习资源

总结

Shader入门的一篇。总结一下。

  • 顶点着色器和片元着色器需要保持变量统一,uniform变量和varying变量
  • 顶点着色器和片元着色器的都需要main函数
  • 顶点着色器的实际修改是gl_Position,片元着色器的实际修改是gl_FragColor
  • 浏览器不执行着色器语言,需要借助Threejs
  • Threejs使用ShaderMaterial来使用着色器,可以通过变量间接修改着色器内容!
  • 着色器是通过GPU渲染的,不会占用CPU资源

诗人塞缪尔·厄尔曼在《青春》中这样写道:“人人心中皆有一台天线,只要还能接受美好、希望、欢乐、勇气和力量的信号,就能青春永驻,风华常存。”