Three.js ShaderMaterial着色器
Three.js ShaderMaterial着色器
本节课介绍下threejs如何通过ShaderMaterial来自定义Shader。
参考资料:Three.js中文网 电子书
学前基础说明
你可以先点下面链接看下学习threejs Shader之前,你需要先了解什么
案例1:ShaderMaterial
着色器材质
原来给大家介绍过threejs的各种材质,比如MeshBasicMaterial
、MeshLambertMaterial
…,对于这些材质,你可以通过color
、map
等属性直接设置物体的外观。
const material = new THREE.MeshBasicMaterial({
color: 0x00ffff,//颜色
map:texture,//颜色贴图
});
这节课介绍一个特殊的threejs材质,就是Shader材质类ShaderMaterial
,单词Shader
就是着色器的意思,ShaderMaterial
是通过着色器GLSL ES语言自定义材质效果,比如颜色。
提醒:如果你图形学或数学的相关基础都不太好,建议本节课的视频和文档内容反复多看几遍。
.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语言的介绍。
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的顶点知识
访问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进阶部分关于矩阵的讲解。
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 模型本地矩阵、世界矩阵
内置变量模型矩阵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. 视图矩阵、投影矩阵,给大家介绍过,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 );
练习案例2:ShaderMaterial半透明、双面显示
这节课给大家演示下,shader材质ShaderMaterial
怎么设置双面显示、半透明效果。
知识回顾
查看上节代码效果,你可以看出来通过ShaderMaterial
自定义着色器GLSL ES代码,实现了类似基础网材质MeshBasicMaterial
的效果。
const material = new THREE.MeshBasicMaterial({
color: 0x00ffff,
});
const vertexShader = `
void main(){
gl_Position = projectionMatrix*modelViewMatrix*vec4( position, 1.0 );
}
`
const fragmentShader = `
void main() {
gl_FragColor = vec4(0.0,1.0,1.0,1.0);
}
`
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
fragmentShader: fragmentShader,// 片元着色器
});
知识回顾:.side
属性
默认单面显示,设置side:THREE.DoubleSide
改为双面显示。
const material = new THREE.MeshBasicMaterial({
color: 0x00ffff,
side:THREE.DoubleSide//双面显示
});
ShaderMaterial
属性.side
ShaderMaterial
和MeshBasicMaterial
一样可以从父类Material
继承.side
属性,通过.side
属性可以设置网格模型Mesh的两面如何显示。
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide//双面显示
});
这时候你会发现ShaderMaterial
和MeshBasicMaterial
可以实现一样的双面显示效果。
知识回顾:MeshBasicMaterial
设置半透明效果
const material = new THREE.MeshBasicMaterial({
color: 0x00ffff,
transparent: true,//允许透明
opacity:0.3,//透明度
});
ShaderMaterial
设置半透明效果
通过片元着色器代码设置ShaderMaterial
透明度,更改内置变量gl_FragColor
的第四个分量即可。
// 片元着色器代码
const fragmentShader = `
void main() {
//透明度1.0不透明
// gl_FragColor = vec4(0.0,1.0,1.0,1.0);
//透明度设置0.3,在0~1之间,半透明
gl_FragColor = vec4(0.0,1.0,1.0,0.3);
}
`
const geometry = new THREE.PlaneGeometry(100, 50);
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,// 顶点着色器
fragmentShader: fragmentShader,// 片元着色器
});
shader材质ShaderMaterial
设置transparent:true,
允许透明度计算,gl_FragColor = vec4(0.0,1.0,1.0,0.3)
设置的半透明效果才会生效。
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide//双面显示
});
案例3 uniform
变量传值
在给shader材质ShaderMaterial
自定义着色器GLSL ES代码的时候,有时候会用uniform关键字声明一个变量,这节课给大家讲解,threejs怎么给uniform声明的变量传值。
1. uniform
声明变量
使用着色器GLSL ES语言的关键字uniform
声明一个透明度变量opacity
,opacity
的数据类型设置为浮点数float
,透明度变量名字你可以自定义,这里命名为opacity
。
// 片元着色器代码
const fragmentShader = `
uniform float opacity;//uniform声明透明度变量opacity
void main() {
gl_FragColor = vec4(0.0,1.0,1.0,0.3);
}
`
2. 给uniform
变量传值
通过ShaderMaterial
参数的uniforms
属性,可以给顶点或片元着色器中的uniform
变量传值。
比如下面片元着色器代码中,有一个uniform变量名称是opacity
,ShaderMaterial
的uniforms
也有一个同名的属性opacity
。这样的话,threejs会把uniforms
中opacity
的值传值片元着色器中同名uniform变量opacity
const fragmentShader = `
uniform float opacity;//uniform声明透明度变量opacity
void main() {
gl_FragColor = vec4(0.0,1.0,1.0,0.3);
}
`
const material = new THREE.ShaderMaterial({
uniforms: {
// 给透明度uniform变量opacity传值
opacity:{value:0.3}
},
vertexShader: vertexShader,// 顶点着色器
fragmentShader: fragmentShader,// 片元着色器
side: THREE.DoubleSide,
transparent: true,//允许透明
});
3. uniform
变量赋值给gl_FragColor
把uniform变量透明度opacity
赋值给gl_FragColor
,查看渲染效果(注意允许透明transparent: true
)。
// 片元着色器代码
const fragmentShader = `
uniform float opacity;//uniform声明透明度变量opacity
void main() {
gl_FragColor = vec4(0.0,1.0,1.0,opacity);
}
`
const material = new THREE.ShaderMaterial({
uniforms: {
// 给透明度uniform变量opacity传值
opacity:{value:0.3}
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,// 片元着色器
transparent: true,//允许透明
});
你可以uniforms
里面opacity的值改为其他值,查看效果变化。
练习:uniform传值颜色数据
通过上面学习,你可以做一个练习题,就是用uniform
声明一个颜色变量color
,然后,用uniforms
给uniform变量颜色color
传值。
// 片元着色器代码
const fragmentShader = `
uniform float opacity;//uniform声明变量opacity表示透明度
uniform vec3 color;//声明一个颜色变量color
void main() {
gl_FragColor = vec4(color,opacity);
}
`
const geometry = new THREE.PlaneGeometry(100, 50);
const material = new THREE.ShaderMaterial({
uniforms: {
// 给透明度uniform变量opacity传值
opacity: { value: 0.3 },
// 给uniform同名color变量传值
color:{value:new THREE.Color(0x00ffff)}
},
vertexShader: vertexShader,// 顶点着色器
fragmentShader: fragmentShader,// 片元着色器
side: THREE.DoubleSide,
transparent: true,//允许透明
});
注意着色器语言GLSL ES中uniform变量数据类型,与threejs中uniforms属性value值的对应关系。
uniform变量数据类型 | uniforms属性数据 |
---|---|
float | Number |
vec2 | THREE.Vector2 |
vec3 | THREE.Vector3 |
vec3 | THREE.Color |
vec4 | THREE.Vector4 |
更多uniform与GLSL数据类型对应关系,可以查看threejs文档关于的Uniform介绍
测试:改变uniforms
数据
你改变uniforms
里面一些属性的值,ShaderMaterial
着色器中同名uniform变量会跟着改变,进而影响threejs渲染效果,你可以改变下面属性进行测试。
material.uniforms.opacity.value = 0.2;
material.uniforms.color.value.set(0xff0000);
更多推荐
所有评论(0)