着色器(Shader)是在 GPU 上运行的程序。它们被称为着色器的原因是,最初它们只处理3D对象的着色,但后来扩展到了3D对象之外。它们需要与传统编程不同的思维方式,因为程序是针对每个顶点或像素并行运行的。
WebGL和OpenGL使用一种名为 GLSL 的语言,它代表OpenGL 着色器语言,类似于 C 语言。在 Three.js 中添加着色器的最简单方法是使用ShaderMaterial。还有一些RawShaderMaterial,它没有包括一些three.js GLSL代码。
在 WebGL 中有顶点(Vertex)着色器和片段(Fragment)着色器。在顶点着色器中,你可以操作几何图形的顶点,在片段着色器中,你可以操作渲染的三角形的像素。在实时图形中,一切都被简化为三角形。顶点着色器返回 2D 位置,因此它使用投影矩阵从 3D 投影到 2D。基本顶点着色器将如下所示:
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
该函数必须命名为 main,并且你必须设置内置变量gl_Position 。projectionMatrix、modelViewMatrix和position由three.js提供。从右到左查看矩阵乘法。 modelViewMatrix是视图矩阵要相乘的模型矩阵。视图矩阵是相机的反变换 - 移动相机和反方向移动模型等效。模型矩阵是在模型上完成的转换。projectionMatrix 负责从3D到2D的投影。
基本片段着色器将如下所示:
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
同样,该函数需要命名为main,并且需要设置内置变量gl_FragColor 。在上例中,我们将每个像素设置为红色。要创建着色器材料,请将顶点着色器和片段着色器指定为字符串,否则three.js将使用默认着色器。
const vertexShader = /*glsl*/`
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = /*glsl*/`
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
const material = new ShaderMaterial({
fragmentShader: fragmentShader,
vertexShader: vertexShader
});
如果你在 VS Code 中安装了标记Comment tagged templates和 Shader language support插件,则在上面的代码中字符串前面的/glsl/注释会添加语法突出显示。使用模板文本,即反引号,可以在字符串中包含多行。你还可以在 HTML 文件的脚本标记中包含 GLSL:
并访问脚本中的 DOM 元素:
const material = new ShaderMaterial({
fragmentShader: document.getElementById('fragment-shader').textContent
});
或者,你可以将着色器保存在单独的文件中,并在使用 Webpack 或 Parcel 等打包工具时导入它们。对于Webpack,你需要安装raw loader,对于Parcel,你需要安装@parcel/transformer-glsl插件。
信息从顶点着色器传递到片段着色器,在片段着色器中,根据像素相对于顶点的位置对值进行插值。例如,如果为每个顶点指定一种颜色并将其向下传递到片段着色器,则像素颜色将从顶点颜色中插值。
着色器具有几种不同类型的变量:
Uniforms是将数据从 JavaScript 传递到着色器的一种方法。
this.material = new ShaderMaterial({
uniforms: {
uTime: { value: 0 }
},
fragmentShader: fragmentShader
});
然后在渲染函数中,我们可以更新Uniforms值:
render() {
this.material.uniforms.uTime.value++;
this.renderer.render(this.scene, this.camera);
}
在顶点或片段着色器中,可以像这样访问它:
uniform float uTime
void main() {
gl_FragColor = vec4(vec3(abs(sin(uTime))), 1.0);
}
Varyings对于将值从顶点着色器传递到片段着色器非常有用。下面,我将一个Varying设置为three.js内置变量:
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
在片段着色器中访问它:
varying vec2 vUv;
uniform sampler2D uImage;
void main() {
gl_FragColor = texture2D(uImage, vUv);
}
在脚本中设置纹理:
const image = new Image();
image.onload = function() {
const texture = new Texture(image);
const material = new ShaderMaterial({
uniforms: {
uTexture: { value: texture }
},
fragmentShader
});
}
image.src = '/images/some_image.jpg';
我通常用 u 作为 uniform 的前缀,用 v 作为 varying 的前缀,但这不是必需的。
设置属性时,Three.js 要求一个类型化数组。类型化数组就是由于WebGL的需要而被添加到了JavaScript中。 默认情况下,BufferGeometry提供position 、normal 和uv属性。如果要使用顶点颜色,则需要在材质的选项中设置一个称为color 的属性并将vertexColors设置为 true。
const geometry = new BufferGeometry();
const displacement = new Float32Array([0, 0.5, 1]);
geometry.setAttribute('displacement', new BufferAttribute(displacement, 1));
可以添加一个标准的 JavaScript 数组,并用three.js将其转换为类型化数组。如果以编程方式构造数组,则可能需要执行此操作:
const vertices = [];
for (let i = 0; i < 3; i++) {
vertices.push(Math.cos(i), Math.sin(i), 0);
}
geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
在着色器中访问属性:
attribute float displacement;
void main() {
vec3 newPosition = position + normal * vec3(displacement);
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
节点模块在编写JavaScript时非常有用。GLSL没有一个官方的模块系统,但有一个非官方的模块系统,叫做Glslify。首先,需要将Glslify模块与 GLSL 模块一起安装:
npm install glslify glsl-noise
将着色器包装在glsl函数中,并将 GLSL 模块导入着色器中:
import glsl from 'glslify';
const fragmentShader = glsl(`
#pragma glslify: noise = require('glsl-noise/simplex/3d');
varying vec3 vPosition;
void main() {
gl_FragColor = vec4(noise(vPosition), 1.0);
}
`);
有一个用于webpack的 Glslify 加载器,因此可以在外部着色器文件中使用 Glslify。
npm install raw-loader glslify-loader
Webpack配置看起来像这样:
module: {
rules: [
{
test: /\.(glsl|vs|fs|vert|frag)$/,
exclude: /node_modules/,
use: ['raw-loader', 'glslify-loader']
}
]
}
Parcel通过@parcel/transformer-glsl插件支持Glslify。
下面是具有顶点着色器和片段着色器的球体示例。顶点着色器使用噪声函数置换顶点,片段着色器使用噪声函数再次混合红色、绿色和蓝色。默认情况下,几何图形中的顶点处于断开连接状态。如果移动顶点,它只会针对它所附着的面移动它,因此面将断开连接。要保持面连接,你必须将顶点转换为索引顶点,即通过索引引用相同的顶点,而不是为每个面复制它。将着色器变量作为uniform的一个优点是,你可以附加 GUI 并对其进行调整。可以在此处查看该示例的源代码。
原文链接:Three.js着色器基础 — BimAnt