Bladeren bron

Addons: Add `SobelOperatorNode`. (#28733)

* Addons: Add `SobelOperatorNode`.

* E2E: Update screenshot.

* SobelOperatorNode: Add comment.
Michael Herzog 1 jaar geleden
bovenliggende
commit
43d276ec5a

+ 1 - 0
examples/files.json

@@ -369,6 +369,7 @@
 		"webgpu_portal",
 		"webgpu_portal",
 		"webgpu_postprocessing_afterimage",
 		"webgpu_postprocessing_afterimage",
 		"webgpu_postprocessing_anamorphic",
 		"webgpu_postprocessing_anamorphic",
+		"webgpu_postprocessing_sobel",
 		"webgpu_reflection",
 		"webgpu_reflection",
 		"webgpu_rtt",
 		"webgpu_rtt",
 		"webgpu_sandbox",
 		"webgpu_sandbox",

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

@@ -125,6 +125,7 @@ export { default as ViewportDepthNode, viewZToOrthographicDepth, orthographicDep
 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 AnamorphicNode, anamorphic } from './display/AnamorphicNode.js';
+export { default as SobelOperatorNode, sobel } from './display/SobelOperatorNode.js';
 
 
 export { default as PassNode, pass, texturePass, depthPass } from './display/PassNode.js';
 export { default as PassNode, pass, texturePass, depthPass } from './display/PassNode.js';
 
 

+ 120 - 0
examples/jsm/nodes/display/SobelOperatorNode.js

@@ -0,0 +1,120 @@
+import TempNode from '../core/TempNode.js';
+import { uv } from '../accessors/UVNode.js';
+import { luminance } from './ColorAdjustmentNode.js';
+import { addNodeElement, tslFn, nodeObject, vec2, vec3, vec4, mat3 } from '../shadernode/ShaderNode.js';
+import { NodeUpdateType } from '../core/constants.js';
+import { uniform } from '../core/UniformNode.js';
+import { add } from '../math/OperatorNode.js';
+import { Vector2 } from 'three';
+
+class SobelOperatorNode extends TempNode {
+
+	constructor( textureNode ) {
+
+		super();
+
+		this.textureNode = textureNode;
+
+		this.updateBeforeType = NodeUpdateType.RENDER;
+
+		this._invSize = uniform( new Vector2() );
+
+	}
+
+	updateBefore() {
+
+		const map = this.textureNode.value;
+
+		this._invSize.value.set( 1 / map.image.width, 1 / map.image.height );
+
+	}
+
+	setup() {
+
+		const { textureNode } = this;
+
+		const uvNode = textureNode.uvNode || uv();
+
+		const sampleTexture = ( uv ) => this.textureNode.cache().context( { getUV: () => uv, forceUVContext: true } );
+
+		const sobel = tslFn( () => {
+
+			// Sobel Edge Detection (see https://youtu.be/uihBwtPIBxM)
+
+			const texel = this._invSize;
+
+			// kernel definition (in glsl matrices are filled in column-major order)
+
+			const Gx = mat3( - 1, - 2, - 1, 0, 0, 0, 1, 2, 1 ); // x direction kernel
+			const Gy = mat3( - 1, 0, 1, - 2, 0, 2, - 1, 0, 1 ); // y direction kernel
+
+			// fetch the 3x3 neighbourhood of a fragment
+
+			// first column
+
+			const tx0y0 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, - 1 ) ) ) ).xyz );
+			const tx0y1 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, 0 ) ) ) ).xyz );
+			const tx0y2 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, 1 ) ) ) ).xyz );
+
+			// second column
+
+			const tx1y0 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 0, - 1 ) ) ) ).xyz );
+			const tx1y1 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 0, 0 ) ) ) ).xyz );
+			const tx1y2 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 0, 1 ) ) ) ).xyz );
+
+			// third column
+
+			const tx2y0 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 1, - 1 ) ) ) ).xyz );
+			const tx2y1 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 1, 0 ) ) ) ).xyz );
+			const tx2y2 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 1, 1 ) ) ) ).xyz );
+
+			// gradient value in x direction
+
+			const valueGx = add(
+				Gx[ 0 ][ 0 ].mul( tx0y0 ),
+				Gx[ 1 ][ 0 ].mul( tx1y0 ),
+				Gx[ 2 ][ 0 ].mul( tx2y0 ),
+				Gx[ 0 ][ 1 ].mul( tx0y1 ),
+				Gx[ 1 ][ 1 ].mul( tx1y1 ),
+				Gx[ 2 ][ 1 ].mul( tx2y1 ),
+				Gx[ 0 ][ 2 ].mul( tx0y2 ),
+				Gx[ 1 ][ 2 ].mul( tx1y2 ),
+				Gx[ 2 ][ 2 ].mul( tx2y2 )
+			);
+
+
+			// gradient value in y direction
+
+			const valueGy = add(
+				Gy[ 0 ][ 0 ].mul( tx0y0 ),
+				Gy[ 1 ][ 0 ].mul( tx1y0 ),
+				Gy[ 2 ][ 0 ].mul( tx2y0 ),
+				Gy[ 0 ][ 1 ].mul( tx0y1 ),
+				Gy[ 1 ][ 1 ].mul( tx1y1 ),
+				Gy[ 2 ][ 1 ].mul( tx2y1 ),
+				Gy[ 0 ][ 2 ].mul( tx0y2 ),
+				Gy[ 1 ][ 2 ].mul( tx1y2 ),
+				Gy[ 2 ][ 2 ].mul( tx2y2 )
+			);
+
+			// magnitute of the total gradient
+
+			const G = valueGx.mul( valueGx ).add( valueGy.mul( valueGy ) ).sqrt();
+
+			return vec4( vec3( G ), 1 );
+
+		} );
+
+		const outputNode = sobel();
+
+		return outputNode;
+
+	}
+
+}
+
+export const sobel = ( node ) => nodeObject( new SobelOperatorNode( nodeObject( node ) ) );
+
+addNodeElement( 'sobel', sobel );
+
+export default SobelOperatorNode;

