OpenGL - 把阴影立方贴图投射到场景上

3

我已经成功地从我的光源视角渲染了场景到深度立方图中,但我不太明白如何将其投影到我的场景上。

这里是当前情况的短片:http://youtu.be/54WXDWxqmXw

我在这里找到了一个实现示例:

http://www.opengl.org/discussion_boards/showthread.php/174093-GLSL-cube-shadows-projecting?p=1219162&viewfull=1#post1219162

这似乎很容易理解,因此我认为这是一个很好的入门方式,但我在矩阵方面遇到了一些困难(如上面的视频所示)。

我的顶点着色器:

#version 330 core
layout(std140) uniform ViewProjection
{
    mat4 V;
    mat4 P;
};

layout(location = 0) in vec3 vertexPosition;
layout(location = 1) in vec2 vertexUV;

out vec2 UV;
out vec4 posCs;

uniform mat4 M;
uniform mat4 lightView;

void main()
{
    mat4 MVP = P *V *M;
    gl_Position = MVP *vec4(vertexPosition,1);

    UV = vertexUV;
    posCs = V *M *vec4(vertexPosition,1);
}

片段着色器:
#version 330 core

in vec2 UV;
in vec4 posCs;

out vec4 color;

// Diffuse texture
uniform sampler2D renderTexture;

uniform samplerCubeShadow shadowCubeMap;

uniform mat4 lightView;
uniform mat4 lightProjection;
uniform mat4 camViewInv;

void main()
{
    color = texture2D(renderTexture,UV).rgba;

    mat4 lView = mat4(1); // The light is currently at the world origin, so we'll skip the transformation for now (The less potential error sources the better)
    vec4 posLs = lView *camViewInv *posCs;
    vec4 posAbs = abs(posLs);
    float fs_z = -max(posAbs.x,max(posAbs.y,posAbs.z));
    vec4 clip = lightProjection *vec4(0.0,0.0,fs_z,1.0);
    float depth = (clip.z /clip.w) *0.5 +0.5;
    vec4 r = shadowCube(shadowCubeMap,vec4(posLs.xyz,depth));
    color *= r;
}

lightProjection是我用来将场景渲染到立方体贴图中的投影矩阵。

关于'camViewInv',我不是完全确定,但从我上面链接的示例中得出了以下结论:

glm::mat4 camViewInv(
    camView[0][0],camView[1][0],camView[2][0],0.0f,
    camView[0][1],camView[1][1],camView[2][1],0.0f,
    camView[0][2],camView[1][2],camView[2][2],0.0f,
    camPos[0],camPos[1],camPos[2],1.0f
);

camView是相机的视图矩阵,camPos是相机的世界空间位置。

我认为其他所有内容都应该是不言自明的。

我看不出着色器有什么问题,但我相当确定场景被正确地渲染到了立方体贴图中(如上面的视频所示)。也许比我更熟练的人可以发现问题。

// 更新:

关于创建/使用阴影立方体贴图的一些额外信息:

创建立方体贴图纹理:

unsigned int frameBuffer;
glGenFramebuffers(1,&frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER,frameBuffer);

unsigned int texture;
glGenTextures(1,&texture);
glBindTexture(GL_TEXTURE_CUBE_MAP,texture);

glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_COMPARE_FUNC,GL_LEQUAL);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_R,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_COMPARE_MODE,GL_COMPARE_R_TO_TEXTURE);

for(int i=0;i<6;i++)
{
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X +i,0,GL_DEPTH_COMPONENT,size,size,0,GL_DEPTH_COMPONENT,GL_FLOAT,0);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_CUBE_MAP_POSITIVE_X +i,texture,0);
    glDrawBuffer(GL_NONE);
}

灯光矩阵:

glm::perspective<float>(90.f,1.f,2.f,m_distance); // Projection Matrix

// View Matrices
glm::vec3 pos = GetPosition(); // Light worldspace position
glm::lookAt(pos,pos +glm::vec3(1,0,0),glm::vec3(0,1,0));
glm::lookAt(pos,pos +glm::vec3(-1,0,0),glm::vec3(0,1,0));
glm::lookAt(pos,pos +glm::vec3(0,1,0),glm::vec3(0,0,-1))
glm::lookAt(pos,pos +glm::vec3(0,-1,0),glm::vec3(0,0,1))
glm::lookAt(pos,pos +glm::vec3(0,0,1),glm::vec3(0,1,0))
glm::lookAt(pos,pos +glm::vec3(0,0,-1),glm::vec3(0,1,0))

顶点着色器:

#version 330 core

