【C++游戏引擎开发】第24篇:级联阴影映射(CSM,Cascaded Shadow Maps)

一、阴影映射技术基础

1.1 阴影映射的核心原理

1.1.1 深度测试与阴影生成

阴影映射(Shadow Mapping)是一种基于深度比较的阴影渲染技术,其核心思想分为两步:

从光源视角渲染深度贴图:将场景渲染到光源的深度缓冲区,生成阴影贴图(Shadow Map)​
从摄像机视角进行深度比较:在正常渲染时,将像素点转换到光源空间,比较其深度值与阴影贴图记录的深度值,决定是否处于阴影中。

数学上,阴影可见性计算可表示为:
V ( p ) = { 0 若  d fragment > d shadow map 1 否则 V(p) = egin{cases} 0 & ext{若 } d_{ ext{fragment}} > d_{ ext{shadow map}} \ 1 & ext{否则} end{cases} V(p)={
01​若 dfragment​>dshadow map​否则​
其中 d fragment d_{ ext{fragment}} dfragment​ 是当前片元到光源的深度, d shadow map d_{ ext{shadow map}} dshadow map​ 是阴影贴图中存储的最近深度。

1.1.2 关键问题:透视走样(Perspective Aliasing)

传统阴影映射在以下场景中表现不佳:

近处物体:阴影贴图分辨率不足导致锯齿(锯齿状阴影边缘)
远处物体:相同纹理像素覆盖过大区域,导致模糊(过度稀疏采样)

根本原因在于透视投影下深度分布的非线性,使得固定分辨率的阴影贴图无法均匀分配精度。

1.2 阴影映射的数学基础

1.2.1 光源空间变换

模型-光源视图矩阵(Light View Matrix)​:将物体从世界坐标系变换到光源视角的视图空间。
光源投影矩阵(Light Projection Matrix)​:通常使用正交投影(方向光)或透视投影(点光源)。

组合后的变换矩阵为:
M light = M projection × M view M_{ ext{light}} = M_{ ext{projection}} imes M_{ ext{view}} Mlight​=Mprojection​×Mview​

1.2.2 深度值非线性分布

在透视投影中,深度值 z z z 的归一化计算公式为:
z ndc = f + n f − n + 2 f n f − n ⋅ 1 z z_{ ext{ndc}} = frac{f+n}{f-n} + frac{2fn}{f-n} cdot frac{1}{z} zndc​=f−nf+n​+f−n2fn​⋅z1​
其中 n n n 和 f f f 为近、远平面,导致深度缓冲区中近处精度高、远处精度低。

1.3 阴影映射的局限性

1.3.1 自阴影问题(Shadow Acne)

由于深度贴图分辨率和浮点精度限制,表面可能错误地判定自身处于阴影中。常用解决方案:

深度偏移(Depth Bias)​:添加固定或斜率缩放偏移量:
d adjusted = d fragment + bias d_{ ext{adjusted}} = d_{ ext{fragment}} + ext{bias} dadjusted​=dfragment​+bias

1.3.2 Peter-Panning 现象

过度使用深度偏移会导致物体与阴影分离(“漂浮”效果)。需在视觉质量与偏移量之间权衡。


二、级联阴影映射(CSM)理论

2.1 CSM 的设计动机

2.1.1 动态分辨率分配

将摄像机视锥体(View Frustum)按深度分割为多个子区域(级联),每个级联独立生成阴影贴图:

近层级:高分辨率贴图,捕捉细节
远层级:低分辨率贴图,节省资源

2.1.2 视锥体分割策略

常用的分割方式包括:

均匀分割(Uniform Split)​:线性划分深度范围
z i = n + i k ( f − n ) z_i = n + frac{i}{k}(f-n) zi​=n+ki​(f−n)
对数分割(Logarithmic Split)​:适应透视分布
z i = n ( f n ) i / k z_i = n left( frac{f}{n}
ight)^{i/k} zi​=n(nf​)i/k
实用分割(Practical Split)​:混合线性与对数分割

2.2 CSM 的算法流程

2.2.1 视锥体分割与层级计算

将摄像机视锥体分割为 k k k 个层级(通常 k = 4 k=4 k=4)
对每个层级计算其包围盒(AABB)

2.2.2 光源投影矩阵优化

为每个层级生成紧贴包围盒的正交投影矩阵,最大化利用阴影贴图像素:

将层级包围盒变换到光源空间
计算包围盒在光源空间的最小/最大坐标
构建正交投影矩阵:
M ortho = [ 2 r − l 0 0 − r + l r − l 0 2 t − b 0 − t + b t − b 0 0 2 f − n − f + n f − n 0 0 0 1 ] M_{ ext{ortho}} = egin{bmatrix} frac{2}{r-l} & 0 & 0 & -frac{r+l}{r-l} \ 0 & frac{2}{t-b} & 0 & -frac{t+b}{t-b} \ 0 & 0 & frac{2}{f-n} & -frac{f+n}{f-n} \ 0 & 0 & 0 & 1 end{bmatrix} Mortho​=
​r−l2​000​0t−b2​00​00f−n2​0​−r−lr+l​−t−bt+b​−f−nf+n​1​

2.2.3 层级混合(Cascade Blending)

为避免层级间可见的硬边界,在相邻层级交界处进行插值:
w = d − z i z i + 1 − z i w = frac{d – z_{i}}{z_{i+1} – z_{i}} w=zi+1​−zi​d−zi​​
V final = ( 1 − w ) V i + w V i + 1 V_{ ext{final}} = (1-w)V_i + wV_{i+1} Vfinal​=(1−w)Vi​+wVi+1​

2.3 CSM 的性能与质量权衡

2.3.1 层级数量选择

增加层级数:提升阴影质量,但增加渲染开销
典型选择:3-4 层级(平衡性能与效果)

2.3.2 纹理分辨率分配

总纹理内存: k × w × h k imes w imes h k×w×h( k k k 为层级数)
动态分辨率:可根据层级重要性动态调整

2.3.3 滤波与抗锯齿

PCF(Percentage Closer Filtering)​:软化阴影边缘
VSM(Variance Shadow Maps)​:预计算深度分布,加速滤波


三、CSM 的关键优化技术

3.1 视锥体包围盒计算优化

3.1.1 摄像机视锥体顶点提取

从摄像机投影矩阵反推视锥体8个顶点的世界坐标:
顶点 = M view − 1 × M projection − 1 × NDC Corner ext{顶点} = M_{ ext{view}}^{-1} imes M_{ ext{projection}}^{-1} imes ext{NDC Corner} 顶点=Mview−1​×Mprojection−1​×NDC Corner

3.1.2 包围盒紧密度优化

通过剔除不可见区域减少阴影贴图浪费,例如:

场景相关裁剪(Scene-Dependent Culling)​
层级包围盒合并

3.2 动态层级切换

3.2.1 基于摄像机运动的更新策略

位置阈值:摄像机移动超过阈值时更新阴影贴图
角度阈值:光源方向变化时重新生成所有层级

3.3 层级过渡伪影消除

3.3.1 深度偏移修正

为每个层级独立计算斜率缩放偏移量:
bias i = Δ x ⋅ tan ⁡ ( θ ) + Δ z ext{bias}_i = Delta x cdot an( heta) + Delta z biasi​=Δx⋅tan(θ)+Δz
其中 Δ x Delta x Δx 为光源空间纹素大小, θ heta θ 为表面法线与光源方向的夹角。

3.3.2 层级间滤波

在着色器中对相邻层级进行采样并混合,例如:
Shadow = mix ( sample ( i ) , sample ( i + 1 ) , w ) ext{Shadow} = ext{mix}( ext{sample}(i), ext{sample}(i+1), w) Shadow=mix(sample(i),sample(i+1),w)


四、实战示例

4.1 核心代码(片段着色器)

#version 460 core
out vec4 FragColor; // 最终输出颜色

in vec3 FragPos;     // 输入世界坐标
in vec3 Normal;      // 输入法线
in vec4 FragPosLightSpace[3]; // 输入三级联光源空间位置

uniform vec3 lightPos;       // 光源位置
uniform vec3 viewPos;        // 观察位置
uniform sampler2D shadowMaps[3]; // 三级联阴影贴图
uniform float cascadePlanes[4];   // 级联分割平面
uniform mat4 view;           // 观察矩阵

// 阴影计算函数
float ShadowCalculation(vec4 fragPosLightSpace, int cascadeIndex) {
            
    // 标准化到[0,1]纹理坐标
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    projCoords = projCoords * 0.5 + 0.5;
    
    // 超出阴影贴图范围的区域不计算阴影
    if(projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 
        || projCoords.y < 0.0 || projCoords.y > 1.0)
        return 0.0;
        
    float closestDepth = texture(shadowMaps[cascadeIndex], projCoords.xy).r; // 最近深度
    float currentDepth = projCoords.z; // 当前片段深度
    
    // 计算阴影偏移(防止阴影痤疮)
    vec3 normal = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
    
    // 返回阴影系数(1.0表示在阴影中)
    return currentDepth - bias > closestDepth ? 1.0 : 0.0;
}

void main() {
            
    vec3 color = vec3(0.8);       // 物体基础颜色
    vec3 ambient = 0.2 * color;    // 环境光分量
    
    // 漫反射计算
    vec3 lightDir = normalize(lightPos - FragPos);
    vec3 normal = normalize(Normal);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * color;
    
    // 镜面反射计算
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = 0.8 * spec * vec3(1.0);
    
    // 根据视图深度选择级联
    float viewDepth = -(view * vec4(FragPos, 1.0)).z; // 计算视图空间深度
    int cascadeIndex = 0;
    for(int i = 0; i < 3; ++i) {
            
        if(viewDepth > cascadePlanes[i]) {
            
            cascadeIndex = i + 1;
        }
    }
    cascadeIndex = clamp(cascadeIndex, 0, 2); // 确保索引在有效范围内
    
    float shadow = ShadowCalculation(FragPosLightSpace[cascadeIndex], cascadeIndex);
    
    // 综合光照效果
    vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;
    FragColor = vec4(lighting, 1.0); // 输出最终颜色
}

4.2 完整代码

/*--------------------------------- 头文件包含 ---------------------------------*/
#include <glad/glad.h>       // 加载OpenGL函数指针
#include <GLFW/glfw3.h>      // 窗口和输入管理
#include <glm/glm.hpp>       // OpenGL数学库(核心)
#include <glm/gtc/matrix_transform.hpp> // 矩阵变换函数
#include <glm/gtc/type_ptr.hpp> // 矩阵内存操作工具
#include <iostream>          // 控制台输入输出
#include <vector>            // 动态数组容器
#include <string>            // 字符串操作
#include <limits>            // 数值极限常量

/*----------------------------- 全局常量定义 ------------------------------*/
const unsigned int SCR_WIDTH = 800;     // 窗口宽度
const unsigned int SCR_HEIGHT = 600;    // 窗口高度
const unsigned int SHADOW_WIDTH = 2048;  // 阴影贴图宽度
const unsigned int SHADOW_HEIGHT = 2048; // 阴影贴图高度
const int NUM_CASCADES = 3;              // 级联阴影层数

/*------------------------ 深度渲染着色器源码 -------------------------*/
const char* depth_vertex_shader = R"glsl(
#version 460 core
layout(location = 0) in vec3 aPos;    // 顶点位置属性
uniform mat4 lightSpaceMatrix;        // 光源空间变换矩阵
uniform mat4 model;                   // 模型矩阵

void main() {
    // 将顶点位置转换到光源空间
    gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0);
})glsl";

