浏览代码

WebGPURenderer: PostProcessing - Anamorphic example (#27621)

* add threshold

* Add AnamorphicNode

* Add getTextureNode()

* Update Nodes.js

* add anamorphic example

* update

* update types
sunag 1 年之前
父节点
当前提交
805a72d932

+ 1 - 0
examples/files.json

@@ -370,6 +370,7 @@
 		"webgpu_tsl_transpiler",
 		"webgpu_tsl_transpiler",
 		"webgpu_video_panorama",
 		"webgpu_video_panorama",
 		"webgpu_postprocessing_afterimage",
 		"webgpu_postprocessing_afterimage",
+		"webgpu_postprocessing_anamorphic",
 		"webgpu_mirror",
 		"webgpu_mirror",
 		"webgpu_multisampled_renderbuffers",
 		"webgpu_multisampled_renderbuffers",
 		"webgpu_materials_texture_anisotropy"
 		"webgpu_materials_texture_anisotropy"

+ 2 - 1
examples/jsm/nodes/Nodes.js

@@ -107,7 +107,7 @@ export { default as UserDataNode, userData } from './accessors/UserDataNode.js';
 // display
 // display
 export { default as BlendModeNode, burn, dodge, overlay, screen } from './display/BlendModeNode.js';
 export { default as BlendModeNode, burn, dodge, overlay, screen } from './display/BlendModeNode.js';
 export { default as BumpMapNode, bumpMap } from './display/BumpMapNode.js';
 export { default as BumpMapNode, bumpMap } from './display/BumpMapNode.js';
-export { default as ColorAdjustmentNode, saturation, vibrance, hue, lumaCoeffs, luminance } from './display/ColorAdjustmentNode.js';
+export { default as ColorAdjustmentNode, saturation, vibrance, hue, lumaCoeffs, luminance, threshold } from './display/ColorAdjustmentNode.js';
 export { default as ColorSpaceNode, linearToColorSpace, colorSpaceToLinear, linearTosRGB, sRGBToLinear } from './display/ColorSpaceNode.js';
 export { default as ColorSpaceNode, linearToColorSpace, colorSpaceToLinear, linearTosRGB, sRGBToLinear } from './display/ColorSpaceNode.js';
 export { default as FrontFacingNode, frontFacing, faceDirection } from './display/FrontFacingNode.js';
 export { default as FrontFacingNode, frontFacing, faceDirection } from './display/FrontFacingNode.js';
 export { default as NormalMapNode, normalMap, TBNViewMatrix } from './display/NormalMapNode.js';
 export { default as NormalMapNode, normalMap, TBNViewMatrix } from './display/NormalMapNode.js';
@@ -120,6 +120,7 @@ export { default as ViewportDepthTextureNode, viewportDepthTexture } from './dis
 export { default as ViewportDepthNode, viewZToOrthographicDepth, orthographicDepthToViewZ, viewZToPerspectiveDepth, perspectiveDepthToViewZ, depth, depthTexture, depthPixel } from './display/ViewportDepthNode.js';
 export { default as ViewportDepthNode, viewZToOrthographicDepth, orthographicDepthToViewZ, viewZToPerspectiveDepth, perspectiveDepthToViewZ, depth, depthTexture, depthPixel } from './display/ViewportDepthNode.js';
 export { default as GaussianBlurNode, gaussianBlur } from './display/GaussianBlurNode.js';
 export { default as GaussianBlurNode, gaussianBlur } from './display/GaussianBlurNode.js';
 export { default as AfterImageNode, afterImage } from './display/AfterImageNode.js';
 export { default as AfterImageNode, afterImage } from './display/AfterImageNode.js';
+export { default as AnamorphicNode, anamorphic } from './display/AnamorphicNode.js';
 
 
 export { default as PassNode, pass, depthPass } from './display/PassNode.js';
 export { default as PassNode, pass, depthPass } from './display/PassNode.js';
 
 

+ 16 - 2
examples/jsm/nodes/display/AfterImageNode.js

@@ -3,6 +3,7 @@ import { nodeObject, addNodeElement, tslFn, float, vec4 } from '../shadernode/Sh
 import { NodeUpdateType } from '../core/constants.js';
 import { NodeUpdateType } from '../core/constants.js';
 import { uv } from '../accessors/UVNode.js';
 import { uv } from '../accessors/UVNode.js';
 import { texture } from '../accessors/TextureNode.js';
 import { texture } from '../accessors/TextureNode.js';
+import { texturePass } from './PassNode.js';
 import { uniform } from '../core/UniformNode.js';
 import { uniform } from '../core/UniformNode.js';
 import { RenderTarget } from 'three';
 import { RenderTarget } from 'three';
 import { sign, max } from '../math/MathNode.js';
 import { sign, max } from '../math/MathNode.js';
@@ -26,10 +27,18 @@ class AfterImageNode extends TempNode {
 		this._oldRT = new RenderTarget();
 		this._oldRT = new RenderTarget();
 		this._oldRT.texture.name = 'AfterImageNode.old';
 		this._oldRT.texture.name = 'AfterImageNode.old';
 
 
+		this._textureNode = texturePass( this, this._compRT.texture );
+
 		this.updateBeforeType = NodeUpdateType.RENDER;
 		this.updateBeforeType = NodeUpdateType.RENDER;
 
 
 	}
 	}
 
 
