1

我正在尝试在 WebGL 2.0 中创建一个门户渲染引擎(到目前为止,我正在尝试移植这个 C++ 项目:https ://github.com/HackerPoet/NonEuclidean ),并且正在尝试实现倾斜投影以对齐剪裁门户相机到其各自门户的平面;尽管在各种 OpenGL 平台上找到了多种实现,但是,我无法让它在我的 WebGL 项目中工作。这是与之相关的代码:

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;
                },
            });
        },
    };
})();

起初,传送门看起来很正常,但是当我尝试从它后面走时,对象仍然被渲染(如果我定位自己,使传送门的相机在它们后面),但是以一种不寻常的方式。我不太擅长矩阵数学或线性代数,希望得到一些帮助/见解;谢谢你。

4

0 回答 0