-1

So i am trying to make a 3D rendering engine with pure JS so i can learn the basics of computer graphics and computer rendering. Since i am trying do make it from scratch i am not using no libraries like webGL or three.js. that makes it really CPU intensive when manipulating the pixels on the canvas as it basically runs on the CPU only and makes everything slow. So after some research i found this post over here: Image manipulation and texture mapping using HTML5 Canvas? about afine transformations in JS using the canvas ctx.Trasnform and ctx.draw Method making it really smooth drawing. and less of a burden to the PC. The problem is that it works perfectly with a simple box and a simple image. And after testing to run a real .obj model with it there where broken images, and not all was rendered and i just can't figure out the reason. Here is an Image of the simple box being draw:

Image of a simple box rendered with the affine transformation

as you can see it works perfectly and draws the texture correctly onto the screen.
While over here it is an image of the House.obj i tried drawing:

image of a House.obj rendered with affine trasnformation

This was not what i expected to happen instead i wanted a correctly draw house without those holes as in the following exemple made by me as well using a pixel by pixel renderization instead:

image of a House.obj rendered with proper pixel by pixel Rasterization

Here is the code i used to draw them:

function fillAfinne(p1,p2,p3,texture_){
    let texture = texture_.image;
    var x0 = p1[0].x, y0 = p1[0].y;
    var x1 = p2[0].x, y1 = p2[0].y;
    var x2 = p3[0].x, y2 = p3[0].y;
    var u0 = p1[1].x, v0 = p1[1].y;
    var u1 = p2[1].x, v1 = p2[1].y;
    var u2 = p3[1].x, v2 = p3[1].y;

    Program.ctx.save();
    Program.ctx.beginPath();
    Program.ctx.moveTo(x0, y0);
    Program.ctx.lineTo(x1, y1);
    Program.ctx.lineTo(x2, y2);
    Program.ctx.closePath();
    Program.ctx.clip();
    var delta = u0*v1+v0*u2+u1*v2-v1*u2-v0*u1-u0*v2;
    Program.ctx.transform(
        (x0*v1+v0*x2+x1*v2-v1*x2-v0*x1-x0*v2)/delta,
        (y0*v1+v0*y2+y1*v2-v1*y2-v0*y1-y0*v2)/delta,
        (u0*x1+x0*u2+u1*x2-x1*u2-x0*u1-u0*x2)/delta,
        (u0*y1+y0*u2+u1*y2-y1*u2-y0*u1-u0*y2)/delta,
        (u0*v1*x2+v0*x1*u2+x0*u1*v2-x0*v1*u2-v0*u1*x2-u0*x1*v2)/delta,
        (u0*v1*y2+v0*y1*u2+y0*u1*v2-y0*v1*u2-v0*u1*y2-u0*y1*v2)/delta
    );
    Program.ctx.drawImage(texture, 0, 0, 1, 1);
    Program.ctx.restore();
}

And here is the Geometric pipeline:

render(transform,camera,mvp){
        for(let i = 0; i < this.faces.length; i++){
            let p1 = this.faces[i][0];
            let p2 = this.faces[i][1];
            let p3 = this.faces[i][2];

            let p1m = mat_multVec(transform.model, this.vertices[p1[0]]);
            let p2m = mat_multVec(transform.model, this.vertices[p2[0]]);
            let p3m = mat_multVec(transform.model, this.vertices[p3[0]]);

            let edge1 = vec_sub(p2m,p1m);
            let edge2 = vec_sub(p3m,p1m);
            let normal = vec_normalize(vec_cross(edge1,edge2));
            let cameraViewOfTriangle = vec_normalize(vec_sub(p1m,camera.owner.Transform.position));
            
            if(vec_dot(cameraViewOfTriangle,normal) >= 0){
                continue
            }

            p1 = [mat_multVec(mvp, this.vertices[p1[0]]), this.uvs[p1[1]], this.normals[p1[2]]];
            p2 = [mat_multVec(mvp, this.vertices[p2[0]]), this.uvs[p2[1]], this.normals[p2[2]]];
            p3 = [mat_multVec(mvp, this.vertices[p3[0]]), this.uvs[p3[1]], this.normals[p3[2]]];


            if(p1[0].w <= camera.near || p2[0].w <= camera.near || p3[0].w <= camera.near){continue};
            if(p1[0].w > camera.far || p2[0].w >= camera.far || p3[0].w >= camera.far){continue};
            
            p1[0] = vec3D(p1[0].x / p1[0].w, p1[0].y / p1[0].w, p1[0].z / p1[0].w, p1[0].w);
            p2[0] = vec3D(p2[0].x / p2[0].w, p2[0].y / p2[0].w, p2[0].z / p2[0].w, p2[0].w);
            p3[0] = vec3D(p3[0].x / p3[0].w, p3[0].y / p3[0].w, p3[0].z / p3[0].w, p3[0].w);
                        
            p1[0] = ToScreen(p1[0], Program.canvas);
            p2[0] = ToScreen(p2[0], Program.canvas);
            p3[0] = ToScreen(p3[0], Program.canvas);

            fillAfinne(p1,p2,p3,TextureHandler.getTexture(this.faces[i][3]));
        }    
    }