const char* depth_fragment_shader = R"glsl(
#version 460 core
void main() {
    // 空片段着色器,仅写入深度缓冲
})glsl";

/*------------------------- 主渲染着色器源码 -------------------------*/
const char* main_vertex_shader = R"glsl(
#version 460 core
layout(location = 0) in vec3 aPos;     // 顶点位置
layout(location = 1) in vec3 aNormal;  // 法线向量
uniform mat4 projection;              // 投影矩阵
uniform mat4 view;                     // 视图矩阵
uniform mat4 model;                    // 模型矩阵
uniform mat4 lightSpaceMatrices[3];    // 三级联光源空间矩阵

out vec3 FragPos;         // 输出片段世界坐标
out vec3 Normal;          // 输出法线向量
out vec4 FragPosLightSpace[3]; // 三级联光源空间坐标

void main() {
    // 标准MVP变换
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    
    // 计算世界空间坐标
    FragPos = vec3(model * vec4(aPos, 1.0));
    
    // 法线矩阵变换(处理非均匀缩放)
    Normal = mat3(transpose(inverse(model))) * aNormal;
    
    // 计算所有级联的光源空间坐标
    for(int i = 0; i < 3; ++i) {
        FragPosLightSpace[i] = lightSpaceMatrices[i] * vec4(FragPos, 1.0);
    }
})glsl";

