浏览代码

TSL: `parallaxUV` (#27739)

* BlendModeNode: Add layout and fix if used with textures

* TSL: Add parallaxUV

* Examples: Add `webgpu_parallax_uv`

* update

* cleanup

* Automatic compute tangents if needed

* use default TBNViewMatrix

* Rename parallaxDelta -> parallaxDirection

* Reg optional overlay using mix()
sunag 1 年之前
父节点
当前提交
a11a7735da

+ 1 - 0
examples/files.json

@@ -356,6 +356,7 @@
 		"webgpu_morphtargets",
 		"webgpu_morphtargets_face",
 		"webgpu_occlusion",
+		"webgpu_parallax_uv",
 		"webgpu_particles",
 		"webgpu_portal",
 		"webgpu_reflection",

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

@@ -76,7 +76,7 @@ export { default as ReflectorNode, reflector } from './utils/ReflectorNode.js';
 export * from './shadernode/ShaderNode.js';
 
 // accessors
-export { TBNViewMatrix } from './accessors/AccessorsUtils.js';
+export { TBNViewMatrix, parallaxDirection, parallaxUV } from './accessors/AccessorsUtils.js';
 export { default as UniformsNode, uniforms } from './accessors/UniformsNode.js';
 export { default as BitangentNode, bitangentGeometry, bitangentLocal, bitangentView, bitangentWorld, transformedBitangentView, transformedBitangentWorld } from './accessors/BitangentNode.js';
 export { default as BufferAttributeNode, bufferAttribute, dynamicBufferAttribute, instancedBufferAttribute, instancedDynamicBufferAttribute } from './accessors/BufferAttributeNode.js';

+ 4 - 0
examples/jsm/nodes/accessors/AccessorsUtils.js

@@ -2,5 +2,9 @@ import { bitangentView } from './BitangentNode.js';
 import { normalView } from './NormalNode.js';
 import { tangentView } from './TangentNode.js';
 import { mat3 } from '../shadernode/ShaderNode.js';
+import { positionViewDirection } from './PositionNode.js';
 
 export const TBNViewMatrix = mat3( tangentView, bitangentView, normalView );
+
+export const parallaxDirection = positionViewDirection.mul( TBNViewMatrix )/*.normalize()*/;
+export const parallaxUV = ( uv, scale ) => uv.sub( parallaxDirection.mul( scale ) );

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

@@ -48,6 +48,12 @@ class TangentNode extends Node {
 
 			outputNode = attribute( 'tangent', 'vec4' );
 
+			if ( builder.geometry.hasAttribute( 'tangent' ) === false ) {
+
+				builder.geometry.computeTangents();
+
+			}
+
 		} else if ( scope === TangentNode.LOCAL ) {
 
 			outputNode = varying( tangentGeometry.xyz );

+ 2 - 1
examples/jsm/nodes/display/BlendModeNode.js

@@ -1,5 +1,5 @@
 import TempNode from '../core/TempNode.js';
-import { EPSILON } from '../math/MathNode.js';
+import { /*mix, step,*/ EPSILON } from '../math/MathNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { addNodeElement, tslFn, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
 
@@ -51,6 +51,7 @@ export const ScreenNode = tslFn( ( { base, blend } ) => {
 export const OverlayNode = tslFn( ( { base, blend } ) => {
 
 	const fn = ( c ) => base[ c ].lessThan( 0.5 ).cond( base[ c ].mul( blend[ c ], 2.0 ), base[ c ].oneMinus().mul( blend[ c ].oneMinus() ).oneMinus() );
+	//const fn = ( c ) => mix( base[ c ].oneMinus().mul( blend[ c ].oneMinus() ).oneMinus(), base[ c ].mul( blend[ c ], 2.0 ), step( base[ c ], 0.5 ) );
 
 	return vec3( fn( 'x' ), fn( 'y' ), fn( 'z' ) );
 

二进制
examples/screenshots/webgpu_parallax_uv.jpg


二进制
examples/textures/ambientcg/Ice002_1K-JPG_Color.jpg


二进制
examples/textures/ambientcg/Ice002_1K-JPG_Displacement.jpg


二进制
examples/textures/ambientcg/Ice002_1K-JPG_NormalGL.jpg


二进制
examples/textures/ambientcg/Ice002_1K-JPG_Roughness.jpg


二进制
examples/textures/ambientcg/Ice003_1K-JPG_Color.jpg


+ 153 - 0
examples/webgpu_parallax_uv.html

@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - parallax uv</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 - parallax uv<br />
+			Textures by <a href="https://ambientcg.com/view?id=Ice002" target="_blank" rel="noopener">ambientCG</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 { MeshStandardNodeMaterial, texture, parallaxUV, uv } from 'three/nodes';
+
+			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() {
+
+				// scene
+
+				scene = new THREE.Scene();
+
+				// camera
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, .1, 50 );
+				camera.position.set( 10, 14, 10 );
+
+				// environment
+
+				const environmentTexture = new THREE.CubeTextureLoader()
+					.setPath( './textures/cube/Park2/' )
+					.load( [ 'posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg' ] );
+
+
+				scene.environment = environmentTexture;
+				scene.background = environmentTexture;
+
+				// textures
+
+				const loader = new THREE.TextureLoader();
+
+				const topTexture = loader.load( 'textures/ambientcg/Ice002_1K-JPG_Color.jpg' );
+				topTexture.colorSpace = THREE.SRGBColorSpace;
+
+				const roughnessTexture = loader.load( 'textures/ambientcg/Ice002_1K-JPG_Roughness.jpg' );
+				roughnessTexture.colorSpace = THREE.SRGBColorSpace;
+
+				const normalTexture = loader.load( 'textures/ambientcg/Ice002_1K-JPG_NormalGL.jpg' );
+				normalTexture.colorSpace = THREE.NoColorSpace;
+
+				const displaceTexture = loader.load( 'textures/ambientcg/Ice002_1K-JPG_Displacement.jpg' );
+				displaceTexture.colorSpace = THREE.SRGBColorSpace;
+
+				//
+
+				const bottomTexture = loader.load( 'textures/ambientcg/Ice003_1K-JPG_Color.jpg' );
+				bottomTexture.colorSpace = THREE.SRGBColorSpace;
+				bottomTexture.wrapS = THREE.RepeatWrapping;
+				bottomTexture.wrapT = THREE.RepeatWrapping;
+
+				// paralax effect
+
+				const parallaxScale = .3;
+				const offsetUV = texture( displaceTexture ).mul( parallaxScale );
+
+				const parallaxUVOffset = parallaxUV( uv(), offsetUV );
+				const parallaxResult = texture( bottomTexture, parallaxUVOffset );
+
+				const iceNode = texture( topTexture ).overlay( parallaxResult );
+
+				// material
+
+				const material = new MeshStandardNodeMaterial();
+				material.colorNode = iceNode.mul( 5 ); // increase the color intensity to 5 ( contrast )
+				material.roughnessNode = texture( roughnessTexture );
+				material.normalMap = normalTexture;
+				material.metalness = 0;
+
+				const geometry = new THREE.BoxGeometry( 10, 10, 10 );
+
+				const ground = new THREE.Mesh( geometry, material );
+				ground.rotateX( - Math.PI / 2 );
+				scene.add( ground );
+
+				// renderer
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.ReinhardToneMapping;
+				renderer.toneMappingExposure = 6;
+				document.body.appendChild( renderer.domElement );
+
+				// controls
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 0, 0 );
+				controls.maxDistance = 40;
+				controls.minDistance = 10;
+				controls.autoRotate = true;
+				controls.autoRotateSpeed = - 1;
+				controls.update();
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				controls.update();
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>