tinyrenderer-透视投影

新增坐标vec与坐标矩阵mat之间的转换

Matrix v2m(Vec3f v) {
    Matrix m(4, 1);
    m[0][0] = v.x;
    m[1][0] = v.y;
    m[2][0] = v.z;
    m[3][0] = 1.f;
    return m;
}
Vec3f m2v(Matrix m) {
    return Vec3f(m[0][0] / m[3][0], m[1][0] / m[3][0], m[2][0] / m[3][0]);
}

模型坐标范围是[-1,1]。转换到设置的视口大小,视口大小为左上角(x,y,0),宽width和高height神depth的立方体构成。将模型坐标转换到视口大小坐标,先缩放,模型立方体本身长度为2,因此宽缩放width/2,高缩放height/2,深度缩放depth/2。缩放后的立方体左上角坐标为(-width/2,-height/2,depth/2),平移至(x,y,0)。
变换矩阵为

Matrix viewport(int x, int y, int width, int height) {
    Matrix m = Matrix::identity(4);
    m[0][0] = width / 2;
    m[1][1] = height / 2;
    m[2][2] = depth / 2;
    m[0][3] = width / 2 + x;
    m[1][3] = height / 2 + y;
    m[2][3] = -depth / 2;
    return m;
}

设置图片左下角区域为视口大小,尺寸位图片宽高的一半

Matrix vpm = viewport(0, 0, Width / 2, Height / 2);

画一个三角形

Vec3f x(1.f, 0.f, 0.f), y(0.f, 1.f, 0.f), o(0.f, 0.f, 0.f);
o = m2v(vpm * v2m(o));
x = m2v(vpm * v2m(x));
y = m2v(vpm * v2m(y));
line(x, y, image, yellow);
line(y, o, image, yellow);
line(o, x, image, yellow);

在这里插入图片描述
画出立方体第一个面

for (int i = 0; i < model->nfaces(); i++) {
   std::vector<int> face = model->face(i);
    for (int j = 0; j < face.size(); j++) {
        Vec3f v0 = model->vert(face[j]);
        Vec3f v1 = model->vert(face[(j+1) % face.size()]);
        {
            Vec3f vp0 = m2v(vpm * v2m(v0));
            Vec3f vp1 = m2v(vpm * v2m(v1));
            line(vp0, vp1, image, white);
        }
    }
    break;
}

在这里插入图片描述
缩放矩阵

Matrix scaleBy(float scale) {
    Matrix m = Matrix::identity(4);
    m[0][0] = m[1][1] = m[2][2] = scale;
    return m;
}

缩放1.5倍

Matrix sm = scaleBy(1.5);
Vec3f vp0 = m2v(vpm * sm * v2m(v0));
Vec3f vp1 = m2v(vpm * sm * v2m(v1));
line(vp0, vp1, image, yellow);

在这里插入图片描述
水平剪切

Matrix shear(float value) {
    Matrix m = Matrix::identity(4);
    m[0][1] = value;
    return m;
}

在这里插入图片描述
垂直剪切和绕z轴旋转

Matrix shearY(float value) {
    Matrix m = Matrix::identity(4);
    m[1][0] = value;
    return m;
}

Matrix rotateZ(float angle) {
    float cosAngle = cos(angle * M_PI / 180.f);
    float sinAngle = sin(angle * M_PI / 180.f);
    Matrix m = Matrix::identity(4);
    m[0][0] = m[1][1] = cosAngle;
    m[0][1] = -sinAngle;
    m[1][0] = sinAngle;
    return m;
}
{
    Matrix translate = shearX(-1/3.f);
    Vec3f vp0 = m2v(vpm * translate * v2m(v0));
    Vec3f vp1 = m2v(vpm * translate * v2m(v1));
    line(vp0, vp1, image, red);

    translate = shearY(1 / 3.f) * translate;
    Vec3f vq0 = m2v(vpm * translate * v2m(v0));
    Vec3f vq1 = m2v(vpm * translate * v2m(v1));
    line(vq0, vq1, image, green);
}
{
    Matrix translate = rotateZ(25);
    Vec3f vp0 = m2v(vpm * translate * v2m(v0));
    Vec3f vp1 = m2v(vpm * translate * v2m(v1));
    line(vp0, vp1, image, blue);
}

在这里插入图片描述
透视矩阵
在这里插入图片描述
因为摄像机在z轴上,所以物体应该按照z轴进行缩放,所以是最后一行的第三列有值
在这里插入图片描述
根据相似三角形推导出 r = -1/c.
得出透视投影矩阵

Matrix projection = Matrix::identity(4);
projection[3][2] = -1.f / camera.z;

将世界坐标经过投影矩阵得变换,在做视口变换

Vec3f v0 = model->vert(face[j]);
worldcoord[j] = v0;
screencoords[j] = m2v(vpm * projection * v2m(v0));

源代码的纹理坐标插值和z值插值都是用屏幕坐标的插值求的,所以我这里也是用屏幕坐标求出重心坐标,以屏幕坐标的重心坐标对纹理和z值进行插值求值

for (int x = minX; x <= maxX; x++) {
    for (int y = minY; y <= maxY; y++) {
        //if (x > 600 && y > 500) x += 0.01;
        auto data = getBarycentricCoordinates(screencoords, x, y);
        auto beta = data.first;
        auto gamma = data.second;
        auto alpha = 1 - beta - gamma;
        if (alpha < -0.001 || beta < -0.001 || gamma < -0.001) continue;
        auto z = alpha * screencoords[0].z + beta * screencoords[1].z + gamma * screencoords[2].z;
        int idx = int(x + y * Width);
        if (zBuffer[idx] < z) {
            zBuffer[idx] = z;

            auto uv = texcoords[0] * alpha + texcoords[1] * beta + texcoords[2] * gamma;
            auto c = model->getDiffuseColor(uv.x, uv.y);

            image.set(x, y, TGAColor(-I * c.r, -I * c.g, -I * c.b));
        }
    }
}

透视变换后的模型
在这里插入图片描述
透视变换后的深度图
在这里插入图片描述
终于经过矩阵变换后得屏幕坐标是浮点型,要转换成int型再着色,否则会有破面问题
在这里插入图片描述
实际上经过透视变换的重心坐标与原空间三角形的重心坐标是对不上的,应该要进行矫正。
项目跟随练习代码地址

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值