My initial thought went to checking the texture uv's coordinates. but considering i tested them with pixel by pixel drawing as well and they worked my guess is that they where not the matter. So i ask to the more experienced if this problem happens because of some mistake in my calculations.

extra code about my calculative functions:

vectors Math:

function vec3D(x,y,z,w){
    return {x:x??0,y:y??0,z:z??0,w:w??0};
}

function vec_sum(v1,v2){
    return vec3D(v1.x+v2.x,v1.y+v2.y,v1.z+v2.z,v1.w+v2.w);
}

function vec_sub(v1,v2){
        if(!v1 || !v2){return}
    return vec3D(v1.x-v2.x,v1.y-v2.y,v1.z-v2.z,v1.w-v2.w);
}

function vec_scale(v1,n){
    return vec3D(v1.x*n,v1.y*n,v1.z*n,v1.w*n);
}

function vec_div(v1,n){
    return vec3D(v1.x/n,v1.y/n,v1.z/n,v1.w/n);
}

function vec_dot(v1,v2){
    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}

function vec_cross(v1,v2){
    return vec3D((v1.y*v2.z - v1.z*v2.y), (v1.z*v2.x - v1.x*v2.z), (v1.x*v2.y - v1.y*v2.x));
}

function vec_mag(v){
    return Math.sqrt(v.x**2 + v.y**2 + v.z**2);
}

function vec_normalize(v){
    let mag = vec_mag(v);
    if(mag > 0){
        let vec = vec_div(v,mag)
        return vec3D(vec.x,vec.y,vec.z,v.w);
    }
    return v;
}

function vec_distance(v1,v2){
    return Math.sqrt((v1.x-v2.x)**2 + (v1.y-v2.y)**2 + (v1.z-v2.z)**2);
}

export{ vec3D, vec_sum, vec_sub, vec_scale, vec_div, vec_dot, vec_cross, vec_mag, vec_normalize, vec_distance };

Matrix Math:

import { quat_normalize } from "quaternionMath";
import { vec3D, vec_cross, vec_dot, vec_normalize, vec_sub } from "vectorMath";

function matrix3D(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){
    return [
        [a??1,b??0,c??0,d??0],
        [e??0,f??1,g??0,h??0],
        [i??0,j??0,k??1,l??0],
        [m??0,n??0,o??0,p??1],
    ]
}

function mat_multMat(m1,m2){
    let resultantMatrix = matrix3D();
    
    for(let row = 0; row < 4; row++){
        for(let column = 0; column < 4; column++){
            let result = 0

            for(let n = 0; n < 4; n++){
                result += m1[row][n] * m2[n][column];
            }
            resultantMatrix[row][column] = result
        }
    }

    return resultantMatrix;
}

function mat_multVec(m,v){
    let VectorMatrix = [v.x,v.y,v.z,v.w??1];
    let resultantVectorMatrix = [];
    for(let row = 0; row < 4; row++){
        let result = 0;
        for(let column = 0; column < 4; column++){
            result += m[row][column] * VectorMatrix[column]
        }
        resultantVectorMatrix[row] = result;
    }

    return vec3D(resultantVectorMatrix[0],resultantVectorMatrix[1],resultantVectorMatrix[2],resultantVectorMatrix[3]);
}

function mat_translate(vec){
    return matrix3D(
        1,0,0,vec.x,
        0,1,0,vec.y,
        0,0,1,vec.z,
        0,0,0,1
    )
}

function mat_scale(vec){
    return matrix3D(
        vec.x,0,0,0,
        0,vec.y,0,0,
        0,0,vec.z,0,
        0,0,0,1
    )
}

function mat_rotate(quat){
    let q = quat_normalize(quat);
    return matrix3D(
        1-2*(q.y**2 + q.z**2), 2*(q.x*q.y - q.w*q.z), 2*(q.x*q.z + q.w*q.y),0,
        2*(q.x*q.y + q.w * q.z), 1-2*(q.x**2 + q.z**2), 2*(q.y*q.z - q.w*q.x),0,
        2*(q.x*q.z - q.w * q.y), 2*(q.y*q.z + q.w * q.x), 1-2*(q.x**2 + q.y**2),0,
        0,0,0,1
    )
}