const char* main_fragment_shader = R"glsl(
#version 460 core
out vec4 FragColor;       // 最终输出颜色

in vec3 FragPos;          // 输入世界坐标
in vec3 Normal;           // 输入法线
in vec4 FragPosLightSpace[3]; // 输入三级联光源空间坐标

uniform vec3 lightPos;     // 光源位置
uniform vec3 viewPos;      // 摄像机位置
uniform sampler2D shadowMaps[3]; // 三级联阴影贴图
uniform float cascadePlanes[4];   // 级联分割平面
uniform mat4 view;         // 视图矩阵

// 阴影计算函数
float ShadowCalculation(vec4 fragPosLightSpace, int cascadeIndex) {
    // 透视除法(标准化到[0,1]范围)
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    projCoords = projCoords * 0.5 + 0.5;
    
    // 检查是否在阴影贴图范围内
    if(projCoords.z > 1.0 || 
       projCoords.x < 0.0 || projCoords.x > 1.0 || 
       projCoords.y < 0.0 || projCoords.y > 1.0) {
        return 0.0; // 超出范围不计算阴影
    }
    
    // 获取最近深度值和当前深度
    float closestDepth = texture(shadowMaps[cascadeIndex], projCoords.xy).r;
    float currentDepth = projCoords.z;
    
    // 计算动态阴影偏移(防止阴影痤疮)
    vec3 normal = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
    
    // 判断是否在阴影中
    return (currentDepth - bias) > closestDepth ? 1.0 : 0.0;
}

