手搓一个小型渲染器(七)

Shadow Mapping

在之前我们为我们的渲染器设计了不同的Shader,现在我们需要考虑模型的阴影。**下面我们讨论的是硬阴影,软阴影的计算与下面的不同。**现在模型中凸起的部分我们可以通过这部分的法线向量的计算得到正确的阴影,但是对于非凸部分,则结果不尽人意。

在这里插入图片描述

可以看到这个模型的光源应该在我们的右侧,模型的左侧,但是在模型右肩处我们看不到阴影,这说明在凹陷的地方我们还没有正确的施加模型的阴影。

解决方法也非常简单,我们只需要做两次渲染即可,第一次,我们将相机放置在光源位置,zbuffer会告诉我们模型中哪些部分是可视的那些部分是会被遮挡的,之后我们根据得到的zbuffer进行渲染。

class DepthShader : public IShader {
private:
    Model* model;
    //设置一个3×3的矩阵,用于存放顶点的坐标(每一列是一个顶点)
    mat<3, 3, float> varying_tri;

public:
    DepthShader(Model* m): model(m), 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;
    }
};
#include <vector>
#include <string>
#include <iostream>
#include "tgaimage.h"
#include "model.h"
#include "gl.h"
#include "gl_shader.h"

Model* model = NULL;
const int width = 2000;
const int height = 2000;
float depth = 255.;

Vec3f light_dir = {1., 1., 1.};
Vec3f eye = { 0, -1, 3 };
Vec3f center{ 0, 0, 0 };
Vec3f up = { 0, 1, 0 };

Matrix ModelView{};
Matrix Viewport{};
Matrix Projection{};

int main() {
    model = new Model("./obj/diablo3_pose/diablo3_pose.obj");
    viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4);

    //depth mapping是将光源作为摄像机的起点,绘制zbuffer
    lookat(light_dir, center, up);
    projection(0);

    light_dir.normalize();

    TGAImage image(width, height, TGAImage::RGB);
    TGAImage Pimage(width, height, TGAImage::RGB);
    //把zbuffer也存成和image同样大小的灰度图
    TGAImage zbuffer(width, height, TGAImage::GRAYSCALE);
    DepthShader Dshader(model);

    //更换环境颜色
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            image.set(i, j, TGAColor(200, 200, 200, 100));
        }
    }

    for (int i = 0; i < model->nfaces(); ++i) {
        Vec4f screen_coords[3];
        for (int j = 0; j < 3; ++j){
            screen_coords[j] = Dshader.vertex(i, j);
        }
        triangle(screen_coords, Dshader, image, zbuffer);
        
    }

    image.write_tga_file("depth.tga");
    zbuffer.write_tga_file("zbuffer.tga");
    delete model;
    return 0;
}

结果如下:

在这里插入图片描述

之后我们要做的就是在真正的相机位置再做一次shader,代码如下:

class ShadowShader : public IShader {
private:
    Model* model;
    mat<4, 4, float> uniform_M;
    mat<4, 4, float> uniform_MIT;
    //下面存放的矩阵是将现在屏幕的中的点乘上逆矩阵,变为空间中的点,然后乘上之前光源时候都MVP矩阵
    mat<4, 4, float> uniform_Mshadow;
    //三角形各个顶点的uv值
    mat<2, 3, float> varying_uv;
    //三角形各个顶点的坐标值
    mat<3, 3, float> varying_tri;
    TGAImage shadowbuffer;

public:

    ShadowShader(Model* m, mat<4, 4, float> M, mat<4, 4, float> MIT, mat<4, 4, float> Mshadow, TGAImage& zbuffer):
        model(m), uniform_M(M), uniform_MIT(MIT), uniform_Mshadow(Mshadow), varying_uv(), varying_tri(), shadowbuffer(zbuffer) {}

    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]));
        varying_uv.set_col(nthvert, model->uv(iface, nthvert));
        return gl_vertex;
    }

    virtual bool fragment(Vec3f bar, TGAColor& color) {
        Vec4f sb_p = uniform_Mshadow * embed<4>(varying_tri * bar);
        sb_p = sb_p / sb_p[3];
        float shadow = .3 + .7 * (shadowbuffer.get(sb_p[0], sb_p[1])[0] < sb_p[2] + 43.4);
        
        Vec2f uv = varying_uv * bar;
        //法线向量
        Vec3f n = proj<3>(uniform_MIT * embed<4>(model->normal(uv))).normalize();
        //光线向量
        Vec3f l = proj<3>(uniform_M * embed<4>(light_dir)).normalize(); 
        //半程向量
        Vec3f r = (n * (n * l * 2.f) - l).normalize();   
        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;
    }
};

