3. ShaderMaterial着色器材质
# ShaderMaterial
着色器材质
原来给大家介绍过threejs的各种材质,比如MeshBasicMaterial
、MeshLambertMaterial
...,对于这些材质,你可以通过color
、map
等属性直接设置物体的外观。
const material = new THREE.MeshBasicMaterial({
color: 0x00ffff,//颜色
map:texture,//颜色贴图
});
这节课介绍一个特殊的threejs材质,就是Shader材质类ShaderMaterial
,单词Shader
就是着色器的意思,ShaderMaterial
是通过着色器GLSL ES语言 (opens new window)自定义材质效果,比如颜色。
提醒:如果你图形学或数学的相关基础都不太好,建议本节课的视频和文档内容反复多看几遍。
# .vertexShader
和fragmentShader
属性
.vertexShader
:顶点着色器.fragmentShader
:片元着色器
本节课主要重点学习ShaderMaterial
的顶点着色器属性.vertexShader
、片元着色器属性.fragmentShader
。
const material = new THREE.ShaderMaterial({
vertexShader: '着色器代码',// 顶点着色器
fragmentShader: '着色器代码',// 片元着色器
});
# 使用Shader材质ShaderMaterial
打开本节课源码演示文件,你可以看到一个矩形网格模型Mesh
,Mesh
的材质是基础网格材质MeshBasicMaterial
。
const geometry = new THREE.PlaneGeometry(100, 50);
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,
});
const mesh = new THREE.Mesh(geometry, material);
使用Shader材质ShaderMaterial
代替MeshBasicMaterial
,外观效果,可以通过顶点着色器.vertexShader
、片元着色器.fragmentShader
实现。
具体替换结果,你可以查看课件案例源码文件,打开测试效果,和演示文件对比下。
const geometry = new THREE.PlaneGeometry(100, 50);
const material = new THREE.ShaderMaterial({
vertexShader: '...',// 顶点着色器
fragmentShader: '...',// 片元着色器
});
const mesh = new THREE.Mesh(geometry, material);
# 设置顶点着色器vertexShader
ShaderMaterial
顶点着色器属性vertexShader
的值是字符串,字符串的内容是着色器GLSL ES语言写的代码。关于着色器GLSL ES语言的语法可以参考前面课程1.2. 着色器GLSL ES语言 (opens new window)的介绍。
const material = new THREE.ShaderMaterial({
vertexShader: '',// 顶点着色器
});
为了方便预览顶点着色器代码,咱们用模板字符串``
的形式去写,模板字符串``
的按键位于键盘Tab键的上面。
const vertexShader = `
// 写顶点着色器的代码
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
});
# 设置顶点着色器主函数
先按照着色器GLSL ES语言的语法,给顶点着色器代码设置一个主函数main
,函数main
无返回值,前面加上关键字void
即可。
const vertexShader = `
void main(){
// 写顶点着色器的代码
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
});
# 内置变量gl_Position
gl_Position
是着色器GLSL ES语言的内置变量,所谓内置变量,就是不用声明,就可以在代码中使用。
着色器内置变量gl_Position
数据类型是四维向量vec4
,可以用函数vec4()
创建,vec4()
有四个参数,每个参数都是浮点数float
gl_Position
的值,前面三个参数表示xyz坐标,第四个参数一般固定设置为1.0。
const vertexShader = `
void main(){
gl_Position = vec4( x, y ,z ,1.0 );
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
});
不过一般不会通过gl_Position
直接写顶点坐标,而是从几何体BufferGeometry
获取顶点坐标数据,下面给大家讲解具体实现方式。
const vertexShader = `
void main(){
gl_Position = vec4(从几何体获取顶点xyz坐标,1.0 );
}
`
# 着色器GLSL ES语言语法:attribute
关键字
attribute
是着色器GLSL ES语言的一个关键字,按照GLSL ES的语法规定,attribute
关键字一般用来声明与顶点数据有关变量。
attribute vec3 pos;
表示用attribute
声明了一个变量pos
,attribute
的作用就是指明pos
是顶点相关变量,pos
的数类型是三维向量vec3
,三维向量vec3
意味着pos
表示的顶点数据有x、y、z三个分量。比如你可以用pos
表示顶点的位置数据xyz(当然也能表示其它类型顶点数据,遇到再讲解)。
const vertexShader = `
attribute vec3 pos;//注意在主函数外面
void main(){
gl_Position = vec4(...,1.0 );
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
});
假设attribute
声明的变量pos
表示顶点位置数据,你就可以赋值给gl_Position
。
执行vec4(pos,1.0 )
,给三维向量vec3
增加一个分量,就可以变成四维向量vec4
(这是GLSL ES基本语法)。
const vertexShader = `
attribute vec3 pos;
void main(){
gl_Position = vec4(pos,1.0 );
}
`
# 知识回顾:几何体geometry
的顶点位置数据
知识回顾:基础课程中讲解过几何体BufferGeometry的顶点知识 (opens new window)
访问geometry.attributes.position
你可以看到几何体所有的顶点位置数据,这些位置数据包含在一个数组中,三个为一组表示一个顶点的x、y、z坐标。这里再强调一遍,threejs默认情况下,几何体的顶点位置数据中的每个顶点都包含x、y、z三个分量。
const geometry = new THREE.PlaneGeometry(100, 50);
console.log('顶点位置数据',geometry.attributes.position);
# ShaderMaterial
的内置变量position
调用shader材质ShaderMaterial
的时候,threejs会在内部给你写的顶点着色器代码中,插入一行代码attribute vec3 position;
,相当于帮你声明了一个变量position
,position
表示顶点的位置数据
const vertexShader = `
attribute vec3 position;//默认提供,不用自己写
void main(){
gl_Position = vec4(...,1.0 );
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
});
# 内置变量position
含义
查看案例代码,可以看到几何体geometry
与ShaderMaterial
材质构成了一个mesh。也就是说材质ShaderMaterial
关联了几何体geometry
。
const geometry = new THREE.PlaneGeometry(100, 50);
console.log('顶点位置数据',geometry.attributes.position);
const material = new THREE.ShaderMaterial();
const mesh = new THREE.Mesh(geometry, material);
当你ShaderMaterial
的时候,threejs会在内部把内置变量position
与几何体的顶点位置数据geometry.attributes.position
关联起来。这意味着,你在顶点着色器代码中访问变量position
,就相当于获取了几何体顶点位置数据geometry.attributes.position
const vertexShader = `
// attribute vec3 position;//默认提供,不用自己写
void main(){
gl_Position = vec4(...,1.0 );
}
`
总而言之,你可以通过执行代码gl_Position = vec4(position,1.0);
,把几何体的顶点位置数据geometry.attributes.position
赋值给内置变量gl_Position
const vertexShader = `
// attribute vec3 position;//默认提供,不用自己写
void main(){
gl_Position = vec4(position,1.0 );
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
});
# 知识回顾:顶点矩阵变换
如果你对矩阵变换的知识点完全不了解,可以去看看前面threejs进阶部分关于矩阵 (opens new window)的讲解。
const vertexShader = `
void main(){
// 通过矩阵对顶点坐标进行几何变换(旋转、缩放、平移)
gl_Position = 矩阵 * vec4(position,1.0 );
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
});
# 着色器GLSL ES语言语法:uniform
关键字
uniform
是着色器GLSL ES语言语言的一个关键字,用来声明非顶点的变量(顶点变量用atribute
声明),比如模型的矩阵、光源位置等等。
执行uniform mat4 mT;
意味着,你通过关键字uniform
声明一个变量mT
,变量mT
的数据类型是mat4
(4x4的矩阵)。
const vertexShader = `
uniform mat4 mT;
void main(){
gl_Position = mT * vec4(position,1.0 );
}
`
假设mT
是一个平移矩阵,mT * vec4(position,1.0 )
就可以平移几何体的顶点位置position
。
# 知识点回顾:世界矩阵.matrixWorld
当网格模型mesh自身或父对象平移、旋转、缩放时候,会改变自身的世界矩阵属性mesh.matrixWorld
,换句话说,就是threejs内部会用世界矩阵.matrixWorld
记录mesh的位置、尺寸和姿态角度变化。
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(100,0,0);//平移改变位置
mesh.scale.set(3,3,3,);//缩放改变尺寸
mesh.rotateY(Math.PI / 2);//旋转改变姿态角度
关于模型世界矩阵mesh.matrixWorld
更多内容可以参考前面课程5.5 模型本地矩阵、世界矩阵 (opens new window)
# 内置变量模型矩阵modelMatrix
调用shader材质ShaderMaterial
的时候,threejs会在内部给你写的顶点着色器代码中,插入一行代码uniform mat4 modelMatrix;
,这意味着帮你声明了一个变量modelMatrix
,modelMatrix
在这里表示4x4的模型矩阵mat4
。
const vertexShader = `
uniform mat4 modelMatrix;//默认提供,不用自己写
void main(){
gl_Position = vec4(...,1.0 );
}
`
使用ShaderMaterial
的时候,threejs会自动获取模型世界矩阵mesh.matrixWorld
的值,赋值给变量modelMatrix
。这意味着,模型矩阵modelMatrix
包含了模型自身的位置、缩放、姿态角度信息。
你当平移、旋转、缩放mesh时候,会改变mesh的世界矩阵属性.matrixWorld
,自然同步改变顶点着色器的模型矩阵modelMatrix
变量。
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(100,0,0);
mesh.rotateY(Math.PI / 2);
在顶点着色器代码中,你可以直接使用modelMatrix
对几何体顶点位置坐标进行旋转、缩放、平移。
const vertexShader = `
// uniform mat4 modelMatrix;//默认提供,不用自己写
void main(){
// 模型矩阵 * 顶点坐标
gl_Position = modelMatrix * vec4(position,1.0 );
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
});
# 知识回顾:视图矩阵和投影矩阵
通过基础课程的学习大家都知道,当你改变相机的参数的时候,场景中模型Mesh渲染位置、尺寸、角度可能会发生变化。其实原因很简单,threejs内部会把相机的参数生成矩阵,对模型的顶点进行矩阵变换。
const width = window.innerWidth;
const height = window.innerHeight;
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
camera.position.set(292, 223, 185);
camera.lookAt(0, 0, 0);
前面进阶课程5.6. 视图矩阵、投影矩阵 (opens new window),给大家介绍过,threejs会读取相机的参数生成两个矩阵,也就是视图矩阵camera.matrixWorldInverse
和投影矩阵camera.projectionMatrix
。
# 内置变量:视图矩阵viewMatrix
和投影矩阵projectionMatrix
刚才给大家介绍过ShaderMaterial
的一个内置变量是模型矩阵modelMatrix
。
const vertexShader = `
uniform mat4 modelMatrix;//默认提供,不用自己写
void main(){
gl_Position = modelMatrix * vec4(position,1.0 );
}
`
使用ShaderMaterial
的时候,除了内置变量模型矩阵modelMatrix
,threejs内部还提供了两个内置的矩阵变量,这两个内置变量分别是相机的视图矩阵viewMatrix
、投影矩阵projectionMatrix
。viewMatrix
的值来自相机视图矩阵属性camera.matrixWorldInverse
,projectionMatrix
的值来自相机的投影矩阵属性camera.projectionMatrix
。
视图矩阵viewMatrix
和投影矩阵projectionMatrix
因为是内置变量,同样不用声明你就可以直接使用。
const vertexShader = `
uniform mat4 modelMatrix;//默认提供,不用自己写
uniform mat4 viewMatrix;//默认提供,不用自己写
uniform mat4 projectionMatrix;//默认提供,不用自己写
void main(){
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标
// 注意矩阵乘法前后顺序不要写错
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position,1.0 );
}
`
通过viewMatrix
和projectionMatrix
来表示相机对场景模型的旋转、缩放、平移变换。
# 设置片元着色器代码fragmentShader
fragmentShader
表示ShaderMaterial
的片元着色器属性。
// 片元着色器代码
const fragmentShader = `
void main() {
...
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
fragmentShader: fragmentShader,// 片元着色器
});
gl_FragColor
和gl_Position
一样是着色器GLSL ES语言的内置变量,不用声明,就可以在代码中使用。
你可以通过gl_FragColor
设置ShaderMaterial
相关模型的颜色值。
着色器内置变量gl_FragColor
数据类型是四维向量vec4
,可以用函数vec4()
创建,vec4()
有四个参数,每个参数都是浮点数float
gl_FragColor
的值,前面三个参数表示像素的RGB值,第四个参数表示透明度,不透明就是1.0。
// 片元着色器代码
const fragmentShader = `
void main() {
// RGB 0.0,1.0,1.0对应16进制颜色是0x00ffff
gl_FragColor = vec4(0.0,1.0,1.0,1.0);
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
fragmentShader: fragmentShader,// 片元着色器
});
# 体验测试
你可以平移网格模型mesh.position.x = 100;
,然后比较下顶点着色器使用modelMatrix
和不使用modelMatrix
的差异。
你可以发现modelMatrix
包含了你的平移变换mesh.position.x = 100;
。
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );
mesh.position.x = 100;
改变模型的颜色值
// 青色
gl_FragColor = vec4(0.0,1.0,1.0,1.0);
// 红色
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
# 内置变量:模型视图矩阵
ShaderMaterial
还提供了一个内置变量模型视图矩阵modelViewMatrix
,就是视图矩阵viewMatrix
和模型矩阵modelMatrix
的乘积。
const vertexShader = `
//模型视图矩阵
uniform mat4 modelViewMatrix;//默认提供,不用自己写
void main(){
// 投影矩阵 * 模型视图矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*modelViewMatrix*vec4( position, 1.0 );
}
`
你可以把上面代码viewMatrix*modelMatrix
简化为modelViewMatrix
。
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );
// 投影矩阵 * 模型视图矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*modelViewMatrix*vec4( position, 1.0 );