SSGIPassProxy.js

import Check from "@lijuhong1981/jscheck/src/Check.js";
import PassNodeProxy from "./PassNodeProxy.js";
import SSGINode, { ssgi } from 'three/addons/tsl/display/SSGINode.js';
import { Camera, Node, TextureNode } from "three/webgpu";
import { add, vec4 } from "three/tsl";

/**
 * SSGIPassProxy是PassNodeProxy的一个子类,用于代理SSGI(Screen Space Global Illumination)后处理特效的Pass节点。
 * @class
 * @extends PassNodeProxy<SSGINode>
*/
class SSGIPassProxy extends PassNodeProxy {
    constructor(library) {
        super(library);
        this._values = {
            sliceCount: 2,
            stepCount: 8,
            aoIntensity: 0,
            giIntensity: 10,
            radius: 12,
            useScreenSpaceSampling: false,
            expFactor: 2,
            thickness: 1,
            useLinearThickness: true,
            backfaceLighting: 0,
            useTemporalFiltering: true,
        };
    }
    /**
     * Number of per-pixel hemisphere slices. This has a big performance cost and should be kept as low as possible.
     * Should be in the range `[1, 4]`.
     *
     * @type {number}
     * @default 2
     */
    set sliceCount(value) {
        Check.typeOf.number('sliceCount', value);
        this._values.sliceCount = value;
        this.node && (this.node.sliceCount.value = value);
    }
    get sliceCount() {
        return this._values.sliceCount;
    }
    /**
     * Number of samples taken along one side of a given hemisphere slice. This has a big performance cost and should
     * be kept as low as possible.  Should be in the range `[1, 32]`.
     *
     * @type {number}
     * @default 8
     */
    set stepCount(value) {
        Check.typeOf.number('stepCount', value);
        this._values.stepCount = value;
        this.node && (this.node.stepCount.value = value);
    }
    get stepCount() {
        return this._values.stepCount;
    }
    /**
     * Power function applied to AO to make it appear darker/lighter. Should be in the range `[0, 4]`.
     *
     * @type {number}
     * @default 0
     */
    set aoIntensity(value) {
        Check.typeOf.number('aoIntensity', value);
        this._values.aoIntensity = value;
        this.node && (this.node.aoIntensity.value = value);
    }
    get aoIntensity() {
        return this._values.aoIntensity;
    }
    /**
     * Intensity of the indirect diffuse light. Should be in the range `[0, 100]`.
     *
     * @type {number}
     * @default 10
     */
    set giIntensity(value) {
        Check.typeOf.number('giIntensity', value);
        this._values.giIntensity = value;
        this.node && (this.node.giIntensity.value = value);
    }
    get giIntensity() {
        return this._values.giIntensity;
    }
    /**
     * Effective sampling radius in world space. AO and GI can only have influence within that radius.
     * Should be in the range `[1, 25]`.
     *
     * @type {number}
     * @default 12
     */
    set radius(value) {
        Check.typeOf.number('radius', value);
        this._values.radius = value;
        this.node && (this.node.radius.value = value);
    }
    get radius() {
        return this._values.radius;
    }
    /**
     * Makes the sample distance in screen space instead of world-space (helps having more detail up close).
     *
     * @type {boolean}
     * @default false
     */
    set useScreenSpaceSampling(value) {
        Check.typeOf.boolean('useScreenSpaceSampling', value);
        this._values.useScreenSpaceSampling = value;
        this.node && (this.node.useScreenSpaceSampling.value = value);
    }
    get useScreenSpaceSampling() {
        return this._values.useScreenSpaceSampling;
    }
    /**
     * Controls samples distribution. It's an exponent applied at each step get increasing step size over the distance.
     * Should be in the range `[1, 3]`.
     *
     * @type {number}
     * @default 2
     */
    set expFactor(value) {
        Check.typeOf.number('expFactor', value);
        this._values.expFactor = value;
        this.node && (this.node.expFactor.value = value);
    }
    get expFactor() {
        return this._values.expFactor;
    }
    /**
     * Constant thickness value of objects on the screen in world units. Allows light to pass behind surfaces past that thickness value.
     * Should be in the range `[0.01, 10]`.
     *
     * @type {number}
     * @default 1
     */
    set thickness(value) {
        Check.typeOf.number('thickness', value);
        this._values.thickness = value;
        this.node && (this.node.thickness.value = value);
    }
    get thickness() {
        return this._values.thickness;
    }
    /**
     * Whether to increase thickness linearly over distance or not (avoid losing detail over the distance).
     *
     * @type {boolean}
     * @default false
     */
    set useLinearThickness(value) {
        Check.typeOf.boolean('useLinearThickness', value);
        this._values.useLinearThickness = value;
        this.node && (this.node.useLinearThickness.value = value);
    }
    get useLinearThickness() {
        return this._values.useLinearThickness;
    }
    /**
     * How much light backface surfaces emit.
     * Should be in the range `[0, 1]`.
     *
     * @type {number}
     * @default 0
     */
    set backfaceLighting(value) {
        Check.typeOf.number('backfaceLighting', value);
        this._values.backfaceLighting = value;
        this.node && (this.node.backfaceLighting.value = value);
    }
    get backfaceLighting() {
        return this._values.backfaceLighting;
    }
    /**
     * Whether to use temporal filtering or not. Setting this property to
     * `true` requires the usage of `TRAANode`. This will help to reduce noise
     * although it introduces typical TAA artifacts like ghosting and temporal instabilities.
     *
     * If setting this property to `false`, a manual denoise via `DenoiseNode` is required.
     *
     * @type {boolean}
     * @default true
     */
    set useTemporalFiltering(value) {
        Check.typeOf.boolean('useTemporalFiltering', value);
        this._values.useTemporalFiltering = value;
        this.node && (this.node.useTemporalFiltering = value);
    }
    get useTemporalFiltering() {
        return this._values.useTemporalFiltering;
    }
    /**
     * 构建SSGI Pass节点。
     * @param {TextureNode} colorNode - 颜色节点对象 
     * @param {TextureNode} depthNode - 深度节点对象
     * @param {TextureNode} normalNode - 法线节点对象
     * @param {Camera} camera - 相机对象
     * @param {TextureNode} diffuseNode - 漫反射节点对象
     * @param {TextureNode} outputNode - 输出节点对象
     * @returns {Node} 处理后的节点对象
     * @override
    */
    build(colorNode, depthNode, normalNode, camera, diffuseNode, outputNode) {
        const ssgiPass = this.node = ssgi(colorNode, depthNode, normalNode, camera);
        this.setValues(this._values);
        const gi = ssgiPass.rgb.toInspector('SSGI');
        const ao = ssgiPass.a.toInspector('SSAO');
        // const compositePass = vec4(add(outputNode.rgb.mul(ao), (diffuseNode.rgb.mul(gi))), outputNode.a);
        // compositePass.name = 'SSGI-Composite';
        // return compositePass;
        outputNode.rgb.mul(ao).add(diffuseNode.rgb.mul(gi));
        return outputNode;
    }
};

export default SSGIPassProxy;
export { SSGIPassProxy };