AnvilKitAnvilKit

PBR 与阴影

Cook-Torrance PBR、级联阴影贴图、骨骼动画

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.wf32级联数量 (3.0)

阴影贴图纹理:Depth32Float2048×2048depth_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 个采样器)
2IBL + 阴影:BRDF LUT、线性采样器、CSM 阴影贴图数组、比较采样器
3关节矩阵(仅蒙皮):storage<array<mat4×4, 128>>

骨骼动画

GPU 线性混合蒙皮,每个骨架最多支持 128 个关节。

顶点格式 (SkinnedVertex,72 字节):

属性类型位置
positionvec3<f32>0
normalvec3<f32>1
texcoordvec2<f32>2
tangentvec4<f32>3
joint_indicesu16×44
joint_weightsf32×45

蒙皮方程(在 skinned_pbr.wgsl 中):

skinned_pos = Σ(weight[i] × joints[index[i]] × position)  for i in 0..4

运行时流程

  1. AnimationPlayer::advance(dt) 更新 current_time
  2. compute_bone_matrices(skeleton, player) 计算每个关节的 global × inverse_bind
  3. BoneMatrices 上传到存储缓冲区(绑定组 3)
  4. 顶点着色器在 model/view-proj 变换之前应用线性混合蒙皮

目录