PBR 与阴影
Cook-Torrance PBR、级联阴影贴图、骨骼动画
级联阴影贴图
3 级级联阴影贴图替代单级阴影贴图,实现距离相关的阴影质量。
工作原理:相机视锥体在远平面的 10%、30% 和 100% 处被分割为 3 个区域。每个区域拥有独立的正交投影,紧密贴合视锥体角点。PBR 片段着色器根据视空间 Z 深度为每个片段选择最紧密的级联。
| 级联 | 覆盖范围 | 质量 |
|---|---|---|
| 0 | 近处(远平面的 0–10%) | 最高分辨率 |
| 1 | 中距(远平面的 10–30%) | 中等分辨率 |
| 2 | 远处(远平面的 30–100%) | 最低分辨率 |
GPU 布局 (PbrSceneUniform):
| 字段 | 类型 | 说明 |
|---|---|---|
cascade_view_projs | [mat4×4; 3] | 每级级联的光空间视图投影矩阵 |
cascade_splits | [f32; 4] | [split0, split1, split2, shadow_texel_size] |
emissive_factor.w | f32 | 级联数量 (3.0) |
阴影贴图纹理:Depth32Float,2048×2048,depth_or_array_layers = 3 (D2Array)。
文件:window/events/lighting.rs (compute_cascade_matrices)、pbr.wgsl (calculate_shadow)
点光源阴影
点光源使用 cubemap 阴影贴图(每个光源 6 个面):
use anvilkit_render::renderer::shadow::PointShadowConfig;
let config = PointShadowConfig {
resolution: 512, // 每个面的分辨率
near: 0.1,
far: 50.0,
bias: 0.005, // 防止阴影失真
};聚光灯阴影
聚光灯使用单张 2D 阴影贴图:
use anvilkit_render::renderer::shadow::SpotShadowConfig;
let config = SpotShadowConfig {
resolution: 1024,
bias: 0.005,
normal_bias: 0.02,
};阴影 Atlas
ShadowAtlas 管理所有阴影投射光源的 GPU 阴影贴图槽位:
use anvilkit_render::renderer::shadow::ShadowAtlas;
use anvilkit_render::renderer::draw::lighting::MAX_SHADOW_LIGHTS;
let mut atlas = ShadowAtlas::new();
assert_eq!(atlas.max_lights, MAX_SHADOW_LIGHTS); // 4
if let Some(slot) = atlas.allocate() {
// 将此光源的阴影贴图渲染到对应槽位
}
atlas.reset(); // 每帧调用最大同时投射阴影的光源数:MAX_SHADOW_LIGHTS = 4。超出限制的光源仍参与光照计算但不投射阴影。
PBR 材质系统
Cook-Torrance 镜面 BRDF,使用 GGX 分布、Smith 几何函数、Fresnel-Schlick 近似。
绑定组:
| 组 | 内容 |
|---|---|
| 0 | 场景 uniform(992 字节):model、view_proj、normal_matrix、camera_pos、lights[8]、级联矩阵 |
| 1 | 材质纹理:基础颜色、法线贴图、金属度-粗糙度、AO、自发光(5 个纹理 + 5 个采样器) |
| 2 | IBL + 阴影:BRDF LUT、线性采样器、CSM 阴影贴图数组、比较采样器 |
| 3 | 关节矩阵(仅蒙皮):storage<array<mat4×4, 128>> |
骨骼动画
GPU 线性混合蒙皮,每个骨架最多支持 128 个关节。
顶点格式 (SkinnedVertex,72 字节):
| 属性 | 类型 | 位置 |
|---|---|---|
position | vec3<f32> | 0 |
normal | vec3<f32> | 1 |
texcoord | vec2<f32> | 2 |
tangent | vec4<f32> | 3 |
joint_indices | u16×4 | 4 |
joint_weights | f32×4 | 5 |
蒙皮方程(在 skinned_pbr.wgsl 中):
skinned_pos = Σ(weight[i] × joints[index[i]] × position) for i in 0..4运行时流程:
AnimationPlayer::advance(dt)更新current_timecompute_bone_matrices(skeleton, player)计算每个关节的global × inverse_bind- 将
BoneMatrices上传到存储缓冲区(绑定组 3) - 顶点着色器在 model/view-proj 变换之前应用线性混合蒙皮