浏览代码

WebGPURenderer: Add `textureGrad` support (#28048)

* support texture grad

* cleanup

* add texture vs textureGrad example
Renaud Rohlinger 1 年之前
父节点
当前提交
75b06a18f0

+ 2 - 1
examples/files.json

@@ -384,7 +384,8 @@
 		"webgpu_materials_texture_anisotropy",
 		"webgpu_storage_buffer",
 		"webgpu_mesh_batch",
-		"webgpu_instancing_morph"
+		"webgpu_instancing_morph",
+		"webgpu_texturegrad"
 	],
 	"webaudio": [
 		"webaudio_orientation",

+ 21 - 3
examples/jsm/nodes/accessors/TextureNode.js

@@ -20,6 +20,7 @@ class TextureNode extends UniformNode {
 		this.levelNode = levelNode;
 		this.compareNode = null;
 		this.depthNode = null;
+		this.gradNode = null;
 
 		this.sampler = true;
 		this.updateMatrix = false;
@@ -155,6 +156,7 @@ class TextureNode extends UniformNode {
 		properties.uvNode = uvNode;
 		properties.levelNode = levelNode;
 		properties.compareNode = this.compareNode;
+		properties.gradNode = this.gradNode;
 		properties.depthNode = this.depthNode;
 
 	}
@@ -165,7 +167,7 @@ class TextureNode extends UniformNode {
 
 	}
 
-	generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet ) {
+	generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet, gradSnippet ) {
 
 		const texture = this.value;
 
@@ -175,6 +177,10 @@ class TextureNode extends UniformNode {
 
 			snippet = builder.generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet );
 
+		} else if ( gradSnippet ) {
+
+			snippet = builder.generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet );
+
 		} else if ( compareSnippet ) {
 
 			snippet = builder.generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet );
@@ -223,18 +229,19 @@ class TextureNode extends UniformNode {
 
 			if ( propertyName === undefined ) {
 
-				const { uvNode, levelNode, compareNode, depthNode } = properties;
+				const { uvNode, levelNode, compareNode, depthNode, gradNode } = properties;
 
 				const uvSnippet = this.generateUV( builder, uvNode );
 				const levelSnippet = levelNode ? levelNode.build( builder, 'float' ) : null;
 				const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null;
 				const compareSnippet = compareNode ? compareNode.build( builder, 'float' ) : null;
+				const gradSnippet = gradNode ? [ gradNode[ 0 ].build( builder, 'vec2' ), gradNode[ 1 ].build( builder, 'vec2' ) ] : null;
 
 				const nodeVar = builder.getVarFromNode( this );
 
 				propertyName = builder.getPropertyName( nodeVar );
 
-				const snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet );
+				const snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet, gradSnippet );
 
 				builder.addLineFlowCode( `${propertyName} = ${snippet}` );
 
@@ -324,6 +331,17 @@ class TextureNode extends UniformNode {
 
 	}
 
+	grad( gradNodeX, gradNodeY ) {
+
+		const textureNode = this.clone();
+		textureNode.gradNode = [ nodeObject( gradNodeX ), nodeObject( gradNodeY ) ];
+
+		textureNode.referenceNode = this;
+
+		return nodeObject( textureNode );
+
+	}
+
 	depth( depthNode ) {
 
 		const textureNode = this.clone();

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

@@ -246,6 +246,12 @@ ${ flowData.code }
 
 	}
 
