Home Reference Source

cables_dev/cables/src/libs/cgl/light/index.js

import {
    getShadowPassVertexShader,
    getShadowPassFragmentShader,
    getBlurPassVertexShader,
    getBlurPassFragmentShader
} from "./createshaders.js";

/**
 *
 * @param cgl
 * @param {Object} config config for light
 */
function Light(cgl, config)
{
    // * common settings for each light
    this.type = config.type || "point";
    this.color = config.color || [1, 1, 1];
    this.specular = config.specular || [0, 0, 0];
    this.position = config.position || null;
    this.intensity = config.intensity || 1;
    this.radius = config.radius || 1;
    this.falloff = config.falloff || 1;

    // * spot light specific config
    this.spotExponent = config.spotExponent || 1;
    this.cosConeAngleInner = config.cosConeAngleInner || 0; // spot light
    this.cosConeAngle = config.cosConeAngle || 0;
    this.conePointAt = config.conePointAt || [0, 0, 0];

    // * shadow specific config
    this.castShadow = config.castShadow || false;
    this.nearFar = config.nearFar || [0, 0];
    this.normalOffset = config.normalOffset || 0;
    this.shadowBias = config.shadowBias || 0;
    this.shadowStrength = config.shadowStrength || 0;
    this.lightMatrix = null;

    this.shadowMap = null;
    this.shadowMapDepth = null;
    this.shadowCubeMap = null;

    // * internal config
    this._cgl = cgl;
    this.state = {
        "isUpdating": false
    };
    this._framebuffer = null;
    this._shaderShadowMap = {
        "shader": null,
        "uniforms": {
            "lightPosition": null,
            "nearFar": null,
        },
        "matrices": {
            "modelMatrix": mat4.create(),
            "viewMatrix": mat4.create(),
            "projMatrix": mat4.create(),
            "biasMatrix": mat4.fromValues(0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0),
        },
        "vectors": {
            "lookAt": vec3.create(),
            "camPos": vec3.create(),
            "up": vec3.fromValues(0, 1, 0),
        },
    };
    this._effectBlur = null;
    this._shaderBlur = {
        "shader": null,
        "uniforms": {
            "XY": null,
        },
    };
    this._cubemap = null;

    return this;
}

Light.prototype.getModifiableParameters = function ()
{
    return [
        "color",
        "specular",
        "position",
        "intensity",
        "radius",
        "falloff",

        // * spot light specific config
        "spotExponent",
        "cosConeAngleInner",
        "cosConeAngle",
        "conePointAt",
    ];
};

Light.prototype.createProjectionMatrix = Light.prototype.updateProjectionMatrix = function (lrBottomTop, near, far, angle)
{
    if (this.type === "spot")
    {
        mat4.perspective(this._shaderShadowMap.matrices.projMatrix, -2 * CGL.DEG2RAD * angle, 1, near, far); // * angle in degrees
    }
    else if (this.type === "directional")
    {
        mat4.ortho(this._shaderShadowMap.matrices.projMatrix, -1 * lrBottomTop, lrBottomTop, -1 * lrBottomTop, lrBottomTop, near, far);
    }
    else if (this.type === "point")
    {
        mat4.perspective(this._shaderShadowMap.matrices.projMatrix, CGL.DEG2RAD * 90, 1, near, far);
        this.nearFar = [near, far];
    }
};

Light.prototype.hasFramebuffer = function ()
{
    return !!this._framebuffer;
};
Light.prototype.hasShadowMapShader = function ()
{
    return !!this._shaderShadowMap.shader;
};

Light.prototype.hasBlurShader = function ()
{
    return !!this._shaderBlur.shader;
};
Light.prototype.hasBlurEffect = function ()
{
    return !!this._effectBlur;
};

Light.prototype.getShadowMap = function ()
{
    if (this.type === "point") return null; // TODO: replace
    return this._framebuffer.getTextureColor();
};

