首页 > 其他分享 >软光栅从零开始——shadow map

软光栅从零开始——shadow map

时间:2023-03-15 21:00:39浏览次数:42  
标签:map int 光源 vertex 阴影 Width 从零开始 shadow Model

前言

​ 上篇我们谈及phone光照、法线贴图与切线空间,实现了高效的光照效果。本篇我们将基于上篇的效果实现阴影贴图添加硬阴影

原理

​ 阴影贴图的思想是光源看不到而摄像机可以看到的地方,基于此思想我们将阴影贴图的步骤分为三步:

  1. 将光源看作一个摄像机进行渲染,将光源空间的深度值写入阴影映射纹理,记为\(z_{map}\),并记录当前的投影矩阵(点光源为透视投影,方向光为正交投影)
    image-20230315152342951
  2. 再以摄像机视角进行渲染,将屏幕空间的深度信息写入z-buffer
    image-20230315152429607
  3. 将摄像机视角的坐标乘以投影矩阵(将当前像素的屏幕空间深度转换到光源空间),得到\(z\)。再将\(z\)和\(z_{map}\)进行比较,若\(z < z_{map}\),那么认为当前片元处于阴影中
    image-20230315152504994

​ 下图展示了,使用shadow map 和不使用shadow map的区别
image-20230315152633752

软阴影和硬阴影

​ shadow map只能实现硬阴影效果,且只用使用点光源。下面展示了两种阴影的区别,上图为硬阴影,下图为软阴影
image-20230315153020963

硬阴影很好理解,但软阴影是如何形成的呢?因为光源是有体积的,这会导致有的地方完全看不到光源(称为本影, Umbra), 但有的地方能看到一部分光源(称为半影,Penumbra),所以阴影的边缘会产生过渡,从而产生软阴影。比如上图中的全日食与半日食

存在的问题

  1. 自遮挡

    原因:数值精度的限制。毕竟计算机中浮点数太难判断,这会造成z-fighting现象的出现

  2. 走样

    原因:因为阴影贴图分辨率是有限的,而每个像素占据一定大小,且离光源越远,每个像素覆盖的片元就越多

由于解决这些问题还需要涉及到其他算法,目前我们只是实现shadow map,待以后使用dx实现实时阴影

实现

第一次PASS:输出光源的zbuffer

  1. shader.h

    在shader头文件添加一个深度值,该值影响最终输出的光源zbuffer的效果(可以根据需要指定)

    const float depth = 500.f;
    
  2. main.cpp

    //用于输出光源的zbuffer
    struct DepthShader : public IShader
    {
        mat <3, 3, float> m_varyingTri;
    
        virtual Vec4f Vertex(int nFace, int nthVert)
        {
            Vec4f vertex = embed<4>(g_Model->vert(nFace, nthVert));
            vertex = g_Viewport * g_Projection * g_ModelView * vertex;
            m_varyingTri.set_col(nthVert, proj<3>(vertex / vertex[3]));
            return vertex;
        }
    
        virtual bool fragment(Vec3f weight, TGAColor& color)
        {
            Vec3f pInter = m_varyingTri * weight;
            color = TGAColor(255, 255, 255) * (pInter.z / depth);
            return false;
        }
    };
    
    float* g_ShadowBuffer = nullptr;
    g_ShadowBuffer = new float[g_Width * g_Height];
    for (int i = g_Width * g_Height; --i; )
        g_ZBuffer[i] = g_ShadowBuffer[i] = std::numeric_limits<float>::min();
    g_LightDir.normalize();
    
    //输出光源的zbuffer
    {
        TGAImage depth(g_Width, g_Height, TGAImage::RGB); 
    
        LookAt(g_Camera, g_Target, g_Up);
        Projection(0);
        ViewPort(g_Width / 8, g_Height / 8, g_Width * 3 / 4, g_Height * 3 / 4);
    
        for (int m = 1; m < argc; ++m)
        {
            g_Model = new Model(argv[m]);
            DepthShader shader;
            Vec4f screen_coords[3];
            for (int i = 0; i < g_Model->nfaces(); ++i)
            {
                for (int j : {0, 1, 2})
                {
                    screen_coords[j] = shader.Vertex(i, j);
                }
                Rasterization(screen_coords, shader, depth, g_ShadowBuffer);
            }
            delete g_Model;
        }
    
        depth.flip_vertically();
        depth.write_tga_file("depth.tga");
    }
    

输出:
image-20230315181131453

第二次PASS:输出结果

struct ShadowMapShader : public IShader
{
    mat <3, 3, float> m_varyingTri;	//coordinates before Viewport transform
    mat <2, 3, float> m_varyingUV;	//uv coordinates
    mat <4, 4, float> m_uniformM;	//Projection*ModelView
    mat <4, 4, float> m_uniformMIT;	//(Projection*ModelView).invert_transpose()
    mat <4, 4, float> m_uniformMShadow; // transform camera's viewport into light's

    ShadowMapShader(Matrix m, Matrix mIT, Matrix mS) : 
        m_uniformM(m), m_uniformMIT(mIT), m_uniformMShadow(mS), m_varyingTri(), m_varyingUV() {}

    virtual Vec4f Vertex(int nFace, int nthVert)
    {
        //从模型文件中获取uv,顶点坐标
        m_varyingUV.set_col(nthVert, g_Model->uv(nFace, nthVert));	
        auto vertex = embed<4>(g_Model->vert(nFace, nthVert));
        vertex = g_Viewport * g_Projection * g_ModelView * vertex;
        m_varyingTri.set_col(nthVert, proj<3>(vertex / vertex[3]));	
        return vertex;
    }

