笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
CSDN视频网址:http://edu.csdn.net/lecturer/144
图形学渲染与算法是紧密相关的,算法对于程序员来说非常重要,掌握算法编程就掌握了编程的精髓,阴影算法与光源是紧密相关的,阴影算法本身是基于从光的角度呈现为阴影图的想法。聚光灯光源的照射方式与相机的视锥是很类似的,聚光源具有发射位置和照射的方向矢量,当我们远离光源时,光线覆盖的区域会增长,效果如下图所示:
通过上图可以看出,聚光灯的照射区域像平截头体一样,使得更容易实现阴影映射,因为我们可以使用相同的透视投影矩阵作为照相机,以便渲染到阴影贴图中。用点光源灯实现阴影映射是一个挑战,但是我们能够通过渲染成立方体图来解决它。 然而,投影仍然是透视的。
现在让我们来思考平行光。 平行光具有方向但不是位置。 它通常用于模仿太阳的光照,由于其大小和距离跟平行光线类似:
在这种情况下,我们不能再使用透视投影, 使用正交投影。 本身的想法是将所有光线聚光在一个点(相机)中,光线保持平行,因此不会产生3D效果。
在下图中,我们使用左侧的透视投影和右侧的正投影像,效果如下图所示:
左边的图看起来比较真实,正如你所期望的那样,并提供深度层次感。右边的图看起来不真实,它没有前后之分。我们知道他们的尺寸是一样的,但是当看着一张照片时,我们期望前面的一个看起来更大。正交投影怎样利用平行光工作?我们知道透视投影是有一个视锥体,并将其映射到标准化的立方体(立方体范围是从[-1,-1,-1]到[1,1,1])。在映射之后,XY坐标用于查找纹理中的位置(在我们的例子中是阴影图),Z表示的是深度值。 正交投影采用一个长方体框,并将其映射到归一化的立方体(l,r,b,t,n,f分别代表左,右,底,顶,近,远):
现在想想平行光的光线,好像它们是从盒子的前面发出的,并且彼此平行,直到它们撞到背面。如果我们在通用框和标准化框之间进行映射(请记住 - 我们称之为NDC空间),阴影映射的其余部分将保持不变。
我们来看看这个映射是如何完成的。 我们沿着XYZ轴有三个范围,我们需要映射到(-1,1),这是一个简单的线性映射,没有除以零(因为它是正交的,而不是透视),那么将范围(a,b)映射到(c,d)的方程的一般形式是:
其中a<=X<=b,我们做x轴上的映射,将上述等式中的范围(l,r)映射到(-1,1),我们得到:
遵循同样的逻辑,我们做Y轴的映射,从(b,t) 映射到 (-1,1):
同样情况,我们将Z轴从(n,f) 映射到(-1,1):
现在我们有三个映射方程让我们创建一个矩阵来很好地将它们包装在一起:
当使用平行灯光进行阴影映射时,您需要注意如何定义正投影像的尺寸。视野范围定义了相机的宽度以及由于我们越来越远离观察者(与眼睛的功能相同),我们的平截头体的所能包含的物体也越来越多。我们还需要定义一个远近裁剪面,通过距离来控制裁剪,在许多情况下,相同的视距范围值,近距离和远距离的平面将正常工作。 但是在正投影的情况下,我们有一个盒子而不是一个平截头体,如果我们不小心,我们可能会“错过”物体,让我们看一个例子。 在左下方的场景为-10,右上方为10,近平面为-10,远方为100:
问题是物体被放置在摄像机距离30处,由于投影边界宽度不够从而不能捕获所有物体。 现在让我们将左/右/底部/顶部乘以10(近/远平面不变):
现在所有的物体都有一个阴影,但是我们又有新的问题,阴影看起来好像只有一个对象有一个阴影, 这个问题称为透视混叠,原因是视图空间中的许多像素(从相机的角度渲染时)被映射到阴影贴图中的相同像素。这使得阴影看起来有很多块状物体组成, 当我们增加正交盒子的尺寸时,影子图像保持不变,但相机渲染到世界的更多部分。 透视混叠可以通过增加阴影贴图的大小来减轻,但是要掌握一个度,因为考虑到对内存占用的影响。 在后面的博客中,我们将探讨先进的技术来处理这个问题。
阴影映射与平行光和点光源之间的主要区别是正交与透视投影的区别。 这就是为什么我只是要用平行灯来检查阴影所需的变化, 如果你有一个运行版本的聚光灯阴影,你只需要做一些微小的变化,使平行光的阴影工作。下面把博客中涉及到的代码给读者展示如下:
void Matrix4f::InitOrthoProjTransform(const OrthoProjInfo& p)
{
float l = p.l;
float r = p.r;
float b = p.b;
float t = p.t;
float n = p.n;
float f = p.f;
m[0][0] = 2.0f/(r - l); m[0][1] = 0.0f; m[0][2] = 0.0f; m[0][3] = -(r + l)/(r - l);
m[1][0] = 0.0f; m[1][1] = 2.0f/(t - b); m[1][2] = 0.0f; m[1][3] = -(t + b)/(t - b);
m[2][0] = 0.0f; m[2][1] = 0.0f; m[2][2] = 2.0f/(f - n); m[2][3] = -(f + n)/(f - n);
m[3][0] = 0.0f; m[3][1] = 0.0f; m[3][2] = 0.0f; m[3][3] = 1.0;
}
该函数实现的功能是初始化正交投影矩阵。void ShadowMapPass()
{
m_shadowMapFBO.BindForWriting();
glClear(GL_DEPTH_BUFFER_BIT);
m_ShadowMapEffect.Enable();
Pipeline p;
p.SetCamera(Vector3f(0.0f, 0.0f, 0.0f), m_dirLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
p.SetOrthographicProj(m_shadowOrthoProjInfo);
for (int i = 0; i < NUM_MESHES ; i++) {
p.Orient(m_meshOrientation[i]);
m_ShadowMapEffect.SetWVP(p.GetWVOrthoPTrans());
m_mesh.Render();
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void RenderPass()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_LightingTech.Enable();
m_LightingTech.SetEyeWorldPos(m_pGameCamera->GetPos());
m_shadowMapFBO.BindForReading(SHADOW_TEXTURE_UNIT);
Pipeline p;
p.SetOrthographicProj(m_shadowOrthoProjInfo);
p.Orient(m_quad.GetOrientation());
p.SetCamera(Vector3f(0.0f, 0.0f, 0.0f), m_dirLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
m_LightingTech.SetLightWVP(p.GetWVOrthoPTrans());
p.SetPerspectiveProj(m_persProjInfo);
p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
m_LightingTech.SetWVP(p.GetWVPTrans());
m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
m_pGroundTex->Bind(COLOR_TEXTURE_UNIT);
m_quad.Render();
for (int i = 0; i < NUM_MESHES ; i++) {
p.Orient(m_meshOrientation[i]);
m_LightingTech.SetWVP(p.GetWVPTrans());
m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
m_mesh.Render();
}
}
以上两个函数实现的是完整的阴影和渲染通道,它们与点光源几乎相同,所以我们不必完全检查它们, 在这里必须注意的几点差异。 首先,我添加了一个名为m_shadowOrthoProjInfo的成员,以便将正交投影变量与用于呈现的现有透视投影变量分开。 m_shadowOrthoProjInfo用于为光点配置WVP,并分别为左,右,底,顶,近,远的值初始化为-100,+ 100,-100,+ 100,-10,+ 100 。第二个变化是当我们配置相机的光源WVP矩阵,我们使用原点作为光的位置。 由于平行光仅具有方向且无位置,所以我们不关心视图矩阵中的该变量。 我们只需旋转世界,使光线指向正Z轴。
下面给读者展示的是编程需要的Shader脚本,主要分为四个脚本:
light.vs
#version 330
layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
uniform mat4 gWVP;
uniform mat4 gLightWVP;
uniform mat4 gWorld;
out vec4 LightSpacePos;
out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;
void main()
{
gl_Position = gWVP * vec4(Position, 1.0);
LightSpacePos = gLightWVP * vec4(Position, 1.0);
TexCoord0 = TexCoord;
Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}
light.fs#version 330
const int MAX_POINT_LIGHTS = 2;
const int MAX_SPOT_LIGHTS = 2;
in vec4 LightSpacePos;
in vec2 TexCoord0;
in vec3 Normal0;
in vec3 WorldPos0;
out vec4 FragColor;
struct BaseLight
{
vec3 Color;
float AmbientIntensity;
float DiffuseIntensity;
};
struct DirectionalLight
{
BaseLight Base;
vec3 Direction;
};
struct Attenuation
{
float Constant;
float Linear;
float Exp;
};
struct PointLight
{
BaseLight Base;
vec3 Position;
Attenuation Atten;
};
struct SpotLight
{
PointLight Base;
vec3 Direction;
float Cutoff;
};
uniform int gNumPointLights;
uniform int gNumSpotLights;
uniform DirectionalLight gDirectionalLight;
uniform PointLight gPointLights[MAX_POINT_LIGHTS];
uniform SpotLight gSpotLights[MAX_SPOT_LIGHTS];
uniform sampler2D gSampler;
uniform sampler2D gShadowMap;
uniform vec3 gEyeWorldPos;
uniform float gMatSpecularIntensity;
uniform float gSpecularPower;
float CalcShadowFactor(vec4 LightSpacePos)
{
vec3 ProjCoords = LightSpacePos.xyz / LightSpacePos.w;
vec2 UVCoords;
UVCoords.x = 0.5 * ProjCoords.x + 0.5;
UVCoords.y = 0.5 * ProjCoords.y + 0.5;
float z = 0.5 * ProjCoords.z + 0.5;
float Depth = texture(gShadowMap, UVCoords).x;
if (Depth < z + 0.00001)
return 0.5;
else
return 1.0;
}
vec4 CalcLightInternal(BaseLight Light, vec3 LightDirection, vec3 Normal,
float ShadowFactor)
{
vec4 AmbientColor = vec4(Light.Color * Light.AmbientIntensity, 1.0f);
float DiffuseFactor = dot(Normal, -LightDirection);
vec4 DiffuseColor = vec4(0, 0, 0, 0);
vec4 SpecularColor = vec4(0, 0, 0, 0);
if (DiffuseFactor > 0) {
DiffuseColor = vec4(Light.Color * Light.DiffuseIntensity * DiffuseFactor, 1.0f);
vec3 VertexToEye = normalize(gEyeWorldPos - WorldPos0);
vec3 LightReflect = normalize(reflect(LightDirection, Normal));
float SpecularFactor = dot(VertexToEye, LightReflect);
if (SpecularFactor > 0) {
SpecularFactor = pow(SpecularFactor, gSpecularPower);
SpecularColor = vec4(Light.Color, 1.0f) * gMatSpecularIntensity * SpecularFactor;
}
}
return (AmbientColor + ShadowFactor * (DiffuseColor + SpecularColor));
}
vec4 CalcDirectionalLight(vec3 Normal, vec4 LightSpacePos)
{
float ShadowFactor = CalcShadowFactor(LightSpacePos);
return CalcLightInternal(gDirectionalLight.Base, gDirectionalLight.Direction, Normal, ShadowFactor);
}
vec4 CalcPointLight(PointLight l, vec3 Normal, vec4 LightSpacePos)
{
vec3 LightDirection = WorldPos0 - l.Position;
float Distance = length(LightDirection);
LightDirection = normalize(LightDirection);
float ShadowFactor = CalcShadowFactor(LightSpacePos);
vec4 Color = CalcLightInternal(l.Base, LightDirection, Normal, ShadowFactor);
float Attenuation = l.Atten.Constant +
l.Atten.Linear * Distance +
l.Atten.Exp * Distance * Distance;
return Color / Attenuation;
}
vec4 CalcSpotLight(SpotLight l, vec3 Normal, vec4 LightSpacePos)
{
vec3 LightToPixel = normalize(WorldPos0 - l.Base.Position);
float SpotFactor = dot(LightToPixel, l.Direction);
if (SpotFactor > l.Cutoff) {
vec4 Color = CalcPointLight(l.Base, Normal, LightSpacePos);
return Color * (1.0 - (1.0 - SpotFactor) * 1.0/(1.0 - l.Cutoff));
}
else {
return vec4(0,0,0,0);
}
}
void main()
{
vec3 Normal = normalize(Normal0);
vec4 TotalLight = CalcDirectionalLight(Normal, LightSpacePos);
for (int i = 0 ; i < gNumPointLights ; i++) {
TotalLight += CalcPointLight(gPointLights[i], Normal, LightSpacePos);
}
for (int i = 0 ; i < gNumSpotLights ; i++) {
TotalLight += CalcSpotLight(gSpotLights[i], Normal, LightSpacePos);
}
vec4 SampledColor = texture2D(gSampler, TexCoord0.xy);
FragColor = SampledColor * TotalLight;
}
shadow_map.vs#version 330
layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
uniform mat4 gWVP;
void main()
{
gl_Position = gWVP * vec4(Position, 1.0);
}
shadow_map.fs
#version 330
in vec3 WorldPos;
uniform vec3 gLightWorldPos;
out float FragColor;
void main()
{
vec3 LightToVertex = WorldPos - gLightWorldPos;
float LightToPixelDistance = length(LightToVertex);
FragColor = LightToPixelDistance;
}
来源:CSDN
作者:海洋_
链接:https://blog.csdn.net/jxw167/article/details/64921028