新增坐标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型再着色,否则会有破面问题
实际上经过透视变换的重心坐标与原空间三角形的重心坐标是对不上的,应该要进行矫正。
项目跟随练习代码地址