我目前遇到了来自移动(类似太阳)光源的定向光影贴图的问题。
当我最初实现时,光投影矩阵被计算为3D,阴影贴图看起来很漂亮。然后我了解到,对于我正在尝试做的事情,正交投影会更好,但我很难替换正确的投影矩阵。
正如人们所期望的那样,每一次滴答声,太阳都会沿着一个圆圈移动一定的量。我使用自制的"lookAt"方法来确定正确的查看矩阵。因此,例如,日光出现在早上6点到下午6点。当太阳位于9AM位置(45度)时,它应该查看原点并将阴影贴图渲染到帧缓冲区。正交投影的情况似乎是它没有向原点"向下倾斜"。它只是一直沿着Z轴看。早上6点和下午6点的情况看起来不错,但例如中午12点,情况完全没有变化。
以下是我的设置方式:
原始3D投影矩阵:
Matrix4f projectionMatrix = new Matrix4f();
float aspectRatio = (float) width / (float) height;
float y_scale = (float) (1 / cos(toRadians(fov / 2f)));
float x_scale = y_scale / aspectRatio;
float frustum_length = far_z - near_z;
projectionMatrix.m00 = x_scale;
projectionMatrix.m11 = y_scale;
projectionMatrix.m22 = (far_z + near_z) / (near_z - far_z);
projectionMatrix.m23 = -1;
projectionMatrix.m32 = -((2 * near_z * far_z) / frustum_length);
LookAt方法:
public Matrix4f lookAt( float x, float y, float z,
float center_x, float center_y, float center_z ) {
Vector3f forward = new Vector3f( center_x - x, center_y - y, center_z - z );
Vector3f up = new Vector3f( 0, 1, 0 );
if ( center_x == x && center_z == z && center_y != y ) {
up.y = 0;
up.z = 1;
}
Vector3f side = new Vector3f();
forward.normalise();
Vector3f.cross(forward, up, side );
side.normalise();
Vector3f.cross(side, forward, up);
up.normalise();
Matrix4f multMatrix = new Matrix4f();
multMatrix.m00 = side.x;
multMatrix.m10 = side.y;
multMatrix.m20 = side.z;
multMatrix.m01 = up.x;
multMatrix.m11 = up.y;
multMatrix.m21 = up.z;
multMatrix.m02 = -forward.x;
multMatrix.m12 = -forward.y;
multMatrix.m22 = -forward.z;
Matrix4f translation = new Matrix4f();
translation.m30 = -x;
translation.m31 = -y;
translation.m32 = -z;
Matrix4f result = new Matrix4f();
Matrix4f.mul( multMatrix, translation, result );
return result;
}
正交投影(使用宽度100,高度75,近1.0,远100)我尝试过很多不同的值:
Matrix4f projectionMatrix = new Matrix4f();
float r = width * 1.0f;
float l = -width;
float t = height * 1.0f;
float b = -height;
projectionMatrix.m00 = 2.0f / ( r - l );
projectionMatrix.m11 = 2.0f / ( t - b );
projectionMatrix.m22 = 2.0f / (far_z - near_z);
projectionMatrix.m30 = - ( r + l ) / ( r - l );
projectionMatrix.m31 = - ( t + b ) / ( t - b );
projectionMatrix.m32 = -(far_z + near_z) / (far_z - near_z);
projectionMatrix.m33 = 1;
阴影贴图顶点着色器:
#version 150 core
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
in vec4 in_Position;
out float pass_Position;
void main(void) {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * in_Position;
pass_Position = gl_Position.z;
}
阴影贴图碎片着色器:
#version 150 core
in vec4 pass_Color;
in float pass_Position;
layout(location=0) out float fragmentdepth;
out vec4 out_Color;
void main(void) {
fragmentdepth = gl_FragCoord.z;
}
我觉得我错过了一些非常简单的东西。正如我所说,这在3D投影矩阵中很好,但我希望用户在世界各地旅行时阴影保持不变,这对定向照明和正交投影都有意义。
实际上,谁告诉过你使用正交投影矩阵是阴影贴图的好主意?这可能适用于像太阳这样的物体,它们实际上是无限远的,但对于局部光线来说,透视是非常相关的。不过,你必须小心透视投影和阴影贴图,因为采样频率随距离而变化,在某些距离上你会得到很大的精度,而在其他距离上则不够,除非你通常使用级联或透视扭曲等方法;这可能比你现在应该考虑的更多:)
此外,正投影矩阵的3D效果与透视效果一样好,因为它们是通过将3D"图像"投影到2D观看平面上。。。它们和透视之间唯一的区别是平行线保持平行。换句话说,在正交投影中,(x,y,near)和(x,y,far)理想地投影到屏幕上的相同位置。
在片段着色器中使用gl_FragCoord.z
是不寻常的。由于这是写入深度缓冲区的值,您还可以在片段着色器中写入NOTHING并重复使用深度缓冲区。除非您的实现不支持浮点深度缓冲区,否则将深度写入两个位置会浪费内存带宽。在构造阴影贴图时,使用glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
的仅深度通道通常会获得更高的吞吐量。
如果你实际使用了pass_Position
的值(这是剪辑空间中的非透视校正Z坐标),我可以看到使用单独的颜色附件来写这篇文章,但你目前正在写透视校正深度范围调整深度(gl_FragDepth
)。
在任何情况下,当太阳直接在头顶上并且使用正交投影时,都不会投射阴影。这可以追溯到我前面解释的属性,其中平行线保持平行。由于对象与太阳的距离不会影响对象的投影位置(正交),因此如果它直接在头顶上,则不会看到任何阴影。试着沿着球体而不是圆形来跟踪太阳的位置,以最大限度地减少这种情况。