适配视锥体的最小阴影地图实现异常:相机旋转致阴影变化排查
视锥体适配阴影地图的实现问题分析
问题背景
我计划实现一款适配视锥体的阴影地图,具体方案如下:
- 计算相机空间下的视锥体坐标;
- 将其转换至世界空间;
- 利用视锥体中心与灯光方向计算临时灯光矩阵,转换至灯光空间;
- 在灯光空间中确定8个顶点的最大最小值,定义灯光位置与阴影地图尺寸;
- 利用灯光位置计算视图矩阵。
但实现后发现阴影地图会随相机旋转发生变化,推测是灯光位置与方向存在问题,请问代码是否存在问题?
实现代码
SimpleMath::Matrix invViewMatrix (SimpleMath::Matrix::CreateLookAt(camera.GetTransform().GetPosition(), camera.GetTransform().GetPosition() + camera.GetTransform().GetForward(), SimpleMath::Vector3::Up).Invert()); float farZ = 800.0f; float nearHeight = cameraParam.nearZ * tanf(cameraParam.fov * 0.5f); float farHeight = farZ * tanf(cameraParam.fov * 0.5f); float nearWidth = nearHeight * cameraParam.aspect; float farWidth = farHeight * cameraParam.aspect; //view frustum position in Camera Space mFrustums[0] = SimpleMath::Vector4(-nearWidth, nearHeight, cameraParam.nearZ, 1.0f); mFrustums[1] = SimpleMath::Vector4(nearWidth, nearHeight, cameraParam.nearZ, 1.0f); mFrustums[2] = SimpleMath::Vector4(-nearWidth, -nearHeight, cameraParam.nearZ, 1.0f); mFrustums[3] = SimpleMath::Vector4(nearWidth, -nearHeight, cameraParam.nearZ, 1.0f); mFrustums[4] = SimpleMath::Vector4(-farWidth, farHeight, farZ, 1.0f); mFrustums[5] = SimpleMath::Vector4(farWidth, farHeight, farZ, 1.0f); mFrustums[6] = SimpleMath::Vector4(-farWidth, -farHeight, farZ, 1.0f); mFrustums[7] = SimpleMath::Vector4(farWidth, -farHeight, farZ, 1.0f); //translation to World Space for (SimpleMath::Vector4& point : mFrustums) { point = SimpleMath::Vector4::Transform(point, invViewMatrix); } SimpleMath::Vector4 averageFrustum = (mFrustums[0] + mFrustums[1] + mFrustums[2] + mFrustums[3] + mFrustums[4] + mFrustums[5] + mFrustums[6] + mFrustums[7]) / 8.0f; //Light Space Matrix SimpleMath::Matrix view = SimpleMath::Matrix::CreateLookAt( SimpleMath::Vector3(averageFrustum.x, averageFrustum.y, averageFrustum.z) + 50.0f * SimpleMath::Vector3(1.0f,1.0f,1.0f), SimpleMath::Vector3(averageFrustum.x, averageFrustum.y, averageFrustum.z), SimpleMath::Vector3(0.0f, 1.0f, 0.0f) ); //translation to Light Space for (SimpleMath::Vector4& point : mFrustums) { point = SimpleMath::Vector4::Transform(point, view); } SimpleMath::Vector4 minPoint = mFrustums[0]; SimpleMath::Vector4 maxPoint = mFrustums[0]; //calculation LightPos to render Shadow map for (const SimpleMath::Vector4& point : mFrustums) { minPoint.x = (std::min)(minPoint.x, point.x); minPoint.y = (std::min)(minPoint.y, point.y); minPoint.z = (std::min)(minPoint.z, point.z); maxPoint.x = (std::max)(maxPoint.x, point.x); maxPoint.y = (std::max)(maxPoint.y, point.y); maxPoint.z = (std::max)(maxPoint.z, point.z); } SimpleMath::Vector4 lightPos((maxPoint.x + minPoint.x) / 2.0f, (maxPoint.y + minPoint.y) / 2.0f, maxPoint.z, 1.0f); lightPos = SimpleMath::Vector4::Transform(lightPos, view.Invert()); mShadowMapWidth = (std::max)(maxPoint.x - minPoint.x, maxPoint.y - minPoint.y); mLightInfo.Position = SimpleMath::Vector3(lightPos.x, lightPos.y, lightPos.z); SimpleMath::Vector4 center((maxPoint.x + minPoint.x) / 2.0f, (maxPoint.y + minPoint.y) / 2.0f, (maxPoint.z + minPoint.z) / 2.0f, 1.0f); center = SimpleMath::Vector4::Transform(center, view.Invert()); mLightInfo.Position = center + 50.0f * SimpleMath::Vector3(1.0f, 1.0f, 1.0f); view = SimpleMath::Matrix::CreateLookAt( mLightInfo.Position, SimpleMath::Vector3(center.x, center.y, center.z), SimpleMath::Vector3(0.0f, 1.0f, 0.0f) ); SimpleMath::Matrix proj = SimpleMath::Matrix::CreateOrthographic(mShadowMapWidth, mShadowMapWidth, 1.0f, 800.0f);
代码问题分析
你的代码存在几个关键问题,直接导致阴影随相机旋转变化:
灯光方向绑定相机视锥体
初始创建临时灯光视图矩阵时,以视锥体中心作为观察目标,而视锥体中心会随相机旋转改变位置,这直接让灯光的观察方向绑定到相机视角,自然会随相机旋转变化。正确做法是使用固定的世界空间灯光方向(比如场景预设的平行光方向),而非依赖相机视锥体位置。灯光位置的重复覆盖与依赖相机
你先计算lightPos赋值给mLightInfo.Position,随后又用临时灯光空间下的视锥体中心转换回世界空间后加固定偏移,重新赋值灯光位置。但临时灯光空间本身依赖相机视锥体,最终灯光位置仍与相机绑定。正交投影裁剪面固定不合理
直接使用CreateOrthographic(..., 1.0f, 800.0f)固定近远裁剪面,应该根据灯光空间下视锥体的minPoint.z和maxPoint.z动态设置,确保视锥体完全被包含,同时避免空间浪费。灯光视图矩阵up向量固定存在风险
固定使用Vector3(0,1,0)作为up向量,若灯光方向接近垂直上下,会导致视图矩阵异常。应通过灯光方向叉乘辅助向量计算正确up向量,例如:SimpleMath::Vector3 lightDir = SimpleMath::Vector3::Normalize(SimpleMath::Vector3(center.x, center.y, center.z) - mLightInfo.Position); SimpleMath::Vector3 right = SimpleMath::Vector3::Normalize(SimpleMath::Vector3::Cross(lightDir, SimpleMath::Vector3(0,1,0))); SimpleMath::Vector3 up = SimpleMath::Vector3::Cross(right, lightDir);
修复建议
- 固定灯光方向:预设平行光的世界空间方向(如
SimpleMath::Vector3(-1,-1,-1)),脱离相机依赖; - 重新计算灯光视图矩阵:基于固定灯光方向转换视锥体顶点到灯光空间,计算包围盒后调整灯光位置,让视锥体刚好被正交投影包含;
- 动态设置正交投影裁剪面:用灯光空间下的
minPoint.z和maxPoint.z作为近远裁剪值,注意DirectX的Z轴方向可能需要调整符号。
内容的提问来源于stack exchange,提问作者nogongchung
相关产品推荐
相关产品推荐