void main() {
    vec3 color = vec3(0.8);       // 物体基础颜色(灰色)
    vec3 ambient = 0.2 * color;    // 环境光分量
    
    // 漫反射计算
    vec3 lightDir = normalize(lightPos - FragPos);
    vec3 normal = normalize(Normal);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * color;
    
    // 镜面反射计算
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = 0.8 * spec * vec3(1.0);
    
    // 根据视图深度选择级联
    float viewDepth = -(view * vec4(FragPos, 1.0)).z;
    int cascadeIndex = 0;
    for(int i = 0; i < 3; ++i) {
        if(viewDepth > cascadePlanes[i]) {
            cascadeIndex = i + 1;
        }
    }
    cascadeIndex = clamp(cascadeIndex, 0, 2); // 限制索引范围
    
    // 计算阴影系数
    float shadow = ShadowCalculation(FragPosLightSpace[cascadeIndex], cascadeIndex);
    
    // 合成最终光照
    vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;
    FragColor = vec4(lighting, 1.0);
})glsl";

/*---------------------------- 几何数据定义 ----------------------------*/
// 立方体顶点数据(位置 + 法线)
float cubeVertices[] = {
            
    // 后表面
    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

    // 前表面
    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,

    // 左表面
    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

    // 右表面
     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
     0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
     0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

    // 底面
    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
     0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

    // 顶面
    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
};

// 平面顶点数据(扩展尺寸)
float planeVertices[] = {
            
     15.0f, -0.5f,  15.0f,  0.0f, 1.0f, 0.0f,
    -15.0f, -0.5f,  15.0f,  0.0f, 1.0f, 0.0f,
    -15.0f, -0.5f, -15.0f,  0.0f, 1.0f, 0.0f,

     15.0f, -0.5f,  15.0f,  0.0f, 1.0f, 0.0f,
    -15.0f, -0.5f, -15.0f,  0.0f, 1.0f, 0.0f,
     15.0f, -0.5f, -15.0f,  0.0f, 1.0f, 0.0f
};

/*--------------------------- 回调函数 ---------------------------*/
// 窗口大小变化回调
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
            
    glViewport(0, 0, width, height); // 重置OpenGL视口
}

