其他分享
首页 > 其他分享> > 级联阴影贴图(CSM)

级联阴影贴图(CSM)

作者:互联网

文章目录

级联阴影贴图(CSM)

摊牌

实现

  1. 首先对摄像机坐标系下的视锥体分割,分割为不同深度的分段
  2. 在光源的视角下,生成每个视锥体分段的“包围盒”,这个包围盒是生成阴影贴图时正交投影的重要参考在这里插入图片描述

摄像机空间中视锥体分段

float nd = camera->near;
float fd = camera->far;
float lambda = 0.75;
float ratio = fd / nd;
frustums[0].near(nd);
 
for(int i = 1 ; i < 分段个数 ; i++) 
{
	float si = i / (float)分段个数;
	float t_near = lambda * (nd * powf(ratio, si)) + (1 - lambda) * (nd + (fd - nd) * si);
	float far = near * 1.005f;//略微增加重合,避免断裂
	frustums[i].near(near);
	frustums[i-1].far(far);
}
frustums[分段个数-1].far(fd);

光源空间中正交投影

详细过程

  1. 先计算出摄像机视锥体分块在世界空间中的坐标
glm::vec3 viewPos = camera->Position;
glm::vec3 viewDir = camera->Front;
glm::vec3 up(0.0f, 1.0f, 0.0f);
glm::vec3 right = glm::cross(viewDir, up);
for(int i = 0 ; i < 分块个数 ; i++) 
{
   Frustum& frustum = m_frustums[i];
   glm::vec3 fc = viewPos + viewDir * frustum.far();
   glm::vec3 nc = viewPos + viewDir * frustum.near();
   right = glm::normalize(right);    
   up = glm::normalize(glm::cross(right, viewDir));
   // 计算当前分片的近平面和远平面宽高的一半
   float near_height = tan(frustum.fov() / 2.0f) * frustum.near();
   float near_width = near_height * frustum.ratio();
   float far_height = tan(frustum.fov() / 2.0f) * frustum.far();
   float far_width = far_height * frustum.ratio();
   //记录视锥8个顶点
   frustum.m_points[0] = nc - up * near_height - right * near_width;
   frustum.m_points[1] = nc + up * near_height - right * near_width;
   frustum.m_points[2] = nc + up * near_height + right * near_width;
   frustum.m_points[3] = nc - up * near_height + right * near_width;
   frustum.m_points[4] = fc - up * far_height - right * far_width;
   frustum.m_points[5] = fc + up * far_height - right * far_width;
   frustum.m_points[6] = fc + up * far_height + right * far_width;
   frustum.m_points[7] = fc - up * far_height + right * far_width;
}
  1. 利用分块的各顶点坐标,计算摄像机视锥体分段的“包围盒”,从而计算出分块对应的正交投影矩阵。注意:这个计算出的投影矩阵是光空间中的投影矩阵,是用于渲染阴影贴图时使用的
glm::mat4 lightProjMat;
for(int i = 0 ; i < 分块个数 ; i++) 
{
   //1. 找出光空间中八个顶点的最大最小z值
   Frustum& frustum = m_frustums[i];
   glm::vec3 max(-1000.0f, -1000.0f, 0.0f);
   glm::vec3 min(1000.0f, 1000.0f, 0.0f);
   glm::vec4 transf = lightViewMat * glm::vec4(frustum.m_points[0], 1.0f);
   min.z = transf.z;
   max.z = transf.z;
   for(int j = 1 ; j < 8 ; j++) 
   {
     transf = lightViewMat * glm::vec4(frustum.m_points[j], 1.0f);
     if(transf.z > max.z) { max.z = transf.z; }
     if(transf.z < min.z) { min.z = transf.z; }
   }
   //1.1 扩展光视锥体的大小,使其包括所有的遮挡物
   for(int j=0; j<场景中物体包围球个数; j++)
   {
   	transf = lightViewMat * vec4f(objBSphereCenter[j], 1.0f);
		if(transf.z + objBSphereRadius[j] > max.z)
		{
			max.z = transf.z + objBSphereRadius[j];
		}
   }
	
   //2. 设光空间的正交投影矩阵为ortho,他的x,y∈[-1,1],z∈[-tmax.z,-tmin.z]
   //使用x,y∈[-1,1],是因为每个分块的投影矩阵都可以使用单位x,y缩放平移后获得
   //使用z∈[-tmax.z,-tmin.z],是因为摄像机空间指向负Z方向,而glm::ortho传入的是近平面和远平面指向正Z方向
   glm::mat4 ortho = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, -tmax.z, -tmin.z);
   //2.1 在光空间中,找出视锥体切片各顶点的x、y的标准设备坐标范围。因为我们设的投影矩阵的x、y都是标准设备坐标,我们需要求出x、y的变化范围,以便对ortho进行缩放平移
   glm::mat4 lightVP = ortho * lightViewMat;
   for(int j = 0 ; j < 8 ; j++) 
   {
     transf = lightVP * glm::vec4(frustum.m_points[j], 1.0f);
     transf.x /= transf.w;
     transf.y /= transf.w;
     if(transf.x > max.x) { max.x = transf.x; }
     if(transf.x < min.x) { min.x = transf.x; }
     if(transf.y > max.y) { max.y = transf.y; }
     if(transf.y < min.y) { min.y = transf.y; }
   }
   //2.2 根据正交投影矩阵的公式,设置缩放平移量(计算过程在后面)
   glm::vec2 scale(2.0f / (max.x - min.x), 2.0f / (max.y - min.y));
   glm::vec2 offset(-0.5f * (max.x + min.x) * scale.x, -0.5f * (max.y + min.y) * scale.y);
   glm::mat4 crop = glm::mat4(1.0);
   //2.3 设置缩放平移的变换矩阵
   crop[0][0] = scale.x;
   crop[1][1] = scale.y;
   crop[0][3] = offset.x;
   crop[1][3] = offset.y;
   crop = glm::transpose(crop);//注意glm按列储存,实际矩阵要转置
   //2.4 计算出光空间中的正交投影矩阵
   lightProjMat = crop * ortho;
 
   //保存光空间的投影矩阵
   projection_matrices[i] = lightProjMat;
   //保存世界坐标到光空间变换的矩阵
   crop_matrices[i] = lightProjMat * lightViewMat;
}
  1. 计算出摄像机视锥体分块的远平面在摄像机空间中投影后的位置,并把他变换到[0.0,1.0]。这么做的目的是为了在片段着色器中判断某个片段属于哪个分块
for(int i = 0 ; i < 分块个数 ; i++) 
{
   far_bounds[i] =0.5f*((-1.0f * frustums[i].far() * projMat[2][2] + projMat[3][2])/
   frustums[i].far())+0.5f;
 }

最后

标签:贴图,CSM,级联,frac,glm,max,阴影,transf
来源: https://blog.csdn.net/m0_57980287/article/details/118554696