Shadow volume实现细节

本文深入探讨了OpenGL中Shadow Volume的实现,从寻找物体轮廓、生成阴影体到使用Z-fail算法判断片段是否在阴影中,详细阐述了每一步的核心思想和具体实现,适合初级OpenGL开发者学习。
摘要由CSDN通过智能技术生成

背景介绍

背景介绍:目前阴影算法较为流行的有两种,分别是shadow mappingshadow volume。前者实现起来相对简单,可以发挥现在GPU可编程流水线的能力,但是由于先天不足,shadow mapping在处理动态光源/物体的时候开销过大,经常作为一种静态场景中的廉价替代物。而 Shadow volume 的强项恰恰是 shadow mapping 的短处,像 DOOM3 这种大量运用动态光源,并且要对时刻都在运动中的物体投射阴影,shadow volume是现阶段唯一的选择。
但是这里并不介绍shadow mapping,网上有很多shadow mapping详细的教程,比如这里有详细教程,而且附带源码,如果感兴趣可以看看。
同样,网上也有很多shadow volume的教程,但是很多仅仅描述了核心的算法思想,具体实现细节并没有给出。对于初次接触OpenGL,或者接触不久的人来说,实现起来有点难度,所以我不仅仅描述核心的算法思想,而且还给出具体的实现细节。希望对于初学者有所帮助。

核心思想

我是根据网上这个教程Shadow Volume核心算法详解学习的,这个教程对于Shadow Volume的核心算法介绍的很详细。虽然很详细,但是为了加深我自己的理解,我决定也要写一下。
算法思想:就是光源照射到物体上,在物体的后面会形成一个阴影空间,如图所示

这里写图片描述

人物模型后面的蓝色空间,和机车后面的黑色空间,就是上面所说的阴影空间。然后再判断片段是否在这个阴影空间中,如果在这个阴影空间中,那么说明这个片段是有阴影的。
梳理一下,就是

  1. 生成阴影体
  2. 如何判断片段是否在阴影空间中(这部分是Shadow Volume的核心)。
  3. 如何将处于阴影体中的片段渲染成阴影。

第一个问题,如何生成阴影体

首先解决第一个问题如何生成阴影体?
如上图所示,光照射物体得到的阴影空间(阴影体),是沿着光照射的物体轮廓而产生的。建立的数学模型就是,物体轮廓的每一个点,沿着光线方向向下延伸到无限远处,然后轮廓所有点得到的延伸线组成一个封闭的空间,这就是所谓的阴影空间(阴影体)。
梳理一下,就是

  1. 寻找物体的轮廓
  2. 轮廓的点,沿着光线方向,向下无限延伸,并组合成一个封闭的空间

那么,下面咱们就解决这两个问题之一,生成一个阴影体。

1:寻找物体轮廓的方法。

要想得到物体的轮廓,我们必须要学习一个绘图方式GL_STRANGLE_ADJACENCY。这个绘图方式是OpenGL新增的。当使用GL_STRANGLE_ADJACENCY绘图的时候,传入着色器的数据不仅仅包括三角面片,还包括三角面片的邻接面。如下图所示。

这里写图片描述

中间实线的三角形是原三角面片,周围虚线的是三角面的邻接三角面。

那么这里存在一个问题。使用GL_STRANGLE方式绘图的时候,仅仅需要上图实线三角面的数据,而使用GL_STRANGLE_ADJACENCY方式绘图的时候,使用上图一个实线三角面和虚线三角面的数据。但是使用Assimp仅仅可以将obj数据导入成GL_STRANGLE格式的,那么如何将GL_STRANGLE换成GL_STRANGLE_ADJACENCY格式的呢?
那就需要将将要存入VAO中的数据扩大二倍,即原先3个顶点的,扩大到6个顶点,以便存放邻接信息,此外还需要更新索引,以便着色器每次可以将三角面片以及三角面片的邻接面片全部取出。
下面给出了实现方法:

myModel(const string& Filename, bool WithAdjacencies)
    {
        m_withAdjacencies = WithAdjacencies;

        Clear();

        glGenVertexArrays(1, &m_VAO);
        glBindVertexArray(m_VAO);

        // Create the buffers for the vertices attributes
        glGenBuffers(ARRAY_SIZE_IN_ELEMENTS(m_Buffers), m_Buffers);

        m_pScene = m_Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate |
            aiProcess_GenSmoothNormals |
            aiProcess_FlipUVs |
            aiProcess_JoinIdenticalVertices);
        if (m_pScene) {
            InitFromScene(m_pScene, Filename);
        }
        else {
            printf("Error parsing '%s': '%s'\n", Filename.c_str(), m_Importer.GetErrorString());
        }

        glBindVertexArray(0);
    }
bool InitFromScene(const aiScene* pScene, const string& Filename) 
    {
        m_Entries.resize(pScene->mNumMeshes);
        m_Textures.resize(pScene->mNumMaterials);

        vector<Vector3f> Positions;
        vector<Vector3f> Normals;
        vector<Vector2f> TexCoords;
        vector<uint> Indices;

        uint NumVertices = 0;
        uint NumIndices = 0;

        uint VerticesPerPrim = m_withAdjacencies ? 6 : 3;

        // Count the number of vertices and indices
        for (uint i = 0; i < m_Entries.size(); i++) {
            m_Entries[i].MaterialIndex = pScene->mMeshes[i]->mMaterialIndex;
            m_Entries[i].NumIndices = pScene->mMeshes[i]->mNumFaces * VerticesPerPrim;
            m_Entries[i].BaseVertex = NumVertices;
            m_Entries[i].BaseIndex = NumIndices;

            NumVertices += pScene->mMeshes[i]->mNumVertices;
            NumIndices += m_Entries[i].NumIndices;
        }

        // Reserve space in the vectors for the vertex attributes and indices
        Positions.reserve(NumVertices);
        Normals.reserve(NumVertices);
        TexCoords.reserve(NumVertices);
        Indices.reserve(NumIndices);


        // Initialize the meshes in the scene one by one
        for (uint i = 0; i < m_Entries.size(); i++) {
            const aiMesh* paiMesh = pScene->mMeshes[i];
            InitMesh(i, paiMesh, Positions, Normals, TexCoords, Indices);
        }


        // Generate and populate the buffers with vertex attributes and the indices
        glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[POS_VB]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(Positions[0]) * Positions.size(), &Positions[0], GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0,
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值