创建材质时使用自定义shader

threejs中使用shader比较繁琐。
首先需要创建一个RawShaderMaterial

const material=new THREE.RawShaderMaterial(
    {
        uniforms: THREE.UniformsUtils.merge([
            THREE.UniformsLib.common,
            {
                color: { value: new THREE.Color(0xff0000) },
            }
        ]),
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
    }

依次解释:

首先我们看vertexShader和fragmentShader。

这两就是我们主要写shader的地方,可以直接通过字符串的方式传给vertexShader,但是那样shader文件就和js脚本混在一起了,所以我希望通过引用的方式使用shader。

uniforms是传入shader的参数,因为需要使用例如color,Obj2WorldMatrix,等参数,我们需要使用uniforms传参。这个类似unity shaderlab的vertexPos:postion

创建vertex和fragment文件

为了方便编写shader,把vertexshader和fragmentshader分别放到一个文件夹中,并且我们需要创建的是.js格式文件而不是.glsl,这是因为需要把写好的shader文件作为一个const常量导出,这样可以在其他js脚本中直接import。

vertexShader.js

const vertexShader = /*glsl*/ `
    attribute vec3 position;

   
    uniform mat4 projectionMatrix;
    uniform mat4 modelViewMatrix;
    void main() {
        gl_Position =  projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;

export default vertexShader;

fragmentshader和vertexShader同理。

代码高亮

因为创建的是.js文件而不是.glsl文件,所以只会有js的代码高亮,为了引用glsl的代码高亮,我们需要使用Comment target templates插件帮助我们代码高亮。

vertexShader的=后面 /*glsl*/就是标明后面的语句是glsl语句,就可以正确显示代码高亮了。

参数

attribute变量

首先是在vertexShader里会用到的各种变量,例如 position,normal,uv这些变量,在threejs中是可以自动帮我们传给shader的,但是名称必须要一致。

attribute变量是传给vertexShader的,只能在vertexShader中使用。

在Three.js中,如果你创建一个Geometry或BufferGeometry,并将其添加到Mesh对象中,那么Three.js会自动将这些几何体属性绑定到相应的着色器变量中。

具体来说,当你在vertexShader中声明了一个attribute变量,比如在vertexShader代码中的attribute vec3 position;,如果你使用BufferGeometry对象来存储顶点属性数据并将其添加到Mesh中,Three.js会自动将BufferGeometry对象中的’position’属性绑定到着色器中的attribute变量’position’上。

我们随意创建一个Mesh然后console.log出来看一下。

const cube=new THREE.Mesh(new THREE.BoxGeometry(1,1,1),new THREE.MeshBasicMaterial({color:0xff0000}));
scene.add(cube);
console.log(cube);

image.png
可以看到这里mesh下的geometry属性下有attribute属性,Threejs就会自动把这些attribute属性赋值给shader里定义的同名attribute变量

uniforms变量

这个是对于vertexShader和fragmentShader的全局变量,但是需要手动定义。

threejs也提供一些内置的uniform变量。

  • modelViewMatrix:用于将顶点从模型空间转换到摄像机空间。
  • projectionMatrix:用于将摄像机空间的顶点转换为裁剪空间中的坐标。

具体内置的变量在官方文档: https://threejs.org/docs/index.html#api/zh/renderers/webgl/WebGLProgram

这些内置变量在js的uniforms中不用显式的写出,threejs会自动给shader赋值。

varying变量

这个变量不是写在js里的,是写在shader中的。这个类似unity中的v2f,是vertexShader向fragmentShader传参使用的。需要在两个shader中都定义相同的名称。
例如:

// vertexShader.js
varying vec3 vWorldPosition;
varying vec3 vWorldNormal;
varying vec3 vNormal;
// fragmentShader.js
varying vec3 vWorldPosition;
varying vec3 vWorldNormal;
varying vec3 vNormal;

内置uniforms

threejs提供了内置打包好了的uniform变量,比如灯光,雾效等。

详细参考:THREE.UniformsLib源码

例如引用灯光:

material=new THREE.RawShaderMaterial(
            {
                uniforms: THREE.UniformsUtils.merge([
                    THREE.UniformsLib.common,
                    THREE.UniformsLib.lights,
                    {
                        color: { value: new THREE.Color(0xff0000) },
                        diffuse: { value: 0.5 },
                        specular: { value: 0.5 },
                        glossiness: { value: 100 },
                    }
                ]),
                vertexShader: vertexShader,
                fragmentShader: fragmentShader,
                // 一定要记得开lights:true否则无法接受光
                lights: true,
            }
            
// fragmentShader
    struct DirectionalLight {
        vec3 direction;
        vec3 color;
     };
    // 其中NUM_DIR_LIGHTS是threejs定义好的常量
    uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];

使用GUI调参

对于一些shader参数我们希望在运行的时候动态修改,所以需要使用GUI动态修改。

// 使用GUI库
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
// 初始化参数控制器
const indexControl = new function() {
    this.color = 0xffffff;
}
//
// 初始化GUI
const gui = new GUI();

const modelControllerFloder = gui.addFolder('ModelController');

//添加color到GUI同时在值变动的时候修改shader里的color参数
modelControllerFloder.addColor(indexControl, 'color').onChange(function(value){
    indexControl.color = value;
    material.uniforms.color.value=new THREE.Color(indexControl.color);
});

参考

Three.js着色器——矩阵变换: http://www.yanhuangxueyuan.com/Three.js_course/advanced/shader2.html

自定义shader:https://csantosbh.wordpress.com/2014/01/09/custom-shaders-with-three-js-uniforms-textures-and-lighting/

Q.E.D.


寄蜉蝣于天地,渺沧海之一粟