3DGS가 explicit representation이다 보니 여러 3DGS를 좌표계만 통일한다면 한 공간에 모아서 렌더링할 수 있다. ply를 위치만 맞춰서 합쳐주면 그대로 합쳐진 scene이 된다. 이전 글 참고.
생각보다 재밌는 것들을 많이 해볼 수 있는데 이 때 맘에 드는 scene이나 object를 3DGS로 만들기 좋은 툴을 소개한다. 직접 찍어서 COLMAP + nerfstudio (or gsplat)을 태워서 복원할 수도 있지만 이게 해보면 COLMAP이 안 깨지게 맞추는 것도 어렵고 real scene이기 때문에 퀄리티 높게 복원하기가 쉽지 않다.
추천하는 대안은 블렌더에서 가상 렌더링 이미지 + 카메라 포즈로 복원해내는 것이다.
BlenderNeRF

https://github.com/maximeraafat/BlenderNeRF
Blender에 원하는 asset을 불러온 상태에서 위 플러그인을 사용하면, 손쉽게 sphere 형태로 카메라를 생성하고 렌더링된 이미지를 얻을 수 있다. 동시에 nerfstudio에서 사용하는 transform.json과 images 폴더 형태로 정리까지 해주기 때문에 사실 상 바로 nerfstudio에 집어넣으면 3DGS로 완성된다.
설치
- 일단 위 링크의 git repository 전체를 zip 파일로 다운받는다. (위치는 상관없음. 압축해제 하지 말 것.)
- Blender 4.0.0 이상 버전 설치 (pip install blender로 하면 3.x.x 가 설치되므로 직접 설치하는 것을 권장.)
- Blender를 켜고 Edit > Preferences > Add-ons 에 들어가 Install From Disk 선택
- 아까 받아놓은 zip 파일 선택