/*------------------------ 着色器编译函数 ------------------------*/
unsigned int createShader(const char* vertexSrc, const char* fragmentSrc) {
            
    // 创建并编译顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexSrc, NULL);
    glCompileShader(vertexShader);

    // 检查编译错误
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success) {
            
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "顶点着色器编译错误:
" << infoLog << std::endl;
    }

    // 创建并编译片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentSrc, NULL);
    glCompileShader(fragmentShader);

    // 检查编译错误
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success) {
            
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "片段着色器编译错误:
" << infoLog << std::endl;
    }

    // 创建着色器程序并链接
    unsigned int program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);

    // 检查链接错误
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
            
        glGetProgramInfoLog(program, 512, NULL, infoLog);
        std::cout << "着色器链接错误:
" << infoLog << std::endl;
    }

    // 删除中间文件
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    return program;
}

/*----------------------- 视锥体计算函数 -----------------------*/
std::vector<glm::vec4> getFrustumCornersWorldSpace(const glm::mat4& proj, const glm::mat4& view) {
            
    glm::mat4 inv = glm::inverse(proj * view); // 计算逆矩阵
    std::vector<glm::vec4> frustumCorners;

    // 生成NDC立方体的8个角点
    for (int x = 0; x < 2; ++x) {
            
        for (int y = 0; y < 2; ++y) {
            
            for (int z = 0; z < 2; ++z) {
            
                glm::vec4 pt = inv * glm::vec4(
                    2.0f * x - 1.0f, 
                    2.0f * y - 1.0f, 
                    2.0f * z - 1.0f, 
                    1.0f
                );
                frustumCorners.push_back(pt / pt.w); // 透视除法
            }
        }
    }
    return frustumCorners;
}

/*--------------------- 光源空间矩阵计算函数 --------------------*/
glm::mat4 getLightSpaceMatrix(float nearPlane, float farPlane,
                             const glm::mat4& proj, const glm::mat4& view,
                             const glm::vec3& lightPos) {
            
    // 获取当前分割的视锥角点
    auto corners = getFrustumCornersWorldSpace(
        glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH/SCR_HEIGHT, nearPlane, farPlane),
        view
    );

    // 计算视锥中心点
    glm::vec3 center(0.0f);
    for (const auto& v : corners)
        center += glm::vec3(v);
    center /= corners.size();

    // 创建光源视图矩阵
    glm::mat4 lightView = glm::lookAt(
        lightPos,           // 光源位置
        center,             // 观察中心
        glm::vec3(0.0f, 1.0f, 0.0f) // 上方向
    );

    // 计算包围盒范围
    float minX = std::numeric_limits<float>::max();
    float maxX = std::numeric_limits<float>::lowest();
    float minY = std::numeric_limits<float>::max();
    float maxY = std::numeric_limits<float>::lowest();
    float minZ = std::numeric_limits<float>::max();
    float maxZ = std::numeric_limits<float>::lowest();

    for (const auto& v : corners) {
            
        glm::vec4 trf = lightView * v;
        minX = std::min(minX, trf.x);
        maxX = std::max(maxX, trf.x);
        minY = std::min(minY, trf.y);
        maxY = std::max(maxY, trf.y);
        minZ = std::min(minZ, trf.z);
        maxZ = std::max(maxZ, trf.z);
    }

    // 创建正交投影矩阵(扩展边界)
    glm::mat4 lightProjection = glm::ortho(
        minX - 2.0f, maxX + 2.0f,
        minY - 2.0f, maxY + 2.0f,
        minZ - 10.0f, maxZ + 10.0f
    );

    return lightProjection * lightView;
}

