Pārlūkot izejas kodu

Nodes: Fix VideoTexture in WebGPUBackend & ColorSpaceNode revision (#26261)

* Nodes: Fix VideoTexture ColorSpace in WebGPUBackend

* Added webgpu_video_panorama example
sunag 2 gadi atpakaļ
vecāks
revīzija
e6f7c4e677

+ 2 - 1
examples/files.json

@@ -332,7 +332,8 @@
 		"webgpu_skinning",
 		"webgpu_skinning_instancing",
 		"webgpu_skinning_points",
-		"webgpu_sprites"
+		"webgpu_sprites",
+		"webgpu_video_panorama"
 	],
 	"webaudio": [
 		"webaudio_orientation",

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

@@ -90,7 +90,7 @@ export { default as UserDataNode, userData } from './accessors/UserDataNode.js';
 // display
 export { default as BlendModeNode, burn, dodge, overlay, screen } from './display/BlendModeNode.js';
 export { default as ColorAdjustmentNode, saturation, vibrance, hue, lumaCoeffs, luminance } from './display/ColorAdjustmentNode.js';
-export { default as ColorSpaceNode, colorSpace } from './display/ColorSpaceNode.js';
+export { default as ColorSpaceNode, linearToColorSpace, colorSpaceToLinear as sRGBToColorSpace, linearTosRGB, sRGBToLinear } from './display/ColorSpaceNode.js';
 export { default as FrontFacingNode, frontFacing, faceDirection } from './display/FrontFacingNode.js';
 export { default as NormalMapNode, normalMap, TBNViewMatrix } from './display/NormalMapNode.js';
 export { default as PosterizeNode, posterize } from './display/PosterizeNode.js';

+ 11 - 1
examples/jsm/nodes/accessors/TextureNode.js

@@ -1,7 +1,9 @@
 import UniformNode from '../core/UniformNode.js';
 import { uv } from './UVNode.js';
 import { textureSize } from './TextureSizeNode.js';
+import { colorSpaceToLinear } from '../display/ColorSpaceNode.js';
 import { context } from '../core/ContextNode.js';
+import { expression } from '../code/ExpressionNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
 
@@ -136,7 +138,15 @@ class TextureNode extends UniformNode {
 
 			}
 
-			return builder.format( propertyName, nodeType, output );
+			let snippet = propertyName;
+
+			if ( builder.needsColorSpace( this.value ) ) {
+
+				snippet = colorSpaceToLinear( expression( snippet, nodeType ), this.value.colorSpace ).construct( builder ).build( builder, nodeType );
+
+			}
+
+			return builder.format( snippet, nodeType, output );
 
 		}
 

+ 6 - 0
examples/jsm/nodes/core/NodeBuilder.js

@@ -390,6 +390,12 @@ class NodeBuilder {
 
 	}
 