위 과정을 따르면 우측 상단에 세로바를 < 눌러서 열어 보면 BlenderNeRF 플러그인이 설치된 것을 볼 수 있다.
*주의 : blender를 설치한 것이 아니라 실행형 파일을 다운받아 사용할 경우, 창을 껐을 때 플러그인이 해제된다.
사용법
1. 맘에 드는 Asset 구하기
무료 에셋 공유 사이트 (예: https://www.cgtrader.com/)를 하나 찾아들어간다.

거기서 원하는 asset을 검색하되, format을 .blend로 (사실 블렌더에 호환만 되면 fbx 같은 것도 된다.) 가격을 Free로 설정한다. 그리고 다운로드 받으면 된다. 위치 상관없음. 어차피 불러올 거라.
2. Blender 설정

기본은 일단 asset과 카메라 1대가 있어야 된다. 경험 상 light도 있어야 된다. light가 없으면 렌더링이 너무 어둡게 나옴. 렌더링 개수는 100이 기본 설정인데 시간적 여유나 메모리 여유가 된다면 늘릴 수록 좋은 건 당연한 얘기.


Camera on Sphere COS 이게 우리가 핵심적으로 다뤄야 할 부분인데 Sphere를 활성화시켰을 때 보이는 저 주황색 가이드가 실제 렌더링 카메라가 움직일 구가 된다. 물체가 만약 원점 중심이 아니라면 Location과 Scale, Radius를 조절해서 asset을 충분히 감싸도록 조절해야 한다. (주황색 가이드 라인이 안 보일 때도 있는데 그 때는 감으로 해야 함.)
밑에 PLAY COS를 눌러보면 실제 렌더링이 시작되는데 설정하는 단계에서는 몇번 테스트 렌더링 해보면서 조절해보면 되겠다.


렌더링을 해보면 뒤지게 느릴 때가 있는데 이럴 경우 asset이 너무 복잡하거나 (잔디나 머리카락처럼 미세 구조물이 너무 많아 vertex, face 수가 엄청 많을 때) 렌더링 설정이 너무 과한 경우다.
이럴 경우 최상단 툴바에서 Rendering을 클릭한 뒤 우측 상단을 보면 위와 같은 Scene탭이 보이는데, 여기서 Render Engine을 EEVEE로 바꾸고 Format에서 렌더링 이미지 해상도를 줄여주면 큰 도움이 된다.

더불어서 다시 Layout 뷰로 돌아왔을 때 우측 하단에 Sampling > Film > Transparent 체크를 해주는게 좋다. 이게 이미지를 PNG로 저장해서 alpha channel을 같이 저장해주는데 nerfstudio에서 PNG를 넣어줄 경우 자동으로 mask로 사용하기 때문에 복원 성능이 크게 좋아진다.

설정을 마친뒤 PLAY COS를 누르면 저장 위치에 이렇게 저장되는데 저 transforms.json을 transforms.json으로만 바꿔주면 nerfstudio에 그대로 가져다 쓸 수 있다.
꿀팁
1. 조명 추가


렌더링해보면 이따위로 어두컴컴하게 나올 확률이 거의 100%인데 이건 사용한 asset이 웬만하면 light를 포함 안하고 있을 것이라 그렇다. 이건 입맛에 맞는 조명을 sphere 주변 위치에 배치하고 power를 엄청 세게 해주면 해결된다. 10000W 이런 수준으로 밝아야 좋다.
2. 카메라 포즈 normalization + dummy poincloud 생성

transforms.json을 열어보면 카메라 포즈가 생성했던 반경 radius에 맞춰져있기 때문에 굉장히 큰 것을 볼 수 있다.
import os
import json
if __name__ == "__main__":
root = "/home/jseob/Downloads/blender_results/korea-traditional-house-sarangchae/dataset"
file_path = os.path.join(root, "transforms.json")
save_path = os.path.join(root, "transforms_scaled.json")
def scale_transforms(input_path, output_path, scale_factor):
with open(input_path, 'r') as f:
data = json.load(f)
for frame in data['frames']:
matrix = frame['transform_matrix']
# Scale down the translation part (the last column of first 3 rows)
for i in range(3):
matrix[i][3] *= scale_factor
with open(output_path, 'w') as f:
json.dump(data, f, indent=4)
# 사용 예시
scale_transforms(file_path, save_path, scale_factor=1/20) # s = 0.05로 예시
요로코롬 간단한 코드를 사용해서 unit sphere로 옮겨주면 좋음. 이건 사실 nerfstudio에서 자체 normalization을 하기 때문에 쓸모 없는 일일 수 있는데 뒤에 추후 3DGS 완성되었을 때 scale이 unit sphere면 후가공이 쉬울 뿐더러 초기 pointcloud를 만들기도 쉽다.
import numpy as np
import struct
def random_points_in_unit_sphere(n=1000):
vec = np.random.normal(size=(n, 3))
vec /= np.linalg.norm(vec, axis=1)[:, None]
radius = np.random.uniform(0, 1, size=(n,)) ** (1 / 3)
return vec * radius[:, None]
def write_custom_ply(filename, points):
num_points = len(points)
header = f"""ply
format binary_little_endian 1.0
element vertex {num_points}
property float x
property float y
property float z
property float nx
property float ny
property float nz
property uchar red
property uchar green
property uchar blue
property uchar class
end_header
"""
with open(filename, 'wb') as f:
f.write(header.encode('utf-8'))
for pt in points:
data = struct.pack(
'<fff' # x, y, z
'fff' # nx, ny, nz
'BBB' # r, g, b
'B', # class
pt[0], pt[1], pt[2],
0.0, 0.0, 0.0,
128, 128, 128,
0
)
f.write(data)
points = random_points_in_unit_sphere(10000)
import open3d as o3d
pcd = o3d.geometry.PointCloud(points=o3d.utility.Vector3dVector(points.reshape(-1, 3)))
colors = np.zeros_like(points.reshape(-1, 3))
colors[:, :] = 0.5
pcd.colors = o3d.utility.Vector3dVector(colors)
o3d.io.write_point_cloud('/home/jseob/Downloads/blender_results/korea-traditional-house-sarangchae/dataset/sparse.ply',
pcd)
그 다음은 초기 pointcloud (unit sphere 모양)을 만들어 주면 좋다. nerfstudio가 pointcloud를 입력으로 안해줄 경우 복원 볼륨 전체에 random point를 생성해서 초기화를 하는데 이렇게 시작하면 성능이 매우 매우 매우 매우 매우 구리다.
따라서 앞서 카메라 포즈를 unit sphere에 맞추어 normalization한 것과 더불어 위 코드로 unit sphere 안에만 가득찬 pointcloud를 생성하고 이걸 초기값으로 쓰는게 훨씬 낫다.


transforms.json에는 위와 같이 "ply_file_path" : "sparse.ply", 와 같은 line을 추가해줘서 nerfstudio가 초기화에 사용할 수 있도록 연결해준다.
ns-train splatfacto --data ./dataset --pipeline.model.use-scale-regularization True --pipeline.model.rasterize-mode antialiased --pipeline.model.stop-split-at 45000 --max_num_iterations=50000
최종적으로 위 커맨드로 nerfstudio splatfacto로 복원하는게 제일 좋더라.
결과

nerfstudio splatfacto를 이용해 결과 진짜 깔끔하게 잘 만들어지고 (카메라 포즈가 100% 정확하니까) supersplat에 불러와서 다른 3DGS랑 합쳐버리면 위와 같이 최종 결과물을 낼 수 있다.
방금 복원한 사랑채 안에 미리 복원했던 사람 올려두기
'Knowhow > Vision' 카테고리의 다른 글
| Nerfstudio CUDA 12에서 설치하기 (0) | 2025.08.12 |
|---|---|
| 3D Gaussian splat PLY 파일 합치기/압축하기 (How to merge/compress 3DGS PLYs) (0) | 2025.07.03 |
| Ava256, Multiface template mesh 분석 (vertex 7306 <-> vertex 5509) (0) | 2025.04.14 |
| SMPL part labeling, SMPL segmentation, SMPL 파트 나누기 (8) | 2025.04.04 |
| SMPLX uv mapping/coordinate 사용 시 유의점 (0) | 2024.11.22 |