那么我们如何使用Shadowbuffer呢?我们可以获取在摄像机位置下模型的所有顶点的空间坐标,而在之前,我们在使用shadow mapping时,保留了那个位置下的MVP矩阵,因此我们可以对现在的点进行逆变换,之后再乘上之前的MVP矩阵,得到当前相机和在光源处相机屏幕之间的点的映射关系,这时shadowbuffer就有作用了,我们可以利用shadowbuffer确定这个点在相机屏幕中是否会发光,如果这个点是不可见的,那么我们将调低贴图上的亮度,以得到最终的正确阴影贴图。

#include <vector>
#include <string>
#include <iostream>
#include "tgaimage.h"
#include "model.h"
#include "gl.h"
#include "gl_shader.h"


Model* model = NULL;
const int width = 2000;
const int height = 2000;
float depth = 255.;


Vec3f light_dir = {5., 5., 5.};
Vec3f eye = { 0, -1, 3 };
Vec3f center{ 0, 0, 0 };
Vec3f up = { 0, 1, 0 };

Matrix ModelView{};
Matrix Viewport{};
Matrix Projection{};



int main() {
    //model = new Model("./obj/african_head/african_head.obj");
    model = new Model("./obj/diablo3_pose/diablo3_pose.obj");
    //把zbuffer也存成和image同样大小的灰度图
    TGAImage shadowbuffer(width, height, TGAImage::GRAYSCALE);
    //将相机放在光源位置,得到光源视角下的zbuffer
    {
        TGAImage image(width, height, TGAImage::RGB);

        //更换背景颜色
        for (int i = 0; i < height; ++i) {
            for (int j = 0; j < width; ++j) {
                image.set(i, j, TGAColor(200, 200, 200, 100));
            }
        }

        viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4);
        //depth mapping是将光源作为摄像机的起点,绘制zbuffer
        lookat(light_dir, center, up);
        projection(0);
        light_dir.normalize();

        DepthShader Dshader(model);

        for (int i = 0; i < model->nfaces(); ++i) {
            Vec4f screen_coords[3];
            for (int j = 0; j < 3; ++j) {
                screen_coords[j] = Dshader.vertex(i, j);
            }
            triangle(screen_coords, Dshader, image, shadowbuffer);

        }
        image.write_tga_file("depth.tga");
    }
    
    Matrix M = Viewport * Projection * ModelView;
    {
        TGAImage simage(width, height, TGAImage::RGB);
        TGAImage zbuffer(width, height, TGAImage::GRAYSCALE);
        //更换背景颜色
        for (int i = 0; i < height; ++i) {
            for (int j = 0; j < width; ++j) {
                simage.set(i, j, TGAColor(200, 200, 200, 100));
            }
        }

        lookat(eye, center, up);
        viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4);
        projection(1. / (eye - center).norm());

        mat<4, 4, float> uniform_M = Projection * ModelView;
        mat<4, 4, float> uniform_MIT = (Projection * ModelView).invert_transpose();

        ShadowShader Sshader(model, uniform_M, uniform_MIT, M * ((Viewport * Projection * ModelView).invert()), shadowbuffer);
    
        for (int i = 0; i < model->nfaces(); ++i) {
            Vec4f screen_coords[3];
            for (int j = 0; j < 3; ++j) {
                screen_coords[j] = Sshader.vertex(i, j);
            }
            triangle(screen_coords, Sshader, simage, zbuffer);

        }
        simage.write_tga_file("Shadow.tga");
    }
    
    delete model;
    return 0;
}

结果如下:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值