three.js着色器材质

当Three.js内置的材质不能满足需求时,就需要通过编写着色器来实现了

也可能是出于性能原因。像MeshStandardMaterial这样的材料非常复杂,涉及大量的代码和计算。如果我们编写自己的着色器,我们可以将功能和计算保持在最低限度。我们可以更好地控制性能。

编写指定要着色器也是向渲染结果添加后处理效果的绝佳方式

[1]RawShaderMaterial

要创建一个着色器,我们需要创建一个特定的材质。这个特定的材质可以时着色器材质ShaderMaterial或者原始材质RawShaderMaterial, 它们的区别是ShaderMaterial会自动将一些代码添加到着色器代码中,而RawShaderMaterial则不会。

这里从比较原始的RawShaderMaterial实验:

typescript 复制代码
// Geometry
const geometry = new THREE.PlaneGeometry(1, 1, 32, 32)

// Material
const material = new THREE.RawShaderMaterial({
    vertexShader: `
      uniform mat4 projectionMatrix;
      uniform mat4 viewMatrix;
      uniform mat4 modelMatrix;
      
      attribute vec3 position;
      
      void main(){
          gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
      }
    `,
    fragmentShader: `
      precision mediump float;
      
      void main(){
          gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
  `
  })

// Mesh
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

更好的写法是将着色器拆分写到不同文件中

这里可以分别创建vertex.glslfragment.glsl文件,将组织到某个文件夹下,因为一个项目通常有很多个着色器代码。

在vite中import shader文件(.glsl)问题

在vite中import shader文件(.glsl)问题,不需要安装插件,只需在导入文件的结尾添加一个?raw符号,vite会将改文件的内容解析为字符串导入
import vertexShader from "./../shader/particels_vs.glsl?raw"
import fragmentShader from "./../shader/particels_fs.glsl?raw"

使用其他打包构建工具估计也需要进行配置才能使用.glsl文件

例如在webpack中则需要添加:一个rules

javascript 复制代码
{
    test: /\.(glsl|vs|fs|vert|frag)$/,
    type: 'asset/source',
    generator:
    {
        filename: 'assets/images/[hash][ext]'
    }
}

[2]Properties

在其他常见材质中使用的属性(例如线框、侧面、透明或平面着色)仍然可用于 RawShaderMaterial

typescript 复制代码
const material = new THREE.RawShaderMaterial({
  vertexShader: testVertexShader,
  fragmentShader: testFragmentShader,
  wireframe: true //网格线条
})

但是map贴图、alphaMapopacitycolor等属性将不再起作用,因为我们需要自己在着色器中编写这些功能。

[3]补充说明

首先看看MVP矩阵,这里的MVP矩阵于WEBGL中有些不同

glsl 复制代码
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
  • modelMatrix 将应用与Mesh相关的所有变换。如果我们缩放、旋转或移动网格,这些转换将包含在 中 modelMatrix 并应用于 position
  • viewMatrix 将应用相对于相机的变换。如果我们将相机向左旋转,顶点应该在右侧。如果我们沿网格体的方向移动相机,顶点应该会变大,依此类推。
  • 最终 projectionMatrix 会将我们的坐标转换为最终的剪辑空间坐标。

参考:LearnOpenGL - Coordinate Systems

关于片段着色器的精度:

当我们使用ShaderMaterial而不是RawShaderMaterial时,这部分会自动处理。

[4]内置函数

开始上点费脑的内置函数

glsl 复制代码
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;

attribute vec3 position;

void main(){
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    modelPosition.z += sin(modelPosition.x * 10.0) * 0.1;
    vec4 viewPosition = viewMatrix * modelPosition;
    vec4 projectedPosition = projectionMatrix * viewPosition;

    gl_Position = projectedPosition;
}

[5]传递Attributes

可以直接将要添加的Attributes属性直接添加到 BufferGeometry 中,例如下面传递一些随机值到顶点着色器重

javascript 复制代码
const count = geometry.attributes.position.count //获取几何体的顶点数
const randoms = new Float32Array(count)

for(let i = 0; i < count; i++){
  randoms[i] = Math.random()
}

geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1))//传递

然后就可以直接在顶点着色器中使用了:

glsl 复制代码
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;

attribute float aRandom;
attribute vec3 position;

void main(){
  vec4 modelPosition = modelMatrix * vec4(position, 1.0);
  // modelPosition.z += sin(modelPosition.x * 10.0) * 0.1;
  modelPosition.z += aRandom * 0.1;
  vec4 viewPosition = viewMatrix * modelPosition;
  vec4 projectedPosition = projectionMatrix * viewPosition;

  gl_Position = projectedPosition;
}

还可以使用varying传递给片元着色器玩玩:

glsl 复制代码
varying float vRandom;

void main(){
  // ...
  
  vRandom = aRandom;
}
glsl 复制代码
precision mediump float;

varying float vRandom;

void main(){
    gl_FragColor = vec4(0.5, vRandom, 1.0, 1.0);
}

[6]传递Uniforms