+	generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet ) {
+
+		return `textureGrad( ${ textureProperty }, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`;
+
+	}
+
 	generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, shaderStage = this.shaderStage ) {
 
 		if ( shaderStage === 'fragment' ) {

+ 15 - 0
examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -254,6 +254,21 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 	}
 
+	generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet, shaderStage = this.shaderStage ) {
+
+		if ( shaderStage === 'fragment' ) {
+
+			// TODO handle i32 or u32 --> uvSnippet, array_index: A, ddx, ddy
+			return `textureSampleGrad( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet },  ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`;
+
+		} else {
+
+			console.error( `WebGPURenderer: THREE.TextureNode.gradient() does not support ${ shaderStage } shader.` );
+
+		}
+
+	}
+
 	generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, shaderStage = this.shaderStage ) {
 
 		if ( shaderStage === 'fragment' ) {

二进制
examples/screenshots/webgpu_texturegrad.jpg


+ 154 - 0
examples/webgpu_texturegrad.html

@@ -0,0 +1,154 @@
+<html lang="en">
+	<head>
+		<title>three.js - WebGPU - Texture Gradient</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>
+			<br />This example demonstrate texture gradient
+			<br /> Left canvas is using WebGPU Backend, right canvas is WebGL Backend.
+			<br /> The bottom half of the texture benefits from the gradient to achieve better blur quality.
+		</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 { If, vec4, float, timerLocal, cos, pow, vec2, uv, texture, tslFn, MeshBasicNodeMaterial } from 'three/nodes';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+
+			// WebGPU Backend
+			init();
+
+			// WebGL Backend
+			init( true );
+
+			async function init( forceWebGL = false ) {
+
+				const aspect = ( window.innerWidth / 2 ) / window.innerHeight;
+				const camera = new THREE.OrthographicCamera( - aspect, aspect );
+				camera.position.z = 2;
+
+				const scene = new THREE.Scene();
+
+				// texture
+
+				const material = new MeshBasicNodeMaterial( { color: 0xffffff } );
+
+				// load async brick_diffuse
+				const map = await new THREE.TextureLoader().loadAsync( 'textures/uv_grid_opengl.jpg' );
+
+				const elapsedTime = timerLocal();
+				material.colorNode = tslFn( () => {
+
+					const color = vec4( 1. ).toVar();
+
+					const vuv = uv().toVar();
+					const blur = pow( float( 0.0625 ).sub( cos( vuv.x.mul( 20.0 ).add( elapsedTime ) ) ).mul( 0.0625 ), 2.0 );
+
+					const grad = vec2( blur ).toVar();
+
+					If( vuv.y.greaterThan( 0.5 ), () => {
+
+						grad.assign( 0 );
+			
+					} );
+
+					color.assign(
+						texture( map, vuv.add( vec2( blur, blur ).mul( 0.5 ) ) ).grad( grad, grad ).mul( 0.25 )
+							.add( texture( map, vuv.add( vec2( blur, blur.negate() ).mul( 0.5 ) ) ).grad( grad, grad ).mul( 0.25 ) )
+							.add( texture( map, vuv.add( vec2( blur.negate(), blur ).mul( 0.5 ) ) ).grad( grad, grad ).mul( 0.25 ) )
+							.add( texture( map, vuv.add( vec2( blur.negate(), blur.negate() ).mul( 0.5 ) ) ).grad( grad, grad ).mul( 0.25 ) )
+					);
+
+
+					If( vuv.y.greaterThan( 0.497 ).and( vuv.y.lessThan( 0.503 ) ), () => {
+
+						color.assign( 1 );
+
+					} );
+			
+
+					return color;
+
+				} )();
+			
+				//
+
+				const box = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), material );
+				scene.add( box );
+
+				const renderer = new WebGPURenderer( { antialias: false, forceWebGL: forceWebGL, trackTimestamp: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth / 2, window.innerHeight );
+
+				document.body.appendChild( renderer.domElement );
+				renderer.domElement.style.position = 'absolute';
+				renderer.domElement.style.top = '0';
+				renderer.domElement.style.left = '0';
+				renderer.domElement.style.width = '50%';
+				renderer.domElement.style.height = '100%';
+
+				if ( forceWebGL ) {
+
+					renderer.domElement.style.left = '50%';
+
+					scene.background = new THREE.Color( 0x212121 );
+			
+				} else {
+
+					scene.background = new THREE.Color( 0x313131 );
+
+				}
+
+				//
+
+
+				 const animate = async function () {
+
+					await renderer.renderAsync( scene, camera );
+
+			
+				};
+
+				renderer.setAnimationLoop( animate );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				function onWindowResize() {
+
+					renderer.setSize( window.innerWidth / 2, window.innerHeight );
+
+					const aspect = ( window.innerWidth / 2 ) / window.innerHeight;
+
+					const frustumHeight = camera.top - camera.bottom;
+
+					camera.left = - frustumHeight * aspect / 2;
+					camera.right = frustumHeight * aspect / 2;
+
+					camera.updateProjectionMatrix();
+
+					renderer.render( scene, camera );
+
+				}
+
+			}
+
+		</script>
+	</body>
+</html>