| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605 |
- import {
- GLSL3,
- UniformsGroup,
- Compatibility,
- Color,
- UniformsLib,
- UniformsUtils,
- } from 'three';
- import {
- context,
- cubeTexture,
- reference,
- texture,
- fog,
- rangeFogFactor,
- densityFogFactor,
- workingToColorSpace,
- } from 'three/tsl';
- import {
- NodeUtils,
- NodeFrame,
- Lighting,
- InspectorBase,
- GLSLNodeBuilder,
- BasicNodeLibrary,
- WebGLCapabilities,
- } from 'three/webgpu';
- // Limitations
- // - VSM shadows not supported
- // - MRT not supported
- // - Transmission not supported
- // - WebGPU postprocessing stack not supported
- // - Storage textures not supported
- // - Fog / environment do not automatically update - must call "dispose"
- // - instanced mesh geometry cannot be shared
- // - Node materials cannot be used with "compile" function
- // hash any object parameters that will impact the resulting shader so we can force
- // a program update
- function getObjectHash( object ) {
- return '' + object.receiveShadow;
- }
- // Mirrors WebGLUniforms.seqWithValue from WebGLRenderer
- function generateUniformsList( program, uniforms ) {
- const progUniforms = program.getUniforms();
- const uniformsList = [];
- for ( let i = 0; i < progUniforms.seq.length; i ++ ) {
- const u = progUniforms.seq[ i ];
- if ( u.id in uniforms ) uniformsList.push( u );
- }
- return uniformsList;
- }
- // overrides shadow nodes to use the built in shadow textures
- class WebGLNodeBuilder extends GLSLNodeBuilder {
- addNode( node ) {
- if ( node.isShadowNode ) {
- node.setupRenderTarget = shadow => {
- return { shadowMap: shadow.map, depthTexture: shadow.map.depthTexture };
- };
- node.updateBefore = () => {
- // no need to rerender shadows since WebGLRenderer is handling it
- };
- }
- super.addNode( node );
- }
- }
- // produce and update reusable nodes for a scene
- class SceneContext {
- constructor( renderer, scene ) {
- // TODO: can / should we update the fog and environment node every frame for recompile?
- this.renderer = renderer;
- this.scene = scene;
- this.lightsNode = renderer.lighting.getNode( scene );
- this.fogNode = null;
- this.environmentNode = null;
- this.prevFog = null;
- this.prevEnvironment = null;
- }
- getCacheKey() {
- const { lightsNode, environmentNode, fogNode } = this;
- const lightsHash = lightsNode.getCacheKey();
- const envHash = environmentNode ? environmentNode.getCacheKey : 0;
- const fogHash = fogNode ? fogNode.getCacheKey() : 0;
- return NodeUtils.hashArray( [ lightsHash, envHash, fogHash ] );
- }
- update() {
- const { scene, lightsNode } = this;
- // update lighting
- const sceneLights = [];
- scene.traverse( object => {
- if ( object.isLight ) {
- sceneLights.push( object );
- }
- } );
- lightsNode.setLights( sceneLights );
- // update fog
- if ( this.prevFog !== scene.fog ) {
- this.fogNode = this.getFogNode();
- this.prevFog = scene.fog;
- }
- // update environment
- if ( this.prevEnvironment !== scene.environment ) {
- this.environmentNode = this.getEnvironmentNode();
- this.prevEnvironment = scene.environment;
- }
- }
- getFogNode() {
- const { scene } = this;
- if ( scene.fog && scene.fog.isFogExp2 ) {
- const color = reference( 'color', 'color', scene.fog );
- const density = reference( 'density', 'float', scene.fog );
- return fog( color, densityFogFactor( density ) );
- } else if ( scene.fog && scene.fog.isFog ) {
- const color = reference( 'color', 'color', scene.fog );
- const near = reference( 'near', 'float', scene.fog );
- const far = reference( 'far', 'float', scene.fog );
- return fog( color, rangeFogFactor( near, far ) );
- } else {
- return null;
- }
- }
- getEnvironmentNode() {
- const { scene } = this;
- if ( scene.environment && scene.environment.isCubeTexture ) {
- return cubeTexture( scene.environment );
- } else if ( scene.environment && scene.environment.isTexture ) {
- return texture( scene.environment );
- } else {
- return null;
- }
- }
- }
- class RendererProxy {
- constructor( renderer ) {
- const backend = {
- isWebGPUBackend: false,
- extensions: renderer.extensions,
- gl: renderer.getContext(),
- capabilities: null,
- };
- backend.capabilities = new WebGLCapabilities( backend );
- this.contextNode = context();
- this.inspector = new InspectorBase();
- this.library = new BasicNodeLibrary();
- this.lighting = new Lighting();
- this.backend = backend;
- const self = this;
- return new Proxy( renderer, {
- get( target, property ) {
- return Reflect.get( property in self ? self : target, property );
- },
- set( target, property, value ) {
- return Reflect.set( property in self ? self : target, property, value );
- }
- } );
- }
- hasInitialized() {
- return true;
- }
- getMRT() {
- return null;
- }
- hasCompatibility( name ) {
- if ( name === Compatibility.TEXTURE_COMPARE ) {
- return true;
- }
- return false;
- }
- getCacheKey() {
- return this.toneMapping + this.outputColorSpace;
- }
- }
- /**
- * Compatibility loader and builder for TSL Node materials in WebGLRenderer.
- */
- export class WebGLNodesHandler {
- /**
- * Constructs a new WebGL node adapter.
- */
- constructor() {
- this.renderer = null;
- this.nodeFrame = new NodeFrame();
- this.sceneContexts = new WeakMap();
- this.programCache = new Map();
- this.renderStack = [];
- const self = this;
- this.onDisposeMaterialCallback = function () {
- // dispose of all the uniform groups
- const { programCache } = self;
- if ( programCache.has( this ) ) {
- self.programCache.get( this ).forEach( ( { uniformsGroups } ) => {
- uniformsGroups.forEach( u => u.dispose() );
- } );
- self.programCache.delete( this );
- }
- this.removeEventListener( 'dispose', self.onDisposeMaterialCallback );
- };
- this.getOutputCallback = function ( outputNode ) {
- // apply tone mapping and color spaces to the output
- const { outputColorSpace, toneMapping } = self.renderer;
- outputNode = outputNode.toneMapping( toneMapping );
- outputNode = workingToColorSpace( outputNode, outputColorSpace );
- return outputNode;
- };
- this.onBeforeRenderCallback = function ( renderer, scene, camera, geometry, object ) {
- // update node frame references for update nodes
- const { nodeFrame } = self;
- nodeFrame.material = this;
- nodeFrame.object = object;
- // increment "frame" here to force uniform buffers to update for the material, which otherwise only get
- // updated once per frame.
- renderer.info.render.frame ++;
- // update the uniform groups and nodes for the program if they're available before rendering
- if ( renderer.properties.has( this ) ) {
- const currentProgram = renderer.properties.get( this ).currentProgram;
- const programs = self.programCache.get( this );
- if ( programs && programs.has( currentProgram ) ) {
- // update the nodes for the current object
- const { updateNodes } = programs.get( currentProgram );
- self.updateNodes( updateNodes );
- }
- }
- const objectHash = getObjectHash( object );
- if ( this.prevObjectHash !== objectHash ) {
- this.prevObjectHash = objectHash;
- this.needsUpdate = true;
- }
- };
- this.customProgramCacheKeyCallback = function () {
- const { renderStack, renderer, nodeFrame } = self;
- const sceneHash = renderStack[ renderStack.length - 1 ].sceneContext.getCacheKey();
- const materialHash = this.constructor.prototype.customProgramCacheKey.call( this );
- const rendererHash = renderer.getCacheKey();
- return materialHash + sceneHash + rendererHash + getObjectHash( nodeFrame.object );
- };
- }
- setRenderer( renderer ) {
- const rendererProxy = new RendererProxy( renderer );
- this.nodeFrame.renderer = rendererProxy;
- this.renderer = rendererProxy;
- }
- onUpdateProgram( material, program, materialProperties ) {
- const { programCache } = this;
- if ( ! programCache.has( material ) ) {
- programCache.set( material, new Map() );
- }
- const programs = programCache.get( material );
- if ( ! programs.has( program ) ) {
- const builder = material._latestBuilder;
- const uniforms = materialProperties.uniforms;
- programs.set( program, {
- uniformsGroups: this.collectUniformsGroups( builder ),
- uniforms: uniforms,
- uniformsList: generateUniformsList( program, uniforms ),
- updateNodes: builder.updateNodes,
- } );
- }
- const { uniformsGroups, uniforms, uniformsList, updateNodes } = programs.get( program );
- material.uniformsGroups = uniformsGroups;
- materialProperties.uniforms = uniforms;
- materialProperties.uniformsList = uniformsList;
- this.updateNodes( updateNodes );
- }
- renderStart( scene, camera ) {
- const { nodeFrame, renderStack, renderer, sceneContexts } = this;
- nodeFrame.update();
- nodeFrame.camera = camera;
- nodeFrame.scene = scene;
- nodeFrame.frameId ++;
- let sceneContext = sceneContexts.get( scene );
- if ( ! sceneContext ) {
- sceneContext = new SceneContext( renderer, scene );
- sceneContexts.set( scene, sceneContext );
- }
- sceneContext.update();
- renderStack.push( { sceneContext, camera } );
- // ensure all node material callbacks are initialized before
- // traversal and build
- const {
- customProgramCacheKeyCallback,
- onBeforeRenderCallback,
- } = this;
- scene.traverse( object => {
- if ( object.material && object.material.isNodeMaterial ) {
- object.material.customProgramCacheKey = customProgramCacheKeyCallback;
- object.material.onBeforeRender = onBeforeRenderCallback;
- }
- } );
- }
- renderEnd() {
- const { nodeFrame, renderStack } = this;
- renderStack.pop();
- const frame = renderStack[ renderStack.length - 1 ];
- if ( frame ) {
- const { camera, sceneContext } = frame;
- nodeFrame.camera = camera;
- nodeFrame.scene = sceneContext.scene;
- }
- }
- build( material, object, parameters ) {
- const {
- nodeFrame,
- renderer,
- getOutputCallback,
- onDisposeMaterialCallback,
- renderStack,
- } = this;
- const {
- camera,
- sceneContext,
- } = renderStack[ renderStack.length - 1 ];
- const {
- fogNode,
- environmentNode,
- lightsNode,
- scene,
- } = sceneContext;
- // prepare the frame
- nodeFrame.material = material;
- nodeFrame.object = object;
- // create & run the builder
- const builder = new WebGLNodeBuilder( object, renderer );
- builder.scene = scene;
- builder.camera = camera;
- builder.material = material;
- builder.fogNode = fogNode;
- builder.environmentNode = environmentNode;
- builder.lightsNode = lightsNode;
- builder.context.getOutput = getOutputCallback;
- builder.build();
- // update the shader parameters and geometry for program creation and rendering
- this.updateShaderParameters( builder, parameters );
- this.updateGeometryAttributes( builder, object.geometry );
- // reset node frame settings to account for any intermediate renders
- nodeFrame.material = material;
- nodeFrame.object = object;
- // set up callbacks for uniforms and node updates
- material._latestBuilder = builder;
- material.addEventListener( 'dispose', onDisposeMaterialCallback );
- this.updateNodes( builder.updateNodes );
- }
- updateGeometryAttributes( builder, geometry ) {
- // TODO: this may cause issues if the material / geometry is used in multiple places
- // add instancing attributes
- builder.bufferAttributes.forEach( v => {
- geometry.setAttribute( v.name, v.node.attribute );
- } );
- // force WebGLAttributes & WebGLBindingStates to refresh
- // could be fixed by running "build" sooner? Or calling "WebGLAttributes" separately for those
- // associated with a material?
- queueMicrotask( () => geometry.dispose() );
- }
- updateShaderParameters( builder, parameters ) {
- // set up shaders
- parameters.isRawShaderMaterial = true;
- parameters.glslVersion = GLSL3;
- parameters.vertexShader = builder.vertexShader.replace( /#version 300 es/, '' );
- parameters.fragmentShader = builder.fragmentShader.replace( /#version 300 es/, '' );
- // add uniforms accessed by WebGLRenderer
- parameters.uniforms = {
- fogColor: { value: new Color() },
- fogNear: { value: 0 },
- fogFar: { value: 0 },
- envMapIntensity: { value: 0 },
- ...UniformsUtils.clone( UniformsLib.lights )
- };
- // init uniforms
- const builderUniforms = [ ...builder.uniforms.vertex, ...builder.uniforms.fragment ];
- for ( const uniform of builderUniforms ) {
- parameters.uniforms[ uniform.name ] = uniform.node;
- }
- }
- collectUniformsGroups( builder ) {
- // create UniformsGroups for regular grouped uniforms
- const uniformsGroups = [];
- for ( const key in builder.uniformGroups ) {
- const { uniforms } = builder.uniformGroups[ key ];
- const group = new UniformsGroup();
- group.name = key;
- group.uniforms = uniforms.map( node => node.nodeUniform );
- uniformsGroups.push( group );
- }
- // init uniforms
- const builderUniforms = [ ...builder.uniforms.vertex, ...builder.uniforms.fragment ];
- for ( const uniform of builderUniforms ) {
- if ( uniform.type === 'buffer' ) {
- // buffer uniforms are all nested in groups
- const group = new UniformsGroup();
- group.name = uniform.node.name;
- group.uniforms = [ uniform ];
- uniformsGroups.push( group );
- }
- }
- return uniformsGroups;
- }
- updateNodes( updateNodes ) {
- // update nodes for render
- const { nodeFrame } = this;
- nodeFrame.renderId ++;
- for ( const node of updateNodes ) {
- nodeFrame.updateNode( node );
- }
- }
- }
|