BIN
examples/screenshots/webgpu_postprocessing_sobel.jpg


+ 129 - 0
examples/webgpu_postprocessing_sobel.html

@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - postprocessing - sobel</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>
+		<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 { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import PostProcessing from 'three/addons/renderers/common/PostProcessing.js';
+			import { pass } from 'three/nodes';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let camera, scene, renderer;
+			let postProcessing;
+
+			const params = {
+				enable: true
+			};
+
+			init();
+
+			function init() {
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( 0, 1, 3 );
+				camera.lookAt( scene.position );
+
+				//
+
+				const ambientLight = new THREE.AmbientLight( 0xe7e7e7 );
+				scene.add( ambientLight );
+
+				const pointLight = new THREE.PointLight( 0xffffff, 20 );
+				camera.add( pointLight );
+				scene.add( camera );
+
+				//
+
+				const geometry = new THREE.TorusKnotGeometry( 1, 0.3, 256, 32 );
+				const material = new THREE.MeshPhongMaterial( { color: 0xffff00 } );
+
+				const mesh = new THREE.Mesh( geometry, material );
+				scene.add( mesh );
+
+				//
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
+				document.body.appendChild( renderer.domElement );
+
+				//
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableZoom = false;
+
+
+				// postprocessing
+
+				postProcessing = new PostProcessing( renderer );
+
+				const scenePass = pass( scene, camera );
+				const scenePassColor = scenePass.getTextureNode();
+
+				postProcessing.outputNode = scenePassColor.sobel();
+
+				//
+
+				const gui = new GUI();
+
+				gui.add( params, 'enable' );
+				gui.open();
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				if ( params.enable === true ) {
+
+					postProcessing.render();
+
+				} else {
+
+					renderer.render( scene, camera );
+
+				}
+
+			}
+
+		</script>
+	</body>
+</html>