搭建基本项目

  1. 首先直接通过vite创建一个Vue3项目。
  2. 安装reset.css 和 three.js
yarn add reset.css three
  1. 复习glsl,如果你已经忘得一干二净了。那么,是时候去复习一下了。不然无法继续。

如果以上三个步骤已经做完,那么可以开始下面的内容。

渲染管线

下面的渲染管线的流程图:

在这里插入图片描述

关键字

attribute 关键字

attribute关键字一般用来声明与顶点数据有关变量。

const vertexShader = `
attribute vec3 pos;//注意在主函数外面
void main(){
    gl_Position = vec4(...,1.0 );
}
`
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,// 顶点着色器
});

解释:

  • attribute vec3 pos; 表示用attribute声明了一个变量pos,attribute的作用就是指明pos是顶点相关变量,pos的数类型是三维向量vec3,三维向量vec3意味着pos表示的顶点数据有x、y、z三个分量。

假设attribute声明的变量pos表示顶点位置数据,你就可以赋值给gl_Position。

const vertexShader = `
attribute vec3 pos;
void main(){
    gl_Position = vec4(pos,1.0 );
}
`

uniform 关键字

uniform是着色器GLSL ES语言语言的一个关键字,用来声明非顶点的变量(顶点变量用atribute声明),比如模型的矩阵、光源位置等等。

const vertexShader = `
uniform mat4 mT;
void main(){
    gl_Position = mT * vec4(position,1.0 );
}
`

执行uniform mat4 mT;意味着,你通过关键字uniform声明一个变量mT,变量mT的数据类型是mat4(4x4的矩阵)。

discard 关键字

discard是着色器语言GLSL ES的一个关键字,用来控制片元着色器功能单元,舍弃某个片元。

if(gl_FragCoord.x < 400.0){
  // 符合条件片元保留,并设置颜色
  gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}else {
  discard;//不符合条件片元直接舍弃掉
}

varying 关键字

在顶点着色器 和 片元着色器中如果有公共变量想要访问,那么可以使用varying关键字声明一个同名变量。

// 顶点着色器代码
const vertexShader = `
// attribute vec3 color;//默认提供不用手写
varying vec3 vColor;// varying关键字声明一个变量表示顶点颜色插值后的结果
void main(){
  vColor = color;// 顶点颜色数据进行插值计算
  // 投影矩阵 * 模型视图矩阵 * 模型顶点坐标
  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`

// 片元着色器代码
const fragmentShader = `
varying vec3 vColor;// 顶点片元化后有多少个片元,顶点颜色插值后就有多少个颜色数据
void main() {
    gl_FragColor = vec4(vColor,1.0);
}
`

sampler2D 关键字

uniform关键字声明一个颜色贴图相关的变量map,纹理贴图对应数据类型需要用关键字sampler2D表示。

// 片元着色器代码
const fragmentShader = `
uniform sampler2D map;//颜色贴图变量
void main() {
    gl_FragColor = vec4(0.0,1.0,1.0,1.0);
}
`

内置变量

所谓内置变量,就是不用声明,就可以在代码中使用。

gl_Position

内置变量gl_Position数据类型是四维向量vec4,可以用函数vec4()创建,vec4()有四个参数,每个参数都是浮点数float 。

gl_Position的值,前面三个参数表示xyz坐标,第四个参数一般固定设置为1.0。

不过一般不会通过gl_Position直接写顶点坐标,而是从几何体BufferGeometry获取顶点坐标数据。

const vertexShader = `
void main(){
    gl_Position = vec4(从几何体获取顶点xyz坐标,1.0 );
}
`

gl_PointSize

gl_PointSize和gl_Position一样,都是顶点着色器GLSL ES的一个内置变量,gl_PointSize作用是设置点渲染的像素大小。

// 顶点着色器代码
const vertexShader = `
void main(){
  gl_PointSize = 20.0;
  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`

gl_PointCoord (Point坐标)

gl_PointCoord是片元着色器着色器GLSL ES的一个内置变量,与Points渲染的方形点坐标相关,具体含义如下图:

Points可以渲染多个方形点,每个方形点的gl_PointCoord坐标原点都位于自身的左上角,x轴水平向右,y轴水平向下,不管gl_PointSize多大,Points方形点右下角gl_PointCoord坐标都是(1.0,1.0)。

在这里插入图片描述

gl_FragColor

可以通过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,// 片元着色器
});

gl_FragCoord

gl_FragCoord是一个四维向量vec4,gl_FragCoord.x表示片元x坐标,gl_FragCoord.y表示片元的y坐标,这里提醒大家,一定注意gl_FragCoord.xy的坐标系,不是平时你threejs代码里面的xyz世界坐标系。

