Selaa lähdekoodia

WebGPURenderer: Depth Pixel & Logarithmic Depth Buffer (#27243)

* WebGPURenderer: Depth Pixel & Logarithmic Depth Buffer

* Update puppeteer.js
sunag 1 vuosi sitten
vanhempi
commit
549a2e347d

+ 1 - 0
examples/files.json

@@ -314,6 +314,7 @@
 	"webgpu (wip)": [
 	"webgpu (wip)": [
 		"webgpu_backdrop",
 		"webgpu_backdrop",
 		"webgpu_backdrop_area",
 		"webgpu_backdrop_area",
+		"webgpu_camera_logarithmicdepthbuffer",
 		"webgpu_clearcoat",
 		"webgpu_clearcoat",
 		"webgpu_compute_audio",
 		"webgpu_compute_audio",
 		"webgpu_compute_particles",
 		"webgpu_compute_particles",

+ 2 - 2
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 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 BufferAttributeNode, bufferAttribute, dynamicBufferAttribute, instancedBufferAttribute, instancedDynamicBufferAttribute } from './accessors/BufferAttributeNode.js';
 export { default as BufferNode, buffer } from './accessors/BufferNode.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 CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js';
 export { default as InstanceNode, instance } from './accessors/InstanceNode.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';
 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 ViewportTextureNode, viewportTexture, viewportMipTexture } from './display/ViewportTextureNode.js';
 export { default as ViewportSharedTextureNode, viewportSharedTexture } from './display/ViewportSharedTextureNode.js';
 export { default as ViewportSharedTextureNode, viewportSharedTexture } from './display/ViewportSharedTextureNode.js';
 export { default as ViewportDepthTextureNode, viewportDepthTexture } from './display/ViewportDepthTextureNode.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
 // code
 export { default as ExpressionNode, expression } from './code/ExpressionNode.js';
 export { default as ExpressionNode, expression } from './code/ExpressionNode.js';

+ 8 - 2
examples/jsm/nodes/accessors/CameraNode.js

@@ -27,7 +27,7 @@ class CameraNode extends Object3DNode {
 
 
 			return 'mat4';
 			return 'mat4';
 
 
-		} else if ( scope === CameraNode.NEAR || scope === CameraNode.FAR ) {
+		} else if ( scope === CameraNode.NEAR || scope === CameraNode.FAR || scope === CameraNode.LOG_DEPTH ) {
 
 
 			return 'float';
 			return 'float';
 
 
@@ -61,6 +61,10 @@ class CameraNode extends Object3DNode {
 
 
 			uniformNode.value = camera.far;
 			uniformNode.value = camera.far;
 
 
+		} else if ( scope === CameraNode.LOG_DEPTH ) {
+
+			uniformNode.value = 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 );
+
 		} else {
 		} else {
 
 
 			this.object3d = camera;
 			this.object3d = camera;
@@ -79,7 +83,7 @@ class CameraNode extends Object3DNode {
 
 
 			this._uniformNode.nodeType = 'mat4';
 			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';
 			this._uniformNode.nodeType = 'float';
 
 
@@ -94,12 +98,14 @@ class CameraNode extends Object3DNode {
 CameraNode.PROJECTION_MATRIX = 'projectionMatrix';
 CameraNode.PROJECTION_MATRIX = 'projectionMatrix';
 CameraNode.NEAR = 'near';
 CameraNode.NEAR = 'near';
 CameraNode.FAR = 'far';
 CameraNode.FAR = 'far';
+CameraNode.LOG_DEPTH = 'logDepth';
 
 
 export default CameraNode;
 export default CameraNode;
 
 
 export const cameraProjectionMatrix = label( nodeImmutable( CameraNode, CameraNode.PROJECTION_MATRIX ), 'projectionMatrix' );
 export const cameraProjectionMatrix = label( nodeImmutable( CameraNode, CameraNode.PROJECTION_MATRIX ), 'projectionMatrix' );
 export const cameraNear = nodeImmutable( CameraNode, CameraNode.NEAR );
 export const cameraNear = nodeImmutable( CameraNode, CameraNode.NEAR );
 export const cameraFar = nodeImmutable( CameraNode, CameraNode.FAR );
 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 cameraViewMatrix = nodeImmutable( CameraNode, CameraNode.VIEW_MATRIX );
 export const cameraNormalMatrix = nodeImmutable( CameraNode, CameraNode.NORMAL_MATRIX );
 export const cameraNormalMatrix = nodeImmutable( CameraNode, CameraNode.NORMAL_MATRIX );
 export const cameraWorldMatrix = nodeImmutable( CameraNode, CameraNode.WORLD_MATRIX );
 export const cameraWorldMatrix = nodeImmutable( CameraNode, CameraNode.WORLD_MATRIX );

+ 12 - 3
examples/jsm/nodes/accessors/ModelViewProjectionNode.js

@@ -4,10 +4,11 @@ import { cameraProjectionMatrix } from './CameraNode.js';
 import { modelViewMatrix } from './ModelNode.js';
 import { modelViewMatrix } from './ModelNode.js';
 import { positionLocal } from './PositionNode.js';
 import { positionLocal } from './PositionNode.js';
 import { nodeProxy } from '../shadernode/ShaderNode.js';
 import { nodeProxy } from '../shadernode/ShaderNode.js';
+import { varying } from '../core/VaryingNode.js';
 
 
 class ModelViewProjectionNode extends TempNode {
 class ModelViewProjectionNode extends TempNode {
 
 
-	constructor( positionNode = positionLocal ) {
+	constructor( positionNode = null ) {
 
 
 		super( 'vec4' );
 		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 );
 
 
 	}
 	}
 
 

+ 31 - 3
examples/jsm/nodes/display/ViewportDepthNode.js

@@ -6,17 +6,31 @@ import { viewportDepthTexture } from './ViewportDepthTextureNode.js';
 
 
 class ViewportDepthNode extends Node {
 class ViewportDepthNode extends Node {
 
 
-	constructor( scope, textureNode = null ) {
+	constructor( scope, valueNode = null ) {
 
 
 		super( 'float' );
 		super( 'float' );
 
 
 		this.scope = scope;
 		this.scope = scope;
-		this.textureNode = textureNode;
+		this.valueNode = valueNode;
 
 
 		this.isViewportDepthNode = true;
 		this.isViewportDepthNode = true;
 
 
 	}
 	}
 
 
+	generate( builder ) {
+
+		const { scope } = this;
+
+		if ( scope === ViewportDepthNode.DEPTH_PIXEL ) {
+
+			return builder.getFragDepth();
+
+		}
+
+		return super.generate( builder );
+
+	}
+
 	setup( /*builder*/ ) {
 	setup( /*builder*/ ) {
 
 
 		const { scope } = this;
 		const { scope } = this;
@@ -29,11 +43,19 @@ class ViewportDepthNode extends Node {
 
 
 		} else if ( scope === ViewportDepthNode.DEPTH_TEXTURE ) {
 		} else if ( scope === ViewportDepthNode.DEPTH_TEXTURE ) {
 
 
-			const texture = this.textureNode || viewportDepthTexture();
+			const texture = this.valueNode || viewportDepthTexture();
 
 
 			const viewZ = perspectiveDepthToViewZ( texture, cameraNear, cameraFar );
 			const viewZ = perspectiveDepthToViewZ( texture, cameraNear, cameraFar );
 			node = viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
 			node = viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
 
 
+		} else if ( scope === ViewportDepthNode.DEPTH_PIXEL ) {
+
+			if ( this.valueNode !== null ) {
+
+				depthPixelBase().assign( this.valueNode );
+
+			}
+
 		}
 		}
 
 
 		return node;
 		return node;
@@ -60,10 +82,16 @@ export const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).d
 
 
 ViewportDepthNode.DEPTH = 'depth';
 ViewportDepthNode.DEPTH = 'depth';
 ViewportDepthNode.DEPTH_TEXTURE = 'depthTexture';
 ViewportDepthNode.DEPTH_TEXTURE = 'depthTexture';
+ViewportDepthNode.DEPTH_PIXEL = 'depthPixel';
 
 
 export default ViewportDepthNode;
 export default ViewportDepthNode;
 
 
+const depthPixelBase = nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_PIXEL );
+
 export const depth = nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH );
 export const depth = nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH );
 export const depthTexture = nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_TEXTURE );
 export const depthTexture = nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_TEXTURE );
+export const depthPixel = nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH_PIXEL );
+
+depthPixel.assign = ( value ) => depthPixelBase( value );
 
 
 addNodeClass( 'ViewportDepthNode', ViewportDepthNode );
 addNodeClass( 'ViewportDepthNode', ViewportDepthNode );

+ 40 - 3
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 AONode from '../lighting/AONode.js';
 import { lightingContext } from '../lighting/LightingContextNode.js';
 import { lightingContext } from '../lighting/LightingContextNode.js';
 import EnvironmentNode from '../lighting/EnvironmentNode.js';
 import EnvironmentNode from '../lighting/EnvironmentNode.js';
+import { depthPixel } from '../display/ViewportDepthNode.js';
+import { cameraLogDepth } from '../accessors/CameraNode.js';
 
 
 const NodeMaterials = new Map();
 const NodeMaterials = new Map();
 
 
@@ -50,6 +52,8 @@ class NodeMaterial extends ShaderMaterial {
 
 
 		this.positionNode = null;
 		this.positionNode = null;
 
 
+		this.depthNode = null;
+
 		this.outputNode = null;
 		this.outputNode = null;
 
 
 		this.fragmentNode = null;
 		this.fragmentNode = null;
@@ -75,7 +79,7 @@ class NodeMaterial extends ShaderMaterial {
 
 
 		builder.addStack();
 		builder.addStack();
 
 
-		builder.stack.outputNode = this.setupPosition( builder );
+		builder.stack.outputNode = this.vertexNode || this.setupPosition( builder );
 
 
 		builder.addFlow( 'vertex', builder.removeStack() );
 		builder.addFlow( 'vertex', builder.removeStack() );
 
 
@@ -87,6 +91,8 @@ class NodeMaterial extends ShaderMaterial {
 
 
 		if ( this.fragmentNode === null ) {
 		if ( this.fragmentNode === null ) {
 
 
+			if ( this.depthWrite === true ) this.setupDepth( builder );
+
 			if ( this.normals === true ) this.setupNormal( builder );
 			if ( this.normals === true ) this.setupNormal( builder );
 
 
 			this.setupDiffuseColor( 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 ) {
 	setupPosition( builder ) {
 
 
-		const object = builder.object;
+		const { object } = builder;
 		const geometry = object.geometry;
 		const geometry = object.geometry;
 
 
 		builder.addStack();
 		builder.addStack();
 
 
+		// Vertex
+
 		if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) {
 		if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) {
 
 
 			morph( object ).append();
 			morph( object ).append();
@@ -147,9 +179,12 @@ class NodeMaterial extends ShaderMaterial {
 
 
 		}
 		}
 
 
+		const mvp = modelViewProjection();
+
 		builder.context.vertex = builder.removeStack();
 		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.positionNode = source.positionNode;
 
 
+		this.depthNode = source.depthNode;
+
 		this.outputNode = source.outputNode;
 		this.outputNode = source.outputNode;
 
 
 		this.fragmentNode = source.fragmentNode;
 		this.fragmentNode = source.fragmentNode;

+ 9 - 1
examples/jsm/renderers/common/Renderer.js

@@ -22,10 +22,16 @@ const _vector3 = new Vector3();
 
 
 class Renderer {
 class Renderer {
 
 
-	constructor( backend ) {
+	constructor( backend, parameters = {} ) {
 
 
 		this.isRenderer = true;
 		this.isRenderer = true;
 
 
+		//
+
+		const {
+			logarithmicDepthBuffer = false,
+		} = parameters;
+
 		// public
 		// public
 
 
 		this.domElement = backend.getDomElement();
 		this.domElement = backend.getDomElement();
@@ -37,6 +43,8 @@ class Renderer {
 		this.autoClearDepth = true;
 		this.autoClearDepth = true;
 		this.autoClearStencil = true;
 		this.autoClearStencil = true;
 
 
+		this.logarithmicDepthBuffer = logarithmicDepthBuffer;
+
 		this.outputColorSpace = SRGBColorSpace;
 		this.outputColorSpace = SRGBColorSpace;
 
 
 		this.toneMapping = NoToneMapping;
 		this.toneMapping = NoToneMapping;

+ 6 - 0
examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js

@@ -406,6 +406,12 @@ ${ flowData.code }
 
 
 	}
 	}
 
 
+	getFragDepth() {
+
+		return 'gl_FragDepth';
+
+	}
+
 	isAvailable( name ) {
 	isAvailable( name ) {
 
 
 		return supports[ name ] === true;
 		return supports[ name ] === true;

+ 1 - 1
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -37,7 +37,7 @@ class WebGPURenderer extends Renderer {
 		const backend = new BackendClass( parameters );
 		const backend = new BackendClass( parameters );
 
 
 		//super( new Proxy( backend, debugHandler ) );
 		//super( new Proxy( backend, debugHandler ) );
-		super( backend );
+		super( backend, parameters );
 
 
 		this.isWebGPURenderer = true;
 		this.isWebGPURenderer = true;
 
 

+ 68 - 31
examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -100,12 +100,7 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 
 		this.uniformGroups = {};
 		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 ) {
 	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 ) {
 		if ( map.has( name ) === false ) {
 
 
@@ -509,7 +504,13 @@ ${ flowData.code }
 
 
 	getFragCoord() {
 	getFragCoord() {
 
 
-		return this.getBuiltin( 'position', 'fragCoord', 'vec4<f32>', 'fragment' ) + '.xy';
+		return this.getBuiltin( 'position', 'fragCoord', 'vec4<f32>' ) + '.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 ) {
 	getAttributes( shaderStage ) {
 
 
 		const snippets = [];
 		const snippets = [];
@@ -531,11 +551,9 @@ ${ flowData.code }
 
 
 		if ( shaderStage === 'vertex' || shaderStage === 'compute' ) {
 		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();
 			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' );
 		const code = snippets.join( ',\n\t' );
 
 
@@ -795,12 +811,25 @@ ${ flowData.code }
 
 
 		for ( const shaderStage in shadersData ) {
 		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';
 			let flow = '// code\n\n';
 			flow += this.flowCode[ shaderStage ];
 			flow += this.flowCode[ shaderStage ];
 
 
 			const flowNodes = this.flowNodes[ shaderStage ];
 			const flowNodes = this.flowNodes[ shaderStage ];
 			const mainNode = flowNodes[ flowNodes.length - 1 ];
 			const mainNode = flowNodes[ flowNodes.length - 1 ];
 
 
+			const outputNode = mainNode.outputNode;
+			const isOutputStruct = ( outputNode !== undefined && outputNode.isOutputStructNode === true );
+
 			for ( const node of flowNodes ) {
 			for ( const node of flowNodes ) {
 
 
 				const flowSlotData = this.getFlowData( node/*, shaderStage*/ );
 				const flowSlotData = this.getFlowData( node/*, shaderStage*/ );
@@ -818,34 +847,42 @@ ${ flowData.code }
 
 
 				if ( node === mainNode && shaderStage !== 'compute' ) {
 				if ( node === mainNode && shaderStage !== 'compute' ) {
 
 
-					flow += '// result\n\t';
+					flow += '// result\n\n\t';
 
 
 					if ( shaderStage === 'vertex' ) {
 					if ( shaderStage === 'vertex' ) {
 
 
-						flow += 'varyings.Vertex = ';
+						flow += `varyings.Vertex = ${ flowSlotData.result };`;
 
 
 					} else if ( shaderStage === 'fragment' ) {
 					} else if ( shaderStage === 'fragment' ) {
 
 
-						flow += 'return ';
+						if ( isOutputStruct ) {
 
 
-					}
+							stageData.returnType = outputNode.nodeType;
+
+							flow += `return ${ flowSlotData.result };`;
+
+						} else {
+
+							let structSnippet = '\t@location(0) color: vec4<f32>';
+
+							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<private> 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<f32>';
 			stageData.flow = flow;
 			stageData.flow = flow;
 
 
 		}
 		}

BIN
examples/screenshots/webgpu_camera_logarithmicdepthbuffer.jpg


+ 353 - 0
examples/webgpu_camera_logarithmicdepthbuffer.html

@@ -0,0 +1,353 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - cameras - logarithmic depth buffer</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+			body{
+				touch-action: none;
+			}
+			.renderer_label {
+				position: absolute;
+				bottom: 1em;
+				width: 100%;
+				color: white;
+				z-index: 10;
+				display: block;
+				text-align: center;
+			}
+
+			#container {
+				display: flex;
+			}
+
+			#container_normal {
+				width: 50%;
+				display: inline-block;
+				position: relative;
+			}
+
+			#container_logzbuf {
+				width: 50%;
+				display: inline-block;
+				position: relative;
+			}
+
+			#renderer_border {
+				position: absolute;
+				top: 0;
+				left: 25%;
+				bottom: 0;
+				width: 2px;
+				z-index: 10;
+				opacity: .8;
+				background: #ccc;
+				border: 1px inset #ccc;
+				cursor: col-resize;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="container">
+			<div id="container_normal"><h2 class="renderer_label">normal z-buffer</h2></div>
+			<div id="container_logzbuf"><h2 class="renderer_label">logarithmic z-buffer</h2></div>
+			<div id="renderer_border"></div>
+		</div>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - cameras - logarithmic depth buffer<br/>
+			mousewheel to dolly out
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+			import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			// 1 micrometer to 100 billion light years in one scene, with 1 unit = 1 meter?  preposterous!  and yet...
+			const NEAR = 1e-6, FAR = 1e27;
+			let SCREEN_WIDTH = window.innerWidth;
+			let SCREEN_HEIGHT = window.innerHeight;
+			let screensplit = .25, screensplit_right = 0;
+			const mouse = [ .5, .5 ];
+			let zoompos = - 100, minzoomspeed = .015;
+			let zoomspeed = minzoomspeed;
+
+			let container, border, stats;
+			const objects = {};
+
+			// Generate a number of text labels, from 1µm in size up to 100,000,000 light years
+			// Try to use some descriptive real-world examples of objects at each scale
+
+			const labeldata = [
+				{ size: .01, scale: 0.0001, label: 'microscopic (1µm)' }, // FIXME - triangulating text fails at this size, so we scale instead
+				{ size: .01, scale: 0.1, label: 'minuscule (1mm)' },
+				{ size: .01, scale: 1.0, label: 'tiny (1cm)' },
+				{ size: 1, scale: 1.0, label: 'child-sized (1m)' },
+				{ size: 10, scale: 1.0, label: 'tree-sized (10m)' },
+				{ size: 100, scale: 1.0, label: 'building-sized (100m)' },
+				{ size: 1000, scale: 1.0, label: 'medium (1km)' },
+				{ size: 10000, scale: 1.0, label: 'city-sized (10km)' },
+				{ size: 3400000, scale: 1.0, label: 'moon-sized (3,400 Km)' },
+				{ size: 12000000, scale: 1.0, label: 'planet-sized (12,000 km)' },
+				{ size: 1400000000, scale: 1.0, label: 'sun-sized (1,400,000 km)' },
+				{ size: 7.47e12, scale: 1.0, label: 'solar system-sized (50Au)' },
+				{ size: 9.4605284e15, scale: 1.0, label: 'gargantuan (1 light year)' },
+				{ size: 3.08567758e16, scale: 1.0, label: 'ludicrous (1 parsec)' },
+				{ size: 1e19, scale: 1.0, label: 'mind boggling (1000 light years)' }
+			];
+
+			init();
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU or WebGL2 support' );
+
+				}
+
+				container = document.getElementById( 'container' );
+
+				const loader = new FontLoader();
+				loader.load( 'fonts/helvetiker_regular.typeface.json', function ( font ) {
+
+					const scene = initScene( font );
+
+					// Initialize two copies of the same scene, one with normal z-buffer and one with logarithmic z-buffer
+					objects.normal = initView( scene, 'normal', false );
+					objects.logzbuf = initView( scene, 'logzbuf', true );
+
+				} );
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				// Resize border allows the user to easily compare effects of logarithmic depth buffer over the whole scene
+				border = document.getElementById( 'renderer_border' );
+				border.addEventListener( 'pointerdown', onBorderPointerDown );
+
+				window.addEventListener( 'mousemove', onMouseMove );
+				window.addEventListener( 'resize', onWindowResize );
+				window.addEventListener( 'wheel', onMouseWheel );
+
+			}
+
+			function initView( scene, name, logDepthBuf ) {
+
+				const framecontainer = document.getElementById( 'container_' + name );
+
+				const camera = new THREE.PerspectiveCamera( 50, screensplit * SCREEN_WIDTH / SCREEN_HEIGHT, NEAR, FAR );
+				scene.add( camera );
+
+				const renderer = new WebGPURenderer( { antialias: true, logarithmicDepthBuffer: logDepthBuf } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( SCREEN_WIDTH / 2, SCREEN_HEIGHT );
+				renderer.setAnimationLoop( render );
+				renderer.domElement.style.position = 'relative';
+				renderer.domElement.id = 'renderer_' + name;
+				framecontainer.appendChild( renderer.domElement );
+
+				return { container: framecontainer, renderer: renderer, scene: scene, camera: camera };
+
+			}
+
+			function initScene( font ) {
+
+				const scene = new THREE.Scene();
+
+				scene.add( new THREE.AmbientLight( 0x777777 ) );
+
+				const light = new THREE.DirectionalLight( 0xffffff, 3 );
+				light.position.set( 100, 100, 100 );
+				scene.add( light );
+
+				const materialargs = {
+					color: 0xffffff,
+					specular: 0x050505,
+					shininess: 50,
+					emissive: 0x000000
+				};
+
+				const geometry = new THREE.SphereGeometry( 0.5, 24, 12 );
+
+				for ( let i = 0; i < labeldata.length; i ++ ) {
+
+					const scale = labeldata[ i ].scale || 1;
+
+					const labelgeo = new TextGeometry( labeldata[ i ].label, {
+						font: font,
+						size: labeldata[ i ].size,
+						height: labeldata[ i ].size / 2
+					} );
+
+					labelgeo.computeBoundingSphere();
+
+					// center text
+					labelgeo.translate( - labelgeo.boundingSphere.radius, 0, 0 );
+
+					materialargs.color = new THREE.Color().setHSL( Math.random(), 0.5, 0.5 );
+
+					const material = new THREE.MeshPhongMaterial( materialargs );
+
+					const group = new THREE.Group();
+					group.position.z = - labeldata[ i ].size * scale;
+					scene.add( group );
+
+					const textmesh = new THREE.Mesh( labelgeo, material );
+					textmesh.scale.set( scale, scale, scale );
+					textmesh.position.z = - labeldata[ i ].size * scale;
+					textmesh.position.y = labeldata[ i ].size / 4 * scale;
+					group.add( textmesh );
+
+					const dotmesh = new THREE.Mesh( geometry, material );
+					dotmesh.position.y = - labeldata[ i ].size / 4 * scale;
+					dotmesh.scale.multiplyScalar( labeldata[ i ].size * scale );
+					group.add( dotmesh );
+
+				}
+
+				return scene;
+
+			}
+
+			function updateRendererSizes() {
+
+				// Recalculate size for both renderers when screen size or split location changes
+
+				SCREEN_WIDTH = window.innerWidth;
+				SCREEN_HEIGHT = window.innerHeight;
+
+				screensplit_right = 1 - screensplit;
+
+				objects.normal.renderer.setSize( screensplit * SCREEN_WIDTH, SCREEN_HEIGHT );
+				objects.normal.camera.aspect = screensplit * SCREEN_WIDTH / SCREEN_HEIGHT;
+				objects.normal.camera.updateProjectionMatrix();
+				objects.normal.camera.setViewOffset( SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH * screensplit, SCREEN_HEIGHT );
+				objects.normal.container.style.width = ( screensplit * 100 ) + '%';
+
+				objects.logzbuf.renderer.setSize( screensplit_right * SCREEN_WIDTH, SCREEN_HEIGHT );
+				objects.logzbuf.camera.aspect = screensplit_right * SCREEN_WIDTH / SCREEN_HEIGHT;
+				objects.logzbuf.camera.updateProjectionMatrix();
+				objects.logzbuf.camera.setViewOffset( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH * screensplit, 0, SCREEN_WIDTH * screensplit_right, SCREEN_HEIGHT );
+				objects.logzbuf.container.style.width = ( screensplit_right * 100 ) + '%';
+
+				border.style.left = ( screensplit * 100 ) + '%';
+
+			}
+
+			function render() {
+
+				// Put some limits on zooming
+				const minzoom = labeldata[ 0 ].size * labeldata[ 0 ].scale * 1;
+				const maxzoom = labeldata[ labeldata.length - 1 ].size * labeldata[ labeldata.length - 1 ].scale * 100;
+				let damping = ( Math.abs( zoomspeed ) > minzoomspeed ? .95 : 1.0 );
+
+				// Zoom out faster the further out you go
+				const zoom = THREE.MathUtils.clamp( Math.pow( Math.E, zoompos ), minzoom, maxzoom );
+				zoompos = Math.log( zoom );
+
+				// Slow down quickly at the zoom limits
+				if ( ( zoom == minzoom && zoomspeed < 0 ) || ( zoom == maxzoom && zoomspeed > 0 ) ) {
+
+					damping = .85;
+
+				}
+
+				zoompos += zoomspeed;
+				zoomspeed *= damping;
+
+				objects.normal.camera.position.x = Math.sin( .5 * Math.PI * ( mouse[ 0 ] - .5 ) ) * zoom;
+				objects.normal.camera.position.y = Math.sin( .25 * Math.PI * ( mouse[ 1 ] - .5 ) ) * zoom;
+				objects.normal.camera.position.z = Math.cos( .5 * Math.PI * ( mouse[ 0 ] - .5 ) ) * zoom;
+				objects.normal.camera.lookAt( objects.normal.scene.position );
+
+				// Clone camera settings across both scenes
+				objects.logzbuf.camera.position.copy( objects.normal.camera.position );
+				objects.logzbuf.camera.quaternion.copy( objects.normal.camera.quaternion );
+
+				// Update renderer sizes if the split has changed
+				if ( screensplit_right != 1 - screensplit ) {
+
+					updateRendererSizes();
+
+				}
+
+				objects.normal.renderer.render( objects.normal.scene, objects.normal.camera );
+				objects.logzbuf.renderer.render( objects.logzbuf.scene, objects.logzbuf.camera );
+
+				stats.update();
+
+			}
+
+			function onWindowResize() {
+
+				updateRendererSizes();
+
+			}
+
+			function onBorderPointerDown() {
+
+				// activate draggable window resizing bar
+				window.addEventListener( 'pointermove', onBorderPointerMove );
+				window.addEventListener( 'pointerup', onBorderPointerUp );
+
+			}
+
+			function onBorderPointerMove( ev ) {
+
+				screensplit = Math.max( 0, Math.min( 1, ev.clientX / window.innerWidth ) );
+
+			}
+
+			function onBorderPointerUp() {
+
+				window.removeEventListener( 'pointermove', onBorderPointerMove );
+				window.removeEventListener( 'pointerup', onBorderPointerUp );
+
+			}
+
+			function onMouseMove( ev ) {
+
+				mouse[ 0 ] = ev.clientX / window.innerWidth;
+				mouse[ 1 ] = ev.clientY / window.innerHeight;
+
+			}
+
+			function onMouseWheel( ev ) {
+
+				const amount = ev.deltaY;
+				if ( amount === 0 ) return;
+				const dir = amount / Math.abs( amount );
+				zoomspeed = dir / 10;
+
+				// Slow down default zoom speed after user starts zooming, to give them more control
+				minzoomspeed = 0.001;
+
+			}
+		</script>
+	</body>
+</html>

+ 1 - 0
test/e2e/puppeteer.js

@@ -125,6 +125,7 @@ const exceptionList = [
 	'webgpu_video_panorama',
 	'webgpu_video_panorama',
 
 
 	// WebGPURenderer: Unknown problem
 	// WebGPURenderer: Unknown problem
+	'webgpu_camera_logarithmicdepthbuffer',
 	'webgpu_materials_video',
 	'webgpu_materials_video',
 	'webgpu_particles',
 	'webgpu_particles',
 	'webgpu_shadertoy',
 	'webgpu_shadertoy',