|
@@ -0,0 +1,605 @@
|
|
|
|
|
+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 );
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|