function mat_model(position,rotation,size){
    let tm = mat_translate(position);
    let rm = mat_rotate(rotation);
    let sm = mat_scale(size);

    return mat_multMat(mat_multMat(tm,rm),sm);
}

function mat_view(eye,target,up){
    let zAxis = vec_normalize(vec_sub(eye,target));
    let xAxis = vec_normalize(vec_cross(zAxis,up));
    let yAxis = vec_cross(xAxis,zAxis);

    return matrix3D(
        xAxis.x,xAxis.y,xAxis.z,-vec_dot(xAxis,eye),
        yAxis.x,yAxis.y,yAxis.z,-vec_dot(yAxis,eye),
        zAxis.x,zAxis.y,zAxis.z,-vec_dot(zAxis,eye),
        0,0,0,1
    );
}

function mat_perspective(near,far,top,bottom,right,left){
    return matrix3D(
        (2*near)*(1/(right - left)),0,(right+left)*(1/(right - left)),0,
        0, (2*near)*(1/(top-bottom)),(top+bottom)*(1/(top-bottom)),0,
        0,0,-((far+near)*(1/(far - near))),-((2*far*near)*(1/(far - near))),
        0,0,-1,0
    ); 
}

function mat_orthographic(near,far,top,bottom,right,left){
    return matrix3D(
        2/(right-left),0,0,-((right+left)/(right-left)),
        0,2/(top-bottom),0,-((top+bottom)/(top-bottom)),
        0,0,-2/(far-near),-((far+near)/(far-near)),
        0,0,0,1
    )
}

function mat_mvp(m,v,p){
    return mat_multMat(mat_multMat(p,v),m);
}

export{ matrix3D, mat_multMat, mat_multVec , mat_translate, mat_rotate, mat_scale, mat_model, mat_view, mat_perspective, mat_orthographic, mat_mvp };

Quaternion Math:

import { vec_normalize, vec_scale } from "vectorMath";

function quaternion(w,x,y,z){
    return {w:w??0,x:x??0,y:y??0,z:z??0};
}

function quat_euler(v){
    let vr = vec_scale(v,(Math.PI/180))
    let qx = quaternion(Math.cos(vr.x/2),Math.sin(vr.x/2),0,0);
    let qy = quaternion(Math.cos(vr.y/2),0,Math.sin(vr.y/2),0);
    let qz = quaternion(Math.cos(vr.z/2),0,0,Math.sin(vr.z/2));
    return quat_mult(quat_mult(qx,qy),qz);
}

function quat_conjugate(q){
    return {w:q.w,x:-q.x,y:-q.y,z:-q.z};
}

function quat_mult(q1,q2){
    return quaternion(
        (q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z),
        (q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y),
        (q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x),
        (q1.w*q2.z + q1.x*q2.y - q1.y*q2.x + q1.z*q2.w),
    );
}

function quat_rotate(v,a){
    let ar = a*(Math.PI/180)
    let vn = vec_normalize(v)
    let q = quaternion(Math.cos(ar/2), Math.sin(ar/2)*vn.x,Math.sin(ar/2)*vn.y,Math.sin(ar/2)*vn.z)

    return q;
}

function quat_magnitude(q){
    return Math.sqrt((q.w**2 + q.x**2 + q.y**2 + q.z**2));
}

function quat_normalize(q){
    let mag = quat_magnitude(q);
    if(mag > 0){
        return quaternion(q.w/mag,q.x/mag,q.y/mag,q.z/mag);
    }
    return q;
}

export{ quaternion, quat_euler, quat_conjugate, quat_mult, quat_rotate, quat_magnitude, quat_normalize }

And baricentric coordinates

function baricentric(p,triangle){
    var DetT = (triangle[1][0].y-triangle[2][0].y)*(triangle[0][0].x-triangle[2][0].x)+(triangle[2][0].x-triangle[1][0].x)*(triangle[0][0].y-triangle[2][0].y);

    var w0 = ((triangle[1][0].y-triangle[2][0].y)*(p.x-triangle[2][0].x)+(triangle[2][0].x-triangle[1][0].x)*(p.y-triangle[2][0].y))/DetT;

    var w1 = ((triangle[2][0].y-triangle[0][0].y)*(p.x-triangle[2][0].x)+(triangle[0][0].x-triangle[2][0].x)*(p.y-triangle[2][0].y))/DetT;

    var w2 = 1 - w0 - w1;
    return [w0,w1,w2];
}

export{baricentric}
1
  • Besides the fact that the posted code misses to explain/show what several things are (for example: Program, TextureHandler, ToScreen), are you sure that you are still doing "basics of computer graphics and computer rendering"? Rendering a complex object like a house usually is no longer a basic thing like rendering a cube, a sphere or a pyramid. Commented Dec 10 at 9:04

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.