gl_FragCoord.xy坐标系的坐标原点,位于threejs canvas画布的左下角,x轴水平向右,y轴竖直向上,单位是像素px。

//根据片元的x坐标,来设置片元的像素值
if(gl_FragCoord.y < 300.0){
  gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}else {
  gl_FragColor = vec4(0.0,0.0,1.0,1.0);
}

positon

访问geometry.attributes.position你可以看到几何体所有的顶点位置数据,这些位置数据包含在一个数组中,三个为一组表示一个顶点的x、y、z坐标。这里再强调一遍,threejs默认情况下,几何体的顶点位置数据中的每个顶点都包含x、y、z三个分量。

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

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

材质ShaderMaterial关联了几何体geometry。

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

执行代码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,// 顶点着色器
});

color

color 表示顶点的颜色数据,数据来自你定义的几何体顶点颜色属性geometry.attributes.color

模型矩阵 modelMatrix

调用shader材质ShaderMaterial的时候,threejs会在内部给你写的顶点着色器代码中,插入一行代码uniform mat4 modelMatrix;,这意味着帮你声明了一个变量modelMatrix,modelMatrix在这里表示4x4的模型矩阵mat4。

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

const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(100,0,0);
mesh.rotateY(Math.PI / 2);
const vertexShader = `
// uniform mat4 modelMatrix;//默认提供,不用自己写
void main(){
    // 模型矩阵 * 顶点坐标
    gl_Position = modelMatrix * vec4(position,1.0 );
}
`
const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,// 顶点着色器
});

解释:

  • 使用ShaderMaterial的时候,threejs会自动获取模型世界矩阵mesh.matrixWorld的值,赋值给变量modelMatrix。这意味着,模型矩阵modelMatrix包含了模型自身的位置、缩放、姿态角度信息。
  • 当你平移、旋转、缩放mesh时候,会改变mesh的世界矩阵属性.matrixWorld,自然同步改变顶点着色器的模型矩阵modelMatrix变量。

视图矩阵 viewMatrix 和 投影矩阵 projectionMatrix

视图矩阵 viewMatrix 的值来自相机视图矩阵属性camera.matrixWorldInverse 。

投影矩阵 projectionMatrix 的值来自相机的投影矩阵属性camera.projectionMatrix。

const vertexShader = `
uniform mat4 modelMatrix;//默认提供,不用自己写
void main(){
    gl_Position = modelMatrix * vec4(position,1.0 );
}
`
const vertexShader = `
uniform mat4 modelMatrix;//默认提供,不用自己写
uniform mat4 viewMatrix;//默认提供,不用自己写
uniform mat4 projectionMatrix;//默认提供,不用自己写
void main(){
    // 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标
    // 注意矩阵乘法前后顺序不要写错
    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position,1.0 );
}
`

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

uv

顶点UV坐标uv,uv的数据来自几何体uv属性geometry.attributes.uv 。

内置函数

texture2D()

用于从纹理中获取颜色值。

distance()

用来计算两个向量之间的距离。

ShaderMaterial着色器材质

ShaderMaterial是通过着色器GLSL ES语言 (opens new window)自定义材质效果,比如颜色。

代码案例:

const geometry = new THREE.PlaneGeometry(100, 50);
const material = new THREE.ShaderMaterial({
    vertexShader: '...',// 顶点着色器
    fragmentShader: '...',// 片元着色器
    side: THREE.DoubleSide,// 双面显示
    transparent: true,//允许透明
  	opacity:0.3,//透明度
    uniforms: {
    // 给透明度uniform变量opacity传值
    opacity:{value:0.3}
  },
});
const mesh = new THREE.Mesh(geometry, material);

uniforms 传值操作

// 片元着色器代码
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,//允许透明
});

顶点颜色插值计算

比如一条直线,你把直线几何体两个端点,分别设置一个颜色,threejs会根据两端的颜色,在两点之间,按照距离远近插入不同颜色,就是所谓的颜色插值计算,距离某个点越近受某个点颜色影响越大。

在这里插入图片描述

对于网格的三角形也是类似,三角形内部一个点的颜色,同时收到三个点的颜色影响,距离某个点越近,受影响越大。

演示:

const geometry = new THREE.BufferGeometry(); //创建一个几何体对象
const vertices = new Float32Array([//类型数组创建顶点数据
  0, 0, 0, //顶点1坐标
  50, 0, 0, //顶点2坐标
  0, 25, 0, //顶点3坐标
]);
geometry.attributes.position = new THREE.BufferAttribute(vertices, 3);

