TemporalSuperSampling.js 4.9 KB
// Temporal Super Sample for static Scene
import halton from './halton';
import Pass from 'claygl/src/compositor/Pass';
import FrameBuffer from 'claygl/src/FrameBuffer';
import Texture2D from 'claygl/src/Texture2D';
import Shader from 'claygl/src/Shader';
import Matrix4 from 'claygl/src/math/Matrix4';

function TemporalSuperSampling (frames) {
    var haltonSequence = [];

    for (var i = 0; i < 30; i++) {
        haltonSequence.push([halton(i, 2), halton(i, 3)]);
    }

    this._haltonSequence = haltonSequence;

    this._frame = 0;

    this._sourceTex = new Texture2D();
    this._sourceFb = new FrameBuffer();
    this._sourceFb.attach(this._sourceTex);

    // Frame texture before temporal supersampling
    this._prevFrameTex = new Texture2D();
    this._outputTex = new Texture2D();

    var blendPass = this._blendPass = new Pass({
        fragment: Shader.source('clay.compositor.blend')
    });
    blendPass.material.disableTexturesAll();
    blendPass.material.enableTexture(['texture1', 'texture2']);

    this._blendFb = new FrameBuffer({
        depthBuffer: false
    });

    this._outputPass = new Pass({
        fragment: Shader.source('clay.compositor.output'),
        // TODO, alpha is premultiplied?
        blendWithPrevious: true
    });
    this._outputPass.material.define('fragment', 'OUTPUT_ALPHA');
    this._outputPass.material.blend = function (_gl) {
        // FIXME.
        // Output is premultiplied alpha when BLEND is enabled ?
        // http://stackoverflow.com/questions/2171085/opengl-blending-with-previous-contents-of-framebuffer
        _gl.blendEquationSeparate(_gl.FUNC_ADD, _gl.FUNC_ADD);
        _gl.blendFuncSeparate(_gl.ONE, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA);
    };
}

TemporalSuperSampling.prototype = {

    constructor: TemporalSuperSampling,

    /**
     * Jitter camera projectionMatrix
     * @parma {clay.Renderer} renderer
     * @param {clay.Camera} camera
     */
    jitterProjection: function (renderer, camera) {
        var viewport = renderer.viewport;
        var dpr = viewport.devicePixelRatio || renderer.getDevicePixelRatio();
        var width = viewport.width * dpr;
        var height = viewport.height * dpr;

        var offset = this._haltonSequence[this._frame % this._haltonSequence.length];

        var translationMat = new Matrix4();
        translationMat.array[12] = (offset[0] * 2.0 - 1.0) / width;
        translationMat.array[13] = (offset[1] * 2.0 - 1.0) / height;

        Matrix4.mul(camera.projectionMatrix, translationMat, camera.projectionMatrix);

        Matrix4.invert(camera.invProjectionMatrix, camera.projectionMatrix);
    },

    /**
     * Reset accumulating frame
     */
    resetFrame: function () {
        this._frame = 0;
    },

    /**
     * Return current frame
     */
    getFrame: function () {
        return this._frame;
    },

    /**
     * Get source framebuffer for usage
     */
    getSourceFrameBuffer: function () {
        return this._sourceFb;
    },

    getOutputTexture: function () {
        return this._outputTex;
    },

    resize: function (width, height) {
        this._prevFrameTex.width = width;
        this._prevFrameTex.height = height;

        this._outputTex.width = width;
        this._outputTex.height = height;

        this._sourceTex.width = width;
        this._sourceTex.height = height;

        this._prevFrameTex.dirty();
        this._outputTex.dirty();
        this._sourceTex.dirty();
    },

    isFinished: function () {
        return this._frame >= this._haltonSequence.length;
    },

    render: function (renderer, sourceTex, notOutput) {
        var blendPass = this._blendPass;
        if (this._frame === 0) {
            // Direct output
            blendPass.setUniform('weight1', 0);
            blendPass.setUniform('weight2', 1);
        }
        else {
            blendPass.setUniform('weight1', 0.9);
            blendPass.setUniform('weight2', 0.1);
        }
        blendPass.setUniform('texture1', this._prevFrameTex);
        blendPass.setUniform('texture2', sourceTex || this._sourceTex);

        this._blendFb.attach(this._outputTex);
        this._blendFb.bind(renderer);
        blendPass.render(renderer);
        this._blendFb.unbind(renderer);

        if (!notOutput) {
            this._outputPass.setUniform('texture', this._outputTex);
            this._outputPass.render(renderer);
        }

        // Swap texture
        var tmp = this._prevFrameTex;
        this._prevFrameTex = this._outputTex;
        this._outputTex = tmp;

        this._frame++;
    },

    dispose: function (renderer) {
        this._sourceFb.dispose(renderer);
        this._blendFb.dispose(renderer);
        this._prevFrameTex.dispose(renderer);
        this._outputTex.dispose(renderer);
        this._sourceTex.dispose(renderer);
        this._outputPass.dispose(renderer);
        this._blendPass.dispose(renderer);
    }
};

export default TemporalSuperSampling;