Skip to content

Commit

Permalink
WebGPURenderer: Transmission - Backdrop 3/3 (#27880)
Browse files Browse the repository at this point in the history
* WebGPURenderer: Apply fog before tonemapping and encoding

* TSL: Add `rendererReference`

* PassNode: Add `getViewZNode()`

* FogNode: Add `.getViewZNode()`, removed `mixAssign()`

* TSL: Add `toneMappingExposure`

* PostProcessing: Add `.renderAsync()`

* Add `webgpu_custom_fog_background` example

* cleanup

* cleanup

* improve description

* update example

* Transmission - WIP

* rename

* Revert

* Introduce singleViewportMipTexture for tests and revisions

* revision

* cleanup

* fix circular deps

* update screenshot `webgpu_skinning`

* revision

* copyFramebufferToTexture(): Added support for multisampling

* revision

* revision

* revision

* cleanup

* Fix faceDirection in WebGLBackend if used BackSide

* Added frontFaceCW

* Added webgpu_materials_transmission

* cleanup

* update

* revision

* update webgpu_skinning

* revision getVolumeTransmissionRay

* revision

* revision

* revision

* update screenshot

* cleanup

* update `webgpu_loader_gltf_anisotropy` example

* cleanup

* Update webgpu_compute_geometry.jpg

* update screenshots

* add material.*Node transmission properties
  • Loading branch information
sunag committed Apr 12, 2024
1 parent 454ada9 commit 500f452
Show file tree
Hide file tree
Showing 50 changed files with 828 additions and 138 deletions.
2 changes: 2 additions & 0 deletions examples/files.json
Expand Up @@ -351,9 +351,11 @@
"webgpu_loader_gltf_compressed",
"webgpu_loader_gltf_iridescence",
"webgpu_loader_gltf_sheen",
"webgpu_loader_gltf_transmission",
"webgpu_loader_materialx",
"webgpu_materials",
"webgpu_materials_sss",
"webgpu_materials_transmission",
"webgpu_materials_video",
"webgpu_materialx_noise",
"webgpu_multiple_rendertargets",
Expand Down
62 changes: 62 additions & 0 deletions examples/jsm/nodes/accessors/MaterialNode.js
Expand Up @@ -99,6 +99,20 @@ class MaterialNode extends Node {

}

} else if ( scope === MaterialNode.SPECULAR_INTENSITY ) {

const specularIntensity = this.getFloat( scope );

if ( material.specularMap ) {

node = specularIntensity.mul( this.getTexture( scope ).a );

} else {

node = specularIntensity;

}

} else if ( scope === MaterialNode.ROUGHNESS ) { // TODO: cleanup similar branches

const roughnessNode = this.getFloat( scope );
Expand Down Expand Up @@ -258,6 +272,38 @@ class MaterialNode extends Node {

}

} else if ( scope === MaterialNode.TRANSMISSION ) {

const transmissionNode = this.getFloat( scope );

if ( material.transmissionMap ) {

node = transmissionNode.mul( this.getTexture( scope ).r );

} else {

node = transmissionNode;

}

} else if ( scope === MaterialNode.THICKNESS ) {

const thicknessNode = this.getFloat( scope );

if ( material.thicknessMap ) {

node = thicknessNode.mul( this.getTexture( scope ).g );

} else {

node = thicknessNode;

}

} else if ( scope === MaterialNode.IOR ) {

node = this.getFloat( scope );

} else {

const outputType = this.getNodeType( builder );
Expand All @@ -278,6 +324,8 @@ MaterialNode.OPACITY = 'opacity';
MaterialNode.SHININESS = 'shininess';
MaterialNode.SPECULAR_COLOR = 'specular';
MaterialNode.SPECULAR_STRENGTH = 'specularStrength';
MaterialNode.SPECULAR_INTENSITY = 'specularIntensity';
MaterialNode.SPECULAR_COLOR2 = 'specularColor';
MaterialNode.REFLECTIVITY = 'reflectivity';
MaterialNode.ROUGHNESS = 'roughness';
MaterialNode.METALNESS = 'metalness';
Expand All @@ -293,6 +341,11 @@ MaterialNode.ANISOTROPY = 'anisotropy';
MaterialNode.IRIDESCENCE = 'iridescence';
MaterialNode.IRIDESCENCE_IOR = 'iridescenceIOR';
MaterialNode.IRIDESCENCE_THICKNESS = 'iridescenceThickness';
MaterialNode.IOR = 'ior';
MaterialNode.TRANSMISSION = 'transmission';
MaterialNode.THICKNESS = 'thickness';
MaterialNode.ATTENUATION_DISTANCE = 'attenuationDistance';
MaterialNode.ATTENUATION_COLOR = 'attenuationColor';
MaterialNode.LINE_SCALE = 'scale';
MaterialNode.LINE_DASH_SIZE = 'dashSize';
MaterialNode.LINE_GAP_SIZE = 'gapSize';
Expand All @@ -308,6 +361,10 @@ export const materialShininess = nodeImmutable( MaterialNode, MaterialNode.SHINI
export const materialEmissive = nodeImmutable( MaterialNode, MaterialNode.EMISSIVE );
export const materialOpacity = nodeImmutable( MaterialNode, MaterialNode.OPACITY );
export const materialSpecularColor = nodeImmutable( MaterialNode, MaterialNode.SPECULAR_COLOR );

export const materialSpecularIntensity = nodeImmutable( MaterialNode, MaterialNode.SPECULAR_INTENSITY );
export const materialSpecularColor2 = nodeImmutable( MaterialNode, MaterialNode.SPECULAR_COLOR2 );

export const materialSpecularStrength = nodeImmutable( MaterialNode, MaterialNode.SPECULAR_STRENGTH );
export const materialReflectivity = nodeImmutable( MaterialNode, MaterialNode.REFLECTIVITY );
export const materialRoughness = nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );
Expand All @@ -323,6 +380,11 @@ export const materialAnisotropy = nodeImmutable( MaterialNode, MaterialNode.ANIS
export const materialIridescence = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE );
export const materialIridescenceIOR = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_IOR );
export const materialIridescenceThickness = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_THICKNESS );
export const materialTransmission = nodeImmutable( MaterialNode, MaterialNode.TRANSMISSION );
export const materialThickness = nodeImmutable( MaterialNode, MaterialNode.THICKNESS );
export const materialIOR = nodeImmutable( MaterialNode, MaterialNode.IOR );
export const materialAttenuationDistance = nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_DISTANCE );
export const materialAttenuationColor = nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_COLOR );
export const materialLineScale = nodeImmutable( MaterialNode, MaterialNode.LINE_SCALE );
export const materialLineDashSize = nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_SIZE );
export const materialLineGapSize = nodeImmutable( MaterialNode, MaterialNode.LINE_GAP_SIZE );
Expand Down
2 changes: 1 addition & 1 deletion examples/jsm/nodes/accessors/TextureSizeNode.js
Expand Up @@ -20,7 +20,7 @@ class TextureSizeNode extends Node {
const textureProperty = this.textureNode.build( builder, 'property' );
const levelNode = this.levelNode.build( builder, 'int' );

return builder.format( `${builder.getMethod( 'textureDimensions' )}( ${textureProperty}, ${levelNode} )`, this.getNodeType( builder ), output );
return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ levelNode } )`, this.getNodeType( builder ), output );

}

Expand Down
1 change: 0 additions & 1 deletion examples/jsm/nodes/core/NodeBuilder.js
Expand Up @@ -72,7 +72,6 @@ class NodeBuilder {
this.lightsNode = null;
this.environmentNode = null;
this.fogNode = null;
this.toneMappingNode = null;

this.clippingContext = null;

Expand Down
3 changes: 2 additions & 1 deletion examples/jsm/nodes/core/OutputStructNode.js
Expand Up @@ -8,9 +8,10 @@ class OutputStructNode extends Node {

super();

this.isOutputStructNode = true;
this.members = members;

this.isOutputStructNode = true;

}

setup( builder ) {
Expand Down
6 changes: 6 additions & 0 deletions examples/jsm/nodes/core/PropertyNode.js
Expand Up @@ -67,10 +67,16 @@ export const anisotropy = nodeImmutable( PropertyNode, 'float', 'Anisotropy' );
export const anisotropyT = nodeImmutable( PropertyNode, 'vec3', 'AnisotropyT' );
export const anisotropyB = nodeImmutable( PropertyNode, 'vec3', 'AnisotropyB' );
export const specularColor = nodeImmutable( PropertyNode, 'color', 'SpecularColor' );
export const specularF90 = nodeImmutable( PropertyNode, 'float', 'SpecularF90' );
export const shininess = nodeImmutable( PropertyNode, 'float', 'Shininess' );
export const output = nodeImmutable( PropertyNode, 'vec4', 'Output' );
export const dashSize = nodeImmutable( PropertyNode, 'float', 'dashSize' );
export const gapSize = nodeImmutable( PropertyNode, 'float', 'gapSize' );
export const pointWidth = nodeImmutable( PropertyNode, 'float', 'pointWidth' );
export const ior = nodeImmutable( PropertyNode, 'float', 'IOR' );
export const transmission = nodeImmutable( PropertyNode, 'float', 'Transmission' );
export const thickness = nodeImmutable( PropertyNode, 'float', 'Thickness' );
export const attenuationDistance = nodeImmutable( PropertyNode, 'float', 'AttenuationDistance' );
export const attenuationColor = nodeImmutable( PropertyNode, 'color', 'AttenuationColor' );

addNodeClass( 'PropertyNode', PropertyNode );
5 changes: 4 additions & 1 deletion examples/jsm/nodes/display/ViewportTextureNode.js
Expand Up @@ -58,7 +58,10 @@ class ViewportTextureNode extends TextureNode {

clone() {

return new this.constructor( this.uvNode, this.levelNode, this.value );
const viewportTextureNode = new this.constructor( this.uvNode, this.levelNode, this.value );
viewportTextureNode.generateMipmaps = this.generateMipmaps;

return viewportTextureNode;

}

Expand Down
169 changes: 161 additions & 8 deletions examples/jsm/nodes/functions/PhysicalLightingModel.js
Expand Up @@ -6,12 +6,135 @@ 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 LightingModel from '../core/LightingModel.js';
import { diffuseColor, specularColor, roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness } from '../core/PropertyNode.js';
import { transformedNormalView, transformedClearcoatNormalView } from '../accessors/NormalNode.js';
import { positionViewDirection } from '../accessors/PositionNode.js';
import { tslFn, float, vec3, mat3 } from '../shadernode/ShaderNode.js';
import { diffuseColor, specularColor, specularF90, roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, ior, thickness, transmission, attenuationDistance, attenuationColor } from '../core/PropertyNode.js';
import { transformedNormalView, transformedClearcoatNormalView, transformedNormalWorld } from '../accessors/NormalNode.js';
import { positionViewDirection, 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, smoothstep } from '../math/MathNode.js';
import { mix, normalize, refract, length, clamp, log2, log, exp, smoothstep } from '../math/MathNode.js';
import { div } from '../math/OperatorNode.js';
import { cameraPosition, cameraProjectionMatrix, cameraViewMatrix } from '../accessors/CameraNode.js';
import { modelWorldMatrix } from '../accessors/ModelNode.js';
import { viewportResolution } from '../display/ViewportNode.js';
import { viewportMipTexture } from '../display/ViewportTextureNode.js';

//
// Transmission
//

const getVolumeTransmissionRay = tslFn( ( [ n, v, thickness, ior, modelMatrix ] ) => {

// Direction of refracted light.
const refractionVector = vec3( refract( v.negate(), normalize( n ), div( 1.0, ior ) ) );

// Compute rotation-independant scaling of the model matrix.
const modelScale = vec3(
length( modelMatrix[ 0 ].xyz ),
length( modelMatrix[ 1 ].xyz ),
length( modelMatrix[ 2 ].xyz )
);

// The thickness is specified in local space.
return normalize( refractionVector ).mul( thickness.mul( modelScale ) );

} ).setLayout( {
name: 'getVolumeTransmissionRay',
type: 'vec3',
inputs: [
{ name: 'n', type: 'vec3' },
{ name: 'v', type: 'vec3' },
{ name: 'thickness', type: 'float' },
{ name: 'ior', type: 'float' },
{ name: 'modelMatrix', type: 'mat4' }
]
} );

const applyIorToRoughness = tslFn( ( [ roughness, ior ] ) => {

// Scale roughness with IOR so that an IOR of 1.0 results in no microfacet refraction and
// an IOR of 1.5 results in the default amount of microfacet refraction.
return roughness.mul( clamp( ior.mul( 2.0 ).sub( 2.0 ), 0.0, 1.0 ) );

} ).setLayout( {
name: 'applyIorToRoughness',
type: 'float',
inputs: [
{ name: 'roughness', type: 'float' },
{ name: 'ior', type: 'float' }
]
} );

const singleViewportMipTexture = viewportMipTexture();

const getTransmissionSample = tslFn( ( [ fragCoord, roughness, ior ] ) => {

const transmissionSample = singleViewportMipTexture.uv( fragCoord );
//const transmissionSample = viewportMipTexture( fragCoord );

const lod = log2( float( viewportResolution.x ) ).mul( applyIorToRoughness( roughness, ior ) );

return transmissionSample.bicubic( lod );

} );

const volumeAttenuation = tslFn( ( [ transmissionDistance, attenuationColor, attenuationDistance ] ) => {

If( attenuationDistance.notEqual( 0 ), () => {

// Compute light attenuation using Beer's law.
const attenuationCoefficient = log( attenuationColor ).negate().div( attenuationDistance );
const transmittance = exp( attenuationCoefficient.negate().mul( transmissionDistance ) );

return transmittance;

} );

// Attenuation distance is +∞, i.e. the transmitted color is not attenuated at all.
return vec3( 1.0 );

} ).setLayout( {
name: 'volumeAttenuation',
type: 'vec3',
inputs: [
{ name: 'transmissionDistance', type: 'float' },
{ name: 'attenuationColor', type: 'vec3' },
{ name: 'attenuationDistance', type: 'float' }
]
} );

const getIBLVolumeRefraction = tslFn( ( [ n, v, roughness, diffuseColor, specularColor, specularF90, position, modelMatrix, viewMatrix, projMatrix, ior, thickness, attenuationColor, attenuationDistance ] ) => {

const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );
const refractedRayExit = position.add( transmissionRay );

// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) );
const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar();
refractionCoords.addAssign( 1.0 );
refractionCoords.divAssign( 2.0 );
refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu

// Sample framebuffer to get pixel the refracted ray hits.
const transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );
const transmittance = diffuseColor.mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ) );
const attenuatedColor = transmittance.rgb.mul( transmittedLight.rgb );
const dotNV = n.dot( v ).clamp();

// Get the specular component.
const F = vec3( EnvironmentBRDF( { // n, v, specularColor, specularF90, roughness
dotNV,
specularColor,
specularF90,
roughness
} ) );

// As less light is transmitted, the opacity should be increased. This simple approximation does a decent job
// of modulating a CSS background, and has no effect when the buffer is opaque, due to a solid object or clear color.
const transmittanceFactor = transmittance.r.add( transmittance.g, transmittance.b ).div( 3.0 );

return vec4( F.oneMinus().mul( attenuatedColor ), transmittedLight.a.oneMinus().mul( transmittanceFactor ).oneMinus() );

} );

//
// Iridescence
Expand Down Expand Up @@ -172,14 +295,15 @@ const clearcoatF90 = vec3( 1 );

class PhysicalLightingModel extends LightingModel {

constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false ) {
constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false ) {

super();

this.clearcoat = clearcoat;
this.sheen = sheen;
this.iridescence = iridescence;
this.anisotropy = anisotropy;
this.transmission = transmission;

this.clearcoatRadiance = null;
this.clearcoatSpecularDirect = null;
Expand All @@ -191,7 +315,7 @@ class PhysicalLightingModel extends LightingModel {

}

start( /*context*/ ) {
start( context ) {

if ( this.clearcoat === true ) {

Expand Down Expand Up @@ -224,6 +348,35 @@ class PhysicalLightingModel extends LightingModel {

}

if ( this.transmission === true ) {

const position = positionWorld;
const v = cameraPosition.sub( positionWorld ).normalize(); // TODO: Create Node for this, same issue in MaterialX
const n = transformedNormalWorld;

context.backdrop = getIBLVolumeRefraction(
n,
v,
roughness,
diffuseColor,
specularColor,
specularF90, // specularF90
position, // positionWorld
modelWorldMatrix, // modelMatrix
cameraViewMatrix, // viewMatrix
cameraProjectionMatrix, // projMatrix
ior,
thickness,
attenuationColor,
attenuationDistance
);

context.backdropAlpha = transmission;

diffuseColor.a.mulAssign( mix( 1, context.backdrop.a, transmission ) );

}

}

// Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
Expand Down Expand Up @@ -319,7 +472,7 @@ class PhysicalLightingModel extends LightingModel {
const multiScattering = vec3().temp( 'multiScattering' );
const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI );

this.computeMultiscattering( singleScattering, multiScattering );
this.computeMultiscattering( singleScattering, multiScattering, specularF90 );

const totalScattering = singleScattering.add( multiScattering );

Expand Down

0 comments on commit 500f452

Please sign in to comment.