Browse Source

Nodes & WebGPU: equirectUV() (#24825)

* Add positionWorldDirection

* Add equirectUV

* cleanup

* add webgpu_equirectangular example

* fix mips interpolation
sunag 2 years ago
parent
commit
4f77503278

+ 1 - 0
examples/files.json

@@ -326,6 +326,7 @@
 		"webgpu_cubemap_adjustments",
 		"webgpu_cubemap_mix",
 		"webgpu_depth_texture",
+		"webgpu_equirectangular",
 		"webgpu_instance_mesh",
 		"webgpu_instance_uniform",
 		"webgpu_lights_custom",

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

@@ -80,6 +80,7 @@ import AnalyticLightNode from './lighting/AnalyticLightNode.js';
 // utils
 import ArrayElementNode from './utils/ArrayElementNode.js';
 import ConvertNode from './utils/ConvertNode.js';
+import EquirectUVNode from './utils/EquirectUVNode.js';
 import JoinNode from './utils/JoinNode.js';
 import MatcapUVNode from './utils/MatcapUVNode.js';
 import MaxMipLevelNode from './utils/MaxMipLevelNode.js';
@@ -205,6 +206,7 @@ const nodeLib = {
 	// utils
 	ArrayElementNode,
 	ConvertNode,
+	EquirectUVNode,
 	JoinNode,
 	MatcapUVNode,
 	MaxMipLevelNode,
@@ -230,7 +232,7 @@ const nodeLib = {
 
 	// parsers
 	WGSLNodeParser,
-	GLSLNodeParser,
+	GLSLNodeParser
 
 };
 
@@ -323,6 +325,7 @@ export {
 	// utils
 	ArrayElementNode,
 	ConvertNode,
+	EquirectUVNode,
 	JoinNode,
 	MatcapUVNode,
 	MaxMipLevelNode,
@@ -348,5 +351,5 @@ export {
 
 	// parsers
 	WGSLNodeParser,
-	GLSLNodeParser,
+	GLSLNodeParser
 };

+ 6 - 0
examples/jsm/nodes/accessors/PositionNode.js

@@ -10,6 +10,7 @@ class PositionNode extends Node {
 	static GEOMETRY = 'geometry';
 	static LOCAL = 'local';
 	static WORLD = 'world';
+	static WORLD_DIRECTION = 'worldDirection';
 	static VIEW = 'view';
 	static VIEW_DIRECTION = 'viewDirection';
 
@@ -56,6 +57,11 @@ class PositionNode extends Node {
 			const vertexPositionNode = new MathNode( MathNode.NEGATE, new PositionNode( PositionNode.VIEW ) );
 			outputNode = new MathNode( MathNode.NORMALIZE, new VaryingNode( vertexPositionNode ) );
 
+		} else if ( scope === PositionNode.WORLD_DIRECTION ) {
+
+			const vertexPositionNode = new MathNode( MathNode.NEGATE, new PositionNode( PositionNode.WORLD ) );
+			outputNode = new MathNode( MathNode.NORMALIZE, new VaryingNode( vertexPositionNode ) );
+
 		}
 
 		return outputNode.build( builder, this.getNodeType( builder ) );

+ 1 - 0
examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js

@@ -266,6 +266,7 @@ export const modelViewPosition = nodeImmutable( ModelNode, ModelNode.VIEW_POSITI
 export const positionGeometry = nodeImmutable( PositionNode, PositionNode.GEOMETRY );
 export const positionLocal = nodeImmutable( PositionNode, PositionNode.LOCAL );
 export const positionWorld = nodeImmutable( PositionNode, PositionNode.WORLD );
+export const positionWorldDirection = nodeImmutable( PositionNode, PositionNode.WORLD_DIRECTION );
 export const positionView = nodeImmutable( PositionNode, PositionNode.VIEW );
 export const positionViewDirection = nodeImmutable( PositionNode, PositionNode.VIEW_DIRECTION );
 

+ 3 - 0
examples/jsm/nodes/shadernode/ShaderNodeElements.js

@@ -18,6 +18,7 @@ import LightsNode from '../lighting/LightsNode.js';
 import LightingContextNode from '../lighting/LightingContextNode.js';
 
 // utils
+import EquirectUVNode from '../utils/EquirectUVNode.js';
 import MatcapUVNode from '../utils/MatcapUVNode.js';
 import MaxMipLevelNode from '../utils/MaxMipLevelNode.js';
 import OscNode from '../utils/OscNode.js';
@@ -101,6 +102,8 @@ export const lightingContext = nodeProxy( LightingContextNode );
 // utils
 
 export const matcapUV = nodeImmutable( MatcapUVNode );
+export const equirectUV = nodeProxy( EquirectUVNode );
+
 export const maxMipLevel = nodeProxy( MaxMipLevelNode );
 
 export const oscSine = nodeProxy( OscNode, OscNode.SINE );

+ 27 - 0
examples/jsm/nodes/utils/EquirectUVNode.js

@@ -0,0 +1,27 @@
+import TempNode from '../core/TempNode.js';
+import { nodeObject, vec2, add, mul, atan2, asin, clamp, positionWorldDirection } from '../shadernode/ShaderNodeElements.js';
+
+class EquirectUVNode extends TempNode {
+
+	constructor( dirNode = positionWorldDirection ) {
+
+		super( 'vec2' );
+
+		this.dirNode = dirNode;
+
+	}
+
+	construct() {
+
+		const dir = nodeObject( this.dirNode );
+
+		const u = add( mul( atan2( dir.z, dir.x ), 1 / ( Math.PI * 2 ) ), 0.5 );
+		const v = add( mul( asin( clamp( dir.y, - 1.0, 1.0 ) ), 1 / Math.PI ), 0.5 );
+
+		return vec2( u, v );
+
+	}
+
+}
+
+export default EquirectUVNode;

BIN
examples/screenshots/webgpu_equirectangular.jpg


+ 102 - 0
examples/webgpu_equirectangular.html

@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - equirectangular</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 webgpu</a> - equirectangular panorama demo. photo by <a href="http://www.flickr.com/photos/jonragnarsson/2294472375/" target="_blank" rel="noopener">Jón Ragnarsson</a>.
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<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 { texture, equirectUV } from 'three/nodes';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			let camera, scene, renderer;
+			let controls;
+
+			init();
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU 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, 0, 0 );
+
+				const equirectTexture = new THREE.TextureLoader().load( 'textures/2294472375_24a3b8ef46_o.jpg' );
+				equirectTexture.flipY = false;
+
+				scene = new THREE.Scene();
+				scene.backgroundNode = texture( equirectTexture, equirectUV(), 0 );
+
+				renderer = new WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( render );
+				container.appendChild( renderer.domElement );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.autoRotate = true;
+				controls.minDistance = 2;
+				controls.maxDistance = 10;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function render() {
+
+				controls.update();
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 0
test/e2e/puppeteer.js

@@ -48,6 +48,7 @@ const exceptionList = [
 	'webgpu_cubemap_adjustments',
 	'webgpu_cubemap_mix',
 	'webgpu_depth_texture',
+	'webgpu_equirectangular',
 	'webgpu_instance_mesh',
 	'webgpu_instance_uniform',
 	'webgpu_lights_custom',