+	getTextureNode() {
+
+		return this._textureNode;
+
+	}
+
 	setSize( width, height ) {
 	setSize( width, height ) {
 
 
 		this._compRT.setSize( width, height );
 		this._compRT.setSize( width, height );
@@ -42,6 +51,12 @@ class AfterImageNode extends TempNode {
 		const { renderer } = frame;
 		const { renderer } = frame;
 
 
 		const textureNode = this.textureNode;
 		const textureNode = this.textureNode;
+		const map = textureNode.value;
+
+		const textureType = map.type;
+
+		this._compRT.texture.type = textureType;
+		this._oldRT.texture.type = textureType;
 
 
 		const currentRenderTarget = renderer.getRenderTarget();
 		const currentRenderTarget = renderer.getRenderTarget();
 		const currentTexture = textureNode.value;
 		const currentTexture = textureNode.value;
@@ -58,7 +73,6 @@ class AfterImageNode extends TempNode {
 		this._compRT = temp;
 		this._compRT = temp;
 
 
 		// set size before swapping fails
 		// set size before swapping fails
-		const map = currentTexture;
 		this.setSize( map.image.width, map.image.height );
 		this.setSize( map.image.width, map.image.height );
 
 
 		renderer.setRenderTarget( currentRenderTarget );
 		renderer.setRenderTarget( currentRenderTarget );
@@ -120,7 +134,7 @@ class AfterImageNode extends TempNode {
 
 
 		//
 		//
 
 
-		return texture( this._compRT.texture );
+		return this._textureNode;
 
 
 	}
 	}
 
 

+ 148 - 0
examples/jsm/nodes/display/AnamorphicNode.js

@@ -0,0 +1,148 @@
+import TempNode from '../core/TempNode.js';
+import { nodeObject, addNodeElement, tslFn, float, vec2, vec3, vec4 } from '../shadernode/ShaderNode.js';
+import { loop } from '../utils/LoopNode.js';
+import { uniform } from '../core/UniformNode.js';
+import { NodeUpdateType } from '../core/constants.js';
+import { threshold } from './ColorAdjustmentNode.js';
+import { uv } from '../accessors/UVNode.js';
+import { texturePass } from './PassNode.js';
+import { Vector2, RenderTarget } from 'three';
+import QuadMesh from '../../objects/QuadMesh.js';
+
+const quadMesh = new QuadMesh();
+
+class AnamorphicNode extends TempNode {
+
+	constructor( textureNode, tresholdNode, scaleNode, samples ) {
+
+		super( 'vec4' );
+
+		this.textureNode = textureNode;
+		this.tresholdNode = tresholdNode;
+		this.scaleNode = scaleNode;
+		this.colorNode = vec3( 0.1, 0.0, 1.0 );
+		this.samples = samples;
+		this.resolution = new Vector2( 1, 1 );
+
+		this._renderTarget = new RenderTarget();
+		this._renderTarget.texture.name = 'anamorphic';
+
+		this._invSize = uniform( new Vector2() );
+
+		this._textureNode = texturePass( this, this._renderTarget.texture );
+
+		this.updateBeforeType = NodeUpdateType.RENDER;
+
+	}
+
+	getTextureNode() {
+
+		return this._textureNode;
+
+	}
+
+	setSize( width, height ) {
+
+		this._invSize.value.set( 1 / width, 1 / height );
+
+		width = Math.max( Math.round( width * this.resolution.x ), 1 );
+		height = Math.max( Math.round( height * this.resolution.y ), 1 );
+
+		this._renderTarget.setSize( width, height );
+
+	}
+
+	updateBefore( frame ) {
+
+		const { renderer } = frame;
+
+		const textureNode = this.textureNode;
+		const map = textureNode.value;
+
+		this._renderTarget.texture.type = map.type;
+
+		const currentRenderTarget = renderer.getRenderTarget();
+		const currentTexture = textureNode.value;
+
+		quadMesh.material = this._material;
+
+		this.setSize( map.image.width, map.image.height );
+
+		// render
+
+		renderer.setRenderTarget( this._renderTarget );
+
+		quadMesh.render( renderer );
+
+		// restore
+
+		renderer.setRenderTarget( currentRenderTarget );
+		textureNode.value = currentTexture;
+
+	}
+
+	setup( builder ) {
+
+		const textureNode = this.textureNode;
+
+		if ( textureNode.isTextureNode !== true ) {
+
+			console.error( 'AnamorphNode requires a TextureNode.' );
+
+			return vec4();
+
+		}
+
+		//
+
+		const uvNode = textureNode.uvNode || uv();
+
+		const sampleTexture = ( uv ) => textureNode.cache().context( { getUV: () => uv, forceUVContext: true } );
+
+		const anamorph = tslFn( () => {
+
+			const samples = this.samples;
+			const halfSamples = Math.floor( samples / 2 );
+
+			const total = vec3( 0 ).toVar();
+
+			loop( { start: - halfSamples, end: halfSamples }, ( { i } ) => {
+
+				const softness = float( i ).abs().div( halfSamples ).oneMinus();
+
+				const uv = vec2( uvNode.x.add( this._invSize.x.mul( i ).mul( this.scaleNode ) ), uvNode.y );
+				const color = sampleTexture( uv );
+				const pass = threshold( color, this.tresholdNode ).mul( softness );
+
+				total.addAssign( pass );
+
+			} );
+
+			return total.mul( this.colorNode );
+
+		} );
+
+		//
+
+		const material = this._material || ( this._material = builder.createNodeMaterial() );
+		material.fragmentNode = anamorph();
+
+		//
+
+		const properties = builder.getNodeProperties( this );
+		properties.textureNode = textureNode;
+
+		//
+
+		return this._textureNode;
+
+	}
+
+}
+
+export const anamorphic = ( node, threshold = .9, scale = 3, samples = 32 ) => nodeObject( new AnamorphicNode( nodeObject( node ), nodeObject( threshold ), nodeObject( scale ), samples ) );
+
+addNodeElement( 'anamorphic', anamorphic );
+
+export default AnamorphicNode;
+

+ 3 - 0
examples/jsm/nodes/display/ColorAdjustmentNode.js

@@ -89,8 +89,11 @@ export const hue = nodeProxy( ColorAdjustmentNode, ColorAdjustmentNode.HUE );
 export const lumaCoeffs = vec3( 0.2125, 0.7154, 0.0721 );
 export const lumaCoeffs = vec3( 0.2125, 0.7154, 0.0721 );
 export const luminance = ( color, luma = lumaCoeffs ) => dot( color, luma );
 export const luminance = ( color, luma = lumaCoeffs ) => dot( color, luma );
 
 
+export const threshold = ( color, threshold ) => mix( vec3( 0.0 ), color, luminance( color ).sub( threshold ).max( 0 ) );
+
 addNodeElement( 'saturation', saturation );
 addNodeElement( 'saturation', saturation );
 addNodeElement( 'vibrance', vibrance );
 addNodeElement( 'vibrance', vibrance );
 addNodeElement( 'hue', hue );
 addNodeElement( 'hue', hue );
+addNodeElement( 'threshold', threshold );
 
 
 addNodeClass( 'ColorAdjustmentNode', ColorAdjustmentNode );
 addNodeClass( 'ColorAdjustmentNode', ColorAdjustmentNode );

+ 15 - 2
examples/jsm/nodes/display/GaussianBlurNode.js

@@ -3,7 +3,7 @@ import { nodeObject, addNodeElement, tslFn, float, vec2, vec4 } from '../shadern
 import { NodeUpdateType } from '../core/constants.js';
 import { NodeUpdateType } from '../core/constants.js';
 import { mul } from '../math/OperatorNode.js';
 import { mul } from '../math/OperatorNode.js';
 import { uv } from '../accessors/UVNode.js';
 import { uv } from '../accessors/UVNode.js';
-import { texture } from '../accessors/TextureNode.js';
+import { texturePass } from './PassNode.js';
 import { uniform } from '../core/UniformNode.js';
 import { uniform } from '../core/UniformNode.js';
 import { Vector2, RenderTarget } from 'three';
 import { Vector2, RenderTarget } from 'three';
 import QuadMesh from '../../objects/QuadMesh.js';
 import QuadMesh from '../../objects/QuadMesh.js';
@@ -33,6 +33,8 @@ class GaussianBlurNode extends TempNode {
 		this._verticalRT = new RenderTarget();
 		this._verticalRT = new RenderTarget();
 		this._verticalRT.texture.name = 'GaussianBlurNode.vertical';
 		this._verticalRT.texture.name = 'GaussianBlurNode.vertical';
 
 
+		this._textureNode = texturePass( this, this._verticalRT.texture );
+
 		this.updateBeforeType = NodeUpdateType.RENDER;
 		this.updateBeforeType = NodeUpdateType.RENDER;
 
 
 		this.resolution = new Vector2( 1, 1 );
 		this.resolution = new Vector2( 1, 1 );
@@ -65,6 +67,11 @@ class GaussianBlurNode extends TempNode {
 
 
 		this.setSize( map.image.width, map.image.height );
 		this.setSize( map.image.width, map.image.height );
 
 
+		const textureType = map.type;
+
+		this._horizontalRT.texture.type = textureType;
+		this._verticalRT.texture.type = textureType;
+
 		// horizontal
 		// horizontal
 
 
 		renderer.setRenderTarget( this._horizontalRT );
 		renderer.setRenderTarget( this._horizontalRT );
@@ -89,6 +96,12 @@ class GaussianBlurNode extends TempNode {
 
 
 	}
 	}
 
 
+	getTextureNode() {
+
+		return this._textureNode;
+
+	}
+
 	setup( builder ) {
 	setup( builder ) {
 
 
 		const textureNode = this.textureNode;
 		const textureNode = this.textureNode;
@@ -149,7 +162,7 @@ class GaussianBlurNode extends TempNode {
 
 
 		//
 		//
 
 
-		return texture( this._verticalRT.texture );
+		return this._textureNode;
 
 
 	}
 	}
 
 

+ 1 - 0
examples/jsm/nodes/display/PassNode.js

@@ -177,6 +177,7 @@ PassNode.DEPTH = 'depth';
 export default PassNode;
 export default PassNode;
 
 
 export const pass = ( scene, camera ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera ) );
 export const pass = ( scene, camera ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera ) );
+export const texturePass = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) );
 export const depthPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera ) );
 export const depthPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera ) );
 
 
 addNodeClass( 'PassNode', PassNode );
 addNodeClass( 'PassNode', PassNode );

