ShaderMaterial着色器材质

adminShaderMaterial着色器材质已关闭评论条评论 26 次浏览

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(4×4的矩阵)。

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在这里表示4×4的模型矩阵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 );

个人代码笔记

 
import * as THREE from "three";

// const geometry = new THREE.PlaneGeometry(100, 50);

// 设置顶点
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([-25, 0, 0, 25, 0, 0, 0, 40, 0]);
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
// 设置颜色
const colors = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));

const material = new THREE.ShaderMaterial({
  vertexShader: `
    varying vec3 vColor; // 顶点颜色差值之后的颜色对应的变量
    varying vec3 vPosition; // 顶点位置差值之后的顶点位置对应的变量
    void main() {

      // 投影矩阵 * 视图矩阵 * 模型矩阵( 视图矩阵 * 模型矩阵 也可用模型视图矩阵代替 modelViewMatrix)  * 顶点位置
      //  projectionMatrix viewMatrix modelMatrix modelViewMatrix position color 都是threeJS提供的默认变量

      vColor = color; // 顶点颜色差值之后的颜色赋给vColor
      vPosition = position; // 顶点位置差值之后的位置赋给vPosition
     
      gl_Position = (projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0));
    }`,
  fragmentShader: `  
   uniform vec3 color;
   uniform float opacity;
   varying vec3 vColor; // 接收顶点着色器的 vColor 变量  主要在片元着色器和顶点着色器中有相同名字的变量 就可以获取到
   varying vec3 vPosition; // 接收顶点着色器的 vPosition 变量  

   // gl_FragCoord 为内置变量 指屏幕的像素坐标
    void main() {
       
      // 1.片元着色器 通过uniform传参赋值
      // gl_FragColor = vec4(color, opacity);
     
      // 2. 片元着色器 通过 gl_FragCoord 内置变量计算颜色
      // 根据 屏幕的像素坐标设置颜色
      // if(gl_FragCoord.x < 1920.0 / 2.0){
      //   gl_FragColor = vec4(1.0,0.0,0.0,1.0);
      // }else{
      //   gl_FragColor = vec4(0.0,0.0,1.0,1.0);
      // }
       
      // 3. 片元着色器 通过 gl_FragCoord 内置变量计算渐变色 横轴的渐变色 屏幕分辨率为1920
      // gl_FragColor = vec4(gl_FragCoord.x / 1920.0,0.0,0.0,1.0);  
     
      // 4. 片元着色器 通过 gl_FragCoord 内置变量颜色 并通过 discard 舍弃某部分片元
      // if(gl_FragCoord.x < 1920.0 / 2.0){
      //   gl_FragColor = vec4(1.0,0.0,0.0,1.0);
      // }else{
      //   discard;
      // }

      // 5. 片元着色器 通过 vPosition  position的位置在 geometry.attributes.position 中
      // if( vPosition.x > -10.0){
      //       gl_FragColor = vec4(1.0 ,0.0,0.0,1.0);  
      // } else {
      //       gl_FragColor = vec4(0.0,1.0,0.0,1.0);
      // }
     
      // 6. 片元着色器 通过 vPosition 计算渐变色
      float per = vPosition.y / 40.0;
      gl_FragColor = vec4(per,1.0,0.0,opacity);

    }`,
  side: THREE.DoubleSide,
  transparent: true,
  vertexColors: true,
  uniforms: {
    color: { value: new THREE.Vector3(0.5, 0.5, 1) },
    opacity: { value: 1 }, // 给着色器定义的传参属性 opacity 赋值给着色器中的 uniform float opacity;
  },
});

// material.uniforms.opacity.value = 1; // 重新修改uniform的值

const mesh = new THREE.Mesh(geometry, material);
mesh.scale.set(3, 3, 3);
mesh.position.set(10, 40, 0);
// mesh.rotation.set(0, 0, 0.5);
// 位置属性.position使用threejs三维向量对象Vector3表示的
console.log("模型位置属性.position的值", mesh.position);

export default mesh;


分类目录