阴影贴图(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);

结果

最后实现渲染器的代码

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

点个喜欢呗
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容