着色器材质,着色器编写入门

337 阅读6分钟

本文为自学总结,写的可能有遗漏,有问题可以看 资料来源

是什么

着色器是一种材质,简单可以分为

  • 顶点着色器(Vertex Shader)
     - 用于确定mesh物体的顶点位置

  • 片元着色器(Fragment Shader)
     - 用于确定mesh物体表面的样式

threejs中编写着色器,使用的是glsl语言,使用时需要注意必须要在每一句结尾加上分号,并且变量为浮点数需要明确有小数点

简单写一个着色器材质

const shaderMaterail = new THREE.ShaderMaterail({
    vertexShader: `
        void main() {
            // 在顶点着色器中,会对每一个顶点执行一次主函数
            // threejs底层会在几何体创建时声明顶点的属性,你可以直接调用,例如这里的position
            // gl_xxxx格式的是glsl内置的固定变量,例如gl_position,详细可以百科
            // gl_position必须要一个四维向量,而threejs中position一般是笛卡尔坐标,即三维的
            // gl_position向量的四个分量分别代表x,y,z,w
            // w用于区分点和向量,以及进行投影变换等操作,详细可以百度
            // 这里使用vec4方法创建一个四维向量,最后一个值就填1.0,代表这是一个点
            gl_position = vec4(...position, 1.0)
        }
    `,
    fragmentShader: `
        void main() {
            // 设置颜色为红色,向量的四个分量分别代表r,g,b,a,
            // 1.0等同于255,并且需要使用浮点数(小数)
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0)
        }
    `
})

现在我们可以得到一个红色大板,而且你会发现摄像机怎么改他都没变化,不会有透视效果,这是因为我们设置位置是固定的,threejs内部默认会在渲染时将这个简单的位置添加上各种转换矩阵,使屏幕上的物体看起来有了透视效果,更加逼真

image.png

那么,我们怎么实现这种透视效果呢?
首先要弄明白,threejs内部使用了哪些转换

  1. 模型矩阵(modelMatrix),即模型变换的矩阵,这个矩阵决定了模型的放大/缩小等变换效果
  2. 视图矩阵(viewMatrix),即透视矩阵,这个矩阵关系着相机看向物体的透视效果,例如近大远小
  3. 投影矩阵(projectionMatrix),即将模拟的三维坐标转换为屏幕上显示的二维左边的矩阵,毕竟屏幕没有z轴

这三个矩阵threejs都已经暴露出来,可以直接在定义着色器时使用

vertexShader: `
      void main() {
          gl_position = modelViewMatrix * viewMatrix * projectionMatrix * vec4(...position, 1.0)
      }
`,

ok,现在我们终于实现了threejs中最简单的大板

image.png

认识着色器的变量声明

我们现在知道了threejs会在内部帮我们声明一些变量,那如果我自己想声明变量要怎么做呢?

认识关键字

使用关键字接收顶点的属性,或声明变量等等

顶点着色器 :

  • attribute / in, 声明(接收)各个顶点不同的属性为变量,例如position, WebGL 1.0 中使用 attribute 关键字,在 WebGL 2.0 和 OpenGL 3.0 及以上版本中使用 in 关键字
  • uniform, 声明和接收全局常量,例如模型/视图/投影变换矩阵
  • varying / out, 声明需要从顶点着色器传递给片元着色器的变量,在片元着色器中也需要声明
  • layout, 布局限定符,不熟,只知道用来限定布局的,在 OpenGL 3.3 及以上版本中可以使用

片元着色器 :

  • varying / in, 声明需要从顶点着色器传递给片元着色器的变量,在片元着色器中也需要声明, 接收out传递的变量时,使用in关键字
  • uniform, 声明和接收全局常量,例如模型/视图/投影变换矩阵

声明的变量类型

我们声明变量要设置变量类型,常见的变量类型,除了float/int/string等等基本类型,传统的引用类型,还有

  • 向量
类型含义
vecn包含n个float分量的默认向量
bvecn包含n个bool分量的向量
ivecn包含n个int分量的向量
uvecn包含n个unsigned int分量的向量
dvecn包含n个double分量的向量
  • 矩阵
类型含义
matNN行N列的默认矩阵,例如mat4
matROWxCOLROW行COL列的默认矩阵,例如mat2x3
  • 纹理
类型含义
sampler2D2D采样器,用于对纹理的采样,可以提取纹理中的属性,例如某个坐标的颜色

这种类型可以当成类似js的构造函数使用,例如上面的颜色: vec4(1.0, 0.0, 0.0, 1.0)

例子:

// 顶点着色器
attribute vec3 position;
uniform mat4 projectionMatrix;
// 声明需要传递的变量时一般在变量名前加一个v
// out vec2 vUv; 或
varying vec2 vUv;


// 片元着色器
// 使用varying传递,就用varying接收,out传递,就用in接收
// in vec2 vUv;
varying vec2 vUv;

利用threejs的外部变量传入

例如,我要传入时间,这是一个全全顶点通用的变量,所以我在实例化ShaderMaterail时配置uniform即可,注意需要.value

const shaderMaterail = new THREE.ShaderMaterail({
        vertexShader: `
            // 接收
            uniform float utime
            void main() {
                gl_position = vec4(...position, 1.0)
            }
        `,
        fragmentShader: `
            void main() {
                gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0)
            }
        `,
        uniform: {
            utime: {
                value: 0
            }
        }
    })

有了外部动态变量传入,我们可以实现很多动态效果,能实现是什么效果就任大家想象了,这里不做例子

设置纹理

光是有图形肯定还是不行的,如果我希望和threejs其他的材质一样,使用贴图该怎么办呢,这里以最简单的颜色贴图为例 , 我们尝试将图片贴在大板上

首先,我们要在声明时传入纹理至着色器程序中,并在着色器中接收,除此之外,还要将uv和纹理传入到片元着色器中,最后,使用glsl自带的texture2D函数提取颜色,并赋值给gl_fragColor

const texture = new THREE.TextureLoader().load('@/assets/texture/img.jpg')

const shaderMaterail = new THREE.ShaderMaterail({
        vertexShader: `
            varying vec2 vUv
            void main() {
                vUv = uv
                gl_position = vec4(...position, 1.0)
            }
        `,
        fragmentShader: `
            // uniform是全局的,可以直接在片元着色器中使用
            uniform sampler2D uTexture
            varying vec2 vUv
            
            void main() {
                gl_FragColor = texture2D(uTexture, vUv)
            }
        `,
        uniform: {
            uTexture: {
                value: texture
            }
        }
    })

常见全局设置

  • 设置浮点数精度, 使用precision关键字 例如 precision lowp float,lowp表示低精度(-2 * 10^8 到 2 * 10^8),此外还有中精度(-2 * 10^10 到 2 * 10^10),高精度(-2 * 10^16 到 2 * 10^16),精度会影响图像清晰程度等
  • 设置glsl版本,使用#version关键字, 例如 #version 330 core

注: 一般全局设置写在最前面

例子:

#version 330 core
precision lowp float

attribute vec3 position;
uniform mat4 projectionMatrix;
其它代码...

常见的内置函数

感谢老陈前端,我直接截图

image.png

image.png

image.png

image.png

image.png