// 顶点着色器代码
const vertexShader = `
// attribute vec3 color;//默认提供不用手写
varying vec3 vColor;// varying关键字声明一个变量表示顶点颜色插值后的结果
void main(){
  vColor = color;// 顶点颜色数据进行插值计算
  // 投影矩阵 * 模型视图矩阵 * 模型顶点坐标
  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`

// 片元着色器代码
const fragmentShader = `
varying vec3 vColor;// 顶点片元化后有多少个片元,顶点颜色插值后就有多少个颜色数据
void main() {
    gl_FragColor = vec4(vColor,1.0);
}
`
// 注意:如果你想在片元着色器中获取顶点着色器中顶点颜色数据插值计算的结果vColor,非常简单,用关键字varying声明一个与同名变量vColor就行。

const material = new THREE.ShaderMaterial({
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  vertexColors:true,//允许设置使用顶点颜色渲染
});

颜色贴图

什么的uv坐标?

在这里插入图片描述

纹理与网格模型面不同区域之间的映射关系是由几何体自身的UV坐标决定的。

// 使用缓冲区来存储顶点数据
const geometry = new THREE.BufferGeometry();
// 定义顶点位置
const vertices = new Float32Array([
    0, 0, 0, //顶点1坐标
    160, 0, 0, //顶点2坐标
    160, 80, 0, //顶点3坐标
    0, 80, 0, //顶点4坐标
]);
// 将顶点数据存储在几何体的 attributes 属性中。vertices 是顶点数据,3 表示每个顶点由三个值(x, y, z)组成。
geometry.attributes.position = new THREE.BufferAttribute(vertices, 3);
// 定义了矩形的两个三角形的顶点索引
const indexes = new Uint16Array([
    0, 1, 2, 0, 2, 3,// 矩形两个三角形对应顶点位置索引值
])
// 用于将索引数据存储在几何体的 index 属性中
geometry.index = new THREE.BufferAttribute(indexes, 1);
/**纹理坐标0~1之间随意定义*/
const uvs = new Float32Array([
    0, 0, //图片左下角
    1, 0, //图片右下角
    1, 1, //图片右上角
    0, 1, //图片左上角
]);
geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2); //2个为一组,表示一个顶点的纹理坐标

1.对顶点uv坐标进行插值计算

const vertexShader = `
// attribute vec2 uv;//默认提供,不用自己写
varying vec2 vUv;
void main(){
  vUv = uv;// UV坐标插值计算
  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`

2.声明颜色贴图变量

uniform关键字声明一个颜色贴图相关的变量map,纹理贴图对应数据类型需要用关键字sampler2D表示。

// 片元着色器代码
const fragmentShader = `
uniform sampler2D map;//颜色贴图变量
void main() {
    gl_FragColor = vec4(0.0,1.0,1.0,1.0);
}
`

3.给uniform变量map传值

const texture = new THREE.TextureLoader().load('./Earth.png');
const material = new THREE.ShaderMaterial({
  uniforms: {
    // 给着色器中同名uniform变量map传值
    map: {value: texture},
  },
  vertexShader: vertexShader,// 顶点着色器
  fragmentShader: fragmentShader,// 片元着色器
});

4.采样纹理赋值给gl_FragColor

执行varying vec2 vUv;从顶点着色器获取插值后UV坐标vUv,然后执行texture2D( map, vUv );从颜色贴图上提取像素值,赋值给模型的片元gl_FragColor。

// 片元着色器代码
const fragmentShader = `
uniform sampler2D map;//颜色贴图变量
varying vec2 vUv;
void main() {
    // 通过几何体的UV坐标从颜色贴图获取像素值
    gl_FragColor = texture2D( map, vUv );
}
`

模仿点材质效果

以这个距离距离r作为临界值,每个方形点的所有片元凡是距离中心vec2(0.5, 0.5)的距离大于r,都舍弃,就会生成一个圆形的点。

// 片元着色器代码
const fragmentShader = `
void main() {
  // vec2(0.5, 0.5)是方形点的圆心
  float r = distance(gl_PointCoord, vec2(0.5, 0.5));
  if(r < 0.5){
    // 方形区域片元距离几何中心半径小于0.5,像素颜色设置红色
    gl_FragColor = vec4(0.0,1.0,1.0,1.0);
  }else {
    // 方形区域距离几何中心半径不小于0.5的片元剪裁舍弃掉:
    discard;
  }
}
`

查看shader代码

你可以通过.onBeforeCompile函数的参数shader获取材质的着色器代码,其实你对照下,就会发现这些代码的来自\src\renderers\shaders\ShaderLib目录下shader文件。

const material = new THREE.MeshLambertMaterial();
material.onBeforeCompile = function (shader) {
  console.log('shader', shader);
  console.log('顶点着色器', shader.vertexShader);
  console.log('片元着色器', shader.fragmentShader);
}

更多推荐