diff --git a/examples/files.json b/examples/files.json index fbfa82a5a9564c..1ede022c2df5eb 100644 --- a/examples/files.json +++ b/examples/files.json @@ -314,6 +314,7 @@ "webgpu (wip)": [ "webgpu_backdrop", "webgpu_backdrop_area", + "webgpu_camera_logarithmicdepthbuffer", "webgpu_clearcoat", "webgpu_compute_audio", "webgpu_compute_particles", diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index 73658f5c15a215..ac252865d46754 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -71,7 +71,7 @@ export * from './shadernode/ShaderNode.js'; export { default as BitangentNode, bitangentGeometry, bitangentLocal, bitangentView, bitangentWorld, transformedBitangentView, transformedBitangentWorld } from './accessors/BitangentNode.js'; export { default as BufferAttributeNode, bufferAttribute, dynamicBufferAttribute, instancedBufferAttribute, instancedDynamicBufferAttribute } from './accessors/BufferAttributeNode.js'; export { default as BufferNode, buffer } from './accessors/BufferNode.js'; -export { default as CameraNode, cameraProjectionMatrix, cameraViewMatrix, cameraNormalMatrix, cameraWorldMatrix, cameraPosition, cameraNear, cameraFar } from './accessors/CameraNode.js'; +export { default as CameraNode, cameraProjectionMatrix, cameraViewMatrix, cameraNormalMatrix, cameraWorldMatrix, cameraPosition, cameraNear, cameraFar, cameraLogDepth } from './accessors/CameraNode.js'; export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js'; export { default as InstanceNode, instance } from './accessors/InstanceNode.js'; export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecularColor, materialSpecularStrength, materialReflectivity, materialRoughness, materialMetalness, materialNormal, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialRotation, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLineScale, materialLineDashSize, materialLineGapSize, materialLineWidth, materialLineDashOffset, materialPointWidth } from './accessors/MaterialNode.js'; @@ -108,7 +108,7 @@ export { default as ViewportNode, viewport, viewportCoordinate, viewportResoluti export { default as ViewportTextureNode, viewportTexture, viewportMipTexture } from './display/ViewportTextureNode.js'; export { default as ViewportSharedTextureNode, viewportSharedTexture } from './display/ViewportSharedTextureNode.js'; export { default as ViewportDepthTextureNode, viewportDepthTexture } from './display/ViewportDepthTextureNode.js'; -export { default as ViewportDepthNode, viewZToOrthographicDepth, orthographicDepthToViewZ, viewZToPerspectiveDepth, perspectiveDepthToViewZ, depth, depthTexture } from './display/ViewportDepthNode.js'; +export { default as ViewportDepthNode, viewZToOrthographicDepth, orthographicDepthToViewZ, viewZToPerspectiveDepth, perspectiveDepthToViewZ, depth, depthTexture, depthPixel } from './display/ViewportDepthNode.js'; // code export { default as ExpressionNode, expression } from './code/ExpressionNode.js'; diff --git a/examples/jsm/nodes/accessors/CameraNode.js b/examples/jsm/nodes/accessors/CameraNode.js index 22de8d5634697f..eccdae1b262634 100644 --- a/examples/jsm/nodes/accessors/CameraNode.js +++ b/examples/jsm/nodes/accessors/CameraNode.js @@ -27,7 +27,7 @@ class CameraNode extends Object3DNode { return 'mat4'; - } else if ( scope === CameraNode.NEAR || scope === CameraNode.FAR ) { + } else if ( scope === CameraNode.NEAR || scope === CameraNode.FAR || scope === CameraNode.LOG_DEPTH ) { return 'float'; @@ -61,6 +61,10 @@ class CameraNode extends Object3DNode { uniformNode.value = camera.far; + } else if ( scope === CameraNode.LOG_DEPTH ) { + + uniformNode.value = 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ); + } else { this.object3d = camera; @@ -79,7 +83,7 @@ class CameraNode extends Object3DNode { this._uniformNode.nodeType = 'mat4'; - } else if ( scope === CameraNode.NEAR || scope === CameraNode.FAR ) { + } else if ( scope === CameraNode.NEAR || scope === CameraNode.FAR || scope === CameraNode.LOG_DEPTH ) { this._uniformNode.nodeType = 'float'; @@ -94,12 +98,14 @@ class CameraNode extends Object3DNode { CameraNode.PROJECTION_MATRIX = 'projectionMatrix'; CameraNode.NEAR = 'near'; CameraNode.FAR = 'far'; +CameraNode.LOG_DEPTH = 'logDepth'; export default CameraNode; export const cameraProjectionMatrix = label( nodeImmutable( CameraNode, CameraNode.PROJECTION_MATRIX ), 'projectionMatrix' ); export const cameraNear = nodeImmutable( CameraNode, CameraNode.NEAR ); export const cameraFar = nodeImmutable( CameraNode, CameraNode.FAR ); +export const cameraLogDepth = nodeImmutable( CameraNode, CameraNode.LOG_DEPTH ); export const cameraViewMatrix = nodeImmutable( CameraNode, CameraNode.VIEW_MATRIX ); export const cameraNormalMatrix = nodeImmutable( CameraNode, CameraNode.NORMAL_MATRIX ); export const cameraWorldMatrix = nodeImmutable( CameraNode, CameraNode.WORLD_MATRIX ); diff --git a/examples/jsm/nodes/accessors/ModelViewProjectionNode.js b/examples/jsm/nodes/accessors/ModelViewProjectionNode.js index 43800259a8f1de..cdbad88af24d6b 100644 --- a/examples/jsm/nodes/accessors/ModelViewProjectionNode.js +++ b/examples/jsm/nodes/accessors/ModelViewProjectionNode.js @@ -4,10 +4,11 @@ import { cameraProjectionMatrix } from './CameraNode.js'; import { modelViewMatrix } from './ModelNode.js'; import { positionLocal } from './PositionNode.js'; import { nodeProxy } from '../shadernode/ShaderNode.js'; +import { varying } from '../core/VaryingNode.js'; class ModelViewProjectionNode extends TempNode { - constructor( positionNode = positionLocal ) { + constructor( positionNode = null ) { super( 'vec4' ); @@ -15,9 +16,17 @@ class ModelViewProjectionNode extends TempNode { } - setup() { + setup( builder ) { - return cameraProjectionMatrix.mul( modelViewMatrix ).mul( this.positionNode ); + if ( builder.shaderStage === 'fragment' ) { + + return varying( builder.context.mvp ); + + } + + const position = this.positionNode || positionLocal; + + return cameraProjectionMatrix.mul( modelViewMatrix ).mul( position ); } diff --git a/examples/jsm/nodes/display/ViewportDepthNode.js b/examples/jsm/nodes/display/ViewportDepthNode.js index b44af4b790b33c..0a9c1df8b19763 100644 --- a/examples/jsm/nodes/display/ViewportDepthNode.js +++ b/examples/jsm/nodes/display/ViewportDepthNode.js @@ -6,17 +6,31 @@ import { viewportDepthTexture } from './ViewportDepthTextureNode.js'; class ViewportDepthNode extends Node { - constructor( scope, textureNode = null ) { + constructor( scope, valueNode = null ) { super( 'float' ); this.scope = scope; - this.textureNode = textureNode; + this.valueNode = valueNode; this.isViewportDepthNode = true; } + generate( builder ) { + + const { scope } = this; + + if ( scope === ViewportDepthNode.DEPTH_PIXEL ) { + + return builder.getFragDepth(); + + } + + return super.generate( builder ); + + } + setup( /*builder*/ ) { const { scope } = this; @@ -29,11 +43,19 @@ class ViewportDepthNode extends Node { } else if ( scope === ViewportDepthNode.DEPTH_TEXTURE ) { - const texture = this.textureNode || viewportDepthTexture(); + const texture = this.valueNode || viewportDepthTexture(); const viewZ = perspectiveDepthToViewZ( texture, cameraNear, cameraFar ); node = viewZToOrthographicDepth( viewZ, cameraNear, cameraFar ); + } else if ( scope === ViewportDepthNode.DEPTH_PIXEL ) { + + if ( this.valueNode !== null ) { + + depthPixelBase().assign( this.valueNode ); + + } + } return node; @@ -60,10 +82,16 @@ export const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).d ViewportDepthNode.DEPTH = 'depth'; ViewportDepthNode.DEPTH_TEXTURE = 'depthTexture'; +ViewportDepthNode.DEPTH_PIXEL = 'depthPixel'; export default ViewportDepthNode; +const depthPixelBase = nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_PIXEL ); + export const depth = nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH ); export const depthTexture = nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_TEXTURE ); +export const depthPixel = nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH_PIXEL ); + +depthPixel.assign = ( value ) => depthPixelBase( value ); addNodeClass( 'ViewportDepthNode', ViewportDepthNode ); diff --git a/examples/jsm/nodes/materials/NodeMaterial.js b/examples/jsm/nodes/materials/NodeMaterial.js index 10deb5bef55bc1..07d4af83e06856 100644 --- a/examples/jsm/nodes/materials/NodeMaterial.js +++ b/examples/jsm/nodes/materials/NodeMaterial.js @@ -17,6 +17,8 @@ import { float, vec3, vec4 } from '../shadernode/ShaderNode.js'; import AONode from '../lighting/AONode.js'; import { lightingContext } from '../lighting/LightingContextNode.js'; import EnvironmentNode from '../lighting/EnvironmentNode.js'; +import { depthPixel } from '../display/ViewportDepthNode.js'; +import { cameraLogDepth } from '../accessors/CameraNode.js'; const NodeMaterials = new Map(); @@ -50,6 +52,8 @@ class NodeMaterial extends ShaderMaterial { this.positionNode = null; + this.depthNode = null; + this.outputNode = null; this.fragmentNode = null; @@ -75,7 +79,7 @@ class NodeMaterial extends ShaderMaterial { builder.addStack(); - builder.stack.outputNode = this.setupPosition( builder ); + builder.stack.outputNode = this.vertexNode || this.setupPosition( builder ); builder.addFlow( 'vertex', builder.removeStack() ); @@ -87,6 +91,8 @@ class NodeMaterial extends ShaderMaterial { if ( this.fragmentNode === null ) { + if ( this.depthWrite === true ) this.setupDepth( builder ); + if ( this.normals === true ) this.setupNormal( builder ); this.setupDiffuseColor( builder ); @@ -116,13 +122,39 @@ class NodeMaterial extends ShaderMaterial { } + setupDepth( builder ) { + + const { renderer } = builder; + + // Depth + + let depthNode = this.depthNode; + + if ( renderer.logarithmicDepthBuffer === true ) { + + const fragDepth = modelViewProjection().w.add( 1 ); + + depthNode = fragDepth.log2().mul( cameraLogDepth ).mul( 0.5 ); + + } + + if ( depthNode !== null ) { + + depthPixel.assign( depthNode ).append(); + + } + + } + setupPosition( builder ) { - const object = builder.object; + const { object } = builder; const geometry = object.geometry; builder.addStack(); + // Vertex + if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) { morph( object ).append(); @@ -147,9 +179,12 @@ class NodeMaterial extends ShaderMaterial { } + const mvp = modelViewProjection(); + builder.context.vertex = builder.removeStack(); + builder.context.mvp = mvp; - return this.vertexNode || modelViewProjection(); + return mvp; } @@ -460,6 +495,8 @@ class NodeMaterial extends ShaderMaterial { this.positionNode = source.positionNode; + this.depthNode = source.depthNode; + this.outputNode = source.outputNode; this.fragmentNode = source.fragmentNode; diff --git a/examples/jsm/renderers/common/Renderer.js b/examples/jsm/renderers/common/Renderer.js index e06c27cdf85693..f7346d1d677eeb 100644 --- a/examples/jsm/renderers/common/Renderer.js +++ b/examples/jsm/renderers/common/Renderer.js @@ -22,10 +22,16 @@ const _vector3 = new Vector3(); class Renderer { - constructor( backend ) { + constructor( backend, parameters = {} ) { this.isRenderer = true; + // + + const { + logarithmicDepthBuffer = false, + } = parameters; + // public this.domElement = backend.getDomElement(); @@ -37,6 +43,8 @@ class Renderer { this.autoClearDepth = true; this.autoClearStencil = true; + this.logarithmicDepthBuffer = logarithmicDepthBuffer; + this.outputColorSpace = SRGBColorSpace; this.toneMapping = NoToneMapping; diff --git a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js index fd5b384b74fed3..09841d2d803dda 100644 --- a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js @@ -406,6 +406,12 @@ ${ flowData.code } } + getFragDepth() { + + return 'gl_FragDepth'; + + } + isAvailable( name ) { return supports[ name ] === true; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderer.js b/examples/jsm/renderers/webgpu/WebGPURenderer.js index 49c4dd077e52a1..8c855e2ffa3b33 100644 --- a/examples/jsm/renderers/webgpu/WebGPURenderer.js +++ b/examples/jsm/renderers/webgpu/WebGPURenderer.js @@ -37,7 +37,7 @@ class WebGPURenderer extends Renderer { const backend = new BackendClass( parameters ); //super( new Proxy( backend, debugHandler ) ); - super( backend ); + super( backend, parameters ); this.isWebGPURenderer = true; diff --git a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js index 979c386aec2b34..4651b058aced22 100644 --- a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -100,12 +100,7 @@ class WGSLNodeBuilder extends NodeBuilder { this.uniformGroups = {}; - this.builtins = { - vertex: new Map(), - fragment: new Map(), - compute: new Map(), - attribute: new Map() - }; + this.builtins = {}; } @@ -433,7 +428,7 @@ class WGSLNodeBuilder extends NodeBuilder { getBuiltin( name, property, type, shaderStage = this.shaderStage ) { - const map = this.builtins[ shaderStage ]; + const map = this.builtins[ shaderStage ] || ( this.builtins[ shaderStage ] = new Map() ); if ( map.has( name ) === false ) { @@ -509,7 +504,13 @@ ${ flowData.code } getFragCoord() { - return this.getBuiltin( 'position', 'fragCoord', 'vec4', 'fragment' ) + '.xy'; + return this.getBuiltin( 'position', 'fragCoord', 'vec4' ) + '.xy'; + + } + + getFragDepth() { + + return 'output.' + this.getBuiltin( 'frag_depth', 'depth', 'f32', 'output' ); } @@ -519,6 +520,25 @@ ${ flowData.code } } + getBuiltins( shaderStage ) { + + const snippets = []; + const builtins = this.builtins[ shaderStage ]; + + if ( builtins !== undefined ) { + + for ( const { name, property, type } of builtins.values() ) { + + snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); + + } + + } + + return snippets.join( ',\n\t' ); + + } + getAttributes( shaderStage ) { const snippets = []; @@ -531,11 +551,9 @@ ${ flowData.code } if ( shaderStage === 'vertex' || shaderStage === 'compute' ) { - for ( const { name, property, type } of this.builtins.attribute.values() ) { + const builtins = this.getBuiltins( 'attribute' ); - snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); - - } + if ( builtins ) snippets.push( builtins ); const attributes = this.getAttributesArray(); @@ -660,11 +678,9 @@ ${ flowData.code } } - for ( const { name, property, type } of this.builtins[ shaderStage ].values() ) { - - snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); + const builtins = this.getBuiltins( shaderStage ); - } + if ( builtins ) snippets.push( builtins ); const code = snippets.join( ',\n\t' ); @@ -795,12 +811,25 @@ ${ flowData.code } for ( const shaderStage in shadersData ) { + const stageData = shadersData[ shaderStage ]; + stageData.uniforms = this.getUniforms( shaderStage ); + stageData.attributes = this.getAttributes( shaderStage ); + stageData.varyings = this.getVaryings( shaderStage ); + stageData.structs = this.getStructs( shaderStage ); + stageData.vars = this.getVars( shaderStage ); + stageData.codes = this.getCodes( shaderStage ); + + // + let flow = '// code\n\n'; flow += this.flowCode[ shaderStage ]; const flowNodes = this.flowNodes[ shaderStage ]; const mainNode = flowNodes[ flowNodes.length - 1 ]; + const outputNode = mainNode.outputNode; + const isOutputStruct = ( outputNode !== undefined && outputNode.isOutputStructNode === true ); + for ( const node of flowNodes ) { const flowSlotData = this.getFlowData( node/*, shaderStage*/ ); @@ -818,34 +847,42 @@ ${ flowData.code } if ( node === mainNode && shaderStage !== 'compute' ) { - flow += '// result\n\t'; + flow += '// result\n\n\t'; if ( shaderStage === 'vertex' ) { - flow += 'varyings.Vertex = '; + flow += `varyings.Vertex = ${ flowSlotData.result };`; } else if ( shaderStage === 'fragment' ) { - flow += 'return '; + if ( isOutputStruct ) { - } + stageData.returnType = outputNode.nodeType; + + flow += `return ${ flowSlotData.result };`; + + } else { + + let structSnippet = '\t@location(0) color: vec4'; + + const builtins = this.getBuiltins( 'output' ); + + if ( builtins ) structSnippet += ',\n\t' + builtins; - flow += `${ flowSlotData.result };`; + stageData.returnType = 'OutputStruct'; + stageData.structs += this._getWGSLStruct( 'OutputStruct', structSnippet ); + stageData.structs += '\nvar output : OutputStruct;\n\n'; + + flow += `output.color = ${ flowSlotData.result };\n\n\treturn output;`; + + } + + } } } - const outputNode = mainNode.outputNode; - const stageData = shadersData[ shaderStage ]; - - stageData.uniforms = this.getUniforms( shaderStage ); - stageData.attributes = this.getAttributes( shaderStage ); - stageData.varyings = this.getVaryings( shaderStage ); - stageData.structs = this.getStructs( shaderStage ); - stageData.vars = this.getVars( shaderStage ); - stageData.codes = this.getCodes( shaderStage ); - stageData.returnType = ( outputNode !== undefined && outputNode.isOutputStructNode === true ) ? outputNode.nodeType : '@location( 0 ) vec4'; stageData.flow = flow; } diff --git a/examples/screenshots/webgpu_camera_logarithmicdepthbuffer.jpg b/examples/screenshots/webgpu_camera_logarithmicdepthbuffer.jpg new file mode 100644 index 00000000000000..a11ed8dbae6059 Binary files /dev/null and b/examples/screenshots/webgpu_camera_logarithmicdepthbuffer.jpg differ diff --git a/examples/webgpu_camera_logarithmicdepthbuffer.html b/examples/webgpu_camera_logarithmicdepthbuffer.html new file mode 100644 index 00000000000000..0a5721e3fc9076 --- /dev/null +++ b/examples/webgpu_camera_logarithmicdepthbuffer.html @@ -0,0 +1,353 @@ + + + + three.js webgpu - cameras - logarithmic depth buffer + + + + + + + +
+

normal z-buffer

+

logarithmic z-buffer

+
+
+ +
+ three.js - cameras - logarithmic depth buffer
+ mousewheel to dolly out +
+ + + + + + diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index c7de2efe69cd98..6a0155bfa170de 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -125,6 +125,7 @@ const exceptionList = [ 'webgpu_video_panorama', // WebGPURenderer: Unknown problem + 'webgpu_camera_logarithmicdepthbuffer', 'webgpu_materials_video', 'webgpu_particles', 'webgpu_shadertoy',