前言:这个教程快要结束了,花了不少时间,这篇博客就写写我所了解的阴影贴图吧!
目标
我们今天的目标就是学会如何从图一变化成图二
怎么生成阴影
生成阴影的必要条件是我们得知道哪个像素会被遮住。
现在,我把我们眼睛移到我们定义的光照方向上。
// 把 eye 暂时移到这里
Vec3f light_dir(1,1,0);
从这里看,会看到啥?
在这个渲染器里面我们得到了两个东西。
- shadow buffer
还记得在之前的渲染器里面我们的 zbuffer 存储了什么吗?存储了在三角形内的每一个像素用重心法算出比例后的 z。这个 z 越大就越在前面,遮住了后面的点。新得到的 shadow buffer 就是把眼睛放在光源处计算的 z-buffer
有了这个 shadow buffer 以后。我们就可以知道这个像素点是否被遮盖。
比如
还是在这个变换空间里面,我现在有三角形的三个顶点的屏幕坐标。现在有一个三角形内的点。通过重心法算出比例,再通过比例算出这个点新的 z、以及屏幕坐标 (x, y)。通过 (x, y) 算出 shadow buffer 的索引。通过比较 z 和 shadowbuffer[x + y * width]。如果 z 大于等于它。那么就没有被遮挡!反之就会被遮挡。
- matrix
矩阵!逃不开的矩阵!我们在上面那个例子里面,重点的说了一句「还是在这个变换空间里面」。
我们并不是要在这个空间里面画,因为我们的眼睛不可能永远在光源处。
但是!我们只需要知道这个点是否被遮挡!也就是说,只要我们能把这个坐标系里的点转换到上面得到 shadow buffer 的坐标系中,就能知道这个点是否被遮挡。
所以我们需要这样的变换矩阵。这个变化矩阵其实也没什么特别之处。我们来看代码
TGAImage depth(width, height, TGAImage::RGB);
lookat(light_dir, center, up);
viewport(width/8, height/8, width*3/4, height*3/4);
projection(0);
DepthShader depthshader;
Vec4f screen_coords[3];
for (int i=0; i<model->nfaces(); i++) {
for (int j=0; j<3; j++) {
screen_coords[j] = depthshader.vertex(i, j);
}
triangle(screen_coords, depthshader, depth, shadowbuffer);
}
depth.flip_vertically(); // to place the origin in the bottom left corner of the image
// 这是我们需要的矩阵
// 为了转换坐标的目的,这个矩阵还要和当前坐标系的转换矩阵组合
Matrix M = Viewport*Projection*ModelView;
其中的 depthshader 代码如下
struct DepthShader : public IShader {
mat<3,3,float> varying_tri;
DepthShader() : varying_tri() {}
virtual Vec4f vertex(int iface, int nthvert) {
Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));
gl_Vertex = Viewport*Projection*ModelView*gl_Vertex;
varying_tri.set_col(nthvert, proj<3>(gl_Vertex/gl_Vertex[3]));
return gl_Vertex;
}
virtual bool fragment(Vec3f bar, TGAColor &color) {
Vec3f p = varying_tri*bar;
color = TGAColor(255, 255, 255)*(p.z/depth);
return false;
}
};
还没完
我们知道这个像素会被遮挡,那么它的颜色就不能像之前那样计算了。
颜色计算的代码、以及真正的着色器
struct Shader : public IShader {
mat<4,4,float> uniform_M; // Projection*ModelView
mat<4,4,float> uniform_MIT; // (Projection*ModelView).invert_transpose()
mat<4,4,float> uniform_Mshadow; // transform framebuffer screen coordinates to shadowbuffer screen coordinates
mat<2,3,float> varying_uv; // triangle uv coordinates, written by the vertex shader, read by the fragment shader
mat<3,3,float> varying_tri; // triangle coordinates before Viewport transform, written by VS, read by FS
Shader(Matrix M, Matrix MIT, Matrix MS) : uniform_M(M), uniform_MIT(MIT), uniform_Mshadow(MS), varying_uv(), varying_tri() {}
virtual Vec4f vertex(int iface, int nthvert) {
varying_uv.set_col(nthvert, model->uv(iface, nthvert));
Vec4f gl_Vertex = Viewport*Projection*ModelView*embed<4>(model->vert(iface, nthvert));
varying_tri.set_col(nthvert, proj<3>(gl_Vertex/gl_Vertex[3]));
return gl_Vertex;
}
virtual bool fragment(Vec3f bar, TGAColor &color) {
Vec4f sb_p = uniform_Mshadow*embed<4>(varying_tri*bar); // corresponding point in the shadow buffer
sb_p = sb_p/sb_p[3];
int idx = int(sb_p[0]) + int(sb_p[1])*width; // index in the shadowbuffer array
float shadow = .3+.7*(shadowbuffer[idx]<sb_p[2]); // magic coeff to avoid z-fighting
Vec2f uv = varying_uv*bar; // interpolate uv for the current pixel
Vec3f n = proj<3>(uniform_MIT*embed<4>(model->normal(uv))).normalize(); // normal
Vec3f l = proj<3>(uniform_M *embed<4>(light_dir )).normalize(); // light vector
Vec3f r = (n*(n*l*2.f) - l).normalize(); // reflected light
float spec = pow(std::max(r.z, 0.0f), model->specular(uv));
float diff = std::max(0.f, n*l);
TGAColor c = model->diffuse(uv);
for (int i=0; i<3; i++) color[i] = std::min<float>(20 + c[i]*shadow*(1.2*diff + .6*spec), 255);
return false;
}
};
重点看
// 计算 shadow 用来填充颜色
// 这里的比例都是自己设置的。。。
float shadow = .3+.7*(shadowbuffer[idx]<sb_p[2]);
以及
// 计算最后的颜色
color[i] = std::min<float>(20 + c[i]*shadow*(1.2*diff + .6*spec), 255);
结果
学了这么长时间,是时候自己实现一个渲染器了。