@openhps/core
Version:
Open Hybrid Positioning System - Core component
143 lines (128 loc) • 5.06 kB
JavaScript
import LightingModel from '../core/LightingModel.js';
import { property } from '../core/PropertyNode.js';
import { float, If, uniform, vec3, vec4 } from '../tsl/TSLBase.js';
import { positionWorld } from '../accessors/Position.js';
import { cameraFar, cameraNear, cameraPosition, cameraViewMatrix } from '../accessors/Camera.js';
import { Loop } from '../utils/LoopNode.js';
import { linearDepth, viewZToPerspectiveDepth } from '../display/ViewportDepthNode.js';
import { modelRadius } from '../accessors/ModelNode.js';
import { LTC_Evaluate_Volume } from './BSDF/LTC.js';
const scatteringDensity = property('vec3');
const linearDepthRay = property('vec3');
const outgoingRayLight = property('vec3');
/**
* VolumetricLightingModel class extends the LightingModel to implement volumetric lighting effects.
* This model calculates the scattering and transmittance of light through a volumetric medium.
* It dynamically adjusts the direction of the ray based on the camera and object positions.
* The model supports custom scattering and depth nodes to enhance the lighting effects.
*
* @augments LightingModel
*/
class VolumetricLightingModel extends LightingModel {
constructor() {
super();
}
start(builder) {
const {
material,
context
} = builder;
const startPos = property('vec3');
const endPos = property('vec3');
// This approach dynamically changes the direction of the ray,
// prioritizing the ray from the camera to the object if it is inside the mesh, and from the object to the camera if it is far away.
If(cameraPosition.sub(positionWorld).length().greaterThan(modelRadius.mul(2)), () => {
startPos.assign(cameraPosition);
endPos.assign(positionWorld);
}).Else(() => {
startPos.assign(positionWorld);
endPos.assign(cameraPosition);
});
//
const viewVector = endPos.sub(startPos);
const steps = uniform('int').onRenderUpdate(({
material
}) => material.steps);
const stepSize = viewVector.length().div(steps).toVar();
const rayDir = viewVector.normalize().toVar(); // TODO: toVar() should be automatic here ( in loop )
const distTravelled = float(0.0).toVar();
const transmittance = vec3(1).toVar();
if (material.offsetNode) {
// reduce banding
distTravelled.addAssign(material.offsetNode.mul(stepSize));
}
Loop(steps, () => {
const positionRay = startPos.add(rayDir.mul(distTravelled));
const positionViewRay = cameraViewMatrix.mul(vec4(positionRay, 1)).xyz;
if (material.depthNode !== null) {
linearDepthRay.assign(linearDepth(viewZToPerspectiveDepth(positionViewRay.z, cameraNear, cameraFar)));
context.sceneDepthNode = linearDepth(material.depthNode).toVar();
}
context.positionWorld = positionRay;
context.shadowPositionWorld = positionRay;
context.positionView = positionViewRay;
scatteringDensity.assign(0);
let scatteringNode;
if (material.scatteringNode) {
scatteringNode = material.scatteringNode({
positionRay
});
}
super.start(builder);
if (scatteringNode) {
scatteringDensity.mulAssign(scatteringNode);
}
// beer's law
const falloff = scatteringDensity.mul(.01).negate().mul(stepSize).exp();
transmittance.mulAssign(falloff);
// move along the ray
distTravelled.addAssign(stepSize);
});
outgoingRayLight.addAssign(transmittance.saturate().oneMinus());
}
scatteringLight(lightColor, builder) {
const sceneDepthNode = builder.context.sceneDepthNode;
if (sceneDepthNode) {
If(sceneDepthNode.greaterThanEqual(linearDepthRay), () => {
scatteringDensity.addAssign(lightColor);
});
} else {
scatteringDensity.addAssign(lightColor);
}
}
direct({
lightNode,
lightColor
}, builder) {
// Ignore lights with infinite distance
if (lightNode.light.distance === undefined) return;
// TODO: We need a viewportOpaque*() ( output, depth ) to fit with modern rendering approaches
const directLight = lightColor.xyz.toVar();
directLight.mulAssign(lightNode.shadowNode); // it no should be necessary if used in the same render pass
this.scatteringLight(directLight, builder);
}
directRectArea({
lightColor,
lightPosition,
halfWidth,
halfHeight
}, builder) {
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 P = builder.context.positionView;
const directLight = lightColor.xyz.mul(LTC_Evaluate_Volume({
P,
p0,
p1,
p2,
p3
})).pow(1.5);
this.scatteringLight(directLight, builder);
}
finish(builder) {
builder.context.outgoingLight.assign(outgoingRayLight);
}
}
export default VolumetricLightingModel;