二进制
examples/screenshots/webgpu_postprocessing_anamorphic.jpg


+ 148 - 0
examples/webgpu_postprocessing_anamorphic.html

@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - postprocessing anamorphic</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">
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu - postprocessing anamorphic<br />
+			Battle Damaged Sci-fi Helmet by
+			<a href="https://sketchfab.com/theblueturtle_" target="_blank" rel="noopener">theblueturtle_</a><br />
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import { pass, cubeTexture, viewportTopLeft, uniform } from 'three/nodes';
+
+			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 PostProcessing from 'three/addons/renderers/common/PostProcessing.js';
+
+			import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let camera, scene, renderer;
+			let postProcessing;
+
+			init();
+
+			async function init() {
+
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU or WebGL2 support' );
+
+				}
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 20 );
+				camera.position.set( - 1.8, - 0.6, 2.7 );
+
+				scene = new THREE.Scene();
+
+				const rgbmUrls = [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ];
+				const cube1Texture = new RGBMLoader()
+					.setMaxRange( 16 )
+					.setPath( './textures/cube/pisaRGBM16/' )
+					.loadCubemap( rgbmUrls );
+
+				scene.environment = cube1Texture;
+				scene.backgroundNode = cubeTexture( cube1Texture ).mul( viewportTopLeft.distance( .5 ).oneMinus().remapClamp( .1, 4 ) ).saturation( 0 );
+
+				const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );
+				loader.load( 'DamagedHelmet.gltf', function ( gltf ) {
+
+					scene.add( gltf.scene );
+
+				} );
+
+				renderer = new WebGPURenderer( { antialias: true } );
+
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 1;
+				renderer.setAnimationLoop( render );
+				container.appendChild( renderer.domElement );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 2;
+				controls.maxDistance = 10;
+
+				// post-processing
+
+				const scenePass = pass( scene, camera );
+
+				const threshold = uniform( 1.4 );
+				const scaleNode = uniform( 5 );
+				const intensity = uniform( 1 );
+				const samples = 64;
+
+				const anamorphicPass = scenePass.getTextureNode().anamorphic( threshold, scaleNode, samples );
+				anamorphicPass.resolution = new THREE.Vector2( .2, .2 ); // 1 = full resolution
+
+				postProcessing = new PostProcessing( renderer );
+				postProcessing.outputNode = scenePass.add( anamorphicPass.mul( intensity ) );
+				//postProcessing.outputNode = scenePass.add( anamorphicPass.getTextureNode().gaussianBlur() );
+
+				// gui
+
+				const gui = new GUI();
+				gui.add( intensity, 'value', 0, 4, 0.1 ).name( 'intensity' );
+				gui.add( threshold, 'value', .8, 3, .001 ).name( 'threshold' );
+				gui.add( scaleNode, 'value', 1, 10, 0.1 ).name( 'scale' );
+				gui.add( anamorphicPass.resolution, 'x', .1, 1, 0.1 ).name( 'resolution' ).onChange( ( v ) => anamorphicPass.resolution.y = v );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function render() {
+
+				postProcessing.render();
+
+			}
+
+		</script>
+
+	</body>
+</html>