/*---------------------------- 主函数 ----------------------------*/
int main() {
            
    // 初始化GLFW
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // OpenGL 4.6
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 核心模式

    // 创建窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "CSM Demo", NULL, NULL);
    if (window == NULL) {
            
        std::cout << "窗口创建失败" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // 注册回调

    // 初始化GLAD
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
            
        std::cout << "GLAD初始化失败" << std::endl;
        return -1;
    }

    // 全局OpenGL状态设置
    glEnable(GL_DEPTH_TEST);             // 启用深度测试
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // 设置清屏颜色

    // 编译着色器
    unsigned int depthShader = createShader(depth_vertex_shader, depth_fragment_shader);
    unsigned int mainShader = createShader(main_vertex_shader, main_fragment_shader);

    /*------------------------ 创建立方体VAO/VBO ------------------------*/
    unsigned int cubeVAO, cubeVBO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);

    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), cubeVertices, GL_STATIC_DRAW);

    // 位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 法线属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    /*------------------------ 创建平面VAO/VBO ------------------------*/
    unsigned int planeVAO, planeVBO;
    glGenVertexArrays(1, &planeVAO);
    glGenBuffers(1, &planeVBO);

    glBindVertexArray(planeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    /*----------------------- 配置级联阴影贴图 -----------------------*/
    unsigned int depthMapFBO[NUM_CASCADES];
    unsigned int depthMap[NUM_CASCADES];

    for (int i = 0; i < NUM_CASCADES; ++i) {
            
        glGenFramebuffers(1, &depthMapFBO[i]); // 创建帧缓冲
        glGenTextures(1, &depthMap[i]);        // 创建深度纹理

        glBindTexture(GL_TEXTURE_2D, depthMap[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, 
                    SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
        
        // 纹理参数设置
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        float borderColor[] = {
             1.0f, 1.0f, 1.0f, 1.0f };
        glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

        // 附加深度纹理到帧缓冲
        glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO[i]);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap[i], 0);
        glDrawBuffer(GL_NONE); // 无颜色输出
        glReadBuffer(GL_NONE);
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0); // 解绑帧缓冲

    // 级联分割平面(视图空间深度)
    float cascadePlanes[] = {
             5.0f, 15.0f, 30.0f, 100.0f };

    /*------------------------ 主渲染循环 ------------------------*/
    while (!glfwWindowShouldClose(window)) {
            
        // 计算光源位置(绕目标立方体旋转)
        float angle = glfwGetTime() * 1.2f; // 旋转速度因子
        glm::vec3 targetCubePos(2.5f, 1.5f, 0.0f); // 目标立方体位置
        float orbitRadius = 2.8f;         // 旋转半径
        float lightHeight = 4.5f;         // 光源高度
        glm::vec3 lightPos = targetCubePos + glm::vec3(
            sin(angle) * orbitRadius, 
            lightHeight, 
            cos(angle) * orbitRadius
        );

        // 设置观察矩阵(摄像机)
        glm::mat4 view = glm::lookAt(
            glm::vec3(15.0f, 20.0f, 20.0f), // 摄像机位置
            glm::vec3(0.0f, 0.0f, 0.0f),    // 观察点
            glm::vec3(0.0f, 1.0f, 0.0f)     // 上方向
        );

        // 计算所有级联的光源空间矩阵
        std::vector<glm::mat4> lightSpaceMatrices;
        for (int i = 0; i < NUM_CASCADES; ++i) {
            
            lightSpaceMatrices.push_back(
                getLightSpaceMatrix(
                    cascadePlanes[i], 
                    cascadePlanes[i + 1],
                    glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / SCR_HEIGHT, 1.0f, 150.0f),
                    view,
                    lightPos
                )
            );
        }

        /*--------------------- 渲染级联阴影贴图 ---------------------*/
        for (int i = 0; i < NUM_CASCADES; ++i) {
            
            glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO[i]); // 绑定当前FBO
            glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);     // 设置视口
            glClear(GL_DEPTH_BUFFER_BIT);                     // 清除深度缓冲

            glUseProgram(depthShader); // 使用深度着色器
            glUniformMatrix4fv(
                glGetUniformLocation(depthShader, "lightSpaceMatrix"),
                1, GL_FALSE, glm::value_ptr(lightSpaceMatrices[i])
            );

            // 渲染立方体1
            glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 1.0f, 0.0f));
            glUniformMatrix4fv(
                glGetUniformLocation(depthShader, "model"),
                1, GL_FALSE, glm::value_ptr(model)
            );
            glBindVertexArray(cubeVAO);
            glDrawArrays(GL_TRIANGLES, 0, 36);

            // 渲染立方体2(目标立方体)
            model = glm::translate(glm::mat4(1.0f), glm::vec3(2.5f, 1.5f, 0.0f));
            model = glm::scale(model, glm::vec3(1.2f));
            glUniformMatrix4fv(
                glGetUniformLocation(depthShader, "model"),
                1, GL_FALSE, glm::value_ptr(model)
            );
            glDrawArrays(GL_TRIANGLES, 0, 36);

            // 渲染平面
            model = glm::mat4(1.0f);
            glUniformMatrix4fv(
                glGetUniformLocation(depthShader, "model"),
                1, GL_FALSE, glm::value_ptr(model)
            );
            glBindVertexArray(planeVAO);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }

        /*--------------------- 主场景渲染 ---------------------*/
        glBindFramebuffer(GL_FRAMEBUFFER, 0); // 绑定默认帧缓冲
        glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glUseProgram(mainShader); // 使用主着色器

        // 设置投影和观察矩阵
        glm::mat4 projection = glm::perspective(
            glm::radians(45.0f),
            (float)SCR_WIDTH / SCR_HEIGHT,
            1.0f, 
            150.0f
        );
        glm::mat4 viewMat = glm::lookAt(
            glm::vec3(15.0f, 20.0f, 20.0f),
            glm::vec3(0.0f),
            glm::vec3(0.0f, 1.0f, 0.0f)
        );

        // 传递统一变量
        glUniformMatrix4fv(
            glGetUniformLocation(mainShader, "projection"),
            1, GL_FALSE, glm::value_ptr(projection)
        );
        glUniformMatrix4fv(
            glGetUniformLocation(mainShader, "view"),
            1, GL_FALSE, glm::value_ptr(viewMat)
        );
        glUniform3fv(
            glGetUniformLocation(mainShader, "lightPos"),
            1, glm::value_ptr(lightPos)
        );
        glUniform3fv(
            glGetUniformLocation(mainShader, "viewPos"),
            1, glm::value_ptr(glm::vec3(15.0f, 20.0f, 20.0f))
        );

        // 绑定阴影贴图纹理
        for (int i = 0; i < NUM_CASCADES; ++i) {
            
            glActiveTexture(GL_TEXTURE0 + i);
            glBindTexture(GL_TEXTURE_2D, depthMap[i]);
            glUniform1i(
                glGetUniformLocation(mainShader, ("shadowMaps[" + std::to_string(i) + "]").c_str()),
                i
            );
            glUniformMatrix4fv(
                glGetUniformLocation(mainShader, ("lightSpaceMatrices[" + std::to_string(i) + "]").c_str()),
                1, GL_FALSE, glm::value_ptr(lightSpaceMatrices[i])
            );
        }

        // 传递级联分割平面
        glUniform1fv(
            glGetUniformLocation(mainShader, "cascadePlanes"),
            4, cascadePlanes
        );

        // 渲染立方体1
        glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 1.0f, 0.0f));
        glUniformMatrix4fv(
            glGetUniformLocation(mainShader, "model"),
            1, GL_FALSE, glm::value_ptr(model)
        );
        glBindVertexArray(cubeVAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);

        // 渲染立方体2(目标立方体)
        model = glm::translate(glm::mat4(1.0f), glm::vec3(2.5f, 1.5f, 0.0f));
        model = glm::scale(model, glm::vec3(1.2f));
        glUniformMatrix4fv(
            glGetUniformLocation(mainShader, "model"),
            1, GL_FALSE, glm::value_ptr(model)
        );
        glDrawArrays(GL_TRIANGLES, 0, 36);

        // 渲染平面
        model = glm::mat4(1.0f);
        glUniformMatrix4fv(
            glGetUniformLocation(mainShader, "model"),
            1, GL_FALSE, glm::value_ptr(model)
        );
        glBindVertexArray(planeVAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        // 交换缓冲并处理事件
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    /*------------------------ 资源清理 ------------------------*/
    glDeleteVertexArrays(1, &cubeVAO);
    glDeleteVertexArrays(1, &planeVAO);
    glDeleteBuffers(1, &cubeVBO);
    glDeleteBuffers(1, &planeVBO);
    glDeleteProgram(mainShader);
    glDeleteProgram(depthShader);
    
    for (int i = 0; i < NUM_CASCADES; ++i) {
            
        glDeleteFramebuffers(1, &depthMapFBO[i]);
        glDeleteTextures(1, &depthMap[i]);
    }

    glfwTerminate(); // 清理GLFW资源
    return 0;
}

4.3 运行结果截图

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
王发发的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容