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课程的学习笔记

相关推荐
汪洪墩4 小时前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
m0_748234346 小时前
webGL硬核知识:图形渲染管渲染流程,各个阶段对应的API调用方式
图形渲染·webgl
每日出拳老爷子6 小时前
【图形渲染】【Unity Shader】【Nvidia CG】有用的参考资料链接
unity·游戏引擎·图形渲染
MossGrower1 天前
36. Three.js案例-创建带光照和阴影的球体与平面
3d图形·webgl·three.js·光照与阴影
MossGrower1 天前
34. Three.js案例-创建球体与模糊阴影
webgl·three.js·3d渲染·阴影效果
程序员_三木3 天前
Three.js资源-贴图材质网站推荐
javascript·webgl·three.js·材质·贴图
程序员_三木3 天前
React和Three.js结合-React Three Fiber
前端·javascript·react.js·前端框架·webgl·材质
MossGrower3 天前
37. Three.js案例-绘制部分球体
3d图形·webgl·three.js·球体几何体
吃豆腐长肉3 天前
着色器 (三)
opengl·着色器
吃豆腐长肉3 天前
opengl 着色器 (四)最终章收尾
opengl·着色器