UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

275 lines (249 loc) 9.6 kB
import ShadowNode from './ShadowNode.js'; import { uniform } from '../core/UniformNode.js'; import { float, vec2, If, Fn, nodeObject } from '../tsl/TSLBase.js'; import { reference } from '../accessors/ReferenceNode.js'; import { texture } from '../accessors/TextureNode.js'; import { max, abs, sign } from '../math/MathNode.js'; import { sub, div } from '../math/OperatorNode.js'; import { renderGroup } from '../core/UniformGroupNode.js'; import { Vector2 } from '../../math/Vector2.js'; import { Vector4 } from '../../math/Vector4.js'; import { Color } from '../../math/Color.js'; import { BasicShadowMap } from '../../constants.js'; const _clearColor = /*@__PURE__*/new Color(); // cubeToUV() maps a 3D direction vector suitable for cube texture mapping to a 2D // vector suitable for 2D texture mapping. This code uses the following layout for the // 2D texture: // // xzXZ // y Y // // Y - Positive y direction // y - Negative y direction // X - Positive x direction // x - Negative x direction // Z - Positive z direction // z - Negative z direction // // Source and test bed: // https://gist.github.com/tschw/da10c43c467ce8afd0c4 export const cubeToUV = /*@__PURE__*/Fn(([pos, texelSizeY]) => { const v = pos.toVar(); // Number of texels to avoid at the edge of each square const absV = abs(v); // Intersect unit cube const scaleToCube = div(1.0, max(absV.x, max(absV.y, absV.z))); absV.mulAssign(scaleToCube); // Apply scale to avoid seams // two texels less per square (one texel will do for NEAREST) v.mulAssign(scaleToCube.mul(texelSizeY.mul(2).oneMinus())); // Unwrap // space: -1 ... 1 range for each square // // #X## dim := ( 4 , 2 ) // # # center := ( 1 , 1 ) const planar = vec2(v.xy).toVar(); const almostATexel = texelSizeY.mul(1.5); const almostOne = almostATexel.oneMinus(); If(absV.z.greaterThanEqual(almostOne), () => { If(v.z.greaterThan(0.0), () => { planar.x.assign(sub(4.0, v.x)); }); }).ElseIf(absV.x.greaterThanEqual(almostOne), () => { const signX = sign(v.x); planar.x.assign(v.z.mul(signX).add(signX.mul(2.0))); }).ElseIf(absV.y.greaterThanEqual(almostOne), () => { const signY = sign(v.y); planar.x.assign(v.x.add(signY.mul(2.0)).add(2.0)); planar.y.assign(v.z.mul(signY).sub(2.0)); }); // Transform to UV space // scale := 0.5 / dim // translate := ( center + 0.5 ) / dim return vec2(0.125, 0.25).mul(planar).add(vec2(0.375, 0.75)).flipY(); }).setLayout({ name: 'cubeToUV', type: 'vec2', inputs: [{ name: 'pos', type: 'vec3' }, { name: 'texelSizeY', type: 'float' }] }); export const BasicPointShadowFilter = /*@__PURE__*/Fn(({ depthTexture, bd3D, dp, texelSize }) => { return texture(depthTexture, cubeToUV(bd3D, texelSize.y)).compare(dp); }); export const PointShadowFilter = /*@__PURE__*/Fn(({ depthTexture, bd3D, dp, texelSize, shadow }) => { const radius = reference('radius', 'float', shadow).setGroup(renderGroup); const offset = vec2(-1.0, 1.0).mul(radius).mul(texelSize.y); return texture(depthTexture, cubeToUV(bd3D.add(offset.xyy), texelSize.y)).compare(dp).add(texture(depthTexture, cubeToUV(bd3D.add(offset.yyy), texelSize.y)).compare(dp)).add(texture(depthTexture, cubeToUV(bd3D.add(offset.xyx), texelSize.y)).compare(dp)).add(texture(depthTexture, cubeToUV(bd3D.add(offset.yyx), texelSize.y)).compare(dp)).add(texture(depthTexture, cubeToUV(bd3D, texelSize.y)).compare(dp)).add(texture(depthTexture, cubeToUV(bd3D.add(offset.xxy), texelSize.y)).compare(dp)).add(texture(depthTexture, cubeToUV(bd3D.add(offset.yxy), texelSize.y)).compare(dp)).add(texture(depthTexture, cubeToUV(bd3D.add(offset.xxx), texelSize.y)).compare(dp)).add(texture(depthTexture, cubeToUV(bd3D.add(offset.yxx), texelSize.y)).compare(dp)).mul(1.0 / 9.0); }); const pointShadowFilter = /*@__PURE__*/Fn(({ filterFn, depthTexture, shadowCoord, shadow }) => { // for point lights, the uniform @vShadowCoord is re-purposed to hold // the vector from the light to the world-space position of the fragment. const lightToPosition = shadowCoord.xyz.toVar(); const lightToPositionLength = lightToPosition.length(); const cameraNearLocal = uniform('float').setGroup(renderGroup).onRenderUpdate(() => shadow.camera.near); const cameraFarLocal = uniform('float').setGroup(renderGroup).onRenderUpdate(() => shadow.camera.far); const bias = reference('bias', 'float', shadow).setGroup(renderGroup); const mapSize = uniform(shadow.mapSize).setGroup(renderGroup); const result = float(1.0).toVar(); If(lightToPositionLength.sub(cameraFarLocal).lessThanEqual(0.0).and(lightToPositionLength.sub(cameraNearLocal).greaterThanEqual(0.0)), () => { // dp = normalized distance from light to fragment position const dp = lightToPositionLength.sub(cameraNearLocal).div(cameraFarLocal.sub(cameraNearLocal)).toVar(); // need to clamp? dp.addAssign(bias); // bd3D = base direction 3D const bd3D = lightToPosition.normalize(); const texelSize = vec2(1.0).div(mapSize.mul(vec2(4.0, 2.0))); // percentage-closer filtering result.assign(filterFn({ depthTexture, bd3D, dp, texelSize, shadow })); }); return result; }); const _viewport = /*@__PURE__*/new Vector4(); const _viewportSize = /*@__PURE__*/new Vector2(); const _shadowMapSize = /*@__PURE__*/new Vector2(); /** * Represents the shadow implementation for point light nodes. * * @augments ShadowNode */ class PointShadowNode extends ShadowNode { static get type() { return 'PointShadowNode'; } /** * Constructs a new point shadow node. * * @param {PointLight} light - The shadow casting point light. * @param {?PointLightShadow} [shadow=null] - An optional point light shadow. */ constructor(light, shadow = null) { super(light, shadow); } /** * Overwrites the default implementation to return point light shadow specific * filtering functions. * * @param {number} type - The shadow type. * @return {Function} The filtering function. */ getShadowFilterFn(type) { return type === BasicShadowMap ? BasicPointShadowFilter : PointShadowFilter; } /** * Overwrites the default implementation so the unaltered shadow position is used. * * @param {NodeBuilder} builder - A reference to the current node builder. * @param {Node<vec3>} shadowPosition - A node representing the shadow position. * @return {Node<vec3>} The shadow coordinates. */ setupShadowCoord(builder, shadowPosition) { return shadowPosition; } /** * Overwrites the default implementation to only use point light specific * shadow filter functions. * * @param {NodeBuilder} builder - A reference to the current node builder. * @param {Object} inputs - A configuration object that defines the shadow filtering. * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF. * @param {Texture} inputs.shadowTexture - A reference to the shadow map's texture. * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. * @param {Node<vec3>} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map. * @param {LightShadow} inputs.shadow - The light shadow. * @return {Node<float>} The result node of the shadow filtering. */ setupShadowFilter(builder, { filterFn, shadowTexture, depthTexture, shadowCoord, shadow }) { return pointShadowFilter({ filterFn, shadowTexture, depthTexture, shadowCoord, shadow }); } /** * Overwrites the default implementation with point light specific * rendering code. * * @param {NodeFrame} frame - A reference to the current node frame. */ renderShadow(frame) { const { shadow, shadowMap, light } = this; const { renderer, scene } = frame; const shadowFrameExtents = shadow.getFrameExtents(); _shadowMapSize.copy(shadow.mapSize); _shadowMapSize.multiply(shadowFrameExtents); shadowMap.setSize(_shadowMapSize.width, _shadowMapSize.height); _viewportSize.copy(shadow.mapSize); // const previousAutoClear = renderer.autoClear; const previousClearColor = renderer.getClearColor(_clearColor); const previousClearAlpha = renderer.getClearAlpha(); renderer.autoClear = false; renderer.setClearColor(shadow.clearColor, shadow.clearAlpha); renderer.clear(); const viewportCount = shadow.getViewportCount(); for (let vp = 0; vp < viewportCount; vp++) { const viewport = shadow.getViewport(vp); const x = _viewportSize.x * viewport.x; const y = _shadowMapSize.y - _viewportSize.y - _viewportSize.y * viewport.y; _viewport.set(x, y, _viewportSize.x * viewport.z, _viewportSize.y * viewport.w); shadowMap.viewport.copy(_viewport); shadow.updateMatrices(light, vp); renderer.render(scene, shadow.camera); } // renderer.autoClear = previousAutoClear; renderer.setClearColor(previousClearColor, previousClearAlpha); } } export default PointShadowNode; /** * TSL function for creating an instance of `PointShadowNode`. * * @tsl * @function * @param {PointLight} light - The shadow casting point light. * @param {?PointLightShadow} [shadow=null] - An optional point light shadow. * @return {PointShadowNode} The created point shadow node. */ export const pointShadow = (light, shadow) => nodeObject(new PointShadowNode(light, shadow));