阴影贴图(Shadow Mapping)

前言:这个教程快要结束了,花了不少时间,这篇博客就写写我所了解的阴影贴图吧!

目标

我们今天的目标就是学会如何从图一变化成图二

怎么生成阴影

生成阴影的必要条件是我们得知道哪个像素会被遮住

现在,我把我们眼睛移到我们定义的光照方向上。

// 把 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);

结果

最后实现渲染器的代码

学了这么长时间,是时候自己实现一个渲染器了。

点个喜欢呗
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容