+	needsColorSpace( /*texture*/ ) {
+
+		return false;
+
+	}
+
 	/** @deprecated, r152 */
 	getTextureEncodingFromMap( map ) {
 

+ 50 - 48
examples/jsm/nodes/display/ColorSpaceNode.js

@@ -1,17 +1,26 @@
 import TempNode from '../core/TempNode.js';
 import { mix } from '../math/MathNode.js';
 import { addNodeClass } from '../core/Node.js';
-import { addNodeElement, ShaderNode, nodeObject, vec4 } from '../shadernode/ShaderNode.js';
+import { addNodeElement, ShaderNode, nodeObject, nodeProxy, vec4 } from '../shadernode/ShaderNode.js';
 
-import { LinearEncoding, LinearSRGBColorSpace, sRGBEncoding, SRGBColorSpace } from 'three';
+import { LinearSRGBColorSpace, SRGBColorSpace } from 'three';
 
-export const LinearToLinear = new ShaderNode( ( inputs ) => {
+const sRGBToLinearShader = new ShaderNode( ( inputs ) => {
 
-	return inputs.value;
+	const { value } = inputs;
+	const { rgb } = value;
+
+	const a = rgb.mul( 0.9478672986 ).add( 0.0521327014 ).pow( 2.4 );
+	const b = rgb.mul( 0.0773993808 );
+	const factor = rgb.lessThanEqual( 0.04045 );
+
+	const rgbResult = mix( a, b, factor );
+
+	return vec4( rgbResult, value.a );
 
 } );
 
-export const LinearTosRGB = new ShaderNode( ( inputs ) => {
+const LinearTosRGBShader = new ShaderNode( ( inputs ) => {
 
 	const { value } = inputs;
 	const { rgb } = value;
@@ -26,62 +35,39 @@ export const LinearTosRGB = new ShaderNode( ( inputs ) => {
 
 } );
 
-const EncodingLib = {
-	LinearToLinear,
-	LinearTosRGB
-};
+const getColorSpaceMethod = ( colorSpace ) => {
 
-class ColorSpaceNode extends TempNode {
+	let method = null;
 
-	constructor( method, node ) {
+	if ( colorSpace === LinearSRGBColorSpace ) {
 
-		super( 'vec4' );
+		method = 'Linear';
 
-		this.method = method;
+	} else if ( colorSpace === SRGBColorSpace ) {
 
-		this.node = node;
+		method = 'sRGB';
 
 	}
 
-	fromColorSpace( colorSpace ) {
-
-		let method = null;
-
-		if ( colorSpace === LinearSRGBColorSpace ) {
-
-			method = 'Linear';
-
-		} else if ( colorSpace === SRGBColorSpace ) {
-
-			method = 'sRGB';
+	return method;
 
-		}
-
-		this.method = 'LinearTo' + method;
-
-		return this;
-
-	}
-
-	fromEncoding( encoding ) { // @deprecated, r152
-
-		console.warn( 'THREE.ColorSpaceNode: Method .fromEncoding renamed to .fromColorSpace.' );
+};
 
-		let method = null;
+const getMethod = ( source, target ) => {
 
-		if ( encoding === LinearEncoding ) {
+	return getColorSpaceMethod( source ) + 'To' + getColorSpaceMethod( target );
 
-			method = 'Linear';
+};
 
-		} else if ( encoding === sRGBEncoding ) {
+class ColorSpaceNode extends TempNode {
 
-			method = 'sRGB';
+	constructor( method, node ) {
 
-		}
+		super( 'vec4' );
 
-		this.method = 'LinearTo' + method;
+		this.method = method;
 
-		return this;
+		this.node = node;
 
 	}
 
@@ -89,19 +75,35 @@ class ColorSpaceNode extends TempNode {
 
 		const { method, node } = this;
 
-		return EncodingLib[ method ].call( { value: node } );
+		if ( method === ColorSpaceNode.LINEAR_TO_LINEAR )
+			return node;
+
+		return Methods[ method ].call( { value: node } );
 
 	}
 
 }
 
 ColorSpaceNode.LINEAR_TO_LINEAR = 'LinearToLinear';
-ColorSpaceNode.LINEAR_TO_SRGB = 'LinearTosRGB';
+ColorSpaceNode.LINEAR_TO_sRGB = 'LinearTosRGB';
+ColorSpaceNode.sRGB_TO_LINEAR = 'sRGBToLinear';
+
+const Methods = {
+	[ ColorSpaceNode.LINEAR_TO_sRGB ]: LinearTosRGBShader,
+	[ ColorSpaceNode.sRGB_TO_LINEAR ]: sRGBToLinearShader
+};
 
 export default ColorSpaceNode;
 
-export const colorSpace = ( node, colorSpace ) => nodeObject( new ColorSpaceNode( null, nodeObject( node ) ).fromColorSpace( colorSpace ) );
+export const linearToColorSpace = ( node, colorSpace ) => nodeObject( new ColorSpaceNode( getMethod( LinearSRGBColorSpace, colorSpace ), nodeObject( node ) ) );
+export const colorSpaceToLinear = ( node, colorSpace ) => nodeObject( new ColorSpaceNode( getMethod( colorSpace, LinearSRGBColorSpace ), nodeObject( node ) ) );
+
+export const linearTosRGB = nodeProxy( ColorSpaceNode, ColorSpaceNode.LINEAR_TO_sRGB );
+export const sRGBToLinear = nodeProxy( ColorSpaceNode, ColorSpaceNode.sRGB_TO_LINEAR );
 
-addNodeElement( 'colorSpace', colorSpace );
+addNodeElement( 'linearTosRGB', linearTosRGB );
+addNodeElement( 'sRGBToLinear', sRGBToLinear );
+addNodeElement( 'linearToColorSpace', linearToColorSpace );
+addNodeElement( 'colorSpaceToLinear', colorSpaceToLinear );
 
 addNodeClass( ColorSpaceNode );

+ 1 - 1
examples/jsm/nodes/materials/NodeMaterial.js

@@ -299,7 +299,7 @@ class NodeMaterial extends ShaderMaterial {
 
 		}
 
-		if ( outputColorSpace !== NoColorSpace ) outputNode = outputNode.colorSpace( outputColorSpace );
+		if ( outputColorSpace !== NoColorSpace ) outputNode = outputNode.linearToColorSpace( outputColorSpace );
 
 		// FOG
 

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

@@ -131,6 +131,12 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
+	needsColorSpace( texture ) {
+
+		return texture.isVideoTexture === true;
+
+	}
+
 	getSampler( textureProperty, uvSnippet, shaderStage = this.shaderStage ) {
 
 		if ( shaderStage === 'fragment' ) {

BIN
examples/screenshots/webgpu_video_panorama.jpg


+ 169 - 0
examples/webgpu_video_panorama.html

@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - video panorama</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">
+		<style>
+			body {
+				touch-action: none;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - video panorama
+		</div>
+
+		<div id="container"></div>
+
+		<video id="video" loop muted crossOrigin="anonymous" playsinline style="display:none">
+			<source src="textures/pano.webm">
+			<source src="textures/pano.mp4">
+		</video>
+
+		<!-- 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/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			let camera, scene, renderer;
+
+			let isUserInteracting = false,
+				lon = 0, lat = 0,
+				phi = 0, theta = 0,
+				onPointerDownPointerX = 0,
+				onPointerDownPointerY = 0,
+				onPointerDownLon = 0,
+				onPointerDownLat = 0;
+
+			const distance = .5;
+
+			init();
+			animate();
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU support' );
+
+				}
+
+				const container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, .25, 10 );
+
+				scene = new THREE.Scene();
+
+				const geometry = new THREE.SphereGeometry( 5, 60, 40 );
+				// invert the geometry on the x-axis so that all of the faces point inward
+				geometry.scale( - 1, 1, 1 );
+
+				const video = document.getElementById( 'video' );
+				video.play();
+
+				const texture = new THREE.VideoTexture( video );
+				texture.colorSpace = THREE.SRGBColorSpace;
+				const material = new THREE.MeshBasicMaterial( { map: texture } );
+
+				const mesh = new THREE.Mesh( geometry, material );
+				scene.add( mesh );
+
+				renderer = new WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				document.addEventListener( 'pointerdown', onPointerDown );
+				document.addEventListener( 'pointermove', onPointerMove );
+				document.addEventListener( 'pointerup', onPointerUp );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function onPointerDown( event ) {
+
+				isUserInteracting = true;
+
+				onPointerDownPointerX = event.clientX;
+				onPointerDownPointerY = event.clientY;
+
+				onPointerDownLon = lon;
+				onPointerDownLat = lat;
+
+			}
+
+			function onPointerMove( event ) {
+
+				if ( isUserInteracting === true ) {
+
+					lon = ( onPointerDownPointerX - event.clientX ) * 0.1 + onPointerDownLon;
+					lat = ( onPointerDownPointerY - event.clientY ) * 0.1 + onPointerDownLat;
+
+				}
+
+			}
+
+			function onPointerUp() {
+
+				isUserInteracting = false;
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+				update();
+
+			}
+
+			function update() {
+
+				lat = Math.max( - 85, Math.min( 85, lat ) );
+				phi = THREE.MathUtils.degToRad( 90 - lat );
+				theta = THREE.MathUtils.degToRad( lon );
+
+				camera.position.x = distance * Math.sin( phi ) * Math.cos( theta );
+				camera.position.y = distance * Math.cos( phi );
+				camera.position.z = distance * Math.sin( phi ) * Math.sin( theta );
+
+				camera.lookAt( 0, 0, 0 );
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 2 - 1
test/e2e/puppeteer.js

@@ -129,7 +129,8 @@ const exceptionList = [
 	'webgpu_skinning',
 	'webgpu_skinning_instancing',
 	'webgpu_skinning_points',
-	'webgpu_sprites'
+	'webgpu_sprites',
+	'webgpu_video_panorama'
 
 ];