与传递attribute不同的是传递Uniforms直接通过RawShaderMaterial的属性传递即可

javascript 复制代码
const material = new THREE.RawShaderMaterial({
    vertexShader: testVertexShader,
    fragmentShader: testFragmentShader,
    uniforms:{
        uFrequency: { value: new THREE.Vector2(10, 5) } //传递一个波动频率
    }
})

使用

glsl 复制代码
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform float uFrequency;

attribute float aRandom;
attribute vec3 position;

varying float vRandom;

void main(){
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    modelPosition.z += sin(modelPosition.x * uFrequency.x) * 0.1;
		modelPosition.z += sin(modelPosition.y * uFrequency.y) * 0.1;
    vec4 viewPosition = viewMatrix * modelPosition;
    vec4 projectedPosition = projectionMatrix * viewPosition;

    gl_Position = projectedPosition;

    vRandom = aRandom;

}

因为这些值是直接在在传递到着色器的,那么就可以动态控制传递过去,比如通过dat.gui控制

javascript 复制代码
gui.add(material.uniforms.uFrequency.value, 'x').min(0).max(20).step(0.01).name('frequencyX')
gui.add(material.uniforms.uFrequency.value, 'y').min(0).max(20).step(0.01).name('frequencyY')

还可以通过模型构建类似于shadtory里的一个自带的uTime变量,让图形动起来

javascript 复制代码
const material = new THREE.RawShaderMaterial({
  vertexShader: testVertexShader,
  fragmentShader: testFragmentShader,
  uniforms:{
    uFrequency: { value: new THREE.Vector2(10, 5) },
    uTime: { value: 0 }
  }
})

在绘制函数中填值即可

javascript 复制代码
const tick = () =>{
  const elapsedTime = clock.getElapsedTime()
  
  // Update material
  material.uniforms.uTime.value = elapsedTime
  
  // ...
}
glsl 复制代码
// ...
uniform float uTime;

// ...

void main(){
  // ...
  modelPosition.z += sin(modelPosition.x * uFrequency.x + uTime) * 0.1;
  modelPosition.z += sin(modelPosition.y * uFrequency.y + uTime) * 0.1;

  // ...
}

这样就动起来了

后面还可以传递更多的uniform,比如color等这里就不演示了

[7]纹理贴图

这里讨论的是给RawShaderMaterial材质添加纹理图片,这里加载映射纹理同原始的webgl一样,需要uv坐标texture2D(uTexture, vUv);这里可以直接将加载好的纹理通过uniform传递到片段着色器:

glsl 复制代码
const flagTexture = textureLoader.load('/textures/flag-french.jpg')
const material = new THREE.RawShaderMaterial({
  // ...
  uniforms:{
  // ...
  uTexture: { value: flagTexture }
  }
})

这里因为geometry会自动构建uv坐标到attribute上,所以这里就可以直接在顶点着色器中获取到

glsl 复制代码
// ...
attribute vec2 uv;

varying vec2 vUv;

void main(){
// ...
vUv = uv;
}
glsl 复制代码
precision mediump float;

uniform vec3 uColor;
uniform sampler2D uTexture;

varying vec2 vUv;

void main(){
  vec4 textureColor = texture2D(uTexture, vUv);
  gl_FragColor = textureColor;
}

[8]ShaderMaterial

ShaderMaterial相比于ShaderMaterial要方便一些,内置了一些变量

如下变量就不需要在着色器中定义就可以直接在glsl中使用了

glsl 复制代码
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
attribute vec3 position;
attribute vec2 uv;
precision mediump float;

本文部分内容为Three.js Journey课程的学习笔记

相关推荐
小彭努力中20 小时前
138. CSS3DRenderer渲染HTML标签
前端·深度学习·3d·webgl·three.js
小春熙子1 天前
Unity图形学之着色器之间传递参数
unity·游戏引擎·技术美术·着色器
优雅永不过时·1 天前
three.js实现地球 外部扫描的着色器
前端·javascript·webgl·three.js·着色器
汪洪墩2 天前
【Mars3d】实现这个地图能靠左,不居中的样式效果
前端·javascript·vue.js·3d·webgl·cesium
allenjiao2 天前
webgl threejs 云渲染(服务器渲染、后端渲染)解决方案
webgl·云渲染·threejs·服务器渲染·后端渲染·云流化·三维云渲染
踏实探索2 天前
OpenLayers教程12_WebGL自定义着色器:实现高级渲染效果
前端·arcgis·vue·webgl·着色器
EasyNTS3 天前
H.265流媒体播放器EasyPlayer.js网页直播/点播播放器WebGL: CONTEXT_LOST_WEBGL错误引发的原因
javascript·webgl·h.265
YxVoyager4 天前
【OpenGL】OpenGL简介
c++·windows·图形渲染
那年那棵树5 天前
【Cesium】自定义材质,添加带有方向的滚动路线
vue·webgl·材质
杳戢5 天前
凹凸/高度贴图、法线贴图、视差贴图、置换贴图异同
unity·图形渲染·贴图·技术美术