Light.prototype.getShadowMapDepth = function ()
{
    if (this.type === "point") return null;
    return this._framebuffer.getTextureDepth();
};

Light.prototype.createFramebuffer = function (width, height, options)
{
    this.state.isUpdating = true;


    const fbWidth = width || 512;
    const fbHeight = height || 512;

    if (this.type === "point")
    {
        if (!this.hasCubemap())
        {
            this._cubemap = new CGL.CubemapFramebuffer(this._cgl, fbWidth, fbHeight, {
                "name": "point light shadowmap"
            });
        }
        else
        {
            this._cubemap.setSize(fbWidth, fbHeight);
        }

        this._cubemap.setCamPos(this.position);
        this._cubemap.setMatrices(
            this._shaderShadowMap.matrices.modelMatrix,
            this._shaderShadowMap.matrices.viewMatrix,
            this._shaderShadowMap.matrices.projMatrix
        );

        this.state.isUpdating = false;
        return;
    }

    if (this.hasFramebuffer()) this._framebuffer.delete();


    if (options)
    {
        if (options.filter)
        {
            // * set FP to false if mipmap filtering is selected
            options.isFloatingPointTexture = options.filter !== CGL.Texture.FILTER_MIPMAP;
        }
    }

    if (this._cgl.glVersion == 1)
    {
        this._framebuffer = new CGL.Framebuffer(
            this._cgl,
            fbWidth,
            fbHeight,
            ({
                "isFloatingPointTexture": true,
                "filter": CGL.Texture.FILTER_LINEAR,
                "wrap": CGL.Texture.WRAP_CLAMP_TO_EDGE,
                ...options,
            }),
        );
    }
    else
    {
        this._framebuffer = new CGL.Framebuffer2(
            this._cgl,
            fbWidth,
            fbHeight,
            ({
                "isFloatingPointTexture": true,
                "filter": CGL.Texture.FILTER_LINEAR,
                "wrap": CGL.Texture.WRAP_CLAMP_TO_EDGE,
                ...options,
            }),
        );
    }

    this.state.isUpdating = false;
};

Light.prototype.hasCubemap = function ()
{
    return !!this._cubemap;
};

Light.prototype.setFramebufferSize = function (size)
{
    if (this.hasFramebuffer()) this._framebuffer.setSize(size, size);
};

Light.prototype.createShadowMapShader = function (vertexShader, fragmentShader)
{
    if (this.hasShadowMapShader()) return;

    this.state.isUpdating = true;

    this._shaderShadowMap.shader = new CGL.Shader(this._cgl, "shadowPass" + this.type.charAt(0).toUpperCase() + this.type.slice(1));
    this._shaderShadowMap.shader.setModules(["MODULE_VERTEX_POSITION", "MODULE_COLOR", "MODULE_BEGIN_FRAG"]);

    const vShader = vertexShader || this.getShadowPassVertexShader();
    const fShader = fragmentShader || this.getShadowPassFragmentShader();

    this._shaderShadowMap.shader.setSource(vShader, fShader);
    this._shaderShadowMap.shader.offScreenPass = true;

    if (this.type === "point")
    {
        this._shaderShadowMap.uniforms.lightPosition = new CGL.Uniform(this._shaderShadowMap.shader, "3f", "inLightPosition", vec3.create());

        this._shaderShadowMap.uniforms.nearFar = new CGL.Uniform(this._shaderShadowMap.shader, "2f", "inNearFar", vec2.create());
    }

    if (this._cgl.glVersion == 1)
    {
        this._cgl.enableExtension("OES_texture_float");
        this._cgl.enableExtension("OES_texture_float_linear");
        this._cgl.enableExtension("OES_texture_half_float");
        this._cgl.enableExtension("OES_texture_half_float_linear");

        this._shaderShadowMap.shader.enableExtension("GL_OES_standard_derivatives");
        this._shaderShadowMap.shader.enableExtension("GL_OES_texture_float");
        this._shaderShadowMap.shader.enableExtension("GL_OES_texture_float_linear");
        this._shaderShadowMap.shader.enableExtension("GL_OES_texture_half_float");
        this._shaderShadowMap.shader.enableExtension("GL_OES_texture_half_float_linear");
    }

    this.state.isUpdating = false;
};