layout(location = 0) in vec4 vertexPosition;

uniform mat4 shadowMVP;

void main()
{
    gl_Position = shadowMVP *vertexPosition;
}

片段着色器:
#version 330 core

layout(location = 0) out float fragmentDepth;

void main()
{
    fragmentdepth = gl_FragCoord.z;
}
1个回答

4
我建议在世界空间中执行此操作,光源位置通常定义在世界空间中,如果您保持这种方式,将减少工作量。如果您在世界空间中执行此操作,可以删除一些不需要的 uniform 变量。

在顶点着色器中计算光照方向和深度:

#version 330 core
layout(std140) uniform ViewProjection
{
    mat4 V;
    mat4 P;
};

layout(location = 0) in vec4 vertexPosition; // W is automatically assigned 1, if missing.
layout(location = 1) in vec2 vertexUV;

out vec2 UV;
out vec4 lightDirDepth; // Direction = xyz, Depth = w

uniform mat4 M;

uniform vec3 lightPos;     // World Space Light Pos
uniform vec2 shadowZRange; // Near / Far clip plane distances for shadow's camera

float vecToDepth (vec3 Vec)
{
  vec3  AbsVec     = abs (Vec);
  float LocalZcomp = max (AbsVec.x, max (AbsVec.y, AbsVec.z));

  const float n = shadowZRange [0]; // Near plane when the shadow map was built
  const float f = shadowZRange [1]; // Far plane when the shadow map was built

  float NormZComp = (f+n) / (f-n) - (2.0*f*n)/(f-n)/LocalZcomp;
  return (NormZComp + 1.0) * 0.5;
}

void main()
{
    mat4 MVP = P *V *M;
    gl_Position = MVP *vertexPosition;

    UV    = vertexUV;

    vec3  lightDir   = lightPos - (M *vertexPosition).xyz;
    float lightDepth = vecToDepth (lightDir);

    lightDirDepth = vec4 (lightDir, lightDepth);
}

修改后的片段着色器(使用光线方向的样本立方体贴图,并根据深度进行测试):

#version 330 core

in vec2 UV;
in vec4 lightDirDepth;  // Direction = xyz, Depth = w

out vec4 color;

// Diffuse texture
uniform sampler2D         renderTexture;
uniform samplerCubeShadow shadowCubeMap;

void main()
{
    const float bias = 0.0001; // Prevent shadow acne

    color   = texture (renderTexture,UV).rgba;   
    float r = texture (shadowCubeMap, vec4 (lightDirDepth.xyz, lightDirDepth.w + bias));

    color *= r;
}

我添加了两个新的uniform:

  1. lightPos -- 你的光源在世界坐标系中的位置
  2. shadowZRange -- 你构建阴影立方体贴图时,近平面和远平面的值打包成一个vec2

如果需要解释或者这些内容没有意义,请告诉我。


谢谢,那个解决了问题。lightDir的z分量被反转了,但是包括光源投影矩阵修复了这个问题:vec3 lightDir = lightPos - (lightProjection * M * vertexPosition).xyz; 然而,它总是只朝一个方向投射(无论上面的更改如何):http://youtu.be/a8tQn2pvB_w - Silverlan
那是用我写的着色器吗?如果在世界空间中进行这个操作,你就不需要使用投影矩阵,这就是重新编写它的目的。你确定光源位置是在世界空间中吗? - Andon M. Coleman
1
我认为您需要展示用于构建立方体贴图的代码,以便诊断此问题。我从未像这样实时展开阴影立方体贴图以查看其应该是什么样子,但它看起来不正确。当企鹅在侧面产生深度时,它应该被旋转。您能否关闭面部裁剪并验证当企鹅位于光源原点时,它在所有6个面上都产生深度? - Andon M. Coleman
1
你不小心编辑了我的答案。但是你编辑的代码有两个问题,一个是你将窗口空间 Z 值写入了颜色缓冲区(如果你使用深度附件,则无需此操作),另一个问题是你没有显示生成每个立方体贴图面的 modelview 矩阵的重要部分。这就是我在要求你展示如何生成立方体贴图时的意思。 - Andon M. Coleman
我立刻注意到你在立方体贴图中对于S和T(X和Y)方向使用了GL_CLAMP_TO_BORDER。默认的边框颜色是0.0(换句话说,它的行为就像深度缓冲区中有一个距离为0单位的东西),因此这可能解释了只有Z方向的光照起作用的异常行为。你应该对所有3个方向使用GL_CLAMP_TO_EDGE。我明天会仔细看一下其余部分。 - Andon M. Coleman
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接