Three.js ShaderMaterial着色器

Three.js ShaderMaterial着色器

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

参考资料:Threejs中文网

学前基础说明

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

1. 学前说明

2. 着色器GLSL ES语言 :

案例1:ShaderMaterial着色器材质

原链接

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

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

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

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

.vertexShaderfragmentShader属性

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

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

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

使用Shader材质ShaderMaterial

打开本节课源码演示文件,你可以看到一个矩形网格模型MeshMesh的材质是基础网格材质MeshBasicMaterial

js 复制代码
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实现。

具体替换结果,你可以查看课件案例源码文件,打开测试效果,和演示文件对比下。

js 复制代码
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语言的介绍。

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

为了方便预览顶点着色器代码,咱们用模板字符串的形式去写,模板字符串的按键位于键盘Tab键的上面。

js 复制代码
const vertexShader = `
    // 写顶点着色器的代码 
`
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,// 顶点着色器
});

设置顶点着色器主函数

先按照着色器GLSL ES语言 的语法,给顶点着色器代码设置一个主函数main,函数main无返回值,前面加上关键字void即可。

js 复制代码
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。

js 复制代码
const vertexShader = `
void main(){
    gl_Position = vec4( x, y ,z ,1.0 );
}
`
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,// 顶点着色器
});

不过一般不会通过gl_Position直接写顶点坐标,而是从几何体BufferGeometry获取顶点坐标数据,下面给大家讲解具体实现方式。

js 复制代码
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(当然也能表示其它类型顶点数据,遇到再讲解)。

js 复制代码
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基本语法)。

js 复制代码
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三个分量。

js 复制代码
const geometry = new THREE.PlaneGeometry(100, 50);
console.log('顶点位置数据',geometry.attributes.position);

ShaderMaterial的内置变量position

调用shader材质ShaderMaterial的时候,threejs会在内部给你写的顶点着色器代码中,插入一行代码attribute vec3 position;,相当于帮你声明了一个变量positionposition表示顶点的位置数据

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

内置变量position含义

查看案例代码,可以看到几何体geometryShaderMaterial材质构成了一个mesh。也就是说材质ShaderMaterial关联了几何体geometry

js 复制代码
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

js 复制代码
const vertexShader = `
// attribute vec3 position;//默认提供,不用自己写
void main(){
    gl_Position = vec4(...,1.0 );
}
`

总而言之,你可以通过执行代码gl_Position = vec4(position,1.0);,把几何体的顶点位置数据geometry.attributes.position赋值给内置变量gl_Position

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

知识回顾:顶点矩阵变换

如果你对矩阵变换的知识点完全不了解,可以去看看前面threejs进阶部分关于矩阵的讲解。

js 复制代码
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的矩阵)。

js 复制代码
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的位置、尺寸和姿态角度变化。

js 复制代码
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

js 复制代码
const vertexShader = `
uniform mat4 modelMatrix;//默认提供,不用自己写
void main(){
    gl_Position = vec4(...,1.0 );
}
`

使用ShaderMaterial的时候,threejs会自动获取模型世界矩阵mesh.matrixWorld的值,赋值给变量modelMatrix。这意味着,模型矩阵modelMatrix包含了模型自身的位置、缩放、姿态角度信息。

你当平移、旋转、缩放mesh时候,会改变mesh的世界矩阵属性.matrixWorld,自然同步改变顶点着色器的模型矩阵modelMatrix变量。

js 复制代码
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(100,0,0);
mesh.rotateY(Math.PI / 2);

在顶点着色器代码中,你可以直接使用modelMatrix对几何体顶点位置坐标进行旋转、缩放、平移。

js 复制代码
const vertexShader = `
// uniform mat4 modelMatrix;//默认提供,不用自己写
void main(){
    // 模型矩阵 * 顶点坐标
    gl_Position = modelMatrix * vec4(position,1.0 );
}
`
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,// 顶点着色器
});

知识回顾:视图矩阵和投影矩阵

通过基础课程的学习大家都知道,当你改变相机的参数的时候,场景中模型Mesh渲染位置、尺寸、角度可能会发生变化。其实原因很简单,threejs内部会把相机的参数生成矩阵,对模型的顶点进行矩阵变换。

js 复制代码
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

js 复制代码
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因为是内置变量,同样不用声明你就可以直接使用。

js 复制代码
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的片元着色器属性。

js 复制代码
// 片元着色器代码
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。

js 复制代码
// 片元着色器代码
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;

js 复制代码
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );
js 复制代码
mesh.position.x = 100;

改变模型的颜色值

js 复制代码
// 青色
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的乘积。

js 复制代码
const vertexShader = `
//模型视图矩阵
uniform mat4 modelViewMatrix;//默认提供,不用自己写
void main(){
    // 投影矩阵 * 模型视图矩阵 * 模型顶点坐标
    gl_Position = projectionMatrix*modelViewMatrix*vec4( position, 1.0 );
}
`

你可以把上面代码viewMatrix*modelMatrix简化为modelViewMatrix

js 复制代码
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );
js 复制代码
// 投影矩阵 * 模型视图矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*modelViewMatrix*vec4( position, 1.0 );

练习案例2:ShaderMaterial半透明、双面显示

这节课给大家演示下,shader材质ShaderMaterial怎么设置双面显示、半透明效果。

知识回顾

查看上节代码效果,你可以看出来通过ShaderMaterial自定义着色器GLSL ES代码,实现了类似基础网材质MeshBasicMaterial的效果。

js 复制代码
const material = new THREE.MeshBasicMaterial({
    color: 0x00ffff,
});
js 复制代码
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改为双面显示。

js 复制代码
const material = new THREE.MeshBasicMaterial({
    color: 0x00ffff,
    side:THREE.DoubleSide//双面显示
});

ShaderMaterial属性.side

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

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

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

知识回顾:MeshBasicMaterial设置半透明效果

js 复制代码
const material = new THREE.MeshBasicMaterial({
  color: 0x00ffff,
  transparent: true,//允许透明
  opacity:0.3,//透明度
});

ShaderMaterial设置半透明效果

通过片元着色器代码设置ShaderMaterial透明度,更改内置变量gl_FragColor的第四个分量即可。

js 复制代码
// 片元着色器代码
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)设置的半透明效果才会生效。

js 复制代码
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

js 复制代码
// 片元着色器代码
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

js 复制代码
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)。

js 复制代码
// 片元着色器代码
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传值。

js 复制代码
// 片元着色器代码
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渲染效果,你可以改变下面属性进行测试。

js 复制代码
material.uniforms.opacity.value = 0.2;
material.uniforms.color.value.set(0xff0000);
相关推荐
SunTecTec22 分钟前
Flink Docker Application Mode 命令解析 - 修改命令以启用 Web UI
大数据·前端·docker·flink
拉不动的猪1 小时前
前端常见数组分析
前端·javascript·面试
小吕学编程2 小时前
ES练习册
java·前端·elasticsearch
Asthenia04122 小时前
Netty编解码器详解与实战
前端
袁煦丞2 小时前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
一个专注写代码的程序媛3 小时前
vue组件间通信
前端·javascript·vue.js
一笑code3 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员3 小时前
layui时间范围
前端·javascript·layui
NoneCoder4 小时前
HTML响应式网页设计与跨平台适配
前端·html
凯哥19704 小时前
在 Uni-app 做的后台中使用 Howler.js 实现强大的音频播放功能
前端