Light.prototype.createBlurEffect = function (options)
{
    if (this.type === "point") return;
    this.state.isUpdating = true;
    if (this.hasBlurEffect()) this._effectBlur.delete();

    this._effectBlur = new CGL.TextureEffect(
        this._cgl,
        ({
            "isFloatingPointTexture": true,
            "filter": CGL.Texture.FILTER_LINEAR,
            "wrap": CGL.Texture.WRAP_CLAMP_TO_EDGE,
            ...options,
        }),
    );
    this.state.isUpdating = false;
};

Light.prototype.createBlurShader = function (vertexShader, fragmentShader)
{
    if (this.hasBlurShader())
    {
        return;
    }
    if (this.type === "point") return; // TODO: add cubemap convolution

    this.state.isUpdating = true;

    const vShader = vertexShader || this.getBlurPassVertexShader();
    const fShader = fragmentShader || this.getBlurPassFragmentShader();

    this._shaderBlur.shader = new CGL.Shader(this._cgl, "blurPass" + this.type.charAt(0).toUpperCase() + this.type.slice(1));
    this._shaderBlur.shader.setModules(["MODULE_VERTEX_POSITION", "MODULE_COLOR", "MODULE_BEGIN_FRAG"]);
    this._shaderBlur.shader.setSource(vShader, fShader);

    this._shaderBlur.uniforms.XY = new CGL.Uniform(this._shaderBlur.shader, "2f", "inXY", vec2.create());
    this._shaderBlur.shader.offScreenPass = true;
    this.state.isUpdating = false;
};

Light.prototype.renderPasses = function (polygonOffset, blurAmount, renderFunction)
{
    if (this.state.isUpdating) return;
    if (this._cgl.frameStore.shadowPass) return;

    this._cgl.pushCullFace(true);
    this._cgl.pushCullFaceFacing(this._cgl.gl.FRONT);
    this._cgl.gl.enable(this._cgl.gl.POLYGON_OFFSET_FILL);
    this._cgl.gl.polygonOffset(polygonOffset, polygonOffset);

    this._cgl.frameStore.renderOffscreen = true;
    this._cgl.frameStore.shadowPass = true;

    this._cgl.pushBlend(false);
    this._cgl.gl.colorMask(true, true, this.type === "point", this.type === "point"); // * for now just 2 channels, with MSM we need 4

    this.renderShadowPass(renderFunction);

    this._cgl.gl.cullFace(this._cgl.gl.BACK);
    this._cgl.gl.disable(this._cgl.gl.CULL_FACE);
    this._cgl.gl.disable(this._cgl.gl.POLYGON_OFFSET_FILL);

    if (this.type !== "point") this.renderBlurPass(blurAmount);

    this._cgl.gl.colorMask(true, true, true, true);

    this._cgl.popBlend();
    this._cgl.popCullFaceFacing();
    this._cgl.popCullFace();

    this._cgl.frameStore.shadowPass = false;
    this._cgl.frameStore.renderOffscreen = false;

    if (this.type !== "point")
    {
        this.shadowMap = this._framebuffer.getTextureColor();
        this.shadowMapDepth = this._framebuffer.getTextureDepth();
    }
    else
    {
        this.shadowMap = null;
        this.shadowMapDepth = null;
    }
};

