绘制物体的深度远近会影响最终投射的效果
画家算法:是z值排序所有物体,从远往近全部绘制,近处物体覆盖远处物体。效率低,且无法处理物体相互穿插的情况
维护一个zBuffer,记录每个像素点的最近zBuffer,根据每个像素点的zBuffer决定 绘制哪个物体的颜色
关键点在已知求三角形三个顶点的情况下,求内部任一点的z值。通过重心坐标求解
求重心坐标系数
std::pair<float, float> getBarycentricCoordinates(Vec3f* pts, float x, float y) {
float beta = ((pts[0].y - pts[1].y) * x + (pts[1].x - pts[0].x) * y + pts[0].x * pts[1].y - pts[1].x * pts[0].y)
/ ((pts[0].y - pts[1].y) * pts[2].x + (pts[1].x - pts[0].x) * pts[2].y + pts[0].x * pts[1].y - pts[1].x * pts[0].y);
float gamma = ((pts[0].y - pts[2].y) * x + (pts[2].x - pts[0].x) * y + pts[0].x * pts[2].y - pts[2].x * pts[0].y)
/ ((pts[0].y - pts[2].y) * pts[1].x + (pts[2].x - pts[0].x) * pts[1].y + pts[0].x * pts[2].y - pts[2].x * pts[0].y);
return { beta,gamma };
}
记录zBuffer
std::vector<float> zBuffer(Width * Height, -std::numeric_limits<float>::infinity());
void triangle(Vec3f* pts, TGAImage& image, TGAColor color) {
float minX = image.get_width() - 1;
float minY = image.get_height() - 1;
float maxX = 0;
float maxY = 0;
for (int i = 0; i < 3; i++) {
minX = std::min(minX, pts[i].x);
maxX = std::max(maxX, pts[i].x);
minY = std::min(minY, pts[i].y);
maxY = std::max(maxY, pts[i].y);
}
minX = std::max(0.f, minX);
maxX = std::min(maxX, (float)image.get_width() - 1);
minY = std::max(0.f, minY);
maxY = std::min(maxY, (float)image.get_height() - 1);
for (float x = minX; x <= maxX; x++) {
for (float y = minY; y <= maxY; y++) {
auto data = getBarycentricCoordinates(pts, x, y);
float alpha = 1 - data.first - data.second;
float beta = data.first;
float gamma = data.second;
if (alpha >= 0 && beta >= 0 && gamma >= 0) {
auto z = alpha * pts[0].z + beta * pts[1].z + gamma * pts[2].z;
int idx = int(x) + int(y) * Width;
if (zBuffer[idx] < z) {
zBuffer[idx] = z;
image.set(x, y, color);
}
}
}
}
}
这里传入的顶点数据vp,x轴y轴是经过坐标变换的,z没有变换,因为z在这里的用处是对比大小的,虽然没有换算,但相对大小是不变的
最终效果
上图中有一些黑点,查代码最终发现是重心坐标记录点在三角形内时有浮动误差,调整了下代码
if (alpha < -0.001 || beta < -0.001 || gamma < -0.001) continue;
附加纹理着色,读取了obj文件的vt(纹理坐标),以及f数组的第二个值(纹理坐标的索引)
else if (!line.compare(0, 2, "vt")) {
iss >> trash >> trash;
Vec3f vt;
for (int i = 0; i < 3; i++) {
iss >> vt.raw[i];
}
tverts_.push_back(vt);
}
else if (!line.compare(0, 2, "f ")) {
std::vector<int> f;
std::vector<int> tf;
int itrash, idx, tidx;
iss >> trash;
while (iss >> idx >> trash >> tidx >> trash >> itrash) {
idx--; // in wavefront obj all indices start at 1, not zero
f.push_back(idx);
tidx--;
tf.push_back(tidx);
}
faces_.push_back(f);
tfaces_.push_back(tf);
}
由于没有进行投影变化,所以屏幕三角形坐标求的重心坐标和实际空间中的坐标是一样的,可以直接用来求纹理插值的坐标
auto uv = ts[0] * alpha + ts[1] * beta + ts[2] * gamma;
auto c = tex.get(uv.x * tex.get_width(), uv.y * tex.get_height());
其中tex是读取的纹理,ts是三角形三个顶点的纹理坐标
Vec3f v1 = model->tverts(tface[j]);
vt[j] = v1;
//........
TGAImage tex = TGAImage();
tex.read_tga_file("obj/african_head_diffuse.tga");
tex.flip_vertically();
最后用纹理插值来涂色
image.set(x, y, TGAColor(-I * c.r, -I * c.g, -I * c.b));