지난 글에서 비디오 생성 모델의 시간 어텐션 구조를 다뤘다. 이번 글에서는 2D 이미지를 넘어 3D 공간을 이해하고 생성하는 기술들을 완전 해설한다. NeRF로 시작해 3D Gaussian Splatting, DreamFusion, Zero123++ 등 최신 3D 생성 AI까지 다룬다.
3D 표현 방식 개요
3D 장면을 컴퓨터로 표현하는 방법은 크게 세 가지다.
명시적 표현: 메시(Mesh), 포인트 클라우드처럼 3D 좌표를 직접 저장한다. 렌더링이 빠르지만 복잡한 장면 편집이 어렵다.
암묵적 표현: NeRF처럼 신경망이 공간 좌표를 입력받아 색과 밀도를 출력한다. 연속적이고 부드럽지만 렌더링이 느리다.
혼합 표현: 3D Gaussian Splatting처럼 명시적 가우시안 포인트가 명시적이지만 렌더링은 소팅·래스터라이저로 빠르게 처리한다.
NeRF: 신경 복사 필드
NeRF는 3D 공간의 임의 위치 (x,y,z)와 관측 방향 (θ,φ)를 MLP에 입력하면, 그 위치의 색 RGB와 밀도 σ를 출력한다. 카메라 레이를 따라 샘플링된 여러 포인트의 출력을 볼륨 렌더링으로 통합해 최종 픽셀 색을 계산한다.
import torch
import torch.nn as nn
import numpy as np
class NeRF(nn.Module):
def __init__(
self,
pos_enc_dim: int = 10, # 위치 인코딩 차수
dir_enc_dim: int = 4, # 방향 인코딩 차수
hidden_dim: int = 256,
):
super().__init__()
pos_in = 3 + 3 * 2 * pos_enc_dim
dir_in = 3 + 3 * 2 * dir_enc_dim
# 밀도 예측 네트워크 (위치만 사용)
self.density_net = nn.Sequential(
nn.Linear(pos_in, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
)
self.density_out = nn.Linear(hidden_dim, 1)
# RGB 예측 (위치 피처 + 방향)
self.color_net = nn.Sequential(
nn.Linear(hidden_dim + dir_in, hidden_dim // 2),
nn.ReLU(),
nn.Linear(hidden_dim // 2, 3),
nn.Sigmoid(),
)
self.pos_enc_dim = pos_enc_dim
self.dir_enc_dim = dir_enc_dim
def positional_encoding(self, x: torch.Tensor, L: int) -> torch.Tensor:
"""사인파 위치 인코딩"""
freqs = 2 ** torch.arange(L, device=x.device).float()
x_freq = (x[..., None] * freqs).reshape(*x.shape[:-1], -1)
return torch.cat([x, x_freq.sin(), x_freq.cos()], dim=-1)
def forward(
self,
pos: torch.Tensor, # (..., 3)
dirs: torch.Tensor, # (..., 3)
) -> tuple[torch.Tensor, torch.Tensor]:
pos_enc = self.positional_encoding(pos, self.pos_enc_dim)
dir_enc = self.positional_encoding(dirs, self.dir_enc_dim)
feat = self.density_net(pos_enc)
sigma = torch.relu(self.density_out(feat)) # 밀도 ≥ 0
color_in = torch.cat([feat, dir_enc], dim=-1)
rgb = self.color_net(color_in) # RGB ∈ [0,1]
return rgb, sigma
def volume_render(
rgb: torch.Tensor, # (N_rays, N_samples, 3)
sigma: torch.Tensor, # (N_rays, N_samples, 1)
t_vals: torch.Tensor, # (N_rays, N_samples)
) -> torch.Tensor:
"""볼륨 렌더링: 레이 적분"""
deltas = t_vals[..., 1:] - t_vals[..., :-1]
deltas = torch.cat([
deltas,
torch.full_like(deltas[..., :1], 1e10)
], dim=-1)
alpha = 1 - torch.exp(-sigma[..., 0] * deltas)
# 누적 투과도
T = torch.cumprod(
torch.cat([
torch.ones_like(alpha[..., :1]),
1 - alpha + 1e-10
], dim=-1), dim=-1
)[..., :-1]
weights = T * alpha # (N_rays, N_samples)
rgb_out = (weights[..., None] * rgb).sum(dim=-2) # (N_rays, 3)
return rgb_out
3D Gaussian Splatting
3DGS는 장면을 수백만 개의 3D 가우시안 타원체로 명시적으로 표현한다. 각 가우시안은 위치·회전·스케일·불투명도·SH 색 계수를 갖는다.
import torch
import torch.nn as nn
class GaussianModel(nn.Module):
def __init__(self, num_gaussians: int = 1_000_000):
super().__init__()
# 각 가우시안의 학습 가능한 속성
self.xyz = nn.Parameter(
torch.randn(num_gaussians, 3) * 0.1
)
self.rotation = nn.Parameter(
torch.zeros(num_gaussians, 4) # 쿼터니언
)
self.scaling = nn.Parameter(
torch.ones(num_gaussians, 3) * 0.01
)
self.opacity = nn.Parameter(
torch.zeros(num_gaussians, 1)
)
self.sh_dc = nn.Parameter(
torch.zeros(num_gaussians, 1, 3) # 구면 조화함수 (DC)
)
def get_covariance(self) -> torch.Tensor:
"""회전 + 스케일 → 3D 공분산 행렬"""
R = self._quat_to_rotmat(self.rotation)
S = torch.diag_embed(torch.exp(self.scaling))
return R @ S @ S.T @ R.T
def _quat_to_rotmat(self, q: torch.Tensor) -> torch.Tensor:
q = nn.functional.normalize(q, dim=-1)
w, x, y, z = q.unbind(-1)
return torch.stack([
1-2*(y**2+z**2), 2*(x*y-w*z), 2*(x*z+w*y),
2*(x*y+w*z), 1-2*(x**2+z**2), 2*(y*z-w*x),
2*(x*z-w*y), 2*(y*z+w*x), 1-2*(x**2+y**2),
], dim=-1).reshape(*q.shape[:-1], 3, 3)
실제 렌더링은 CUDA 커스텀 래스터라이저(gaussian-splatting 라이브러리)를 사용한다. 파이썬으로는 전체 파이프라인을 재현하기 어렵다.
3D 생성 방법 비교
DreamFusion: 텍스트 → 3D (SDS Loss)
DreamFusion은 사전학습된 2D 확산 모델을 "점수 함수"로 활용해 NeRF를 텍스트로 최적화한다. 핵심은 Score Distillation Sampling(SDS) 손실이다.
def sds_loss(
nerf,
diffusion_model,
text_embeddings: torch.Tensor,
camera_poses: torch.Tensor,
guidance_scale: float = 100,
) -> torch.Tensor:
"""Score Distillation Sampling 손실"""
# NeRF로 랜덤 시점 이미지 렌더링
rendered = render_nerf(nerf, camera_poses) # (B, 3, H, W)
# [0,1] → [-1,1] 정규화
rendered_norm = rendered * 2 - 1
# 랜덤 타임스텝 샘플링
t = torch.randint(50, 950, (rendered.shape[0],))
# 노이즈 추가
noise = torch.randn_like(rendered_norm)
noisy = diffusion_model.scheduler.add_noise(
rendered_norm, noise, t
)
# 확산 모델로 노이즈 예측
with torch.no_grad():
noise_pred_cond = diffusion_model.unet(
noisy, t, encoder_hidden_states=text_embeddings
).sample
noise_pred_uncond = diffusion_model.unet(
noisy, t,
encoder_hidden_states=text_embeddings[:1].repeat(
rendered.shape[0], 1, 1
)
).sample
# Classifier-Free Guidance
noise_pred = noise_pred_uncond + guidance_scale * (
noise_pred_cond - noise_pred_uncond
)
# SDS 그래디언트: 예측 노이즈 - 실제 추가된 노이즈
grad = noise_pred - noise
# NeRF 파라미터에 역전파
return (grad * rendered_norm).mean()
Zero123++: 단일 이미지 → 멀티뷰 3D
from diffusers import Zero123PlusPipeline
import torch
from PIL import Image
pipe = Zero123PlusPipeline.from_pretrained(
"sudo-ai/zero123plus-v1.2",
torch_dtype=torch.float16,
).to("cuda")
# 단일 입력 이미지
image = Image.open("object.png").convert("RGBA")
# 6방향 멀티뷰 이미지 생성
result = pipe(
image,
num_inference_steps=75,
guidance_scale=4.0,
).images[0]
# 결과는 6개 뷰(30°, 90°, 150°, 210°, 270°, 330°)
# 이를 NeRF 또는 3DGS에 입력해 3D 재구성
Instant-NGP: 빠른 NeRF 학습
# COLMAP으로 카메라 포즈 추정
colmap automatic_reconstructor \
--workspace_path colmap_output/ \
--image_path images/
# Instant-NGP로 학습 (수 분 내 완료)
python scripts/run.py \
--scene images/ \
--n_steps 5000 \
--save_snapshot model.ingp
# 학습된 NeRF로 새 시점 렌더링
python scripts/run.py \
--load_snapshot model.ingp \
--video_camera_path transforms_test.json \
--video_output output.mp4
실전 사용 가이드
게임 에셋 빠르게 만들 때: Meshy·Tripo3D 같은 상용 서비스로 텍스트/이미지 → 3D 메시 30초 생성.
특정 물체를 사진으로 3D로 만들 때: 폰 카메라 30~50장 촬영 → COLMAP 포즈 추정 → Instant-NGP 또는 3DGS로 학습.
텍스트로 창의적 3D 만들 때: DreamFusion·Fantasia3D (품질↑ 시간↑) 또는 Zero123++(빠름).
실시간 뷰어 필요할 때: 3DGS + SuperSplat 뷰어 → 브라우저에서 30fps 렌더.
다음 글에서는 CV에서 오디오 처리로 영역을 옮겨 자동 음성 인식(ASR)의 원리와 Whisper 실전 사용법을 다룬다.
지난 글: 비디오 생성 모델: Sora·AnimateDiff·SVD 완전 해설
다음 글: 자동 음성 인식(ASR): Whisper와 스트리밍 음성 처리 완전 해설
읽어주셔서 감사합니다. 😊

