29 KiB
XCVolumeRenderer 深入方向规划
本文档详细列出本科毕业设计可深入的所有方向,供选择和规划。
一、渲染效果提升
1.1 多散射 (Multiple Scattering)
背景
当前实现的是单散射(Single Scattering),光线只从光源到体素再到相机弹射一次。真实云层中,光线会在体积内多次弹射,产生柔和的边缘和通透的内部效果。
当前问题
- 云内部过暗,缺乏体积感
- 边缘过于锐利,不自然
- 逆光效果不明显
解决方案
方法A:迭代多散射
for (散射次数) {
for (每个采样点) {
计算该点的入射光(来自光源 + 其他体素的散射光)
累积散射光
}
}
- 优点:物理正确
- 缺点:性能开销大,O(n²) 或更高
方法B:近似多散射
// 使用扩散近似(Diffusion Approximation)
// 参考: Jensen et al. "A Practical Model for Subsurface Light Transport"
float multiScatter = exp(-sigmaT * d) * diffusionTerm;
- 优点:性能好
- 缺点:近似,不够精确
方法C:预计算多散射
// 离线预计算散射LUT
// 运行时查表
Texture3D scatteringLUT;
float multiScatter = scatteringLUT.Sample(sampler, uvw);
- 优点:运行时快
- 缺点:需要额外存储
实现步骤
- 实现简单的双散射(Double Scattering)
- 观察效果变化
- 优化性能(重要性采样)
- 扩展到多阶散射
参考资料
- Jensen, H. W., et al. "A Practical Model for Subsurface Light Transport" (SIGGRAPH 2001)
- Hachisuka, T., et al. "Progressive Photon Mapping" (SIGGRAPH 2008)
- Kutz, P., et al. "Spectral and Decomposition Tracking for Rendering Heterogeneous Volumes" (SIGGRAPH 2017)
工作量估计
- 双散射基础实现:1周
- 完整多散射:2-3周
- 性能优化:1周
1.2 相函数 (Phase Function)
背景
相函数描述光线在介质中散射的方向分布。当前使用的是均匀相函数 return 1.0,即各向同性散射。真实云层的相函数是各向异性的,具有强烈的前向散射特性。
当前问题
- 光照效果过于均匀
- 缺乏真实云层的光晕效果
- 逆光时的"银边"效果不明显
相函数类型
1. Henyey-Greenstein (HG) 相函数
float hg_phase(float cosTheta, float g) {
float g2 = g * g;
return (1 - g2) / pow(1 + g2 - 2 * g * cosTheta, 1.5) / (4 * PI);
}
g = 0: 各向同性g > 0: 前向散射(云层特性)g < 0: 后向散射- 参数
g范围:(-1, 1)
2. 双 Henyey-Greenstein 相函数
float double_hg_phase(float cosTheta, float g1, float g2, float w) {
return w * hg_phase(cosTheta, g1) + (1 - w) * hg_phase(cosTheta, g2);
}
- 结合两个HG相函数
- 更好地模拟真实云层
g1 ≈ 0.8(强前向),g2 ≈ -0.2(弱后向)
3. Mie 相函数
// 查表实现,更精确但复杂
// 通常用HG近似
- 物理最正确
- 计算复杂
- 通常用双HG近似
4. Rayleigh 相函数
float rayleigh_phase(float cosTheta) {
return (3 / (16 * PI)) * (1 + cosTheta * cosTheta);
}
- 适用于大气散射
- 小粒子(空气分子)
实现步骤
- 实现 HG 相函数
- 添加参数 UI 控制
g值 - 观察不同
g值的效果 - 实现双 HG 相函数
- 对比效果
参数建议
| 场景 | g 值 | 说明 |
|---|---|---|
| 云层 | 0.7 ~ 0.9 | 强前向散射 |
| 雾 | 0.1 ~ 0.3 | 弱前向散射 |
| 烟雾 | -0.2 ~ 0.2 | 接近各向同性 |
参考资料
- Henyey, L. G., & Greenstein, J. L. "Diffuse radiation in the galaxy" (1941)
- Nishita, T., et al. "Display of clouds taking into account multiple anisotropic scattering and sky light" (SIGGRAPH 1996)
工作量估计
- HG相函数实现:1-2天
- 双HG相函数:2-3天
- 参数UI:1天
1.3 体积自发光 (Volumetric Emission)
背景
火焰、熔岩、霓虹气体等发光体积。除了散射外部光源,自身也发出光。
实现方法
// 在着色器中添加自发光项
float3 emission = emissionColor * emissionIntensity * emissionMask;
float3 S = sigmaS * phase_function() * shadow * float3(1, 1, 1) + emission;
扩展
- 温度场驱动发光颜色(黑体辐射)
- 发光影响周围体积(需多散射支持)
工作量估计
- 基础实现:1-2天
- 温度场驱动:3-4天
二、性能优化
2.1 Temporal Reprojection
背景
当前渲染存在噪点,尤其是阴影采样。Temporal Reprojection 利用历史帧信息进行降噪。
原理
当前帧颜色 = α * 当前帧渲染 + (1-α) * 历史帧重投影颜色
核心步骤
1. 速度缓冲 (Motion Vector)
// 计算当前像素在上一帧的位置
float2 motionVector = currentPos - previousPos;
float2 prevUV = currentUV - motionVector;
2. 历史帧采样
float3 historyColor = historyTexture.Sample(sampler, prevUV);
3. 颜色约束 (Clamping)
// 防止鬼影
float3 minColor = min(neighbors);
float3 maxColor = max(neighbors);
historyColor = clamp(historyColor, minColor, maxColor);
4. 混合
float3 finalColor = lerp(currentColor, historyColor, 0.9);
需要的资源
- 历史帧颜色缓冲
- 深度缓冲(当前帧 + 历史帧)
- Motion Vector 缓冲
实现步骤
- 创建历史帧缓冲
- 实现 Motion Vector 计算
- 实现重投影逻辑
- 实现颜色约束
- 调试和参数调优
注意事项
- 相机快速移动时会产生鬼影
- 需要处理物体移动(动态场景)
- 需要处理体积自身的旋转/移动
参考资料
- Karis, B. "High Quality Temporal Supersampling" (SIGGRAPH 2014)
- Schied, C., et al. "Temporal Sample Anti-Aliasing" (EGSR 2020)
工作量估计
- 基础实现:1周
- 调优和调试:1周
2.2 Compute Shader 重构
背景
当前使用 Pixel Shader 进行 Ray Marching,每个像素独立计算。Compute Shader 可以更灵活地控制线程,实现更好的优化。
优势
1. 线程组共享内存
groupshared float sharedDensity[8][8];
// 相邻像素可共享中间计算结果
2. 自适应工作分配
// 可以跳过空像素,让活跃线程处理更多
3. 更好的缓存利用
// 可以手动控制数据加载,优化缓存
实现架构
Compute Shader Dispatch (32x32 线程组)
↓
每个线程处理一个像素
↓
Ray Marching
↓
输出到 UAV 纹理
代码示例
[numthreads(8, 8, 1)]
void MainCS(uint3 DTid : SV_DispatchThreadID)
{
float2 uv = (float2(DTid.xy) + 0.5) / Resolution;
float3 color = RayMarch(uv);
OutputTexture[DTid.xy] = float4(color, 1);
}
可实现的高级优化
- 波前追踪 (Wavefront Tracing)
- 空间跳跃优化
- 自适应采样
工作量估计
- 基础重构:1-2周
- 高级优化:2-3周
2.3 自适应步长
背景
当前使用固定步长,但在低密度区域可以加大步长,高密度区域减小步长。
实现方法
方法A:基于密度
float density = SampleDensity(pos);
float stepSize = baseStep * (1 + (1 - density) * maxScale);
方法B:基于梯度
float gradient = length(SampleGradient(pos));
float stepSize = baseStep / (1 + gradient * gradientScale);
方法C:预计算重要性场
// 离线预计算每个区域的重要性
// 运行时根据重要性调整步长
注意事项
- 步长变化过大可能导致伪影
- 需要注意光线一致性
工作量估计
- 基础实现:2-3天
- 调优:1周
三、功能扩展
3.1 多光源支持
背景
当前只支持单一方向光。真实场景可能有多个光源(太阳 + 天光 + 点光源)。
实现内容
1. 多方向光
struct DirectionalLight {
float3 direction;
float3 color;
float intensity;
};
#define MAX_DIR_LIGHTS 4
DirectionalLight dirLights[MAX_DIR_LIGHTS];
2. 点光源
struct PointLight {
float3 position;
float3 color;
float intensity;
float radius;
};
float3 SamplePointLight(PointLight light, float3 pos) {
float3 dir = light.position - pos;
float dist = length(dir);
dir /= dist;
// 衰减
float attenuation = 1.0 / (1.0 + dist * dist);
// 阴影采样
float shadow = VolumetricShadow(pos, dir, dist);
return light.color * light.intensity * attenuation * shadow;
}
3. 聚光灯
struct SpotLight {
float3 position;
float3 direction;
float3 color;
float intensity;
float innerAngle;
float outerAngle;
float range;
};
实现步骤
- 设计光源数据结构
- 实现多方向光循环
- 实现点光源阴影采样
- 实现聚光灯锥形衰减
- 添加参数 UI
性能考虑
- 每个光源都需要额外的阴影采样
- 限制光源数量或使用延迟渲染思路
工作量估计
- 多方向光:2-3天
- 点光源:3-4天
- 聚光灯:2-3天
- UI和参数:1-2天
3.2 大气散射
背景
将体积渲染与天空大气结合,实现更真实的室外场景。
核心算法
1. Rayleigh 散射
// 空气分子散射,产生蓝天
float3 RayleighScattering(float cosTheta, float height) {
float3 betaR = float3(5.8e-6, 13.5e-6, 33.1e-6); // Rayleigh系数
float density = exp(-height / 8500); // 大气密度衰减
return betaR * density * (3 / (16 * PI)) * (1 + cosTheta * cosTheta);
}
2. Mie 散射
// 气溶胶散射,产生雾和光晕
float3 MieScattering(float cosTheta, float g) {
float3 betaM = float3(21e-6, 21e-6, 21e-6); // Mie系数
return betaM * HGPhase(cosTheta, g);
}
3. 单次散射积分
float3 IntegrateAtmosphere(float3 rayOrigin, float3 rayDir, float3 sunDir) {
float3 color = 0;
float transmittance = 1;
for (int i = 0; i < steps; i++) {
float3 pos = rayOrigin + rayDir * t;
float height = length(pos) - earthRadius;
// Rayleigh
float densityR = exp(-height / 8500);
float3 scatterR = RayleighScattering(dot(rayDir, sunDir), height);
// Mie
float densityM = exp(-height / 1200);
float3 scatterM = MieScattering(dot(rayDir, sunDir), 0.76);
// 光学深度
float3 opticalDepth = (scatterR + scatterM) * stepSize;
transmittance *= exp(-opticalDepth);
color += transmittance * (scatterR + scatterM) * stepSize;
t += stepSize;
}
return color;
}
效果
- 蓝天
- 日落/日出
- 远山大气透视
- 太阳光晕
参考资料
- Nishita, T., et al. "Display of The Earth Taking into Account Atmospheric Scattering" (SIGGRAPH 1993)
- Bruneton, E., & Neyret, F. "Precomputed Atmospheric Scattering" (EGSR 2008)
- Hillaire, S. "A Scalable and Production Ready Sky and Atmosphere Rendering Technique" (SIGGRAPH 2020)
工作量估计
- 基础实现:1周
- 优化和调参:1周
3.3 体积雾
背景
将体积渲染作为后处理效果,与场景几何体融合。
实现方法
1. 深度重建世界坐标
float depth = depthTexture.Sample(sampler, uv);
float4 worldPos = mul(invViewProj, float4(ndc, depth, 1));
worldPos /= worldPos.w;
2. 沿视线积分
float3 cameraPos = ...;
float3 rayDir = normalize(worldPos - cameraPos);
float dist = length(worldPos - cameraPos);
float3 color = IntegrateVolume(cameraPos, rayDir, dist);
3. 与场景混合
float3 sceneColor = sceneTexture.Sample(sampler, uv);
float3 finalColor = lerp(sceneColor, volumeColor, volumeAlpha);
工作量估计
- 基础实现:3-4天
四、交互与控制
4.1 相机交互
功能需求
- 鼠标左键拖动:旋转视角
- 鼠标右键拖动:平移
- 鼠标滚轮:缩放
- WASD:自由移动
实现方法
1. 弧球相机 (Arcball Camera)
// 围绕目标点旋转
XMVECTOR target = XMVectorSet(0, 0, 0, 1);
float distance = 500.0f;
float yaw = 0, pitch = 0;
void UpdateCamera() {
XMVECTOR eye = XMVectorSet(
sin(yaw) * cos(pitch) * distance,
sin(pitch) * distance,
cos(yaw) * cos(pitch) * distance,
1.0f
);
viewMatrix = XMMatrixLookAtLH(eye, target, XMVectorSet(0, 1, 0, 0));
}
2. 自由相机 (Free Camera)
XMVECTOR position;
float yaw = 0, pitch = 0;
void Move(float dx, float dy, float dz) {
XMVECTOR forward = XMVectorSet(sin(yaw), 0, cos(yaw), 0);
XMVECTOR right = XMVectorSet(cos(yaw), 0, -sin(yaw), 0);
XMVECTOR up = XMVectorSet(0, 1, 0, 0);
position += forward * dz + right * dx + up * dy;
}
void Rotate(float dYaw, float dPitch) {
yaw += dYaw;
pitch += dPitch;
pitch = clamp(pitch, -PI/2 + 0.01f, PI/2 - 0.01f);
}
输入处理
// Windows 消息处理
case WM_LBUTTONDOWN:
isDragging = true;
lastMousePos = GET_MOUSE_POS(lParam);
break;
case WM_MOUSEMOVE:
if (isDragging) {
POINT currentPos = GET_MOUSE_POS(lParam);
float dx = (currentPos.x - lastMousePos.x) * sensitivity;
float dy = (currentPos.y - lastMousePos.y) * sensitivity;
camera.Rotate(dx, dy);
lastMousePos = currentPos;
}
break;
工作量估计
- 弧球相机:2-3天
- 自由相机:2-3天
- 输入处理:1-2天
4.2 实时参数 UI
功能需求
- 密度缩放 (Density Scale)
- 步长 (Step Size)
- 最大步数 (Max Steps)
- 光照方向 (Light Direction)
- 光照强度 (Light Intensity)
- 相函数参数 (Phase Function G)
- 阴影采样数 (Shadow Samples)
UI 库选择
选项A:Dear ImGui (推荐)
#include "imgui.h"
void RenderUI() {
ImGui::Begin("Volume Parameters");
ImGui::SliderFloat("Density Scale", &densityScale, 0.0f, 1.0f);
ImGui::SliderFloat("Step Size", &stepSize, 0.1f, 5.0f);
ImGui::SliderInt("Max Steps", &maxSteps, 100, 5000);
ImGui::SliderFloat3("Light Dir", lightDir, -1.0f, 1.0f);
ImGui::SliderFloat("Phase G", &phaseG, -0.99f, 0.99f);
ImGui::End();
}
选项B:Win32 原生控件
- 不需要额外库
- 实现复杂
- 样式受限
集成步骤
- 添加 ImGui 到项目
- 实现 D3D12 后端
- 创建参数面板
- 绑定参数到常量缓冲区
工作量估计
- ImGui 集成:2-3天
- 参数面板:1-2天
4.3 体积数据编辑
功能需求
- 实时修改体积密度
- 添加/删除体素
- 画笔工具
实现思路
1. CPU 端编辑
// 读取当前体积数据
std::vector<float> volumeData = ReadVolumeData();
// 修改
int x = floor(pos.x / voxelSize);
int y = floor(pos.y / voxelSize);
int z = floor(pos.z / voxelSize);
volumeData[index(x,y,z)] += strength;
// 重新上传 GPU
UpdateGPUBuffer(volumeData);
2. 画笔工具
void PaintVolume(float3 center, float radius, float strength) {
for each voxel in range:
float dist = length(voxelPos - center);
if (dist < radius) {
float falloff = 1 - dist / radius;
voxel.density += strength * falloff;
}
}
挑战
- NanoVDB 是只读结构,需要重建
- 或改用 3D Texture
工作量估计
- 基础编辑:1周
- 画笔工具:3-4天
五、数据生成
5.1 程序化云生成
背景
使用 Noise 函数程序化生成云层,无需外部数据文件。
核心算法
1. Perlin Noise
float perlinNoise(float3 p) {
// 梯度噪声实现
float3 i = floor(p);
float3 f = frac(p);
float3 u = f * f * (3 - 2 * f); // smoothstep
return lerp(
lerp(lerp(hash(i + float3(0,0,0)), hash(i + float3(1,0,0)), u.x),
lerp(hash(i + float3(0,1,0)), hash(i + float3(1,1,0)), u.x), u.y),
lerp(lerp(hash(i + float3(0,0,1)), hash(i + float3(1,0,1)), u.x),
lerp(hash(i + float3(0,1,1)), hash(i + float3(1,1,1)), u.x), u.y),
u.z
);
}
2. Fractal Brownian Motion (FBM)
float fbm(float3 p, int octaves) {
float value = 0;
float amplitude = 0.5;
float frequency = 1;
for (int i = 0; i < octaves; i++) {
value += amplitude * perlinNoise(p * frequency);
amplitude *= 0.5;
frequency *= 2;
}
return value;
}
3. 云密度函数
float cloudDensity(float3 pos) {
// 基础形状(球体或盒子)
float shape = sphere(pos, center, radius);
// 噪声扰动
float noise = fbm(pos * scale, 5);
// 密度
float density = shape * noise;
// 阈值和锐化
density = smoothstep(threshold, threshold + edgeSoftness, density);
return density;
}
4. Worley Noise (细胞噪声)
// 用于云的细节和孔洞
float worleyNoise(float3 p) {
float minDist = 1e10;
int3 cell = floor(p);
for (int x = -1; x <= 1; x++)
for (int y = -1; y <= 1; y++)
for (int z = -1; z <= 1; z++) {
int3 neighbor = cell + int3(x, y, z);
float3 point = neighbor + hash3(neighbor);
float dist = distance(p, point);
minDist = min(minDist, dist);
}
return minDist;
}
云层模型
Cumulus (积云)
- 底部平坦,顶部蓬松
- 使用多个 FBM 叠加
- 高度衰减
Stratus (层云)
- 薄层状
- 低频噪声
- 均匀分布
Cirrus (卷云)
- 高空,纤维状
- 高频噪声
- 方向性
动态演化
// 随时间演化
float density = cloudDensity(pos + wind * time);
// 形态变化
float growth = sin(time * growthSpeed);
density *= 1 + growth * 0.2;
参考资料
- Ebert, D. S., et al. "Texturing & Modeling: A Procedural Approach"
- Gili, A. "The Real-Time Volumetric Cloudscapes of Horizon Zero Dawn" (SIGGRAPH 2015)
- Hillaire, S. "Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite" (SIGGRAPH 2016)
工作量估计
- Perlin/FBM 实现:2-3天
- Worley Noise:2天
- 云密度函数:3-4天
- 参数调优:1周
5.2 离线流体模拟器
背景
实现 CPU 端流体模拟,导出 OpenVDB/NanoVDB 格式。
核心算法:Navier-Stokes 方程
∂u/∂t = -(u·∇)u - ∇p/ρ + ν∇²u + f
∇·u = 0
其中:
- u:速度场
- p:压力场
- ρ:密度
- ν:粘度
- f:外力
求解步骤
1. 对流 (Advection)
void advect(VelocityField& u, ScalarField& q, float dt) {
for each cell (i, j, k):
// 回溯粒子位置
Vec3 pos = Vec3(i, j, k) - u(i, j, k) * dt;
// 插值采样
q_new(i, j, k) = interpolate(q, pos);
}
2. 扩散 (Diffusion)
void diffuse(ScalarField& q, float viscosity, float dt) {
// Jacobi 迭代
for (int iter = 0; iter < iterations; iter++) {
for each cell (i, j, k):
q_new(i, j, k) = (q(i-1,j,k) + q(i+1,j,k)
+ q(i,j-1,k) + q(i,j+1,k)
+ q(i,j,k-1) + q(i,j,k+1)
+ q_old(i,j,k) * alpha) / (6 + alpha);
}
}
3. 压力求解 (Pressure Solve)
void project(VelocityField& u) {
// 计算散度
for each cell:
divergence(i, j, k) = -0.5 * (u(i+1,j,k).x - u(i-1,j,k).x
+ u(i,j+1,k).y - u(i,j-1,k).y
+ u(i,j,k+1).z - u(i,j,k-1).z);
// Jacobi 迭代求解压力
for (int iter = 0; iter < iterations; iter++) {
for each cell:
p_new(i, j, k) = (p(i-1,j,k) + p(i+1,j,k)
+ p(i,j-1,k) + p(i,j+1,k)
+ p(i,j,k-1) + p(i,j,k+1)
- divergence(i,j,k)) / 6;
}
// 速度校正
for each cell:
u(i, j, k).x -= 0.5 * (p(i+1,j,k) - p(i-1,j,k));
u(i, j, k).y -= 0.5 * (p(i,j+1,k) - p(i,j-1,k));
u(i, j, k).z -= 0.5 * (p(i,j,k+1) - p(i,j,k-1));
}
4. 外力 (External Forces)
void addForce(VelocityField& u, Vec3 forcePos, Vec3 forceDir, float strength) {
for each cell near forcePos:
u(i, j, k) += forceDir * strength * falloff(distance);
}
烟雾模拟扩展
密度场
// 对流密度
advect(u, density, dt);
// 添加烟雾源
for each source cell:
density(i, j, k) = sourceStrength;
// 衰减
density *= (1 - decay * dt);
温度场
// 浮力
for each cell:
float buoyancy = temperature(i,j,k) * buoyancyStrength;
u(i,j,k).y += buoyancy * dt;
涡度约束 (Vorticity Confinement)
// 增加细节
Vec3 curl = computeCurl(u);
Vec3 N = normalize(curl);
Vec3 omega = curl;
u += dt * vorticityStrength * N * omega;
OpenVDB 集成
#include <openvdb/openvdb.h>
#include <openvdb/tools/Composite.h>
void exportToVDB(const ScalarField& density, const std::string& filename) {
openvdb::initialize();
// 创建网格
openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create();
openvdb::FloatGrid::Accessor accessor = grid->getAccessor();
// 填充数据
for each active voxel (i, j, k):
openvdb::Coord coord(i, j, k);
accessor.setValue(coord, density(i, j, k));
// 保存
openvdb::io::File file(filename);
file.write({grid});
file.close();
}
NanoVDB 转换
#include <nanovdb/util/OpenToNanoVDB.h>
void convertToNanoVDB(const std::string& openvdbFile, const std::string& nanovdbFile) {
// 读取 OpenVDB
openvdb::io::File file(openvdbFile);
openvdb::GridBase::Ptr baseGrid = file.readGrid("density");
// 转换
auto handle = nanovdb::openToNanoVDB(baseGrid);
// 保存
nanovdb::io::writeToFile(nanovdbFile, handle);
}
工作流程
1. 初始化网格和场
2. 循环帧:
a. 对流速度场
b. 添加外力
c. 扩散(粘度)
d. 压力求解
e. 对流密度/温度
f. 导出当前帧
3. 批量转换为 NanoVDB
参考资料
- Bridson, R. "Fluid Simulation for Computer Graphics" (书籍)
- Stam, J. "Stable Fluids" (SIGGRAPH 1999)
- Fedkiw, R., et al. "Visual Simulation of Smoke" (SIGGRAPH 2001)
工作量估计
- 基础流体求解器:2周
- 烟雾扩展:1周
- OpenVDB 集成:1周
- NanoVDB 导出:3-4天
- UI 和参数:1周
5.3 多格式导入
支持格式
| 格式 | 描述 | 库 |
|---|---|---|
| OpenVDB (.vdb) | 行业标准 | openvdb 库 |
| DDS 体积纹理 | DirectX 格式 | DirectX |
| Raw 体素数据 | 自定义格式 | 手动解析 |
实现思路
class VolumeLoader {
public:
virtual bool load(const std::string& path) = 0;
virtual float sample(int x, int y, int z) const = 0;
virtual Vec3 size() const = 0;
};
class OpenVDBLoader : public VolumeLoader { ... };
class DDSLoader : public VolumeLoader { ... };
class RawLoader : public VolumeLoader { ... };
工作量估计
- OpenVDB 支持:3-4天
- DDS 支持:2-3天
- 统一接口:1-2天
六、高级算法
6.1 重要性采样
背景
根据体积密度分布,在重要区域增加采样密度,在空区域减少采样。
方法
1. 基于密度的重要性采样
// 根据密度调整采样概率
float pdf = density / totalDensity;
float step = baseStep / pdf;
2. 分层采样
// 将光线分成段,每段内均匀采样
for each segment:
float randomOffset = rand();
float samplePos = segmentStart + randomOffset * segmentLength;
3. 多重要性采样
// 结合多种采样策略
float sampleByDensity = ...;
float sampleByLight = ...;
float weight = misWeight(densityWeight, lightWeight);
工作量估计
- 基础实现:3-4天
- 优化:1周
6.2 屏幕空间降噪
背景
低采样率导致的噪点,通过屏幕空间滤波器降噪。
算法
1. 双边滤波 (Bilateral Filter)
float3 bilateralFilter(float2 uv, float3 centerColor, float centerDepth) {
float3 sum = 0;
float weightSum = 0;
for each neighbor:
float3 neighborColor = sample(neighborUV);
float neighborDepth = sampleDepth(neighborUV);
float spatialWeight = gaussian(distance);
float rangeWeight = gaussian(length(neighborColor - centerColor));
float depthWeight = gaussian(abs(neighborDepth - centerDepth));
float weight = spatialWeight * rangeWeight * depthWeight;
sum += neighborColor * weight;
weightSum += weight;
return sum / weightSum;
}
2. SVGF (Spatiotemporal Variance-Guided Filtering)
- Temporal 累积
- 空间滤波
- 方差估计
3. BMFR (Blockwise Multi-Order Feature Regression)
- 块处理
- 特征回归
参考资料
- Schied, C., et al. "Spatiotemporal Variance-Guided Filtering" (EGSR 2017)
工作量估计
- 双边滤波:2-3天
- SVGF:2-3周
七、场景融合
7.1 体积与场景遮挡
问题
当前体积渲染不考虑场景几何体的遮挡。
解决方案
1. 深度测试
// 从深度缓冲重建世界坐标
float depth = depthTexture.Sample(sampler, uv);
float3 sceneWorldPos = reconstructWorldPos(uv, depth);
// 计算到场景的距离
float sceneDist = length(sceneWorldPos - cameraPos);
// Ray Marching 时限制最大距离
float tmax = min(rayTmax, sceneDist);
2. 阴影遮挡
// 场景几何体遮挡体积
float sceneShadow = 1.0;
if (isOccludedByScene(pos)) {
sceneShadow = 0.0;
}
工作量估计
- 深度测试:2-3天
- 完整遮挡:1周
7.2 阴影接收
问题
体积应该接收场景几何体的阴影。
解决方案
float sceneShadow = SampleSceneShadowMap(pos);
float3 S = sigmaS * phase_function() * shadow * sceneShadow;
工作量估计
- 基础实现:2-3天
八、应用场景
8.1 动态天气系统
功能
- 云层动态演化
- 日出日落光照变化
- 雨雾效果
实现
// 云层演化
float timeOfDay = sin(time * dayCycleSpeed);
float cloudDensity = baseDensity * (0.5 + 0.5 * timeOfDay);
// 光照变化
float3 sunColor = lerp(sunriseColor, noonColor, timeOfDay);
float3 ambientColor = lerp(nightAmbient, dayAmbient, timeOfDay);
// 雾气
float fogDensity = fogBase + fogVariation * sin(time);
工作量估计
- 1-2周
8.2 火焰/爆炸效果
需求
- 温度场驱动
- 黑体辐射颜色
- 自发光
实现
// 温度到颜色 (黑体辐射)
float3 temperatureToColor(float T) {
// Planck 定律近似
float x = T / 10000.0;
float3 color;
color.r = 1.0;
color.g = clamp(1.0 - exp(-5.0 * x), 0, 1);
color.b = clamp(1.0 - exp(-10.0 * x), 0, 1);
return color;
}
// 发光
float3 emission = temperatureToColor(temperature) * emissionIntensity;
float3 S = sigmaS * phase_function() * shadow + emission;
工作量估计
- 1周
九、推荐方案总结
方案A:渲染深化(学术向)
多散射 + 相函数 + Temporal Reprojection
- 难度:⭐⭐⭐⭐
- 工作量:4-5周
- 论文价值:高
- 适合:想发论文/读研
方案B:工程完整(展示向)
相机交互 + 参数UI + 多光源 + 大气散射
- 难度:⭐⭐⭐
- 工作量:3-4周
- 展示效果:好
- 适合:答辩演示
方案C:离线工具链(创新向)
离线流体模拟器 + OpenVDB集成 + NanoVDB导出
- 难度:⭐⭐⭐⭐⭐
- 工作量:6-8周
- 创新性:强
- 适合:想深入图形学
方案D:程序化生成(创意向)
程序化云生成 + 参数控制 + 动态天气
- 难度:⭐⭐⭐⭐
- 工作量:3-4周
- 展示性:强
- 适合:想展示效果
方案E:平衡方案(推荐)
多散射 + Temporal Reprojection + 相函数 + 程序化扰动动画
- 难度:⭐⭐⭐⭐
- 工作量:4-5周
- 效果提升明显
- 有技术深度
- 工作量可控
十、时间规划建议
单方向深入(4-5周)
| 阶段 | 内容 | 时间 |
|---|---|---|
| 第1周 | 多散射基础实现 | 7天 |
| 第2周 | 多散射优化 + 相函数 | 7天 |
| 第3周 | Temporal Reprojection | 7天 |
| 第4周 | 程序化扰动 + 效果调优 | 7天 |
| 第5周 | 文档 + 测试 | 7天 |
多方向扩展(6-8周)
| 阶段 | 内容 | 时间 |
|---|---|---|
| 第1-2周 | 渲染效果提升 | 14天 |
| 第3-4周 | 性能优化 | 14天 |
| 第5-6周 | 交互与控制 | 14天 |
| 第7周 | 效果调优 | 7天 |
| 第8周 | 文档 + 测试 | 7天 |
十一、参考资料汇总
论文
- Jensen, H. W., et al. "A Practical Model for Subsurface Light Transport" (SIGGRAPH 2001)
- Kutz, P., et al. "Spectral and Decomposition Tracking for Rendering Heterogeneous Volumes" (SIGGRAPH 2017)
- Stam, J. "Stable Fluids" (SIGGRAPH 1999)
- Fedkiw, R., et al. "Visual Simulation of Smoke" (SIGGRAPH 2001)
- Karis, B. "High Quality Temporal Supersampling" (SIGGRAPH 2014)
- Schied, C., et al. "Spatiotemporal Variance-Guided Filtering" (EGSR 2017)
书籍
- Bridson, R. "Fluid Simulation for Computer Graphics"
- Ebert, D. S., et al. "Texturing & Modeling: A Procedural Approach"
- Pharr, M., et al. "Physically Based Rendering: From Theory to Implementation"
开源项目
- OpenVDB: https://github.com/AcademySoftwareFoundation/openvdb
- NanoVDB: https://github.com/AcademySoftwareFoundation/nanovdb
- Mantaflow: https://mantaflow.uni-mainz.de/
- Dear ImGui: https://github.com/ocornut/imgui
文档版本: 1.0 更新日期: 2026年3月