本文为自学总结,写的可能有遗漏,有问题可以看 资料来源
是什么
着色器是一种材质,简单可以分为
-
顶点着色器(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内部默认会在渲染时将这个简单的位置添加上各种转换矩阵,使屏幕上的物体看起来有了透视效果,更加逼真
那么,我们怎么实现这种透视效果呢?
首先要弄明白,threejs内部使用了哪些转换
- 模型矩阵(modelMatrix),即模型变换的矩阵,这个矩阵决定了模型的放大/缩小等变换效果
- 视图矩阵(viewMatrix),即透视矩阵,这个矩阵关系着相机看向物体的透视效果,例如近大远小
- 投影矩阵(projectionMatrix),即将模拟的三维坐标转换为屏幕上显示的二维左边的矩阵,毕竟屏幕没有z轴
这三个矩阵threejs都已经暴露出来,可以直接在定义着色器时使用
vertexShader: `
void main() {
gl_position = modelViewMatrix * viewMatrix * projectionMatrix * vec4(...position, 1.0)
}
`,
ok,现在我们终于实现了threejs中最简单的大板
认识着色器的变量声明
我们现在知道了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分量的向量 |
- 矩阵
| 类型 | 含义 |
|---|---|
matN | N行N列的默认矩阵,例如mat4 |
matROWxCOL | ROW行COL列的默认矩阵,例如mat2x3 |
- 纹理
| 类型 | 含义 |
|---|---|
sampler2D | 2D采样器,用于对纹理的采样,可以提取纹理中的属性,例如某个坐标的颜色 |
这种类型可以当成类似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;
其它代码...
常见的内置函数
感谢老陈前端,我直接截图