Light.prototype.renderShadowPass = function (renderFunction)
{
    if (this.state.isUpdating) return;
    if (this.type === "point")
    {
        this._shaderShadowMap.uniforms.nearFar.setValue(this.nearFar);
        this._shaderShadowMap.uniforms.lightPosition.setValue(this.position);

        this._cubemap.setCamPos(this.position);
        this._cubemap.setMatrices(this._shaderShadowMap.matrices.modelMatrix, this._shaderShadowMap.matrices.viewMatrix, this._shaderShadowMap.matrices.projMatrix);

        this._cgl.pushShader(this._shaderShadowMap.shader);

        // this._cubemap.renderCubemap(renderFunction);

        this._cubemap.renderStart();

        for (let i = 0; i < 6; i += 1)
        {
            this._cubemap.renderStartCubemapFace(i);
            if (renderFunction) renderFunction();
            this._cubemap.renderEndCubemapFace();
        }

        this._cubemap.renderEnd();


        this._cgl.popShader();
        this.shadowCubeMap = this._cubemap.getTextureColor(); // getCubemap();
        return;
    }

    this._cgl.pushShader(this._shaderShadowMap.shader);

    this._cgl.pushModelMatrix();
    this._cgl.pushViewMatrix();
    this._cgl.pushPMatrix();

    this._framebuffer.renderStart(this._cgl);

    // * create MVP matrices
    mat4.copy(this._cgl.mMatrix, this._shaderShadowMap.matrices.modelMatrix);

    vec3.set(this._shaderShadowMap.vectors.camPos, this.position[0], this.position[1], this.position[2]);

    if (this.type === "spot") vec3.set(this._shaderShadowMap.vectors.lookAt, this.conePointAt[0], this.conePointAt[1], this.conePointAt[2]);

    mat4.lookAt(this._cgl.vMatrix, this._shaderShadowMap.vectors.camPos, this._shaderShadowMap.vectors.lookAt, this._shaderShadowMap.vectors.up);

    mat4.copy(this._cgl.pMatrix, this._shaderShadowMap.matrices.projMatrix);

    if (!this.lightMatrix) this.lightMatrix = mat4.create();

    // * create light mvp bias matrix
    mat4.mul(this.lightMatrix, this._cgl.pMatrix, this._cgl.vMatrix);
    mat4.mul(this.lightMatrix, this._cgl.mMatrix, this.lightMatrix);
    mat4.mul(this.lightMatrix, this._shaderShadowMap.matrices.biasMatrix, this.lightMatrix);

    this._cgl.gl.clearColor(1, 1, 1, 1);
    this._cgl.gl.clear(this._cgl.gl.DEPTH_BUFFER_BIT | this._cgl.gl.COLOR_BUFFER_BIT);

    if (renderFunction) renderFunction(); // * e.g. op.trigger();
    this._framebuffer.renderEnd(this._cgl);
    this._cgl.popPMatrix();
    this._cgl.popModelMatrix();
    this._cgl.popViewMatrix();

    this._cgl.popShader();
};

Light.prototype.renderBlurPass = function (blurAmount)
{
    if (this.state.isUpdating) return;
    this._cgl.pushShader(this._shaderBlur.shader);

    this._effectBlur.setSourceTexture(this._framebuffer.getTextureColor()); // take shadow map as source
    this._effectBlur.startEffect();

    this._effectBlur.bind();

    this._cgl.setTexture(0, this._effectBlur.getCurrentSourceTexture().tex);
    this._shaderBlur.uniforms.XY.setValue([blurAmount, 0]);
    this._effectBlur.finish();

    this._effectBlur.bind();
    this._cgl.setTexture(0, this._effectBlur.getCurrentSourceTexture().tex);
    this._shaderBlur.uniforms.XY.setValue([0, blurAmount]);

    this._effectBlur.finish();

    this._effectBlur.endEffect();

    this._cgl.popShader();
};

Light.prototype.getShadowPassVertexShader = getShadowPassVertexShader;
Light.prototype.getShadowPassFragmentShader = getShadowPassFragmentShader;
Light.prototype.getBlurPassVertexShader = getBlurPassVertexShader;
Light.prototype.getBlurPassFragmentShader = getBlurPassFragmentShader;

CGL.Light = Light;