Three.js 创建自定义材质

问题

让我们从以下示例开始。

如果尚未设置项目,请从此处克隆 Three.js 模板并复制以下代码。

js 复制代码
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 设置背景颜色。
renderer.setClearColor(0xfefefe);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

// 设置轨道控制以移动相机。
const orbit = new OrbitControls(camera, renderer.domElement);

camera.position.set(0, -2, 300);
camera.lookAt(0, 0, 0);

const ambLight = new THREE.AmbientLight(0xa3a3a3, 0.8);
scene.add(ambLight);

const dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(2, 2, 8);
scene.add(dirLight);

const geo = new THREE.IcosahedronGeometry(4, 30);
const material1 = new THREE.MeshPhysicalMaterial();
for (let i = 0; i < 500; i++) {
  const mesh = new THREE.Mesh(geo, material1);
  mesh.position.x = Math.random() * 1000 - 500;
  mesh.position.y = Math.random() * 1000 - 500;
  mesh.position.z = Math.random() * 1000 - 500;
  mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
  scene.add(mesh);
}

function animate() {
  renderer.render(scene, camera);
}

renderer.setAnimationLoop(animate);

window.addEventListener('resize', function () {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

完整示例

我们有 500 个网格,都使用 MeshPhysicalMaterial,可以清楚地看到光线如何影响它们。

现在,假设我们根据视窗中的位置动态着色它们。

为此,我们需要使用自定义材质。

首先,我们将创建一个变量,例如分辨率。此外,我们还将创建一个 ShaderMaterial 实例。

js 复制代码
const uniforms = {
  u_resolution: {
    value: new THREE.Vector2(window.innerWidth, window.innerHeight),
  },
};

const material2 = new THREE.ShaderMaterial({
  uniforms,
  vertexShader: document.getElementById('vertexshader').textContent,
  fragmentShader: document.getElementById('fragmentshader').textContent,
});

const geo = new THREE.IcosahedronGeometry(4, 30);
// const material1 = new THREE.MeshPhysicalMaterial();
for (let i = 0; i < 500; i++) {
  // const mesh = new THREE.Mesh(geo, material1);
  const mesh = new THREE.Mesh(geo, material2);
  mesh.position.x = Math.random() * 1000 - 500;
  mesh.position.y = Math.random() * 1000 - 500;
  mesh.position.z = Math.random() * 1000 - 500;
  mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
  scene.add(mesh);
}

当然,我们还需要创建顶点和片段着色器。

html 复制代码
<script id="vertexshader" type="vertex">
  void main() {
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
</script>
<script id="fragmentshader" type="fragment">
  uniform vec2 u_resolution;
  void main() {
    vec2 st = gl_FragCoord.xy / u_resolution.xy;

    gl_FragColor = vec4(vec3(st.x / 0.6, st.y / 1.8, (st.x + st.y) / 1.2), 1.0);
  }
</script>

完整示例。

一切似乎都正常......对吗?

实际上并非如此。

正如你所见,我们失去了一个重要细节:球体如何响应场景中的光线。

在 Three.js 中使用 ShaderMaterial 时,你不仅会失去着色,还会失去金属度、粗糙度、反射等重要信息。

"那么,如何在保持这些重要细节的同时创建自定义材质呢?"

我们有三种选择。

第一种是在片段着色器中手动处理计算。

第二种是通过使用 onBeforeCompile() 替换现有材质的部分 GLSL 代码来扩展它。

第三种与第二种类似,但我们使用一个库。这正是我们要做的。

Three 自定义着色器材质

Three 自定义着色器材质是一个库,允许你在现有材质的基础上构建新材料。

首先,我们通过在终端中运行以下命令来安装该库:

npm install three-custom-shader-material

接下来,我们导入它。

javascript 复制代码
import CustomShaderMaterial from 'three-custom-shader-material/vanilla';

注意:如果你使用的是标准 Three.js 而不是 React Three Fiber,请确保在路径中添加 '/vanilla'。

现在,我们只需创建一个 CustomShaderMaterial 的实例,并使用 baseMaterial 属性扩展材质。

js 复制代码
const material3 = new CustomShaderMaterial({
  baseMaterial: THREE.MeshPhysicalMaterial,
});
js 复制代码
// const mesh = new THREE.Mesh(geo, material2);
const mesh = new THREE.Mesh(geo, material3);

正如你所见,一切如预期般工作。此外,我们还可以设置 MeshPhysicalMaterial 的颜色、贴图等属性。

js 复制代码
const material3 = new CustomShaderMaterial({
  baseMaterial: THREE.MeshPhysicalMaterial,
  color: 0xffea00,
  metalness: 0.6,
  roughness: 0.4,
});

现在,让我们回到我之前的例子。

我们将设置片段着色器并传递分辨率变量。

js 复制代码
const material3 = new CustomShaderMaterial({
  baseMaterial: THREE.MeshPhysicalMaterial,
  color: 0xffea00,
  metalness: 0.6,
  roughness: 0.4,
  fragmentShader: document.getElementById('fragmentshader').textContent,
  uniforms: {
    u_resolution: {
      value: new THREE.Vector2(window.innerWidth, window.innerHeight),
    },
  },
});

这样做不会改变任何东西,因为我们需要在片段着色器中进行一些更新。

目前,gl_FragColor 是着色器的输出。

但使用此库时,你肯定不想修改那个变量。相反,我们需要使用一组预定义的变量。

在我们的情况下,我们将使用 csm_DiffuseColor 变量。确保我们只修改材质的颜色,同时保留其他数据,例如阴影计算。

js 复制代码
csm_DiffuseColor = vec4(vec3(st.x / 0.6, st.y / 1.8, (st.x + st.y) / 1.2), 1.0);
// gl_FragColor = vec4(vec3(st.x / 0.6, st.y / 1.8, (st.x + st.y) / 1.2), 1.0);

完整代码

现在,我们使用顶点着色器网格设置顶点动画。

为此,我们将顶点着色器添加到 CustomShaderMaterial 实例中。

js 复制代码
const material3 = new CustomShaderMaterial({
    baseMaterial: THREE.MeshPhysicalMaterial,
    color: 0xFFEA00,
    metalness: 0.6,
    roughness: 0.4,
    uniforms:{
        u_resolution: {value: new THREE.Vector2(window.innerWidth, window.innerHeight)},
        u_time: {value: 0}
    },
    vertexShader: document.getElementById('vertexshader').textContent,
    fragmentShader: document.getElementById('fragmentshader').textContent
});
js 复制代码
const clock = new THREE.Clock();
function animate() {
  material3.uniforms.u_time.value = clock.getElapsedTime();
  renderer.render(scene, camera);
}

我们需要在顶点着色器中使用一个名为 csm_Position 的特殊变量。

js 复制代码
uniform float u_time;
void main() {
  vec3 newPosition = position * sin(u_time);
  csm_Position = newPosition;
  // gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

完整示例。

总结

至此,本文结束。

现在,你可以尽情发挥创意使用着色器。

再见!

原文:waelyasmina.net/articles/cr...

相关推荐
Aphasia3118 分钟前
快速上手tailwindcss
前端·css·面试
程序员荒生20 分钟前
基于 Next.js 搞定个人公众号登陆流程
前端·微信·开源
deckcode34 分钟前
css基础-选择器
前端·css
倔强青铜三34 分钟前
WXT浏览器开发中文教程(2)----WXT项目目录结构详解
前端·javascript·vue.js
1024小神39 分钟前
html5-qrcode前端打开摄像头扫描二维码功能
前端·html·html5
beibeibeiooo39 分钟前
【Vue3入门2】01-图片轮播示例
前端·vue.js
倔强青铜三39 分钟前
WXT浏览器开发中文教程(1)----安装WXT
前端·javascript·vue.js
2301_815357701 小时前
Spring:IOC
java·前端·spring
萌萌哒草头将军1 小时前
🍍Pinia党福音,🍍Pinia伴侣:🍍pinia-colada
前端·javascript·vue.js
pe7er1 小时前
nuxtjs3使用同一个编译产物运行在多个环境中
前端·javascript