Three.js ShaderMaterial着色器

本节课介绍下threejs如何通过ShaderMaterial来自定义Shader。

参考资料:Three.js中文网 电子书

学前基础说明

你可以先点下面链接看下学习threejs Shader之前,你需要先了解什么

1. 学前说明

2. 着色器GLSL ES语言
:

案例1:ShaderMaterial着色器材质

原链接

原来给大家介绍过threejs的各种材质,比如MeshBasicMaterialMeshLambertMaterial…,对于这些材质,你可以通过colormap等属性直接设置物体的外观。

const material = new THREE.MeshBasicMaterial({
    color: 0x00ffff,//颜色
    map:texture,//颜色贴图
});

这节课介绍一个特殊的threejs材质,就是Shader材质类ShaderMaterial,单词Shader就是着色器的意思,ShaderMaterial是通过着色器GLSL ES语言自定义材质效果,比如颜色。

提醒:如果你图形学或数学的相关基础都不太好,建议本节课的视频和文档内容反复多看几遍。

.vertexShaderfragmentShader属性

  • .vertexShader:顶点着色器
  • .fragmentShader:片元着色器

本节课主要重点学习ShaderMaterial的顶点着色器属性.vertexShader、片元着色器属性.fragmentShader

const material = new THREE.ShaderMaterial({
    vertexShader: '着色器代码',// 顶点着色器
    fragmentShader: '着色器代码',// 片元着色器
});

使用Shader材质ShaderMaterial

打开本节课源码演示文件,你可以看到一个矩形网格模型MeshMesh的材质是基础网格材质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声明了一个变量posattribute的作用就是指明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;,相当于帮你声明了一个变量positionposition表示顶点的位置数据

const vertexShader = `
attribute vec3 position;//默认提供,不用自己写
void main(){
    gl_Position = vec4(...,1.0 );
}
`
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,// 顶点着色器
});

内置变量position含义

查看案例代码,可以看到几何体geometryShaderMaterial材质构成了一个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;,这意味着帮你声明了一个变量modelMatrixmodelMatrix在这里表示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、投影矩阵projectionMatrixviewMatrix的值来自相机视图矩阵属性camera.matrixWorldInverseprojectionMatrix的值来自相机的投影矩阵属性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 );
}
`

通过viewMatrixprojectionMatrix来表示相机对场景模型的旋转、缩放、平移变换。

设置片元着色器代码fragmentShader

fragmentShader表示ShaderMaterial的片元着色器属性。

// 片元着色器代码
const fragmentShader = `
void main() {
    ...
}
`
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,// 顶点着色器
    fragmentShader: fragmentShader,// 片元着色器
});

gl_FragColorgl_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

ShaderMaterialMeshBasicMaterial一样可以从父类Material继承.side属性,通过.side属性可以设置网格模型Mesh的两面如何显示。

const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    side: THREE.DoubleSide//双面显示
});

这时候你会发现ShaderMaterialMeshBasicMaterial可以实现一样的双面显示效果。

知识回顾: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声明一个透明度变量opacityopacity的数据类型设置为浮点数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,ShaderMaterialuniforms也有一个同名的属性opacity。这样的话,threejs会把uniformsopacity的值传值片元着色器中同名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属性数据
floatNumber
vec2THREE.Vector2
vec3THREE.Vector3
vec3THREE.Color
vec4THREE.Vector4

更多uniform与GLSL数据类型对应关系,可以查看threejs文档关于的Uniform介绍

测试:改变uniforms数据

你改变uniforms里面一些属性的值,ShaderMaterial着色器中同名uniform变量会跟着改变,进而影响threejs渲染效果,你可以改变下面属性进行测试。

material.uniforms.opacity.value = 0.2;
material.uniforms.color.value.set(0xff0000);
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