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()RenderObjectsRenderObject|-- _nodes: Nodes|-- _monitor: NodeMaterialObserver|-- _nodeBuilderState: NodeBuilderState|-- getNodeBuilderState()|-- getMonitor()WebGPUBackendWGSLNodeBuilder|-- object: Mesh|-- stack: StackNode // 被代理|-- renderer: WebGPURenderer|-- cache: NodeCatche|-- build()NodeFrameNodeBuilderState|-- binding: BindGroup[]|-- updateNodes: NodeAttribute []|-- nodeAttributes: UniformNode []NodeMaterialObserver|-- monitor: NodeMaterialObserverNodeMaterial|-- build()|-- setup()MeshBasicNodeMaterial extends NodeMaterial
2、执行流程
2.1 注册材质解析类
在创建 WebGPURenderer 时,会创建 StandardNodeLibrary。
StandardNodeLibrary.addMaterial( MeshBasicNodeMaterial, 'MeshBasicMaterial' );
2.2 创建 WGSLNodeBuilder
这个 WGSLNodeBuilder 是和 Mesh 一一对应。
WebGPURenderer.render()._renderObjectDirect()// step1: 创建 RenderObjectconst renderObject: RenderObject = this._objects.get();// step2: 创建 WGSLNodeBuilderthis._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() {// 根据材质类型,获取对应的 NodeMateriallet 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>