在上一次计算的代码中,我们把模型的坐标$v_x,v_y$映射投影到了一定的区间,其实对于$v_z$我们也可以这样来做。这样可以更多更好的运用我们的齐次坐标,为了方便,我们可以把$v_z$映射到[0,255]之间,这样当然我们的 viewport 矩阵要跟着改了, d = 255:
我们把$v_z$映射到0~255之间还有一个附加原因(好处)是因为TGAImage支持灰度图像,我们可以利用$v_z$生成一副灰度图像,这样更利于我们debug,同时我们也不需要再使用zbuffer数组了。
看 code 关键部分:
float z = pts[0][2]*c.x + pts[1][2]*c.y + pts[2][2]*c.z;
float w = pts[0][3]*c.x + pts[1][3]*c.y + pts[2][3]*c.z;
int frag_depth = std::max(0, std::min(255, int(z/w+.5)));
if (c.x < 0 || c.y < 0 || c.z < 0 || zbuffer.get(P.x, P.y)[0] > frag_depth ) continue;
image.set(P.x, P.y, color);
zbuffer.set(P.x, P.y, TGAColor(frag_depth));
我们的zbuffer是一幅TGA图像,方便我们直接从中取值,同时z的计算我们变得更加方便了一些,我们也新增了一些几何计算的部分,比如 Vec3f - Vec3i 互相转换, Vec2f - Vec2i 互相转换。
现在compile就需要:
$ g++ -std=c++11 main.cpp tgaimage.cpp model.cpp geometry.cpp -o main
$ .\main
生成的output跟之前一样,附加生成了一幅zbuffer的图像:
之前写过为了生成这个output,我们加了一束方向光,并做了一些假设:假设每个三角形收到光照的强度相同,都是 Icosα,法向量是我们根据三角形的边叉乘算出来的。
这种给物体上色的方式就叫做平面着色(Flat Shading),三角形这个平面着一样的色。除了平面着色以外,我们还有别的着色方式: 高洛德着色(Gouraud shading) 和冯氏着色 (Phong shading).
高洛德着色(Gouraud shading) 跟我们处理三角形的z值差不多,算出每个顶点的颜色,然后根据重心坐标,算出三角形内每个P点的颜色,给P点上色。
至于每个顶点的颜色怎么算,别忘了我们的 wavefront file
f 1193/1240/1193 1180/1227/1180 1179/1226/1179
顶点索引/顶点法向量索引/顶点纹理索引
顶点法向量在文件中是 vn开头的数字:
vn 0.001 0.482 -0.876
有了顶点法向量我们当然就可以算顶点颜色,进一步修改model文件读入更多的数据。
不同的着色方式会有不同的代码,如果我们每次都要回去修改都太麻烦了,这里我们来整理一次代码,让它靠近OpenGL,把顶点变换和着色抽象出来,变成专门的shader部分,也就是所谓的 vertex shader(顶点着色器) 和 fragment shader(片段着色器)。
我们定义一个IShader基类,所有的找色方式都继承和实现里面的 virtual method.
struct IShader {
virtual ~IShader();
virtual Vec4f vertex(int iface, int nthvert) = 0;
virtual bool fragment(Vec3f bar, TGAColor &color) = 0;
};
顶点着色器主要做两件事:
- 变换顶点
- 准备数据给片段着色器用
片段着色器也主要做两件事:
- 决定当前像素的颜色
- 是否要丢弃当前像素
平面着色器:
struct FlatShader: public IShader{
mat<3,3,float> varying_tri; // 用来记录transform之后的三角形
virtual Vec4f vertex(int iface, int nthvert){
Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));
gl_Vertex = Projection*ModelView*gl_Vertex;
varying_tri.set_col(nthvert, proj<3>(gl_Vertex/gl_Vertex[3])); //记录transform后的三角形,这个在我们片段着色器决定三角形的颜色的时候使用来使用
gl_Vertex = ViewPort*gl_Vertex;
return gl_Vertex;
}
virtual bool fragment(Vec3f bar, TGAColor &color){
Vec3f n = cross(varying_tri.col(1)- varying_tri.col(0),varying_tri.col(2)-
varying_tri.col(0)).normalize(); // 计算法向量
float intensity = CLAMP(n*light_dir); // 光强度clamp到0,1之间
color = TGAColor(255,255,255)*intensity; // 计算颜色
return false;
}
};
之所以我们叫这个为 varying_tri 是因为 varying是GLSL中的保留字,我们之后会再聊到它,改变一下光的方向、眼睛位置,看最终效果:
代码:
run:
$ g++ -std=c++11 main.cpp tgaimage.cpp model.cpp geometry.cpp our_gl.cpp -o main
$ ./main
鉴于我们的compile越来越复杂,之后我们会开始使用makefile.