Source

Renderers/Commands/SkyboxCommand.js

import { mat3, mat4 } from 'gl-matrix';
import { DepthFunction, DrawingMode } from '../../StateBlock';
import Geometry from '../../Geometry';
import { Program } from '../../Program';
import RenderCommand from './RenderCommand';
import Type from '../../Types';

/**
 * Draw Skyboxes
 *
 * @category Rendering
 * @extends {RenderCommand}
 */
class SkyboxCommand extends RenderCommand {
    /**
     * Constructor
     *
     * @param {Skybox} skybox A Skybox instance
     */
    constructor(skybox) {
        super();

        /**
         * The Skybox instance to draw
         *
         * @type {Skybox}
         * @private
         */
        this.skybox = skybox;
    }

    /**
     * Execute the command
     *
     * @param {RenderAPI} renderAPI RenderAPI instance used to process the commands
     */
    execute(renderAPI) {
        const texture = this.skybox.getTexture();
        if (!texture || !texture.isReady()) {
            return;
        }

        // Use custom or default program.
        let program = this.skybox.getCustomProgram();
        if (!program) {
            if (SkyboxCommand.isDefaultProgramLoaded()) {
                program = SkyboxCommand.sharedProgram;
            } else {
                return;
            }
        }

        // Program
        const programCode = renderAPI.setProgram(program);
        if (programCode === -1) {
            return;
        }

        // Must send/update shared uniforms
        if (programCode === 1) {
            renderAPI.setUniform(program, 'projection', Type.Matrix, renderAPI.getActiveCamera().getProjectionMatrix());

            // Tip: Remove last row and col from the matrix to get an infinite Skybox
            const viewMatrix = renderAPI.getActiveCamera().getViewMatrix();
            const m = mat3.fromMat4([], viewMatrix);

            renderAPI.setUniform(program, 'view', Type.Matrix, this.toMat4(m));
        }

        // Send uniforms
        renderAPI.setUniform(program, 'uModel', Type.Matrix, this.skybox.getTransformationMatrix());

        // States and apparence
        renderAPI.setDepthState(false, false, DepthFunction.Less);
        renderAPI.bindTextureCube(0, texture);

        // Bind geometry
        renderAPI.setGeometry(SkyboxCommand.sharedGeometry);

        // Draw object
        renderAPI.drawIndexedPrimitives(DrawingMode.Triangles, 0, SkyboxCommand.sharedGeometry.getIndexCount());
    }

    toMat4(mat) {
        const result = mat4.create();
        result[15] = 1; result[14] = 0; result[13] = 0; result[12] = 0;
        result[11] = 0; result[10] = mat[8]; result[9] = mat[7]; result[8] = mat[6];
        result[7] = 0; result[6] = mat[5]; result[5] = mat[4]; result[4] = mat[3];
        result[3] = 0; result[2] = mat[2]; result[1] = mat[1]; result[0] = mat[0];

        return result;
    }

    /**
     * Check if the default program is ready, otherwise the function load it
     *
     * @return {boolean} Return true if the default program is loaded
     */
    static isDefaultProgramLoaded() {
        if (SkyboxCommand.sharedProgram.isReady()) {
            return true;
        }

        const vertexShader = `
        uniform mat4 projection;
        uniform mat4 view;
        uniform mat4 uModel;

        attribute vec4 aPosition;
        attribute vec4 aColor;
        varying vec4 vColor;
        varying vec4 vUV;

        void main() {
            gl_Position = projection * view * aPosition;
            vColor      = aColor;
            vUV         = aPosition;
        }`;

        const fragmentShader = `
        uniform lowp samplerCube skybox;
        varying lowp vec4 vColor;
        varying mediump vec4 vUV;

        void main() {
            gl_FragColor = textureCube(skybox, vUV.xyz) * vColor;
        }`;

        SkyboxCommand.sharedProgram.loadFromData(vertexShader, fragmentShader);

        return false;
    }
}

/**
 * Default geometry for skybox rendering
 *
 * @type {Geometry}
 * @private
 */
SkyboxCommand.sharedGeometry = Geometry.createCube(0.5, 0.5, 0.5);

/**
 * Default program for skybox rendering
 *
 * @type {Program}
 * @private
 */
SkyboxCommand.sharedProgram = new Program();

export default SkyboxCommand;