From 2f335437dfc013ae4fdc90ffc88280c0e7f98daf Mon Sep 17 00:00:00 2001 From: aardgoose Date: Tue, 11 Jun 2024 02:08:09 +0100 Subject: [PATCH] WebGPURenderer: RectAreaLight support (#28580) * WebGPURenderer: RectAreaLight support * detect float32 texture filetring support * cache runtime availbility checks * remove testing code * update screenshot * excude from test --------- Co-authored-by: aardgoose --- examples/files.json | 1 + examples/jsm/nodes/Nodes.js | 1 + examples/jsm/nodes/core/LightingModel.js | 2 + examples/jsm/nodes/functions/BSDF/LTC.js | 131 ++++++++++++++++++ .../nodes/functions/PhysicalLightingModel.js | 35 ++++- .../jsm/nodes/lighting/RectAreaLightNode.js | 90 ++++++++++++ .../renderers/webgl/nodes/GLSLNodeBuilder.js | 30 +++- .../renderers/webgpu/nodes/WGSLNodeBuilder.js | 17 ++- .../webgpu_lights_rectarealight.jpg | Bin 0 -> 23784 bytes examples/webgpu_lights_rectarealight.html | 118 ++++++++++++++++ test/e2e/puppeteer.js | 1 + 11 files changed, 422 insertions(+), 4 deletions(-) create mode 100644 examples/jsm/nodes/functions/BSDF/LTC.js create mode 100644 examples/jsm/nodes/lighting/RectAreaLightNode.js create mode 100644 examples/screenshots/webgpu_lights_rectarealight.jpg create mode 100644 examples/webgpu_lights_rectarealight.html diff --git a/examples/files.json b/examples/files.json index ffc30d148d4cf2..1a2a02f3264c11 100644 --- a/examples/files.json +++ b/examples/files.json @@ -331,6 +331,7 @@ "webgpu_lights_custom", "webgpu_lights_ies_spotlight", "webgpu_lights_phong", + "webgpu_lights_rectarealight", "webgpu_lights_selective", "webgpu_lines_fat", "webgpu_loader_gltf", diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index 60cf7802aa9ecb..808467ea0f2821 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -151,6 +151,7 @@ export { default as ComputeNode, compute } from './gpgpu/ComputeNode.js'; export { default as LightNode, lightTargetDirection } from './lighting/LightNode.js'; export { default as PointLightNode } from './lighting/PointLightNode.js'; export { default as DirectionalLightNode } from './lighting/DirectionalLightNode.js'; +export { default as RectAreaLightNode } from './lighting/RectAreaLightNode.js'; export { default as SpotLightNode } from './lighting/SpotLightNode.js'; export { default as IESSpotLightNode } from './lighting/IESSpotLightNode.js'; export { default as AmbientLightNode } from './lighting/AmbientLightNode.js'; diff --git a/examples/jsm/nodes/core/LightingModel.js b/examples/jsm/nodes/core/LightingModel.js index a30b7ac69ba9d2..7f5eea838834be 100644 --- a/examples/jsm/nodes/core/LightingModel.js +++ b/examples/jsm/nodes/core/LightingModel.js @@ -6,6 +6,8 @@ class LightingModel { direct( /*input, stack, builder*/ ) { } + directRectArea( /*input, stack, builder*/ ) {} + indirectDiffuse( /*input, stack, builder*/ ) { } indirectSpecular( /*input, stack, builder*/ ) { } diff --git a/examples/jsm/nodes/functions/BSDF/LTC.js b/examples/jsm/nodes/functions/BSDF/LTC.js new file mode 100644 index 00000000000000..79e68984b56261 --- /dev/null +++ b/examples/jsm/nodes/functions/BSDF/LTC.js @@ -0,0 +1,131 @@ +import { tslFn, If, mat3, vec2, vec3 } from '../../shadernode/ShaderNode.js'; +import { max } from '../../math/MathNode.js'; + +// Rect Area Light + +// Real-Time Polygonal-Light Shading with Linearly Transformed Cosines +// by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt +// code: https://github.com/selfshadow/ltc_code/ + +const LTC_Uv = tslFn( ( { N, V, roughness } ) => { + + const LUT_SIZE = 64.0; + const LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE; + const LUT_BIAS = 0.5 / LUT_SIZE; + + const dotNV = N.dot( V ).saturate(); + + // texture parameterized by sqrt( GGX alpha ) and sqrt( 1 - cos( theta ) ) + const uv = vec2( roughness, dotNV.oneMinus().sqrt() ); + + uv.assign( uv.mul( LUT_SCALE ).add( LUT_BIAS ) ); + + return uv; + +} ).setLayout( { + name: 'LTC_Uv', + type: 'vec2', + inputs: [ + { name: 'N', type: 'vec3' }, + { name: 'V', type: 'vec3' }, + { name: 'roughness', type: 'float' } + ] +} ); + +const LTC_ClippedSphereFormFactor = tslFn( ( { f } ) => { + + // Real-Time Area Lighting: a Journey from Research to Production (p.102) + // An approximation of the form factor of a horizon-clipped rectangle. + + const l = f.length(); + + return max( l.mul( l ).add( f.z ).div( l.add( 1.0 ) ), 0 ); + +} ).setLayout( { + name: 'LTC_ClippedSphereFormFactor', + type: 'float', + inputs: [ + { name: 'f', type: 'vec3' } + ] +} ); + +const LTC_EdgeVectorFormFactor = tslFn( ( { v1, v2 } ) => { + + const x = v1.dot( v2 ); + const y = x.abs().toVar(); + + // rational polynomial approximation to theta / sin( theta ) / 2PI + const a = y.mul( 0.0145206 ).add( 0.4965155 ).mul( y ).add( 0.8543985 ).toVar(); + const b = y.add( 4.1616724 ).mul( y ).add( 3.4175940 ).toVar(); + const v = a.div( b ); + + const theta_sintheta = x.greaterThan( 0.0 ).cond( v, max( x.mul( x ).oneMinus(), 1e-7 ).inverseSqrt().mul( 0.5 ).sub( v ) ); + + return v1.cross( v2 ).mul( theta_sintheta ); + +} ).setLayout( { + name: 'LTC_EdgeVectorFormFactor', + type: 'vec3', + inputs: [ + { name: 'v1', type: 'vec3' }, + { name: 'v2', type: 'vec3' } + ] +} ); + +const LTC_Evaluate = tslFn( ( { N, V, P, mInv, p0, p1, p2, p3 } ) => { + + // bail if point is on back side of plane of light + // assumes ccw winding order of light vertices + const v1 = p1.sub( p0 ).toVar(); + const v2 = p3.sub( p0 ).toVar(); + + const lightNormal = v1.cross( v2 ); + const result = vec3().toVar(); + + If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => { + + // construct orthonormal basis around N + const T1 = V.sub( N.mul( V.dot( N ) ) ).normalize(); + const T2 = N.cross( T1 ).negate(); // negated from paper; possibly due to a different handedness of world coordinate system + + // compute transform + const mat = mInv.mul( mat3( T1, T2, N ).transpose() ).toVar(); + + // transform rect + // & project rect onto sphere + const coords0 = mat.mul( p0.sub( P ) ).normalize().toVar(); + const coords1 = mat.mul( p1.sub( P ) ).normalize().toVar(); + const coords2 = mat.mul( p2.sub( P ) ).normalize().toVar(); + const coords3 = mat.mul( p3.sub( P ) ).normalize().toVar(); + + // calculate vector form factor + const vectorFormFactor = vec3( 0 ).toVar(); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) ); + + // adjust for horizon clipping + result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor } ) ) ); + + } ); + + return result; + +} ).setLayout( { + name: 'LTC_Evaluate', + type: 'vec3', + inputs: [ + { name: 'N', type: 'vec3' }, + { name: 'V', type: 'vec3' }, + { name: 'P', type: 'vec3' }, + { name: 'mInv', type: 'mat3' }, + { name: 'p0', type: 'vec3' }, + { name: 'p1', type: 'vec3' }, + { name: 'p2', type: 'vec3' }, + { name: 'p3', type: 'vec3' } + ] +} ); + + +export { LTC_Evaluate, LTC_Uv }; diff --git a/examples/jsm/nodes/functions/PhysicalLightingModel.js b/examples/jsm/nodes/functions/PhysicalLightingModel.js index ecfc0085bc7901..c24a9df326345f 100644 --- a/examples/jsm/nodes/functions/PhysicalLightingModel.js +++ b/examples/jsm/nodes/functions/PhysicalLightingModel.js @@ -5,10 +5,11 @@ import EnvironmentBRDF from './BSDF/EnvironmentBRDF.js'; import F_Schlick from './BSDF/F_Schlick.js'; import Schlick_to_F0 from './BSDF/Schlick_to_F0.js'; import BRDF_Sheen from './BSDF/BRDF_Sheen.js'; +import { LTC_Evaluate, LTC_Uv } from './BSDF/LTC.js'; import LightingModel from '../core/LightingModel.js'; import { diffuseColor, specularColor, specularF90, roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, ior, thickness, transmission, attenuationDistance, attenuationColor, dispersion } from '../core/PropertyNode.js'; import { transformedNormalView, transformedClearcoatNormalView, transformedNormalWorld } from '../accessors/NormalNode.js'; -import { positionViewDirection, positionWorld } from '../accessors/PositionNode.js'; +import { positionViewDirection, positionView, positionWorld } from '../accessors/PositionNode.js'; import { tslFn, float, vec2, vec3, vec4, mat3, If } from '../shadernode/ShaderNode.js'; import { cond } from '../math/CondNode.js'; import { mix, normalize, refract, length, clamp, log2, log, exp, smoothstep } from '../math/MathNode.js'; @@ -474,6 +475,38 @@ class PhysicalLightingModel extends LightingModel { } + directRectArea( { lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 } ) { + + const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction + const p1 = lightPosition.sub( halfWidth ).sub( halfHeight ); + const p2 = lightPosition.sub( halfWidth ).add( halfHeight ); + const p3 = lightPosition.add( halfWidth ).add( halfHeight ); + + const N = transformedNormalView; + const V = positionViewDirection; + const P = positionView.toVar(); + + const uv = LTC_Uv( { N, V, roughness } ); + + const t1 = ltc_1.uv( uv ).toVar(); + const t2 = ltc_2.uv( uv ).toVar(); + + const mInv = mat3( + vec3( t1.x, 0, t1.y ), + vec3( 0, 1, 0 ), + vec3( t1.z, 0, t1.w ) + ).toVar(); + + // LTC Fresnel Approximation by Stephen Hill + // http://blog.selfshadow.com/publications/s2016-advances/s2016_ltc_fresnel.pdf + const fresnel = specularColor.mul( t2.x ).add( specularColor.oneMinus().mul( t2.y ) ).toVar(); + + reflectedLight.directSpecular.addAssign( lightColor.mul( fresnel ).mul( LTC_Evaluate( { N, V, P, mInv, p0, p1, p2, p3 } ) ) ); + + reflectedLight.directDiffuse.addAssign( lightColor.mul( diffuseColor ).mul( LTC_Evaluate( { N, V, P, mInv: mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ), p0, p1, p2, p3 } ) ) ); + + } + indirectDiffuse( { irradiance, reflectedLight } ) { reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) ); diff --git a/examples/jsm/nodes/lighting/RectAreaLightNode.js b/examples/jsm/nodes/lighting/RectAreaLightNode.js new file mode 100644 index 00000000000000..f9b4da911a5b1e --- /dev/null +++ b/examples/jsm/nodes/lighting/RectAreaLightNode.js @@ -0,0 +1,90 @@ +import AnalyticLightNode from './AnalyticLightNode.js'; +import { addLightNode } from './LightsNode.js'; +import { texture } from '../accessors/TextureNode.js'; +import { uniform } from '../core/UniformNode.js'; +import { objectViewPosition } from '../accessors/Object3DNode.js'; +import { addNodeClass } from '../core/Node.js'; + +import { RectAreaLight, Matrix4, Vector3, UniformsLib } from 'three'; + +const _matrix41 = new Matrix4(); +const _matrix42 = new Matrix4(); +let ltc_1, ltc_2; + +class RectAreaLightNode extends AnalyticLightNode { + + constructor( light = null ) { + + super( light ); + + this.halfHeight = uniform( new Vector3() ); + this.halfWidth = uniform( new Vector3() ); + + } + + update( frame ) { + + super.update( frame ); + + const { light } = this; + + const viewMatrix = frame.camera.matrixWorldInverse; + + _matrix42.identity(); + _matrix41.copy( light.matrixWorld ); + _matrix41.premultiply( viewMatrix ); + _matrix42.extractRotation( _matrix41 ); + + this.halfWidth.value.set( light.width * 0.5, 0.0, 0.0 ); + this.halfHeight.value.set( 0.0, light.height * 0.5, 0.0 ); + + this.halfWidth.value.applyMatrix4( _matrix42 ); + this.halfHeight.value.applyMatrix4( _matrix42 ); + + } + + setup( builder ) { + + super.setup( builder ); + + if ( ltc_1 === undefined ) { + + if ( builder.isAvailable( 'float32Filterable' ) ) { + + ltc_1 = texture( UniformsLib.LTC_FLOAT_1 ); + ltc_2 = texture( UniformsLib.LTC_FLOAT_2 ); + + } else { + + ltc_1 = texture( UniformsLib.LTC_HALF_1 ); + ltc_2 = texture( UniformsLib.LTC_HALF_2 ); + + } + + } + + const { colorNode, light } = this; + const lightingModel = builder.context.lightingModel; + + const lightPosition = objectViewPosition( light ); + const reflectedLight = builder.context.reflectedLight; + + lightingModel.directRectArea( { + lightColor: colorNode, + lightPosition, + halfWidth: this.halfWidth, + halfHeight: this.halfHeight, + reflectedLight, + ltc_1, + ltc_2 + }, builder.stack, builder ); + + } + +} + +export default RectAreaLightNode; + +addNodeClass( 'RectAreaLightNode', RectAreaLightNode ); + +addLightNode( RectAreaLight, RectAreaLightNode ); diff --git a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js index c10e2f95220979..1c6ad302f9006b 100644 --- a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js @@ -20,7 +20,8 @@ const precisionLib = { }; const supports = { - swizzleAssign: true + swizzleAssign: true, + storageBuffer: false }; const defaultPrecisions = ` @@ -606,7 +607,32 @@ ${ flowData.code } isAvailable( name ) { - return supports[ name ] === true; + let result = supports[ name ]; + + if ( result === undefined ) { + + if ( name === 'float32Filterable' ) { + + const extentions = this.renderer.backend.extensions; + + if ( extentions.has( 'OES_texture_float_linear' ) ) { + + extentions.get( 'OES_texture_float_linear' ); + result = true; + + } else { + + result = false; + + } + + } + + supports[ name ] = result; + + } + + return result; } diff --git a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js index 6c2d8e0a55dd0f..21d276d85ccb29 100644 --- a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -26,6 +26,7 @@ const gpuShaderStageLib = { }; const supports = { + swizzleAssign: false, storageBuffer: true }; @@ -1010,7 +1011,21 @@ ${ flowData.code } isAvailable( name ) { - return supports[ name ] === true; + let result = supports[ name ]; + + if ( result === undefined ) { + + if ( name === 'float32Filterable' ) { + + result = this.renderer.hasFeature( 'float32-filterable' ); + + } + + supports[ name ] = result; + + } + + return result; } diff --git a/examples/screenshots/webgpu_lights_rectarealight.jpg b/examples/screenshots/webgpu_lights_rectarealight.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0e5e4854b049eadecefdc923fd5260da6276d0ec GIT binary patch literal 23784 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 literal 0 HcmV?d00001 diff --git a/examples/webgpu_lights_rectarealight.html b/examples/webgpu_lights_rectarealight.html new file mode 100644 index 00000000000000..3ac4f1e6ecbcd4 --- /dev/null +++ b/examples/webgpu_lights_rectarealight.html @@ -0,0 +1,118 @@ + + + + three.js webGPU - lights - rect area light + + + + + + +
+ three.js WebGPU - THREE.RectAreaLight
+ by abelnation +
+ + + + + + diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index a177485772054c..e7cd24b3ee587a 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -145,6 +145,7 @@ const exceptionList = [ 'webgpu_mesh_batch', 'webgpu_texturegrad', 'webgpu_performance_renderbundle', + 'webgpu_lights_rectarealight', // WebGPU idleTime and parseTime too low 'webgpu_compute_particles',