    virtual bool fragment(Vec3f weight, TGAColor& color)
    {
        //将当前片元变换至光源空间
        Vec4f shadBuffP = m_uniformMShadow * embed<4>(m_varyingTri * weight);	
        shadBuffP = shadBuffP / shadBuffP[3];
        
        //计算光源buffer的索引
        int index = static_cast<int>(shadBuffP[0]) + static_cast<int>(shadBuffP[1]) * g_Width;
        //std::cout << index << std::endl;
        double shadow = 0.3 + (g_ShadowBuffer[index] < shadBuffP[2]) * .7;	//0.3是一个offset,避免z-flighting

        auto uv = m_varyingUV * weight;	//uv插值
        auto n = proj<3>(m_uniformMIT * embed<4>(g_Model->normal(uv))).normalize();	//由模型空间变换至其他空间可能会导致法线不是等比变换,需要乘以逆转置矩阵
        auto l = proj<3>(m_uniformM * embed<4>(g_LightDir));	//将光源变换至透视投影空间
        auto r = (n * (n * l) * 2 - l).normalize();		//镜面反射的反射光
        float spec = std::pow(std::max(r.z, 0.f), g_Model->specular(uv));	//削弱镜面光的范围
        float diff = std::max(0.f, n * l);	//漫反射
        TGAColor c = g_Model->diffuse(uv);
        for (int i : {0, 1, 2})
            color[i] = std::min<float>(20 + c[i] * shadow * (1.2 * diff + 0.6 * spec), 255);	//阴影相当于是一个系数
        return false;
    }
};

//用于变换至光源空间
auto lightM = g_Viewport * g_Projection * g_ModelView;
    {
        TGAImage frameBuffer(g_Width, g_Height, TGAImage::RGB); // the output image
        //摄像机的变换
        LookAt(g_Camera, g_Target, g_Up);
        Projection(-1.f / (g_Camera - g_Target).norm());
        ViewPort(g_Width / 8, g_Height / 8, g_Width * 3 / 4, g_Height * 3 / 4);

        //vert f x/y/z x/y/z x/y/z : x = vertex coord, y = uv coor, z = normal coor
        //face : triangle face
        for (int m = 1; m < argc; ++m)
        {
            g_Model = new Model(argv[m]);
            ShadowMapShader shader(g_ModelView, g_Projection * g_ModelView.invert_transpose(), lightM * (g_Viewport * g_Projection * g_ModelView).invert());
            Vec4f screen_coords[3];
            for (int i = 0; i < g_Model->nfaces(); ++i)
            {
                for (int j : {0, 1, 2})
                {
                    screen_coords[j] = shader.Vertex(i, j);
                }
                Rasterization(screen_coords, shader, frameBuffer, g_ZBuffer);
            }
            delete g_Model;
        }

        //Image.flip_vertically();
        frameBuffer.flip_vertically();
        frameBuffer.write_tga_file("output.tga");
    }

输出:
image-20230315205024505

reference

https://github.com/ssloy/tinyrenderer/wiki/Lesson-7:-Shadow-mapping

https://games-cn.org/intro-graphics/

标签:map,int,光源,vertex,阴影,Width,从零开始,shadow,Model
From: https://www.cnblogs.com/chenglixue/p/17220000.html

相关文章

  • Java中List、Map常见实现类
    一、List1.ArrayList底层是数组实现,线程不安全publicclassArrayList<E>extendsAbstractList<E>implementsList<E>,RandomAccess,Cloneable,java.io.S......
  • HashMap、Hashtable、ConcurrentHashMap线程安全问题
    publicclassHashMapDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{//HashMap是线程不安全的//Hashtable是线......
  • Java的HashMap
    基于hash值的K-V结构数据容器。重要计算方法计算key的hash值(key==null)?0:(h=key.hashCode())^(h>>>16)利用hash计算tab中的位置p=tab[i=(n-1)&......
  • MIT 6.824-Lab1. MapReduce 实现思路
    参考资料MapReduce论文((20221029103443-4pqvf1n"Lecture-MapReduce"))Lab1实验文档Lab1实验文档译文任务需求在一个分布式存储系统上(实验是单机),实现coord......
  • ThreadLocalMap.key到期之'探测是清理'+'启发式清理'流程
    1.ThreadLocalMap.key到期的两种清理方式上文中:ThreadLocal内存泄露问题-lihewei-博客园(cnblogs.com)我们提到ThreadLocalMap的key会因为GC导致过期,在ThreadLoca......
  • JAVA8 lambda中map和flatMap
     lambda中map是对流元素进行转换,flatMap是对流中的元素(集合)进行平铺后合并,即对流中的每个元素平铺后又转换成为了Stream流。 flatMap首先将一个函数应用于元素,然后......
  • 面试官:怎么删除 HashMap 中的重复元素?第 3 种实现思路,99% 的人不会!
    背景大家好,我是栈长。前些天,栈长给大家分享了3篇实用的文章:带了一个3年的开发,不会循环删除List中的元素,我简直崩溃!!面试官:怎么去除List中的重复元素?我一行代码......
  • tryhackme_nmap
    https://www.cnblogs.com/-Lucky-/p/17100073.htmlNmap基本端口扫描nmap中考虑的端口状态Open:表示服务正在侦听指定端口。Closed:表示没有服务在指定端口上侦听,尽管该端......
  • B+Tree/Hash_Map/STL Map
    1、Hash操作能根据散列值直接定位数据的存储地址,设计良好的hash表能在常数级时间下找到需要的数据,但是更适合于内存中的查找。2、B+树是一种是一种树状的数据结构,适合做索......
  • Hashtable 和 HashMap 的区别
     Hashtable:(1)Hashtable是一个散列表,它存储的内容是键值对(key-value)映射。(2)Hashtable的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。(3)Has......