Vulkan AzureRender 应用开发
基于 Vulkan 与渲染图组织资源生命周期和同步,呈现黑洞光线积分、风格化角色与可复查的画面捕获。
项目概况
- 图形 API
- Vulkan 1.3
- 语言 / 着色
- C++17 / GLSL
- 验证工具
- RenderDoc
- HDR 交接
- RGBA16F Scene Color
- 阴影
- 2048 × 2048 Shadow Map + PCSS
- 黑洞积分
- 1.5Rs–8Rs 自适应步长,Cinematic 档上限 1800 步
Vulkan 渲染器架构
宿主管理 Vulkan 设备、交换链、队列、帧同步和公共渲染目标。Character、Blackhole 与 Sample 通过 ISceneRenderer 接入,持有各自的管线、纹理和描述符,并向渲染图注册通道及资源读写。渲染图根据依赖安排执行顺序与资源屏障,GPU 资源按提交完成状态回收。
所有场景都把结果写进 RGBA16F Scene Color。这个纹理是明确的 HDR 交接点,后面统一经过后处理、ACES fitted tone mapping、HUD 和 swapchain 输出。窗口尺寸变化时,宿主先等待相关 frame fence,再重建尺寸相关资源并通知场景。这样黑洞的历史帧、角色 Shadow Map 和编辑器 viewport 可以同时存在,不会因为一次 resize 留下悬空 framebuffer。
画面捕获由宿主统一管理,记录固定时间步、相机、场景参数、设备信息和输出清单,为渲染调试提供可复查输入。底层同时开发 ECS、资产与场景管理、Lua 脚本及编辑器撤销机制,支撑场景组织和资源调试。
渲染帧数据流
- 01Scene plug-in
Character 或 Blackhole 创建私有 GPU 资源,并通过稳定 scene ID 接入。
- 02HDR Scene Color
各场景统一写入 RGBA16F,公共核心从这里接管输出。
- 03Post process
TAA、Bloom 与 ACES tone mapping 使用按帧隔离的 descriptor。
- 04Present / Capture
HUD 输出到 swapchain;捕获路径同时记录图像和 manifest。
场景通过 RenderContext 和渲染图声明资源与绘制,输出统一交给 HDR Scene Color、后处理和捕获流程。
Schwarzschild 黑洞渲染
观察光线进入片元着色器后,会在 Schwarzschild 场中不断更新位置和方向。靠近光子球时,方向变化明显,固定大步长会直接跨过高曲率区域;全程使用很小的步长又会把大部分计算浪费在远处。因此步长会在约 1.5Rs 到 8Rs 之间连续缩短。Cinematic 档最多执行 1800 步,并为每个像素做 4 次 trace。逃逸光线按最终方向采样环境纹理,所以背景也会随测地线弯曲。
吸积盘由径向密度、旋臂遮罩、分形噪声和厚度共同构成。这里有一个很实用的小处理:不要把 atan 得到的角度直接喂给非周期噪声。正负 pi 的交界必然产生接缝。我把角度改写成 cos(theta)、sin(theta) 的圆周坐标,边界两侧会落到相邻采样位置。盘面亮度再乘相对论 Doppler 因子的三次方,接近观察者的一侧更亮、更偏蓝。
Ray trace 的结果先写入私有 RGBA16F 目标,再由两张 ping-pong 历史图做 TAA、邻域 clamp 和 HDR Bloom。首帧、resize、开始捕获、质量切换以及相机跳变都会清空历史。TAA 只负责压住积分噪声,不能让旧帧把快速旋转的盘面拖成一片。
ds² = -(1-rs/r)c²dt² + dr²/(1-rs/r) + r²dΩ²Schwarzschild 度规给出无自旋球对称质量附近的时空尺度。实际 shader 用它的近似测地线形式迭代光线,而不是做屏幕空间扭曲。
δ = 1 / [γ(1 - β cos θ)], I' = δ³Iδ 同时影响频率和亮度。三次方 beaming 让盘面接近侧明显增强,远离侧则衰减。
Hₜ = lerp(clamp(Hₜ₋₁, Nmin, Nmax), Cₜ, α)先把历史颜色限制在当前邻域范围,再做累积。这个 clamp 是压噪与防拖影之间最重要的保险。
风格化角色渲染
角色 glTF 导入后会先分为 Skin、Face、Hair、Fabric、Eye 与 Metal。不同类别使用不同的粗糙度下限、镜面能量和 Toon Ramp。Base Color、Normal、Metallic-Roughness、Emissive、Style Mask、Matcap、Hair Data、Toon Ramp 与 Face SDF 也保留各自的通道语义。把所有贴图塞进一套通用 PBR 公式看似省事,最后往往会得到发灰的皮肤和塑料感头发。
Face SDF 在头部局部空间判断主光来自左侧还是右侧,再选择对应距离场并与 Ramp 阈值比较。角色旋转时,阴影分界跟着头部朝向变化,而不是黏在世界坐标里。头发 HN 贴图的 RG 轻量影响基础发束方向,BA 驱动两层 Kajiya-Kay 高光。高光不再重复乘主光强和 Shadow Visibility,远景中的窄光带因此仍然可读。
方向光使用 2048 x 2048 Shadow Map。PCSS 先用 12 个 Poisson 样本寻找 blocker 平均深度,再用 16 个样本做 PCF。接收点离遮挡物越远,滤波半径越大。最大半径只保存在 RenderSettings 中,编辑器、序列化和捕获 manifest 都读取同一参数,避免同一个阴影设置出现三份值。
Ihair = [1 - (T · H)²]ᵖᐟ²T 是发束切线,H 是半角向量。指数 p 控制高光带宽,再通过窄 Ramp 把连续响应整理成易读的条带。
wpenumbra ≈ (zr - zb) / zb · wlightBlocker search 估计平均遮挡深度,距离比值决定 PCF 半径,所以阴影会从接触处的硬边逐渐变软。