Procházet zdrojové kódy

Examples: WebGPU WGSL/TSL Node Interoperability (#28379)

* Got a wgsl shader generally working. Need to start small before working up

* Fixed bugs and errors

* Finished draft of wgsl tsl node interoperability example, which demonstrates how to use the same node variables across both wgsl and tsl version of the same shader, as well as demonstrating how to use varying attributes in both wgsl and tsl shaders

* Renamed file, ran eslint, created screenshot

* fix path in files.json

* remove screenshot button

* Fix screenshot

* Added example to exception list due to persistent issue with screenshots

* Remove unused imports

* offset wgsl upper shader and tsl lower shader to create single image

* ran linter again

* attempt style fix

* vscode convert indentation to tabs

* fix indentation in files.json and puppeteer.js

* Added uniforms that better clue user into the use of two separate shaders using the same uniforms, removed black line between quads, fixed cellOffset issue

* fix screenshot

* cleanup

---------
Christian Helgeson před 1 rokem
rodič
revize
d2c12dfe2d

+ 1 - 0
examples/files.json

@@ -372,6 +372,7 @@
 		"webgpu_rtt",
 		"webgpu_sandbox",
 		"webgpu_shadertoy",
+		"webgpu_tsl_interoperability",
 		"webgpu_shadowmap",
 		"webgpu_skinning",
 		"webgpu_skinning_instancing",

binární
examples/screenshots/webgpu_tsl_interoperability.jpg


+ 301 - 0
examples/webgpu_tsl_interoperability.html

@@ -0,0 +1,301 @@
+<html lang="en">
+	<head>
+		<title>three.js - shadertoy</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 - WGSL/TSL Node Interoperability
+			<br />CRT Shader adapted from <a href="https://mini.gmshaders.com/p/gm-shaders-mini-crt" target="_blank" rel="noopener"> Xor</a>.
+		</div>
+		<div id="container">
+			<canvas id="c"></canvas>
+		</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 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 { tslFn, attribute, varyingProperty, timerLocal, uniform, wgslFn, texture, uv, clamp, float, vec2, vec3, fract, floor, MeshBasicNodeMaterial, positionGeometry, sin } from 'three/nodes';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let renderer, camera, scene;
+			const dpr = window.devicePixelRatio;
+
+			const crtWidthUniform = uniform( 1608 );
+			const crtHeightUniform = uniform( 1608 );
+
+			const canvas = document.getElementById( 'c' );
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				renderer.render( scene, camera );
+
+			}
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU or WebGL2 support' );
+
+				}
+
+				const vUv = varyingProperty( 'vec2', 'vUv' );
+
+				// In WGSL, access varying properties from the varying struct
+				const wgslVertexShader = wgslFn( `
+					fn crtVertex(
+	 					position: vec3f,
+						uv: vec2f
+					) -> vec3<f32> {
+						varyings.vUv = uv;
+						return position;
+					}
+				`, [
+					vUv
+				] );
+
+				// Only wgsl vertex shaders take varyings arguments when defined.
+				// For a wgsl fragment shader, pass the varyingProperty node to the
+				// fragment shader's constructor to acess the varying value computed
+				// by the vertex shader.
+				const wgslFragmentShader = wgslFn( `
+					fn crtFragment(
+						vUv: vec2f,
+						tex: texture_2d<f32>,
+						texSampler: sampler,
+						crtWidth: f32, 
+						crtHeight: f32,
+						cellOffset: f32,
+						cellSize: f32,
+						borderMask: f32,
+						time: f32,
+						speed: f32,
+						pulseIntensity: f32,
+						pulseWidth: f32,
+						pulseRate: f32
+					) -> vec3<f32> {
+						// Convert uv into map of pixels
+						var pixel = ( vUv * 0.5 + 0.5 ) * vec2<f32>(
+							crtWidth, 
+							crtHeight
+						);
+						// Coordinate for each cell in the pixel map
+						var coord = pixel / cellSize;
+						// Three color values for each cell (r, g, b)
+						var subcoord = coord * vec2f( 3.0, 1.0 );
+						var offset = vec2<f32>( 0, fract( floor( coord.x ) * cellOffset ) );
+
+						var maskCoord = floor( coord + offset ) * cellSize;
+
+						var samplePoint = maskCoord / vec2<f32>(crtWidth, crtHeight);
+						samplePoint.x += fract( time * speed / 20 );
+
+						var color = textureSample(
+							tex,
+							texSampler,
+							samplePoint
+						).xyz;
+
+						// Current implementation does not give an even amount of space to each r, g, b unit of a cell
+						// Fix/hack this by multiplying subCoord.x by cellSize at cellSizes below 6
+						var ind = floor( subcoord.x ) % 3;
+
+						var maskColor = vec3<f32>(
+							f32( ind == 0.0 ), 
+							f32( ind == 1.0 ), 
+							f32( ind == 2.0 )
+						) * 3.0;
+
+						var cellUV = fract( subcoord + offset ) * 2.0 - 1.0;
+						var border: vec2<f32> = 1.0 - cellUV * cellUV * borderMask;
+
+						maskColor *= vec3f( clamp( border.x, 0.0, 1.0 ) * clamp( border.y, 0.0, 1.0) );
+
+						color *= maskColor;
+
+						color.r *= 1.0 + pulseIntensity * sin( pixel.y / pulseWidth + time * pulseRate );
+						color.b *= 1.0 + pulseIntensity * sin( pixel.y / pulseWidth + time * pulseRate );
+						color.g *= 1.0 + pulseIntensity * sin( pixel.y / pulseWidth + time * pulseRate );
+
+						return color;
+					}
+				` );
+
+				const textureLoader = new THREE.TextureLoader();
+				const planetTexture = textureLoader.load( 'textures/planets/earth_lights_2048.png' );
+				planetTexture.wrapS = THREE.RepeatWrapping;
+				planetTexture.wrapT = THREE.RepeatWrapping;
+
+				// Node Uniforms:
+				// Passed to WGSL Functions.
+				// Manipulated directly in TSL Functions.
+				const cellOffsetUniform = uniform( 0.5 );
+				const cellSizeUniform = uniform( 6 );
+				const borderMaskUniform = uniform( 1 );
+				const pulseIntensityUniform = uniform( 0.06 );
+				const pulseWidthUniform = uniform( 60 );
+				const pulseRateUniform = uniform( 20 );
+				const wgslShaderSpeedUniform = uniform( 1.0 );
+				const tslShaderSpeedUniform = uniform( 1.0 );
+
+				//
+
+				const wgslShaderMaterial = new MeshBasicNodeMaterial();
+
+				// Accessed attributes correspond to a Mesh or BufferGeometry's setAttribute() calls.
+				wgslShaderMaterial.positionNode = wgslVertexShader( {
+					position: attribute( 'position' ),
+					uv: attribute( 'uv' )
+				} );
+
+				wgslShaderMaterial.fragmentNode = wgslFragmentShader( {
+					vUv: vUv,
+					tex: texture( planetTexture ),
+					texSampler: texture( planetTexture ),
+					crtWidth: crtWidthUniform,
+					crtHeight: crtHeightUniform,
+					cellOffset: cellOffsetUniform,
+					cellSize: cellSizeUniform,
+					borderMask: borderMaskUniform,
+					time: timerLocal(),
+					speed: wgslShaderSpeedUniform,
+					pulseIntensity: pulseIntensityUniform,
+					pulseWidth: pulseWidthUniform,
+					pulseRate: pulseRateUniform
+				} );
+
+				//
+
+				const tslVertexShader = tslFn( () => {
+
+					vUv.assign( uv() );
+					return positionGeometry;
+
+				} );
+
+				const tslFragmentShader = tslFn( () => {
+
+					const dimensions = vec2( crtWidthUniform, crtHeightUniform );
+					const translatedUV = vUv.mul( 0.5 ).add( 0.5 );
+					const pixel = translatedUV.mul( dimensions );
+
+					const coord = pixel.div( cellSizeUniform );
+					const subCoord = coord.mul( vec2( 3.0, 1.0 ) );
+
+					const cellOffset = vec2(
+						0.0,
+						fract( floor( coord.x ).mul( cellOffsetUniform ) )
+					);
+
+					const maskCoord = floor( coord.add( cellOffset ) ).mul( cellSizeUniform );
+					const samplePoint = maskCoord.div( dimensions );
+					const time = timerLocal().mul( tslShaderSpeedUniform );
+					samplePoint.x = samplePoint.x.add( fract( time.div( 20 ) ) );
+					samplePoint.y = samplePoint.y.sub( 1.5 );
+
+					let color = texture( planetTexture, samplePoint );
+
+					const ind = floor( subCoord.x ).mod( 3 );
+
+					let maskColor = vec3(
+						ind.equal( 0.0 ),
+						ind.equal( 1.0 ),
+						ind.equal( 2.0 )
+					).mul( 3.0 );
+
+					const subCoordOffset = fract( subCoord.add( cellOffset ) );
+					let cellUV = subCoordOffset.mul( 2.0 );
+					cellUV = cellUV.sub( 1.0 );
+
+					const border = float( 1.0 ).sub(
+						cellUV.mul( cellUV ).mul( borderMaskUniform )
+					);
+
+					const clampX = clamp( border.x, 0.0, 1.0 );
+					const clampY = clamp( border.y, 0.0, 1.0 );
+					const borderClamp = clampX.mul( clampY );
+					maskColor = maskColor.mul( borderClamp );
+
+					color = color.mul( maskColor );
+
+					const pixelDampen = pixel.y.div( pulseWidthUniform );
+					let pulse = sin( pixelDampen.add( time.mul( pulseRateUniform ) ) );
+					pulse = pulse.mul( pulseIntensityUniform );
+					color = color.mul( float( 1.0 ).add( pulse ) );
+
+					return color;
+
+				} );
+
+				const tslShaderMaterial = new MeshBasicNodeMaterial();
+				tslShaderMaterial.positionNode = tslVertexShader();
+				tslShaderMaterial.colorNode = tslFragmentShader();
+
+				camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
+				scene = new THREE.Scene();
+
+				const geometry = new THREE.PlaneGeometry( 2, 1 );
+
+				const wgslQuad = new THREE.Mesh( geometry, wgslShaderMaterial );
+				wgslQuad.position.y += 0.5;
+				scene.add( wgslQuad );
+
+				const tslQuad = new THREE.Mesh( geometry, tslShaderMaterial );
+				tslQuad.position.y -= 0.5;
+				scene.add( tslQuad );
+
+				renderer = new WebGPURenderer( { antialias: true, canvas: canvas } );
+				renderer.setPixelRatio( dpr );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
+				document.body.appendChild( renderer.domElement );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				const gui = new GUI();
+
+				gui.add( cellSizeUniform, 'value', 6, 50, 1 ).name( 'Cell Size' );
+				gui.add( cellOffsetUniform, 'value', 0, 1, 0.1 ).name( 'Cell Offset' );
+				gui.add( borderMaskUniform, 'value', 0, 5, 0.1 ).name( 'Border Mask' );
+				gui.add( pulseIntensityUniform, 'value', 0, 0.5, 0.01 ).name( 'Pulse Intensity' );
+				gui.add( pulseWidthUniform, 'value', 10, 100, 5 ).name( 'Pulse Width' );
+				gui.add( wgslShaderSpeedUniform, 'value', 1, 10 ).name( 'WGSL Shader Speed' );
+				gui.add( tslShaderSpeedUniform, 'value', 1, 10 ).name( 'TSL Shader Speed' );
+
+			}
+
+			init();
+
+		</script>
+	</body>
+</html>

+ 1 - 0
test/e2e/puppeteer.js

@@ -139,6 +139,7 @@ const exceptionList = [
 	'webgpu_shadowmap',
 	'webgpu_tsl_editor',
 	'webgpu_tsl_transpiler',
+	'webgpu_tsl_interoperability',
 	'webgpu_portal',
 	'webgpu_custom_fog',
 	'webgpu_instancing_morph',