diff --git a/examples/files.json b/examples/files.json index 6294a84efbf8fc..0d5047fcf322aa 100644 --- a/examples/files.json +++ b/examples/files.json @@ -295,6 +295,12 @@ "webgl_worker_offscreencanvas", "webgl_performance" ], + "webgl / tsl": [ + "webgl_tsl_shadowmap", + "webgl_tsl_skinning", + "webgl_tsl_clearcoat", + "webgl_tsl_instancing" + ], "webgpu (wip)": [ "webgpu_animation_retargeting", "webgpu_animation_retargeting_readyplayer", diff --git a/examples/jsm/tsl/WebGLNodesHandler.js b/examples/jsm/tsl/WebGLNodesHandler.js new file mode 100644 index 00000000000000..584b93d0e2c176 --- /dev/null +++ b/examples/jsm/tsl/WebGLNodesHandler.js @@ -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 ); + + } + + } + +} diff --git a/examples/screenshots/webgl_tsl_clearcoat.jpg b/examples/screenshots/webgl_tsl_clearcoat.jpg new file mode 100644 index 00000000000000..3f23098f88b9ca Binary files /dev/null and b/examples/screenshots/webgl_tsl_clearcoat.jpg differ diff --git a/examples/screenshots/webgl_tsl_instancing.jpg b/examples/screenshots/webgl_tsl_instancing.jpg new file mode 100644 index 00000000000000..d6181ff421bd6a Binary files /dev/null and b/examples/screenshots/webgl_tsl_instancing.jpg differ diff --git a/examples/screenshots/webgl_tsl_shadowmap.jpg b/examples/screenshots/webgl_tsl_shadowmap.jpg new file mode 100644 index 00000000000000..9d068f4adc3c68 Binary files /dev/null and b/examples/screenshots/webgl_tsl_shadowmap.jpg differ diff --git a/examples/screenshots/webgl_tsl_skinning.jpg b/examples/screenshots/webgl_tsl_skinning.jpg new file mode 100644 index 00000000000000..218485520bd7fc Binary files /dev/null and b/examples/screenshots/webgl_tsl_skinning.jpg differ diff --git a/examples/webgl_tsl_clearcoat.html b/examples/webgl_tsl_clearcoat.html new file mode 100644 index 00000000000000..76414bcd508d3a --- /dev/null +++ b/examples/webgl_tsl_clearcoat.html @@ -0,0 +1,243 @@ + + + + three.js webgpu - materials - clearcoat + + + + + +
+ + +
+ three.jsClearcoat +
+ + PBR Clearcoat effect. +
+ + + + + + diff --git a/examples/webgl_tsl_instancing.html b/examples/webgl_tsl_instancing.html new file mode 100644 index 00000000000000..4300ea4a76618a --- /dev/null +++ b/examples/webgl_tsl_instancing.html @@ -0,0 +1,375 @@ + + + + three.js webgl - instancing - performance + + + + + + + +
+ + three.js webgl - instancing - performance + +
+ +
+ + + + + + + diff --git a/examples/webgl_tsl_shadowmap.html b/examples/webgl_tsl_shadowmap.html new file mode 100644 index 00000000000000..2c61458a74e390 --- /dev/null +++ b/examples/webgl_tsl_shadowmap.html @@ -0,0 +1,208 @@ + + + + three.js webgpu - shadow map + + + + + + +
+ + +
+ three.jsShadow Map +
+ + + Shadow map example. + +
+ + + + + + diff --git a/examples/webgl_tsl_skinning.html b/examples/webgl_tsl_skinning.html new file mode 100644 index 00000000000000..103341ba28b25d --- /dev/null +++ b/examples/webgl_tsl_skinning.html @@ -0,0 +1,121 @@ + + + + three.js webgpu - skinning + + + + + + +
+ + +
+ three.jsSkinning +
+ + + Basic skinning example using a model from Mixamo. + +
+ + + + + + diff --git a/src/Three.WebGPU.js b/src/Three.WebGPU.js index ab8505ee35aa2e..c42e634fa5538c 100644 --- a/src/Three.WebGPU.js +++ b/src/Three.WebGPU.js @@ -4,6 +4,7 @@ export * from './materials/nodes/NodeMaterials.js'; export { default as WebGPURenderer } from './renderers/webgpu/WebGPURenderer.js'; export { default as WebGPUBackend } from './renderers/webgpu/WebGPUBackend.js'; export { default as WebGLBackend } from './renderers/webgl-fallback/WebGLBackend.js'; +export { default as WebGLCapabilities } from './renderers/webgl-fallback/utils/WebGLCapabilities.js'; export { default as Lighting } from './renderers/common/Lighting.js'; export { default as BundleGroup } from './renderers/common/BundleGroup.js'; export { default as QuadMesh } from './renderers/common/QuadMesh.js'; @@ -27,6 +28,8 @@ export { default as NodeMaterialLoader } from './loaders/nodes/NodeMaterialLoade export { default as InspectorBase } from './renderers/common/InspectorBase.js'; export { default as CanvasTarget } from './renderers/common/CanvasTarget.js'; export { default as BlendMode } from './renderers/common/BlendMode.js'; +export { default as GLSLNodeBuilder } from './renderers/webgl-fallback/nodes/GLSLNodeBuilder.js'; +export { default as BasicNodeLibrary } from './renderers/webgpu/nodes/BasicNodeLibrary.js'; export { ClippingGroup } from './objects/ClippingGroup.js'; export * from './nodes/Nodes.js'; import * as TSL from './nodes/TSL.js'; diff --git a/src/nodes/accessors/StorageTextureNode.js b/src/nodes/accessors/StorageTextureNode.js index 62c6ac5809222b..4e0a2219a01aac 100644 --- a/src/nodes/accessors/StorageTextureNode.js +++ b/src/nodes/accessors/StorageTextureNode.js @@ -146,19 +146,14 @@ class StorageTextureNode extends TextureNode { */ generate( builder, output ) { - let snippet; - if ( this.storeNode !== null ) { - snippet = this.generateStore( builder ); - - } else { - - snippet = super.generate( builder, output ); + this.generateStore( builder ); + return ''; } - return snippet; + return super.generate( builder, output ); } @@ -233,7 +228,7 @@ class StorageTextureNode extends TextureNode { const storeSnippet = storeNode.build( builder, 'vec4' ); const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null; - const snippet = builder.generateTextureStore( builder, textureProperty, uvSnippet, depthSnippet, storeSnippet ); + const snippet = builder.generateTextureStore( this.value, textureProperty, uvSnippet, depthSnippet, storeSnippet ); builder.addLineFlowCode( snippet, this ); diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index c8c83b67485b10..a3cb34e35704a2 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -292,6 +292,7 @@ class WebGLRenderer { const _this = this; let _isContextLost = false; + let _nodesHandler = null; // internal state cache @@ -1042,6 +1043,20 @@ class WebGLRenderer { }; + /** + * Sets a compatibility node builder for rendering node materials with WebGLRenderer. + * This enables using TSL (Three.js Shading Language) node materials to prepare + * for migration to WebGPURenderer. + * + * @param {WebGLNodesHandler} nodesHandler - The node builder instance. + */ + this.setNodesHandler = function ( nodesHandler ) { + + nodesHandler.setRenderer( this ); + _nodesHandler = nodesHandler; + + }; + /** * Frees the GPU-related resources allocated by this instance. Call this * method whenever this instance is no longer used in your app. @@ -1602,6 +1617,13 @@ class WebGLRenderer { if ( _isContextLost === true ) return; + // update node builder if available + if ( _nodesHandler !== null ) { + + _nodesHandler.renderStart( scene, camera ); + + } + // use internal render target for HalfFloatType color buffer (only when tone mapping is enabled) const isXRPresenting = xr.enabled === true && xr.isPresenting === true; @@ -1795,6 +1817,12 @@ class WebGLRenderer { } + if ( _nodesHandler !== null ) { + + _nodesHandler.renderEnd(); + + } + }; function projectObject( object, camera, groupOrder, sortObjects ) { @@ -2171,6 +2199,13 @@ class WebGLRenderer { parameters.uniforms = programCache.getUniforms( material ); + // Use node builder for node materials if available + if ( _nodesHandler !== null && material.isNodeMaterial ) { + + _nodesHandler.build( material, object, parameters ); + + } + material.onBeforeCompile( parameters, _this ); program = programCache.acquireProgram( parameters, programCacheKey ); @@ -2436,6 +2471,14 @@ class WebGLRenderer { program = getProgram( material, scene, object ); + // notify the node builder that the program has changed so uniforms and update nodes can + // be cached and triggered. + if ( _nodesHandler && material.isNodeMaterial ) { + + _nodesHandler.onUpdateProgram( material, program, materialProperties ); + + } + } let refreshProgram = false; @@ -2665,7 +2708,7 @@ class WebGLRenderer { // UBOs - if ( material.isShaderMaterial || material.isRawShaderMaterial ) { + if ( material.uniformsGroups !== undefined ) { const groups = material.uniformsGroups; diff --git a/src/renderers/common/extras/PMREMGenerator.js b/src/renderers/common/extras/PMREMGenerator.js index a689daa1bebe41..6c0eb8bf9b21a2 100644 --- a/src/renderers/common/extras/PMREMGenerator.js +++ b/src/renderers/common/extras/PMREMGenerator.js @@ -410,7 +410,7 @@ class PMREMGenerator { this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); outputTarget.scissorTest = false; - _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); + this._setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); } @@ -566,7 +566,7 @@ class PMREMGenerator { const size = this._cubeSize; - _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); + this._setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); renderer.render( scene, cubeCamera ); @@ -608,9 +608,7 @@ class PMREMGenerator { mesh.material = material; const size = this._cubeSize; - - _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); - + this._setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); renderer.setRenderTarget( cubeUVRenderTarget ); renderer.render( mesh, _flatCamera ); @@ -678,7 +676,7 @@ class PMREMGenerator { ggxUniforms.roughness.value = adjustedRoughness; ggxUniforms.mipInt.value = _lodMax - lodIn; // Sample from input LOD - _setViewport( pingPongRenderTarget, x, y, 3 * outputSize, 2 * outputSize ); + this._setViewport( pingPongRenderTarget, x, y, 3 * outputSize, 2 * outputSize ); renderer.setRenderTarget( pingPongRenderTarget ); renderer.render( ggxMesh, _flatCamera ); @@ -688,7 +686,7 @@ class PMREMGenerator { ggxUniforms.roughness.value = 0.0; // Direct copy ggxUniforms.mipInt.value = _lodMax - lodOut; // Read from the level we just wrote - _setViewport( cubeUVRenderTarget, x, y, 3 * outputSize, 2 * outputSize ); + this._setViewport( cubeUVRenderTarget, x, y, 3 * outputSize, 2 * outputSize ); renderer.setRenderTarget( cubeUVRenderTarget ); renderer.render( ggxMesh, _flatCamera ); @@ -814,12 +812,29 @@ class PMREMGenerator { const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); const y = 4 * ( this._cubeSize - outputSize ); - _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); + this._setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); renderer.setRenderTarget( targetOut ); renderer.render( blurMesh, _flatCamera ); } + _setViewport( target, x, y, width, height ) { + + if ( this._renderer.isWebGLRenderer ) { + + target.viewport.set( x, target.height - height - y, width, height ); + target.scissor.set( x, target.height - height - y, width, height ); + + } else { + + target.viewport.set( x, y, width, height ); + target.scissor.set( x, y, width, height ); + + } + + } + + } function _createPlanes( lodMax ) { @@ -925,13 +940,6 @@ function _createRenderTarget( width, height ) { } -function _setViewport( target, x, y, width, height ) { - - target.viewport.set( x, y, width, height ); - target.scissor.set( x, y, width, height ); - -} - function _getMaterial( type ) { const material = new NodeMaterial(); diff --git a/src/renderers/webgl/WebGLMaterials.js b/src/renderers/webgl/WebGLMaterials.js index 7c2258e722b372..ce10982b1a9fcf 100644 --- a/src/renderers/webgl/WebGLMaterials.js +++ b/src/renderers/webgl/WebGLMaterials.js @@ -41,7 +41,11 @@ function WebGLMaterials( renderer, properties ) { function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { - if ( material.isMeshBasicMaterial ) { + if ( material.isNodeMaterial ) { + + material.uniformsNeedUpdate = false; + + } else if ( material.isMeshBasicMaterial ) { refreshUniformsCommon( uniforms, material ); diff --git a/src/renderers/webgl/WebGLUniformsGroups.js b/src/renderers/webgl/WebGLUniformsGroups.js index 7d7cb09a3baebb..947f5607e17e1a 100644 --- a/src/renderers/webgl/WebGLUniformsGroups.js +++ b/src/renderers/webgl/WebGLUniformsGroups.js @@ -141,6 +141,11 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) { uniform.__data[ 10 ] = value.elements[ 8 ]; uniform.__data[ 11 ] = 0; + } else if ( ArrayBuffer.isView( value ) ) { + + // copy the buffer data using "set" + uniform.__data.set( new value.constructor( value.buffer, value.byteOffset, uniform.__data.length ) ); + } else { value.toArray( uniform.__data, arrayOffset ); @@ -176,6 +181,10 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) { cache[ indexString ] = value; + } else if ( ArrayBuffer.isView( value ) ) { + + cache[ indexString ] = value.slice(); + } else { cache[ indexString ] = value.clone(); @@ -199,6 +208,11 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) { } + } else if ( ArrayBuffer.isView( value ) ) { + + // always update the array buffers + return true; + } else { if ( cachedObject.equals( value ) === false ) { @@ -339,6 +353,11 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) { warn( 'WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); + } else if ( ArrayBuffer.isView( value ) ) { + + info.boundary = 16; + info.storage = value.byteLength; + } else { warn( 'WebGLRenderer: Unsupported uniform value type.', value );