背景

我正在使用C++和现代OpenGL(3.3)进行3D游戏。我现在正在进行照明和阴影渲染,并且已经成功实现了定向阴影贴图。阅读完游戏要求后,我决定需要点光源阴影贴图。经过研究后,我发现要进行全向阴影贴图,我将执行类似于定向阴影贴图的操作,但是要使用立方体贴图。

我以前没有立方体贴图的知识,但是我对它们的理解是,立方体贴图是六个纹理,无缝地相连。
我环顾四周,但不幸的是,我很难找到关于现代OpenGL的权威“教程”。我首先寻找从头到尾对其进行解释的教程,因为我非常努力地从源代码片段或概念片段中学习,但是我尝试了。

当前的理解

这是我对该想法的一般理解,不包括技术知识。请纠正我。

  • 对于每个点光源,都设置了帧缓冲区,如定向阴影贴图
  • 然后生成一个多维数据集贴图纹理,并与glBindTexture(GL_TEXTURE_CUBE_MAP, shadowmap)绑定(bind)。
  • 多维数据集映射使用以下属性设置:
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    

  • (这也类似于定向阴影贴图)
  • 现在glTexImage2D()循环了六次,每张脸一次。我这样做是这样的:
     for (int face = 0; face < 6; face++) // Fill each face of the shadow cubemap
         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_DEPTH_COMPONENT32F , 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    
  • 通过调用将纹理附加到帧缓冲区
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowmap, 0);
    
  • 要渲染场景时,将分两遍渲染,如定向阴影贴图。
  • 首先,绑定(bind)阴影帧缓冲区,将视口(viewport)调整为阴影贴图的大小(在这种情况下为1024 x 1024)。
  • 使用glCullFace(GL_FRONT)剔除设置为正面
  • Activity 的着色器程序已切换到顶点和片段阴影着色器,我将提供进一步向下的源
  • 计算所有六个 View 的浅 View 矩阵。我通过创建glm::mat4的 vector 并用push_back()矩阵来做到这一点,如下所示:
    // Create the six view matrices for all six sides
    for (int i = 0; i < renderedObjects.size(); i++) // Iterate through all rendered objects
    {
        renderedObjects[i]->bindBuffers(); // Bind buffers for rendering with it
    
        glm::mat4 depthModelMatrix = renderedObjects[i]->getModelMatrix(); // Set up model matrix
    
        for (int i = 0; i < 6; i++) // Draw for each side of the light
        {
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, shadowmap, 0);
            glClear(GL_DEPTH_BUFFER_BIT); // Clear depth buffer
    
            // Send MVP for shadow map
            glm::mat4 depthMVP = depthProjectionMatrix * depthViewMatrices[i] * depthModelMatrix;
            glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "depthMVP"), 1, GL_FALSE, glm::value_ptr(depthMVP));
    
            glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "lightViewMatrix"), 1, GL_FALSE, glm::value_ptr(depthViewMatrices[i]));
            glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "lightProjectionMatrix"), 1, GL_FALSE, glm::value_ptr(depthProjectionMatrix));
            glDrawElements(renderedObjects[i]->getDrawType(), renderedObjects[i]->getElementSize(), GL_UNSIGNED_INT, 0);
        }
    }
    
  • 绑定(bind)了默认的帧缓冲区,并且正常绘制了场景。

  • 问题

    现在,到着色器。这是我的理解干dry的地方。我完全不确定该怎么做,我的研究似乎彼此冲突,因为它适用于不同的版本。我最终从随机来源中无味地复制和粘贴代码,希望它能实现除黑屏以外的其他功能。我知道这很糟糕,但是似乎没有明确的定义。我在什么空间工作?像在定向点照明中使用的那样,我是否还需要单独的阴影着色器?我到底该用什么作为阴影立方体贴图的类型? samplerCube? samplerCubeShadow?我如何正确采样所说的多维数据集映射?我希望有人可以帮我解决这个问题,并提供一个很好的解释。
    我目前对着色器部分的了解是:
    -将场景渲染到立方体贴图中时,顶点着色器仅采用我在C++代码中计算出的depthMVP统一并通过它们转换输入顶点。
    -立方体贴图传递的片段着色器仅将单输出值分配给gl_FragCoord.z。 (这一部分与我实现定向阴影贴图时没有什么变化。我假设它与立方体贴图是相同的,因为着色器甚至不与立方体贴图进行交互-OpenGL只是将它们的输出渲染到立方体贴图,对吗?因为它是帧缓冲区?)
  • 普通渲染的顶点着色器不变。
  • 在用于常规渲染的片段着色器中,顶点位置与光源的投影和 View 矩阵一起转换为光源的空间。
  • 在立方贴图纹理查找中以某种方式使用了它。 ???
  • 一旦使用魔术手段获得了深度,就将其与光线到顶点的距离进行比较,就像定向阴影贴图一样。如果更少,则必须遮盖该点,反之亦然。

  • 这不是什么了解。关于顶点如何转换以及如何用于查找立方体贴图,我一无所知,因此我将为着色器粘贴源代码,以希望人们能够阐明这一点。请注意,这段代码很多都是盲目复制和粘贴,我没有做任何改动以免损害任何理解。

    阴影顶点着色器:
    #version 150
    
    in vec3 position;
    
    uniform mat4 depthMVP;
    
    void main()
    {
        gl_Position = depthMVP * vec4(position, 1);
    }
    

    阴影片段着色器:
    #version 150
    
    out float fragmentDepth;
    
    void main()
    {
        fragmentDepth = gl_FragCoord.z;
    }
    

    标准顶点着色器:
    #version 150
    
    in vec3 position;
    in vec3 normal;
    in vec2 texcoord;
    
    uniform mat3 modelInverseTranspose;
    uniform mat4 modelMatrix;
    uniform mat4 viewMatrix;
    uniform mat4 projectionMatrix;
    
    out vec3 fragnormal;
    out vec3 fragnormaldirection;
    out vec2 fragtexcoord;
    out vec4 fragposition;
    out vec4 fragshadowcoord;
    
    void main()
    {
        fragposition = modelMatrix * vec4(position, 1.0);
        fragtexcoord = texcoord;
        fragnormaldirection = normalize(modelInverseTranspose * normal);
        fragnormal = normalize(normal);
        fragshadowcoord = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
    
    
        gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
    }
    

    标准片段着色器:
    #version 150
    
    out vec4 outColour;
    
    in vec3 fragnormaldirection;
    in vec2 fragtexcoord;
    in vec3 fragnormal;
    in vec4 fragposition;
    in vec4 fragshadowcoord;
    
    uniform mat4 modelMatrix;
    uniform mat4 viewMatrix;
    uniform mat4 projectionMatrix;
    uniform mat4 viewMatrixInversed;
    
    uniform mat4 lightViewMatrix;
    uniform mat4 lightProjectionMatrix;
    
    uniform sampler2D tex;
    uniform samplerCubeShadow shadowmap;
    
    float VectorToDepthValue(vec3 Vec)
    {
        vec3 AbsVec = abs(Vec);
        float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));
    
        const float f = 2048.0;
        const float n = 1.0;
        float NormZComp = (f+n) / (f-n) - (2*f*n)/(f-n)/LocalZcomp;
        return (NormZComp + 1.0) * 0.5;
    }
    
    float ComputeShadowFactor(samplerCubeShadow ShadowCubeMap, vec3 VertToLightWS)
    {
        float ShadowVec = texture(ShadowCubeMap, vec4(VertToLightWS, 1.0));
        if (ShadowVec + 0.0001 > VectorToDepthValue(VertToLightWS)) // To avoid self shadowing, I guess
            return 1.0;
    
        return 0.7;
    }
    
    void main()
    {
        vec3 light_position = vec3(0.0, 0.0, 0.0);
        vec3 VertToLightWS = light_position - fragposition.xyz;
        outColour = texture(tex, fragtexcoord) * ComputeShadowFactor(shadowmap, VertToLightWS);
    }
    

    我不记得ComputerShadowFactor和VectorToDepthValue函数代码的来源,因为我正在我的笔记本电脑上研究它,但现在还不行,但这是这些着色器的结果:

    这是一个由阴影空间包围的无阴影空间的小方块。

    由于缺乏相关知识,我在这里显然做错了很多事情,可能集中在我的着色器上,因为我很难从教程中学习任何东西,对此我感到非常抱歉。我很茫然,如果有人可以清楚地解释我在做什么,为什么做错,如何修复它甚至是一些代码,这可以很好地阐明这一点。我认为问题可能是因为我在错误的地方工作。

    最佳答案

    我希望为您的某些问题提供答案,但首先需要一些定义:

    什么是立方体贴图?

    它是从方向 vector 投影到一对[face,该面上的2d坐标]的映射,该映射是通过将方向 vector 投影在假设的立方体上而获得的。

    什么是OpenGL立方体贴图纹理?

    它是一组六个“图像”。

    什么是GLSL立方体贴图采样器?

    它是一个采样器原语,可以从中进行立方体贴图采样。这意味着它是使用方向 vector 而不是通常的纹理坐标进行采样的。然后,硬件将方向 vector 投影到一个假设的立方体上,并使用生成的[face,2d纹理坐标]对在正确的2d位置采样正确的“图像”。

    什么是GLSL阴影采样器?

    它是一个采样器原语,绑定(bind)到包含NDC空间深度值的纹理,并且在使用阴影特定的采样函数进行采样时,返回NDC空间深度(在阴影贴图的相同空间中,显然),NDC空间深度存储在有界纹理内。在调用采样函数时,将要比较的深度指定为纹理坐标中的附加元素。请注意,提供阴影采样器是为了易于使用和提高速度,但是始终可以在着色器中“手动”进行比较。

    现在,对于您的问题:

    OpenGL只是将渲染到立方体贴图,对吧?

    不,OpenGL渲染到当前绑定(bind)的帧缓冲区中的一组目标。

    对于立方体贴图,通常的渲染方法是:

  • 创建它们,并将它们的六个“图像”分别附加到同一图像上
    帧缓冲区(显然在不同的连接点)
  • 一次仅启用一个目标(因此,您分别渲染每个多维数据集的面)
  • 以在立方体贴图的面孔中呈现您想要的内容(可能使用特定于面孔的“ View ”和“投影”矩阵)

  • 点光源阴影贴图

    除了关于立方体贴图的所有内容外,使用立方体贴图实现点光源阴影贴图还有很多问题,因此很少使用硬件深度比较。

    相反,常见的做法如下:
  • 而不是写NDC空间深度,而是从
    点光源
  • 查询阴影贴图时的
  • (请参见底部的示例代码):
  • 不使用硬件深度比较(使用samplerCube而不是samplerCubeShadow)
  • 在“立方体空间”中转换要测试的点(根本不包括投影)
  • 使用“多维数据集空间” vector 作为查找方向以对多维数据集映射进行采样
  • 比较从立方体贴图采样的径向距离与测试点的径向距离

  • 示例代码
    // sample radial distance from the cubemap
    float radial_dist = texture(my_cubemap, cube_space_vector).x;
    
    // compare against test point radial distance
    bool shadowed = length(cube_space_vector) > radial_dist;
    

    09-30 00:09