@openhps/core
Version:
Open Hybrid Positioning System - Core component
275 lines (249 loc) • 9.6 kB
JavaScript
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));