在 Three.js 中创建自定义着色器
三个 JS
着色器
资源
浏览器中的 3D 内容真是太棒了。在学校玩了一段时间 Three.js,做了个小游戏之后,我开始对它爱不释手。一位对图形编程非常感兴趣的同学跟我讲了一些 WebGL 和着色器的知识。这感觉很酷,我决定自己做一个着色器。当然,后来又有其他新奇的东西吸引了我的注意,后来就忘了。不过,从今天开始,我终于可以说我已经创建了一个着色器,并在 Three.js 中使用了它。
三个 JS
在深入探讨着色器之前,最好先解释一下Three.js是什么。Three.js 是一个 JavaScript 库,用于简化在画布上创建 3D 场景的过程。其他流行的解决方案,例如a-frame和whitestorm.js,都是基于它构建的。如果你曾经尝试过这些,但想要更强大的控制力,一定要尝试一下!(如果你是 TypeScript 爱好者,Three.js 有类型定义😉)。
这个库最受欢迎的入门教程是创建一个立方体并使其旋转。ThreeJS文档中有一个书面教程,CJ Gammon 也在他的“深入学习:ThreeJS”系列中提供了一个精彩的 YouTube 教程。
创建这个立方体基本上就是准备一个电影场景,并将其放置在其中。你需要创建一个场景和一个摄像机,并将它们传递给渲染器,告诉它:“嘿,这是我的电影场景”。然后,你可以在场景中放置网格,网格本质上是一个对象。这个网格由几何体(对象的形状)和材质(颜色、对光的行为等等)组成。根据你选择的材质,你可能需要向场景添加不同类型的灯光。为了使对象动起来并真正显示所有内容,你需要创建一个循环。在这个循环中,你告诉渲染器显示场景。你的代码可能如下所示:
window.addEventListener('load', init)
let scene
let camera
let renderer
let sceneObjects = []
function init() {
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 5
renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
adjustLighting()
addBasicCube()
animationLoop()
}
function adjustLighting() {
let pointLight = new THREE.PointLight(0xdddddd)
pointLight.position.set(-5, -3, 3)
scene.add(pointLight)
let ambientLight = new THREE.AmbientLight(0x505050)
scene.add(ambientLight)
}
function addBasicCube() {
let geometry = new THREE.BoxGeometry(1, 1, 1)
let material = new THREE.MeshLambertMaterial()
let mesh = new THREE.Mesh(geometry, material)
mesh.position.x = -2
scene.add(mesh)
sceneObjects.push(mesh)
}
function animationLoop() {
renderer.render(scene, camera)
for(let object of sceneObjects) {
object.rotation.x += 0.01
object.rotation.y += 0.03
}
requestAnimationFrame(animationLoop)
}
着色器
着色器本质上是一些由 GPU 执行的函数或小脚本。这正是WebGL和GLSL(OpenGL 着色语言)发挥作用的地方。WebGL 是一个浏览器 API,允许 JavaScript 在 GPU 上运行代码。这可以提高某些脚本的性能,因为你的 GPU 针对图形相关计算进行了优化。WebGL 甚至允许我们用 GLSL 语言编写直接由 GPU 执行的代码。这些 GLSL 代码片段就是我们的着色器,由于 Three.js 拥有 WebGL 渲染器,我们可以编写着色器来修改网格。在 Three.js 中,你可以使用“着色器材质”创建自定义材质。该材质接受两个着色器:一个顶点着色器和一个片段着色器。让我们尝试制作“渐变材质”。
顶点着色器
顶点着色器是一个作用于网格每个顶点(点)的函数。它通常用于扭曲或动画网格的形状。在我们的脚本中,它看起来像这样:
function vertexShader() {
return `
varying vec3 vUv;
void main() {
vUv = position;
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
`
}
您可能注意到的第一件事是,我们所有的 GLSL 代码都包含在一个字符串中。这样做是因为 WebGL 会将这段代码传递给我们的 GPU,而我们必须在 JavaScript 中将代码传递给 WebGL。您可能注意到的第二件事是,我们使用了并非我们创建的变量。这是因为 ThreeJS 会将这些变量传递给 GPU。
在这段代码中,我们计算了网格点的放置位置。我们通过将网格在场景中的位置(modelViewMatrix)乘以点的位置来计算这些点在场景中的位置。之后,我们将这个值乘以相机与场景的相对位置(projectionMatrix),这样我们的着色器就能遵循 ThreeJS 中的相机设置。gl_Position 是 GPU 用来绘制点的值。
目前,这个顶点着色器不会改变我们的形状。那么,为什么还要创建它呢?我们需要网格各部分的位置来创建漂亮的渐变。通过创建一个“可变”变量,我们可以将位置传递给另一个着色器。
片段着色器
片段着色器是一个应用于网格每个片段的函数。片段是光栅化过程的结果,该过程将整个网格转换为三角形的集合。网格覆盖的每个像素至少对应一个片段。片段着色器通常用于对像素进行颜色变换。我们的片段着色器如下所示:
return `
uniform vec3 colorA;
uniform vec3 colorB;
varying vec3 vUv;
void main() {
gl_FragColor = vec4(mix(colorA, colorB, vUv.z), 1.0);
}
`
}
如你所见,我们获取了顶点着色器传递的位置值。我们希望根据片段在网格 z 轴上的位置,混合颜色 A 和 B。但是颜色 A 和 B 从何而来呢?它们是“统一”变量,这意味着它们是从外部传入着色器的。混合函数将计算我们想要为该片段绘制的 RGB 值。这个颜色和一个额外的不透明度值被传递给 gl_FragColor。我们的 GPU 会将片段的颜色设置为这个颜色。
创建材质
现在我们已经创建了着色器,我们最终可以使用自定义材质构建我们的 threejs 网格。
function addExperimentalCube() {
let uniforms = {
colorB: {type: 'vec3', value: new THREE.Color(0xACB6E5)},
colorA: {type: 'vec3', value: new THREE.Color(0x74ebd5)}
}
let geometry = new THREE.BoxGeometry(1, 1, 1)
let material = new THREE.ShaderMaterial({
uniforms: uniforms,
fragmentShader: fragmentShader(),
vertexShader: vertexShader(),
})
let mesh = new THREE.Mesh(geometry, material)
mesh.position.x = 2
scene.add(mesh)
sceneObjects.push(mesh)
}
一切就绪,一切就绪了。我们的“uniform”colorA 和 colorB 被创建,并连同顶点着色器和片段着色器一起传递到着色器材质中。材质和几何体用于创建网格,并将网格添加到场景中。
我在 Glitch 里做了这个。一个朋友推荐的,效果很棒!不过有些插件会阻止你加载嵌入文件,所以为了以防万一,这里直接提供一个链接。
左边的立方体使用了网格兰伯特材质,右边的立方体使用了我们自己的“渐变材质”。正如你所见,我们的材质看起来很漂亮,但却忽略了场景中的光照设置。这是因为我们在片段着色器中没有进行光照计算。希望我能尽快解决这个问题😝。
资源
我花了一些时间来弄清楚这一点,如果你喜欢这个,你真的应该看看我用来学习和理解这一点的资料:
鏂囩珷鏉ユ簮锛�https://dev.to/maniflames/creating-a-custom-shader-in-thirdjs-3bhi