一、阴影映射技术基础
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−l20000t−b20000f−n20−r−lr+l−t−bt+b−f−nf+n1
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−zid−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 运行结果截图














暂无评论内容