Three.js 使用 WebGPU 的关键 TSL
TSL: three.js shader language
介绍 three.js 材质转为webgpu的关键流程, 从而引出 TSL.
1、关键类关系
WebGPURenderer
|-- library: StandardNodeLibrary
|-- _nodes: Nodes
|-- _objects: RenderObjects
|-- createRenderObject()
StandardNodeLibrary extends NodeLibrary
|-- materialNodes: Map
|-- fromMaterial()
Nodes
|-- backend: WebGPUBackend
|-- needsRefresh()
|-- getForRender()
RenderObjects
RenderObject
|-- _nodes: Nodes
|-- _monitor: NodeMaterialObserver
|-- _nodeBuilderState: NodeBuilderState
|-- getNodeBuilderState()
|-- getMonitor()
WebGPUBackend
WGSLNodeBuilder
|-- object: Mesh
|-- stack: StackNode // 被代理
|-- renderer: WebGPURenderer
|-- cache: NodeCatche
|-- build()
NodeFrame
NodeBuilderState
|-- binding: BindGroup[]
|-- updateNodes: NodeAttribute []
|-- nodeAttributes: UniformNode []
NodeMaterialObserver
|-- monitor: NodeMaterialObserver
NodeMaterial
|-- build()
|-- setup()
MeshBasicNodeMaterial extends NodeMaterial
2、执行流程
2.1 注册材质解析类
在创建 WebGPURenderer 时,会创建 StandardNodeLibrary。
StandardNodeLibrary.addMaterial( MeshBasicNodeMaterial, 'MeshBasicMaterial' );
2.2 创建 WGSLNodeBuilder
这个 WGSLNodeBuilder 是和 Mesh 一一对应。
WebGPURenderer.render()._renderObjectDirect()
// step1: 创建 RenderObject
const renderObject: RenderObject = this._objects.get();
// step2: 创建 WGSLNodeBuilder
this._nodes.needsRefresh( renderObject );
详细调用过程:
Nodes.needsRefresh() {
const monitor = renderObject.getMonitor();
}
RenderObject.getMonitor() {
return this._monitor = this.getNodeBuilderState().monitor;
}
RenderObject.getNodeBuilderState() {
return this._nodeBuilderState = this._nodes.getForRender();
}
Nodes.getForRender() {
const nodeBuilder: WGSLNodeBuilder = this.backend.createNodeBuilder();
nodeBuilder.build();
}
2.3 开始构建
// 书接上文, 创建后开始构建
nodeBuilder.build() {
// 根据材质类型,获取对应的 NodeMaterial
let nodeMaterial = renderer.library.fromMaterial( material );
if ( nodeMaterial === null ) {
console.error( `NodeMaterial: Material "${ material.type }" is not compatible.` );
nodeMaterial = new NodeMaterial();
}
nodeMaterial.build( this );
}
这里以MeshBasicMaterial为例, 那么nodeMaterial就是 MeshBasicNodeMaterial。
NodeMaterial.build() {
this.setup( this.nodeBuilder );
}
NodeMaterial.setup() {
const vertexNode = this.setupVertex()
}
NodeMaterial.setupVertex() {
return modelViewProjection;
}
// modelViewProjection 被引入:
import { modelViewProjection } from '../../nodes/accessors/ModelViewProjectionNode.js';
至此, 就拉开了 TSL 的序幕。
附录、测试例子
<html lang="en">
<body>
<div id="container"></div>
<script type="importmap">
{
"imports": {
"three": "../build/three.webgpu.js",
"three/webgpu": "../build/three.webgpu.js",
"three/tsl": "../build/three.tsl.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
let container, stats;
let camera, scene, renderer;
let controls, water, sun, mesh;
init();
function init() {
container = document.getElementById('container');
//
renderer = new THREE.WebGPURenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.5;
container.appendChild(renderer.domElement);
window.renderer = renderer;
//
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 1, 20000);
camera.position.set(30, 30, 100);
const waterGeometry = new THREE.PlaneGeometry( 100, 100 );
mesh = new THREE.Mesh(waterGeometry, new THREE.MeshBasicMaterial({
color: 0x00ff00
}));
scene.add(mesh)
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
render();
}
function render() {
const time = performance.now() * 0.001;
renderer.render(scene, camera);
}
</script>
</body>
</html>