項目地址:
GitHub - NJU-3DV/Relightable3DGaussian: [ECCV2024] 可重新照明的 3D 高斯:使用 BRDF 分解和光線追蹤的實時點云重新照明
可優化參數
gaussians.training_setup(opt)
if is_pbr::?direct_env_light.training_setup(opt)
光照解耦
update_visibility
分塊處理高斯
對每個高斯體,采樣?sample_num?個入射方向,計算每個方向的可見性(是否被遮擋)、方向向量、該方向在球面上的面積權重,并將這些結果保存下來
?fibonacci_sphere_sampling斐波那契球面采樣
輸出
- incident_dirs:每個點/法線的?sample_num?個采樣方向(單位向量),用于模擬不同角度的入射光。
- ?normals?是?[N,?3],則輸出?incident_dirs?是?[N,?sample_num,?3]
- incident_areas:每個采樣方向的面積權重(都相同),用于積分時加權。
- incident_dirs形狀[N, sample_num, 1]
在?fibonacci_sphere_sampling?代碼中,采樣方向是圍繞每個高斯體的法線方向生成的:
- 首先在?z?軸方向上生成均勻分布的球面采樣(fibonacci?sphere)。
- 然后通過旋轉,把這些方向對齊到每個高斯體的法線方向(rotation_between_z(normals))。
- 如果?random_rotate=True,還會加上一個隨機旋轉,使得每個高斯體的采樣方向分布更加均勻、隨機。
rotation_matrix?=?rotation_between_z(normals)
incident_dirs?=?rotation_matrix?@?z_samples
- 這里?rotation_matrix?是針對每個高斯體的法線單獨計算的。
- 所以?incident_dirs?是每個高斯體獨立的一組方向。
render(is_training=true)
render_pkg = render_fn(viewpoint_cam, gaussians, pipe, background,opt=opt, is_training=True, dict_params=pbr_kwargs, iteration=iteration)pbr_kwargs['sample_num'] = pipe.sample_num#64pbr_kwargs["env_light"] = direct_env_light
高斯增加優化屬性
- _base_color:表面基礎色(反射率/漫反射色),PBR渲染的核心屬性。
- _roughness:表面粗糙度,影響高光和反射的模糊程度。
- _incidents_dc/incidents_rest:每個高斯體的入射光照分布(用球諧函數表示),用于高效地近似全局光照。
- _visibility_dc/visibility_rest:每個高斯體的可見性分布(同樣用球諧函數表示),用于陰影和遮擋的近似。
first_iter = gaussians.create_from_ckpt(args.checkpoint, restore_optimizer=True)
?初始化這些屬性為0
環境光照(IBL - Image-Based Lighting)??
- ?模擬全局光照?:環境貼圖存儲了來自各個方向的環境光強度與顏色,為物體表面提供非直接光照(如漫反射、鏡面反射)。
- ?PBR 材質依賴?:在基于物理的渲染中,環境貼圖是計算材質反射、折射的基礎輸入(如金屬高光、玻璃透射)
PBR最終像素顏色和渲染方程Rendering equation
?參考徹底看懂PBR/BRDF方程 - 張亞坤的文章 - 知乎
https://zhuanlan.zhihu.com/p/158025828
?base_color:點云的“固有色”。
?pbr:這個點在當前光照、視角、材質等條件下,真實應該呈現出來的顏色。
pbr
?:最終像素顏色(Lambert 漫反射 + GGX 鏡面反射)
輸出結果brdf_color和extra_results
extra_results = {"incident_dirs": incident_dirs,"incident_lights": incident_lights,"local_incident_lights": local_incident_lights,"global_incident_lights": global_incident_lights,"incident_visibility": incident_visibility,"diffuse_light": diffuse_light,"specular": specular,}
基于物理的渲染方程(PBR)??:
其中:
? ? f_d = base_color[:, None] / np.pi
? ? f_s = GGX_specular(normals, viewdirs, incident_dirs, roughness, fresnel=0.04)
- Lo?:出射光(最終顏色)
- fdiffuse?:漫反射 BRDF
- fspecular?:鏡面反射 BRDF
- Li?:入射光強度
- n?ωi?:?余弦項?(法線與入射光方向的點積)
diffuse_light = transport.mean(dim=-2):: 表示表面接收到的總光照強度
rasterizer結果
變量名 | 說明 | 典型形狀 |
---|---|---|
num_rendered | 渲染的高斯點總數 | 標量/int |
num_contrib | 每像素有貢獻的高斯點數 | [H, W] |
rendered_image | 渲染出的RGB圖像 | [3, H, W] |
rendered_opacity | 渲染出的不透明度圖 | [1, H, W] |
rendered_depth | 渲染出的深度圖 | [1, H, W] |
rendered_feature | 渲染出的特征圖(多通道) | [C, H, W] |
rendered_pseudo_normal | 渲染出的偽法線圖 | [3, H, W] |
rendered_surface_xyz | 渲染出的表面3D坐標 | [3, H, W] |
weights | 高斯點對像素的貢獻權重 | [N, H, W] |
radii | 高斯點在屏幕上的半徑 | [N] |
render輸出results
feature和rendered_image、rendered_pbr
if is_training:features = torch.cat([depths, depths2, brdf_color, normal, base_color, roughness, extra_results["diffuse_light"], extra_results["incident_visibility"].mean(-2)], dim=-1)else:features = torch.cat([depths, depths2, brdf_color, normal, base_color, roughness,extra_results["diffuse_light"], extra_results["specular"], extra_results["incident_lights"].mean(-2),extra_results["local_incident_lights"].mean(-2),extra_results["global_incident_lights"].mean(-2),extra_results["incident_visibility"].mean(-2)], dim=-1)
?在gaussian_renderer\neilf.py里的render_view
?在gaussian_renderer\r3dg_rasterization.py中
num_rendered, num_contrib, color, opacity, depth, feature, normal, surface_xyz, weights, radii, geomBuffer, binningBuffer, imgBuffer = _C.rasterize_gaussians(*args)
在r3dg-rasterization\ext.cpp中
?在r3dg-rasterization\rasterize_points.cu中
return std::make_tuple(
rendered, n_contrib, out_color, out_opacity, out_depth, out_feature, out_normal, out_surface_xyz, out_weights, radii, geomBuffer, binningBuffer, imgBuffer);
與 下圖一一對應
num_rendered, num_contrib, color, opacity, depth, feature, normal, surface_xyz, weights, radii, geomBuffer, binningBuffer, imgBuffer = _C.rasterize_gaussians(*args)
在r3dg-rasterization\cuda_rasterizer\forward.cu中?
rendered_image:是通過球諧函數(Spherical?Harmonics, SH)渲染得到的圖像,即上圖中的out_color
rendered_pbr:是通過物理基礎渲染(Physically Based Rendering, PBR)得到的圖像,即上圖中的out_feature(對應render_feature)中的一部分
pbr = rendered_pbr
rendered_pbr = pbr * rendered_opacity + (1 - rendered_opacity) * bg_color[:, None, None]
out_feature(對應render_feature)由features加權得出,rendered_pbr對應由brdf_color加權
if is_training:features = torch.cat([depths, depths2, brdf_color, normal, base_color, roughness, extra_results["diffuse_light"], extra_results["incident_visibility"].mean(-2)], dim=-1)else:features = torch.cat([depths, depths2, brdf_color, normal, base_color, roughness,extra_results["diffuse_light"], extra_results["specular"], extra_results["incident_lights"].mean(-2),extra_results["local_incident_lights"].mean(-2),extra_results["global_incident_lights"].mean(-2),extra_results["incident_visibility"].mean(-2)], dim=-1)
重照明relighting.py
?文件路徑變量
scene_config_file
路徑:{args.config}/transform.json
作用:場景的空間變換配置文件,通常描述每個子場景或物體的變換矩陣(如平移、旋轉、縮放)。
traject_config_file
路徑:{args.config}/trajectory.json
作用:相機軌跡配置文件,通常描述渲染時相機的運動路徑、每一幀的相機參數等。
light_config_file
路徑:{args.config}/light_transform.json
作用:光源軌跡或光照變換配置文件,描述光源的位置、方向、變化等(如果有動態光照)。
為什么可以實現
if iteration % args.save_interval == 0 or iteration == args.iterations:#5000print("\n[ITER {}] Saving Gaussians".format(iteration))scene.save(iteration)
在運行script\run_nerf.sh后?保存高斯各屬性在output/NeRF_Syn/hotdog/neilf/point_cloud/iteration_40000/point_cloud.ply類似路徑,在configs\nerf_syn_light\transform.json中保存各場景路徑和transform矩陣
?在scene_composition中加載ply文件,
relighting.py?的流程:讀取高斯體模型(如?.ply?文件);讀取環境光照(如?.hdr?文件;讀取相機參數;用渲染器(如?render_fn_dict['neilf'])直接渲染圖片。
只要有一個已經擬合好的3D場景(高斯體參數),可以隨時改變光照條件(比如換環境貼圖),然后用渲染器重新渲染出不同光照下的圖片。這就是重照明。因為場景的幾何和材質參數已經在訓練階段學好了,渲染時只需要前向推理(forward),不需要再優化參數。
?`scene_composition`
功能
scene_composition
?的作用是:
將多個場景(或點云)的高斯體模型合成為一個整體高斯體模型,并做必要的初始化。
具體流程如下:
-
遍歷?
scene_dict
,為每個子場景加載高斯體點云(.ply
?文件),并應用相應的變換(transform
),把每個子場景的點云(高斯體)通過指定的?4x4?變換矩陣變換到全局坐標系,。 -
把所有加載好的高斯體模型合成為一個大模型(調用?
GaussianModel.create_from_gaussians
)。 -
對合成后的模型做一些初始化(如可見性、入射光參數等)。
-
返回合成后的高斯體模型。
-
scene_dict: dict
-
字典格式,key 是場景名,value 是一個字典,包含:
-
"path"
:點云文件路徑(.ply) -
"transform"
:4x4 的變換矩陣(list 或 array)
-
-
-
dataset: ModelParams
-
數據集參數對象,至少包含?
sh_degree
?等高斯體模型初始化所需參數。
-
-
gaussians_composite: GaussianModel
-
合成后的高斯體模型對象,包含所有子場景的點云和參數,已經應用了各自的變換,并做了初始化。
-
-
加載每個子場景的高斯體模型并變換
for scene in scene_dict:gaussians = GaussianModel(dataset.sh_degree, render_type="neilf")gaussians.load_ply(scene_dict[scene]["path"])torch_transform = torch.tensor(scene_dict[scene]["transform"], device="cuda").reshape(4, 4)gaussians.set_transform(transform=torch_transform)gaussians_list.append(gaussians)
-
合成所有高斯體模型
gaussians_composite = GaussianModel.create_from_gaussians(gaussians_list, dataset)
-
初始化可見性和入射光參數
n = gaussians_composite.get_xyz.shape[0] gaussians_composite._visibility_rest = (torch.nn.Parameter(torch.cat([gaussians_composite._visibility_rest.data,torch.zeros(n, 5 ** 2 - 4 ** 2, 1, device="cuda", dtype=torch.float32)],dim=1).requires_grad_(True))) gaussians_composite._incidents_dc.data[:] = 0 gaussians_composite._incidents_rest.data[:] = 0
-
返回合成后的模型
return gaussians_composite
-
功能:合成多個高斯體點云為一個整體,并初始化相關參數。
-
輸入:場景字典(含路徑和變換)、數據集參數。
-
輸出:合成后的高斯體模型對象(
GaussianModel
)。
render(is_training=false)
render_kwargs = {"pc": gaussians_composite,"pipe": pipe,"bg_color": background,"is_training": False,"dict_params": {"env_light": light,"sample_num": args.sample_num,#384},"bake": args.bake}
with torch.no_grad():render_pkg = render_fn(viewpoint_camera=custom_cam, **render_kwargs)
在?render_view
?函數中,is_training
?參數為 True 或 False 時,渲染流程和輸出內容有明顯區別,is_training
?為 True 時,流程更高效、特征更精簡、用于訓練和損失計算;為 False 時,流程更全面、特征更豐富、用于推理、評估和可視化。
主要體現在以下幾個方面:
is_training=True
- 直接對所有點一次性調用?
rendering_equation
,效率高,適合訓練時的批量處理。 - 代碼片段:
if is_training:brdf_color, extra_results = rendering_equation(base_color, roughness, normal.detach(), viewdirs, incidents,direct_light_env_light, visibility_precompute=pc._visibility_tracing, incident_dirs_precompute=pc._incident_dirs, incident_areas_precompute=pc._incident_areas)
- 直接對所有點一次性調用?
is_training=False
- 為了節省顯存,采用分塊(chunk)處理,每次只處理一部分點,適合測試/推理時大規模渲染。
- 代碼片段:
else:chunk_size = 100000brdf_color = []extra_results = []for i in range(0, means3D.shape[0], chunk_size):_brdf_color, _extra_results = rendering_equation(...)brdf_color.append(_brdf_color)extra_results.append(_extra_results)brdf_color = torch.cat(brdf_color, dim=0)extra_results = {k: torch.cat([_extra_results[k] for _extra_results in extra_results], dim=0) for k in extra_results[0]}torch.cuda.empty_cache()
-
is_training=True
-
拼接的特征較少,只包含訓練所需的內容。
- 代碼片段:
features = torch.cat([depths, depths2, brdf_color, normal, base_color, roughness, extra_results["diffuse_light"], extra_results["incident_visibility"].mean(-2)], dim=-1)
-
-
is_training=False
-
拼接的特征更豐富,包含更多渲染細節,便于評估和可視化。
- 代碼片段:
features = torch.cat([depths, depths2, brdf_color, normal, base_color, roughness,extra_results["diffuse_light"], extra_results["specular"], extra_results["incident_lights"].mean(-2),extra_results["local_incident_lights"].mean(-2),extra_results["global_incident_lights"].mean(-2),extra_results["incident_visibility"].mean(-2)], dim=-1)
-
-
is_training=True
-
rendered_feature
?只拆分出訓練所需的特征(如 base_color、roughness、diffuse、visibility)。
-
-
is_training=False
-
rendered_feature
?拆分出更多特征(如 specular、lights、local_lights、global_lights 等),便于評估和可視化。
-
-
is_training=False
-
還會輸出?
render_env
、pbr_env
、env_only
?等環境光相關的渲染結果,便于評估不同光照下的表現。
-
-
is_training=True
-
這些環境光相關的輸出不會被計算,節省計算資源。
-
-
is_training=True
-
會進一步調用?
calculate_loss
?計算損失和訓練日志。
-
-
is_training=False
-
不計算損失,只輸出渲染結果。
-
is_training | 主要用途 | BRDF計算 | 特征拼接 | 輸出內容 | 損失計算 | 評估/可視化 |
---|---|---|---|---|---|---|
True | 訓練 | 一次性 | 精簡 | 精簡 | 有 | 少 |
False | 推理/評估 | 分塊 | 豐富 | 豐富 | 無 | 多 |
render輸出results
評估?
- train.py?的評估是為了訓練過程中的監控和調優,通常和訓練流程綁定。
- eval_nvs.py?是專門為獨立評測和論文展示設計的,方便單獨運行和批量評測。