three
Version:
JavaScript 3D library
196 lines (155 loc) • 6.83 kB
JavaScript
import { add, float, Fn, If, luminance, max, struct, vec2, vec4, getViewPosition, logarithmicDepthToViewZ, viewZToOrthographicDepth, viewZToPerspectiveDepth } from 'three/tsl';
/**
* Optimized version of AABB clipping.
*
* Reference: {@link https://github.com/playdeadgames/temporal}
*
* @tsl
* @private
* @function
* @param {Node<vec4>} currentColor - The current color.
* @param {Node<vec4>} historyColor - The history color.
* @param {Node<vec4>} minColor - The minimum color of the AABB.
* @param {Node<vec4>} maxColor - The maximum color of the AABB.
* @return {Node<vec4>} The clipped history color.
*/
export const clipAABB = Fn( ( [ currentColor, historyColor, minColor, maxColor ] ) => {
const pClip = maxColor.rgb.add( minColor.rgb ).mul( 0.5 ).toConst();
const eClip = maxColor.rgb.sub( minColor.rgb ).mul( 0.5 ).add( 1e-7 ).toConst();
const vClip = historyColor.sub( vec4( pClip, currentColor.a ) ).toConst();
const vUnit = vClip.xyz.div( eClip ).toConst();
const absUnit = vUnit.abs().toConst();
const maxUnit = max( absUnit.x, absUnit.y, absUnit.z ).toConst();
return maxUnit.greaterThan( 1 ).select(
vec4( pClip, currentColor.a ).add( vClip.div( maxUnit ) ),
historyColor
);
} ).setLayout( {
name: 'clipAABB',
type: 'vec4',
inputs: [
{ name: 'currentColor', type: 'vec4' },
{ name: 'historyColor', type: 'vec4' },
{ name: 'minColor', type: 'vec4' },
{ name: 'maxColor', type: 'vec4' }
]
} );
/**
* Blends the current and history color with a flicker reduction based on luminance weighing.
*
* @tsl
* @private
* @function
* @param {Node<vec4>} currentColor - The current color.
* @param {Node<vec4>} historyColor - The history color.
* @param {Node<float>} currentWeight - The weight of the current color.
* @return {Node<vec4>} The blended color.
*/
export const flickerReduction = Fn( ( [ currentColor, historyColor, currentWeight ] ) => {
const compressedCurrent = currentColor.mul( float( 1 ).div( ( max( currentColor.r, currentColor.g, currentColor.b ).add( 1 ) ) ) ).toConst();
const compressedHistory = historyColor.mul( float( 1 ).div( ( max( historyColor.r, historyColor.g, historyColor.b ).add( 1 ) ) ) ).toConst();
const luminanceCurrent = luminance( compressedCurrent.rgb ).toConst();
const luminanceHistory = luminance( compressedHistory.rgb ).toConst();
const weightCurrent = currentWeight.div( luminanceCurrent.add( 1 ) ).toConst();
const weightHistory = currentWeight.oneMinus().div( luminanceHistory.add( 1 ) ).toConst();
return add( currentColor.mul( weightCurrent ), historyColor.mul( weightHistory ) ).div( max( weightCurrent.add( weightHistory ), 0.00001 ) );
} ).setLayout( {
name: 'flickerReduction',
type: 'vec4',
inputs: [
{ name: 'currentColor', type: 'vec4' },
{ name: 'historyColor', type: 'vec4' },
{ name: 'currentWeight', type: 'float' }
]
} );
/**
* Samples the 3×3 neighborhood of the given texel position in the depth buffer and returns
* the closest depth, its texel position and the farthest depth. Reversed and logarithmic depth
* values are converted to perspective depth.
*
* @tsl
* @private
* @function
* @param {TextureNode} depthNode - The depth buffer.
* @param {Node<vec2>} positionTexel - The texel position.
* @param {Node<vec2>} cameraNearFar - The camera's near and far.
* @return {Node<struct>} A struct with the members `closestDepth`, `closestPositionTexel` and `farthestDepth`.
*/
export const sampleCurrentDepth = Fn( ( [ depthNode, positionTexel, cameraNearFar ], builder ) => {
const closestDepth = float( 2 ).toVar();
const closestPositionTexel = vec2( 0 ).toVar();
const farthestDepth = float( - 1 ).toVar();
for ( let x = - 1; x <= 1; ++ x ) {
for ( let y = - 1; y <= 1; ++ y ) {
const neighbor = positionTexel.add( vec2( x, y ) ).toVar();
let depth = depthNode.load( neighbor ).r;
if ( builder.renderer.reversedDepthBuffer ) depth = depth.oneMinus();
if ( builder.renderer.logarithmicDepthBuffer ) depth = logarithmicToPerspectiveDepth( depth, cameraNearFar );
depth = depth.toVar();
If( depth.lessThan( closestDepth ), () => {
closestDepth.assign( depth );
closestPositionTexel.assign( neighbor );
} );
If( depth.greaterThan( farthestDepth ), () => {
farthestDepth.assign( depth );
} );
}
}
return currentDepthStruct( closestDepth, closestPositionTexel, farthestDepth );
} );
/**
* Samples the previous depth buffer and reprojects the depth into the current view.
*
* @tsl
* @private
* @function
* @param {TextureNode} previousDepthNode - The previous depth buffer.
* @param {Node<vec2>} uv - The uv coordinates.
* @param {Node<mat4>} previousCameraProjectionMatrixInverse - The previous camera projection matrix inverse.
* @param {Node<mat4>} previousCameraWorldMatrix - The previous camera world matrix.
* @param {Node<mat4>} cameraWorldMatrixInverse - The current camera world matrix inverse.
* @param {Node<vec2>} cameraNearFar - The camera's near and far.
* @param {Camera} camera - The camera.
* @return {Node<float>} The reprojected depth.
*/
export const samplePreviousDepth = Fn( ( [ previousDepthNode, uv, previousCameraProjectionMatrixInverse, previousCameraWorldMatrix, cameraWorldMatrixInverse, cameraNearFar, camera ], builder ) => {
let depth = previousDepthNode.sample( uv ).r;
if ( builder.renderer.logarithmicDepthBuffer ) depth = logarithmicToPerspectiveDepth( depth, cameraNearFar );
const positionView = getViewPosition( uv, depth, previousCameraProjectionMatrixInverse );
const positionWorld = previousCameraWorldMatrix.mul( vec4( positionView, 1 ) ).xyz;
const viewZ = cameraWorldMatrixInverse.mul( vec4( positionWorld, 1 ) ).z;
return camera.isOrthographicCamera
? viewZToOrthographicDepth( viewZ, cameraNearFar.x, cameraNearFar.y )
: viewZToPerspectiveDepth( viewZ, cameraNearFar.x, cameraNearFar.y );
} );
/**
* Computes a sequence of Halton(2, 3) jitter offsets in the range [0, 1].
*
* @private
* @function
* @param {number} length - The number of offsets.
* @return {Array<Array<number>>} The jitter offsets.
*/
export function computeHaltonOffsets( length ) {
return Array.from( { length }, ( _, index ) => [ halton( index + 1, 2 ), halton( index + 1, 3 ) ] );
}
const currentDepthStruct = struct( {
closestDepth: 'float',
closestPositionTexel: 'vec2',
farthestDepth: 'float'
} );
const logarithmicToPerspectiveDepth = ( depth, cameraNearFar ) => {
const { x: near, y: far } = cameraNearFar;
const viewZ = logarithmicDepthToViewZ( depth, near, far );
return viewZToPerspectiveDepth( viewZ, near, far );
};
function halton( index, base ) {
let fraction = 1;
let result = 0;
while ( index > 0 ) {
fraction /= base;
result += fraction * ( index % base );
index = Math.floor( index / base );
}
return result;
}