2

I am trying to create a portal rendering engine in WebGL 2.0 (so far I am trying to port this C++ project: https://github.com/HackerPoet/NonEuclidean), and am trying to implement oblique projection in order to align the clipping plane of the portal cameras to their respective portals; despite finding multiple implementations across various OpenGL platforms, however, I can't get it to work in my WebGL project. Here is the code related to it:

const Mat4 = (() =>
{
    const { mat3, mat4, vec2, vec3, vec4 } = glMatrix;
    return {
        ...mat4,
        zAxis(out, mat)
        {
            return vec3.set(out, mat[2], mat[6], mat[10]);
        },
        mulVec4(out, mat, vec)
        {
            const [x, y, z, w] = vec;
            return vec3.set(out,
                mat[ 0] * x + mat[ 1] * y + mat[ 2] * z + mat[ 3] * w,
                mat[ 4] * x + mat[ 5] * y + mat[ 6] * z + mat[ 7] * w,
                mat[ 8] * x + mat[ 9] * y + mat[10] * z + mat[11] * w,
                mat[12] * x + mat[13] * y + mat[14] * z + mat[15] * w);
        },
        mulPoint(out, mat, vec)
        {
            const [x, y, z] = vec;
            const w = mat[12] * x + mat[13] * y + mat[14] * z + mat[15];
            return vec3.set(out,
                (mat[0] * x + mat[1] * y + mat[ 2] * z + mat[ 3]) / w,
                (mat[4] * x + mat[5] * y + mat[ 6] * z + mat[ 7]) / w,
                (mat[8] * x + mat[9] * y + mat[10] * z + mat[11]) / w);
        },
    };
})();

const Camera = () =>
{
    const { vec3, vec4 } = glMatrix;
    const projection = Mat4.create();
    const view = Mat4.create();
    return {
        width:  0,
        height: 0,
        aspect: 1,
        near:   0.1,
        far:    100.0,
        fov:    Math.PI / 3,
        pos:    [0, 0, 0],
        euler:  [0, 0, 0],
        get view()
        {
            return view;
        },
        set view(matrix)
        {
            return Mat4.copy(view, matrix);
        },
        get matrix()
        {
            return Mat4.multiply(Mat4.create(), projection, view)
        },
        get projection()
        {
            return projection;
        },
        set transform([x = 0, y = 0, z = 0, rx = 0, ry = 0])
        {
            const matrix = Mat4.fromXRotation(Mat4.create(), rx);
            Mat4.multiply(matrix, matrix, Mat4.fromYRotation(Mat4.create(), ry));
            Mat4.multiply(matrix, matrix, Mat4.fromTranslation(Mat4.create(), [-x, -y, -z]));
            Mat4.copy(view, matrix);
            return view;
        },
        inverse()
        {
            const inv = Mat4.create();
            const a = projection[0];
            const b = projection[5];
            const c = projection[10];
            const d = projection[11];
            const e = projection[14];
            inv[0]  = 1 / a;
            inv[5]  = 1 / b;
            inv[11] = 1 / e;
            inv[14] = 1 / d;
            inv[15] = -c / (d * e)
            return inv;
        },
        clipOblique(pos, norm)
        {
            const cpos = Mat4.mulPoint(vec3.create(), view, pos);
            const cnorm = vec3.normalize(vec3.create(), Mat4.mulPoint(vec3.create(), view, norm));
            const point = Mat4.mulPoint(vec3.create(), Mat4.invert(Mat4.create(), view), [0, 0, 0]);
            cpos[1] -= point[1];
            const cplane = vec4.set(vec4.create(), cnorm[0], cnorm[1], cnorm[2], -vec3.dot(cpos, cnorm));
            const q = Mat4.mulVec4(vec4.create(), Mat4.invert(Mat4.create(), projection), [
                (cplane[0] > 0 ? 1 : -1),
                (cplane[1] > 0 ? 1 : -1),
                1,
                1]);
            const c = cplane.map(x => x * 2 / vec4.dot(cplane, q));
            projection[ 2] = c[0] - projection[ 3];
            projection[ 6] = c[1] - projection[ 7];
            projection[10] = c[2] - projection[11];
            projection[14] = c[3] - projection[15];
            return this;
        },
        copy(that)
        {
            this.setup(that.width, that.height, that.near, that.far, that.fov, that.aspect);
            Mat4.copy(view, that.view);
            Mat4.copy(projection, that.projection);
            return this;
        },
        setup(width = this.width, height = this.height, near = this.near, far = this.far, fov = this.fov, aspect = height / width)
        {
            this.width = width;
            this.height = height;
            this.near = near;
            this.far = far;
            this.fov = fov;
            this.aspect = aspect;
            
            const f = 1.0 / Math.tan(fov / 2);
            const range = 1.0 / (near - far);
            
            projection[0] = f * aspect
            projection[1] = 0.0;
            projection[2] = 0.0;
            projection[3] = 0.0;
            
            projection[4] = 0.0;
            projection[5] = f;
            projection[6] = 0.0;
            projection[7] = 0.0;
            
            projection[8] = 0.0;
            projection[9] = 0.0;
            projection[10] = (near + far) * range;
            projection[11] = -1.0;
            
            projection[12] = 0.0;
            projection[13] = 0.0;
            projection[14] = 2 * near * far * range;
            projection[15] = 0.0;
            return this;
        }
    }
};
const GameObject = (gl, mesh) =>
{
    const { vec3 } = glMatrix;
    return {
        pos:   [0, 0, 0],
        euler: [0, 0, 0],
        scale: [1, 1, 1],
        mesh,
        forward()
        {
            const matrix = Mat4.fromZRotation(Mat4.create(), this.euler[2]);
            Mat4.multiply(matrix, matrix, Mat4.fromXRotation(Mat4.create(), this.euler[0]));
            Mat4.multiply(matrix, matrix, Mat4.fromYRotation(Mat4.create(), this.euler[1]));
            return vec3.negate(vec3.create(), Mat4.zAxis(vec3.create(), matrix));
        },
        localToWorld()
        {
            const matrix = Mat4.fromTranslation(Mat4.create(), this.pos);
            Mat4.multiply(matrix, matrix, Mat4.fromYRotation(Mat4.create(), this.euler[1]));
            Mat4.multiply(matrix, matrix, Mat4.fromXRotation(Mat4.create(), this.euler[0]));
            Mat4.multiply(matrix, matrix, Mat4.fromZRotation(Mat4.create(), this.euler[2]));
            Mat4.multiply(matrix, matrix, Mat4.fromScaling(Mat4.create(), this.scale));
            return matrix;
        },
        worldToLocal()
        {
            const matrix = Mat4.fromScaling(Mat4.create(), this.scale);
            Mat4.invert(matrix, matrix);
            Mat4.multiply(matrix, matrix, Mat4.fromZRotation(Mat4.create(), this.euler[2]));
            Mat4.multiply(matrix, matrix, Mat4.fromXRotation(Mat4.create(), this.euler[0]));
            Mat4.multiply(matrix, matrix, Mat4.fromYRotation(Mat4.create(), this.euler[1]));
            Mat4.multiply(matrix, matrix, Mat4.fromTranslation(Mat4.create(), vec3.negate(vec3.create(), this.pos)));
            return matrix;
        },
        render(camera, fbo = null)
        {
            const mv = Mat4.transpose(Mat4.create(), this.worldToLocal());
            const mvp = Mat4.multiply(Mat4.create(), camera.matrix, this.localToWorld());
            this.mesh.render(mvp, mv);
            return this;
        },
    };
};

const Portal = (() =>
{
    const { vec3 } = glMatrix;
    const Warp = (fromPortal = {}) =>
    {
        const delta = Mat4.identity(Mat4.create());
        const deltaInv = Mat4.identity(Mat4.create());
        return {
            fromPortal,
            toPortal: null,
            delta, deltaInv,
        };
    };
    return {
        connectWarp(a, b)
        {
            a.toPortal = b.fromPortal;
            b.toPortal = a.toPortal;
            a.delta = Mat4.multiply(Mat4.create(), a.fromPortal.localToWorld(), b.fromPortal.worldToLocal());
            b.delta = Mat4.multiply(Mat4.create(), b.fromPortal.localToWorld(), a.fromPortal.worldToLocal());
            a.deltaInv = b.delta;
            b.deltaInv = a.delta;
            return this;
        },
        connect(a, b)
        {
            this.connectWarp(a.front, b.back);
            this.connectWarp(b.front, a.back);
            return this;
        },
        create: async (gl) =>
        {
            const mesh = await Mesh.load('double_quad.obj');
            const shader = await Shader(gl, '.', 'portal-shader');
            const { a_position } = shader.attribute;
            const vao = gl.createVertexArray();
            gl.bindVertexArray(vao);
            const buffers = createBuffers(gl, new Array(1));
            setupArrayBuffer(gl, buffers[0], new Float32Array(mesh.geometries[0].data.position), 3, a_position);
            
            const portalCam = Camera();
            return (self =>
            {
                self.front = Warp(self);
                self.back  = Warp(self);
                return self;
            })({
                ...GameObject(gl, null),
                front: null,
                back:  null,
                get cam()
                {
                    return portalCam;
                },
                /*Override*/ render(camera, fbo, func)
                {
                    console.assert(this.euler[0] === 0);
                    console.assert(this.euler[2] === 0);
                    const normal = this.forward();
                    const camPos = Mat4.getTranslation(vec3.create(), Mat4.invert(Mat4.create(), camera.view));
                    const isFront = vec3.dot(vec3.subtract(vec3.create(), camPos, this.pos), normal) > 0;
                    
                    if (isFront)
                    {
                        vec3.negate(normal, normal);
                    }
                    const warp = isFront? this.front : this.back;
                    const mvp = Mat4.multiply(Mat4.create(), camera.matrix, this.localToWorld());
                    
                    portalCam.copy(camera);
                    portalCam.clipOblique(vec3.add(vec3.create(), this.pos, normal.map(x => x * 0.1)), vec3.negate(vec3.create(), normal));
                    Mat4.multiply(portalCam.view, portalCam.view, warp.delta);
                    shader.use();
                    gl.bindVertexArray(vao);
                    
                    const { u_mvp, u_texture } = shader.uniform;
                    
                    gl.uniform1i(u_texture, 0);
                    gl.activeTexture(gl.TEXTURE0 + 0);
                    fbo.use();
                    
                    gl.uniformMatrix4fv(u_mvp, false, mvp);
                    gl.drawArrays(gl.TRIANGLES, 0, mesh.geometries[0].data.position.length / 3);
                    return this;
                },
            });
        },
    };
})();

At first the portal seems normal, but when I try to go behind it, the objects are still rendered (if I position myself so that the portal's camera is behind them), but in an unusual way. I am not very good at matrix math or linear algebra, and would appreciate some help/insight; thank you.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.