renderer/PhongDeferredShading.tstypescript
import { Framebuffer } from "../webgl/Framebuffer";
import { deferredShading } from "../shaders/deferredShading/deferredShading";
import { applyDeferredDepth } from "../shaders/applyDeferredDepth";
import { ProjectorsPass } from "./projectors/ProjectorsPass";
import { DEFAULT_CASCADE_SHADOW_TEXTURE_UNIT_START, DEFAULT_SHADOW_TEXTURE_UNIT_START } from "./textureUnits";
import type { IDeferredShadingPass } from "./IDeferredShadingPass";
import type { Renderer } from "./Renderer";

export class PhongDeferredShading implements IDeferredShadingPass {
    protected _renderer: Renderer;
    protected _framebuffer: Framebuffer | null = null;
    protected _projectorPass: ProjectorsPass;

    constructor(renderer: Renderer) {
        this._renderer = renderer;
        this._projectorPass = new ProjectorsPass(renderer);
    }

    public init() {
        let h = this._renderer.handler;

        h.addProgram(deferredShading());
        h.addProgram(applyDeferredDepth());
        this._projectorPass.init();

        this._framebuffer = new Framebuffer(h, {
            useDepth: false,
            targets: [
                {
                    internalFormat: this._renderer.internalFormat,
                    filter: "NEAREST"
                },
                {
                    internalFormat: this._renderer.internalFormat,
                    filter: "NEAREST"
                },
                {
                    internalFormat: this._renderer.internalFormat,
                    filter: "NEAREST"
                },
                {
                    internalFormat: "RGBA32F",
                    filter: "NEAREST"
                },
                {
                    attachment: "DEPTH_ATTACHMENT",
                    internalFormat: this._renderer.depthComponent,
                    filter: "NEAREST"
                }
            ]
        });

        this._framebuffer.init();
    }

    public beginPass() {
        let gl = this._renderer.handler.gl!;
        this._framebuffer!.activate();
        gl.clearColor(0, 0, 0, 0.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        gl.disable(gl.BLEND);
    }

    public endPass() {
        this._framebuffer!.deactivate();
    }

    public applyLighting() {
        this._applyDeferredDepth();
        this._deferredShadingPASS();
    }

    public resize(width: number, height: number) {
        this._framebuffer?.setSize(width, height, true);
    }

    public dispose() {
        if (this._framebuffer) {
            this._framebuffer.destroy();
            this._framebuffer = null;
        }
        this._projectorPass.dispose();
        this._renderer.handler.removeProgram("deferredShading");
        this._renderer.handler.removeProgram("applyDeferredDepth");
    }

    protected _applyDeferredDepth() {
        let r = this._renderer;
        let h = r.handler,
            gl = h.gl!,
            sh = h.programs.applyDeferredDepth,
            p = sh;

        gl.disable(gl.BLEND);
        gl.colorMask(false, false, false, false);
        gl.depthMask(true);
        gl.enable(gl.DEPTH_TEST);
        gl.depthFunc(gl.ALWAYS);
        gl.clear(gl.DEPTH_BUFFER_BIT);

        gl.bindBuffer(gl.ARRAY_BUFFER, r.screenFramePositionBuffer!);
        gl.vertexAttribPointer(p.attributes.corners, 2, gl.FLOAT, false, 0, 0);

        sh.activate();

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, this._framebuffer!.textures[4]);
        gl.uniform1i(p.uniforms.depthTexture, 0);

        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

        gl.colorMask(true, true, true, true);
        gl.depthFunc(r.activeCamera.reverseDepthActive ? gl.GREATER : gl.LESS);
        gl.enable(gl.DEPTH_TEST);
        gl.enable(gl.BLEND);
    }

    protected _deferredShadingPASS() {
        let r = this._renderer;
        let h = r.handler;
        let sh = h.programs.deferredShading,
            p = sh,
            gl = h.gl!;

        gl.disable(gl.DEPTH_TEST);
        gl.depthMask(false);
        r.enableBlendDefault();

        gl.bindBuffer(gl.ARRAY_BUFFER, r.screenFramePositionBuffer!);
        gl.vertexAttribPointer(p.attributes.corners, 2, gl.FLOAT, false, 0, 0);

        sh.activate();

        gl.uniformMatrix3fv(p.uniforms.normalMatrix, false, r.activeCamera.getNormalMatrix());
        gl.uniform3fv(p.uniforms.lightPosition, r._lightPosition);
        gl.uniform3fv(p.uniforms.lightAmbient, r._lightAmbient);
        gl.uniform3fv(p.uniforms.lightDiffuse, r._lightDiffuse);
        gl.uniform4fv(p.uniforms.lightSpecular, r._lightSpecular);
        gl.uniform1f(p.uniforms.frameOpacity, r.frameOpacity);

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, this._framebuffer!.textures[0]);
        gl.uniform1i(p.uniforms.baseTexture, 0);

        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, this._framebuffer!.textures[1]);
        gl.uniform1i(p.uniforms.materialsTexture, 1);

        gl.activeTexture(gl.TEXTURE2);
        gl.bindTexture(gl.TEXTURE_2D, this._framebuffer!.textures[2]);
        gl.uniform1i(p.uniforms.normalTexture, 2);

        gl.activeTexture(gl.TEXTURE3);
        gl.bindTexture(gl.TEXTURE_2D, this._framebuffer!.textures[3]);
        gl.uniform1i(p.uniforms.viewPositionTexture, 3);

        r.shadows.bindForward(p, DEFAULT_SHADOW_TEXTURE_UNIT_START);
        r.cascadeShadowManager.bindForward(p, DEFAULT_CASCADE_SHADOW_TEXTURE_UNIT_START);

        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

        // Per-projector additive frustum-geometry pass.
        this._projectorPass.apply(this._framebuffer!);

        gl.depthMask(true);
        gl.enable(gl.DEPTH_TEST);
    }
}