用 LixelStudio 网格为 3DGS 重打光
插件支持混合重打光:把 3DGS 与同比例的网格模型结合使用,让 3DGS 获得接近网格的光照响应,同时还能产生自阴影。
效果取决于 3DGS 下方那份网格的几何精度。网格与 3DGS 对齐得越好,光照响应和阴影就越真实。
选哪种网格
LCC Studio 和 LixelStudio 都能生成网格,但用途不同。
| LCC Studio 网格 | LixelStudio 网格 | |
|---|---|---|
| 表面精度 | 较粗糙 | 更贴合真实形状 |
| 文件体积 | 小 | 大 |
| 适合 | 碰撞检测 | 重打光 |
用于碰撞时选 LCC Studio 网格就够了,见碰撞。用于重打光要选 LixelStudio 网格。
LCC Studio 网格:


LixelStudio 网格:


步骤 1:把分块模型集中到一个文件夹
LixelStudio 导出的网格是分块的。场景大的时候会得到很多文件夹,每个里面有一个 .obj 文件和贴图。导入虚幻引擎前要先合并成一个网格,否则会得到许多独立的 .obj。
下面用 Blender 完成,其他三维软件同理。Blender 从官网下载。
Blender 不支持一次性把一批文件夹拖进去,得先把散在各个子文件夹里的 .obj 提取到同一层。手动拖几十个文件夹不现实,用一条 PowerShell 命令解决。
进入包含所有子文件夹的根目录,在地址栏输入 powershell 后按 Enter,终端会在当前目录打开。

在资源管理器地址栏输入 powershell
复制粘贴下面这条命令:
Get-ChildItem -Recurse -File | Move-Item -Destination .
注:命令会把所有子文件夹里的文件提取到当前目录,运行前先确认自己在正确的目录下。比如目录 A 下有 B 和 C,就要在 A 的地址栏打开 PowerShell,运行后 B 和 C 里的文件都会被移动到 A。

运行前的目录结构

粘贴命令后执行
粘贴后按 Enter 运行,需要几秒。出现新的提示符表示执行完毕。
这时所有内容都被提取出来了,点开几个子文件夹确认里面已经清空。

所有 .obj 与贴图已集中到同一层
步骤 2:在 Blender 中合并为一个文件
按 Ctrl + A 选中所有文件,拖进 Blender。

全选所有文件

拖入 Blender
点击 Import Wavefront OBJ 导入。为保证朝向正确,把 Up Axis 设为 Z,Forward Axis 设为 X。

Up Axis 设为 Z,Forward Axis 设为 X
这时能看到所有贴片都已导入。

所有贴片已导入
按 A 选中所有贴片,按 Ctrl + J 合并成单个网格。场景大的时候要等一分钟左右,等它跑完。

合并后得到单个网格
注:不需要整个场景参与重打光时,可以删掉用不到的部分,能提升虚幻引擎中的性能。
步骤 3:减少渲染调用
这份网格导入虚幻引擎用于重打光时,材质全都用不上。3DGS 负责画面呈现,代理网格只参与计算,材质留着既没用又占资源。
LixelStudio 网格的每张贴图在虚幻引擎里都是一次渲染调用。规模大的时候会产生大量渲染调用,直接拿去当代理网格会卡。
在 Collection 里选中网格,用 Blender 的 Python 控制台查看渲染调用次数。

切换到 Python 控制台
粘贴下面的命令后按 Enter 执行:
obj = bpy.context.object
print("Material slots:", len(obj.data.materials))

输出的数值就是渲染调用次数
图中这份网格有 8859 次渲染调用。
要把渲染调用降下来,删掉所有现有材质,替换为单一材质:
import bpy
# Get the currently selected object in Blender
obj = bpy.context.object
# Make sure a valid Mesh object is selected
if obj is None or obj.type != "MESH":
raise Exception("Please select a Mesh object first.")
# Create a new white clay material
mat = bpy.data.materials.new("M_White_Clay")
mat.use_nodes = True
# Configure the Principled BSDF shader settings
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = (1, 1, 1, 1) # White color
bsdf.inputs["Roughness"].default_value = 1.0 # Fully rough / matte surface
bsdf.inputs["Metallic"].default_value = 0.0 # Non-metallic material
bsdf.inputs["Alpha"].default_value = 1.0 # Fully opaque
# Remove all existing material slots from the mesh
obj.data.materials.clear()
# Add the single new white material
obj.data.materials.append(mat)
# Assign every face to material slot 0
for poly in obj.data.polygons:
poly.material_index = 0
# Print the result for verification
print("Done")
print("Material slots:", len(obj.data.materials))
print("Used material indices:", len(set(p.material_index for p in obj.data.polygons)))
print("Estimated draw calls:", len(set(p.material_index for p in obj.data.polygons)))
运行后确认渲染调用次数已经降到一次。

渲染调用已降为一次
步骤 4:导入虚幻引擎
把网格导出为 .obj 或 .fbx,导入虚幻引擎。

网格导入虚幻引擎后的效果
接着按代理网格的步骤放置 ProxyMesh Actor,并把法线模式设为 ProxyMesh。
效果不对时见代理网格故障排查。