만개한 통로
Bloom Passage
Bloom Passage - Thin-Petal Volumetric Flower Cavern
해당 글은 AI로 대부분의 코드가 작성됨을 알림.
글은 사람이 작성했으나, 수식 및 및 코드 대부분 AI가 작성함.
bloom-passage는 카메라가 굽은 동굴을 통과하면 벽과 천장에서 안쪽을 향해 자란 타원형 봉오리가 가까운 거리에서 열리는 WebGL2 볼륨 레이마칭 장면이다.
꽃잎은 두꺼운 SDF 입체나 CPU 메쉬가 아니다. 곡선 중간면 주위에 매우 얇은 밀도층을 정의하고, 암벽 밀도와 함께 광선을 따라 방출·흡수와 투과율을 적분한다.
현재 구현의 기준은 다음과 같다.
개화 상태는 시간 난수가 아니라 카메라 거리만 소유한다.
꽃잎 부착점은 고정되고 자유단부터 갈라진다.
꽃잎은 타원 단면 튜브가 아니라 8.8–10.2 mm 두께의 sheet density다.
설상화는 끝 폭을 유지한 채 중심선 바깥을 타원 cap으로 닫아 자유단이 뾰족해지지 않는다.
백색 조직은 높은 흡광계수로 거의 불투명하되 가장자리에서만 약한 투과를 남긴다.
줄기 끝점 하나가 줄기, 꽃받침, 화심, 꽃잎 프레임을 모두 소유한다.
모든 꽃의 줄기는 동굴 외벽에서 중심을 향하는 국소 법선에 정렬된다.
최종 색은 surface hit가 아니라 Beer–Lambert front-to-back 적분으로 얻는다.
1. 곡선 동굴과 방사–축 반복
동굴 중심선은 진행 좌표 z에 대한 두 주파수의 합이다.
C(z) = (
0.82sin(0.18z) + 0.24sin(0.47z + 1.30),
0.55cos(0.15z + 0.40) + 0.20sin(0.39z − 0.70)
)단면 반지름은 호흡하는 저주파 성분과 각도 방향 암벽 요철의 합이다.
R(z,θ) = 2.28 + 0.16sin(0.21z+0.50) + 0.07sin(0.53z−0.80)
+ 0.13sin(3θ+0.31z) + 0.065sin(7θ−0.47z)
+ 0.035sin(11θ+0.29z)축 방향은 L_z=2.65, 단면은 N=6 섹터로 반복한다. 각 축 셀은 해시로 회전되고 각 꽃은 축 위치와 각도를 다시 작은 범위에서 흔든다.
k_z = floor((p_z + L_z/2)/L_z)
Δθ = 2π/N
k_θ = floor((θ − φ(k_z) + Δθ/2)/Δθ)
θ_a = φ(k_z) + k_θΔθ + jitter(hash(k_z,k_θ))꽃의 바닥점은 C(z_a)+R(z_a,θ_a)(cosθ_a,sinθ_a)다. 바깥 법선 n_o, 원주 접선 t, 안쪽 법선 n_i와 축 a로 꽃의 로컬 프레임을 만든다.
n_o = (cosθ_a, sinθ_a, 0)
t = (−sinθ_a, cosθ_a, 0)
n_i = −n_o
a = (0,0,1)
q = (dot(p−b,t), dot(p−b,n_i), dot(p−b,a))따라서 같은 꽃 모델을 바닥, 측벽, 천장에 따로 회전해 복제하지 않아도 모든 꽃이 동굴 내부를 향한다. 벽 안쪽 법선만 줄기축으로 사용하면 3시·9시 측벽의 얇은 화관은 진행 카메라에 정확히 edge-on이 된다. 화면을 따라 도는 billboard 대신 각 셀에 고정된 상류 방향 기울기 ℓ_z를 넣어 꽃의 물리적 방향은 유지하면서 투영 특이점을 피한다.
ℓ_z = −mix(0.24,0.30,hash(k_z,k_θ))
s = normalize(ℓ_t t + h_stem n_i + ℓ_z a)굽은 동굴에서는 C(p_z)와 ring 기준 C(z_ring) 사이의 차이 때문에 꽃의 포괄구가 명목상 각도 셀을 넘을 수 있다. 한 셀만 평가하면 이 지점에서 꽃 ID가 즉시 교체되어 화관이 평면으로 잘린다. 구현은 가까운 각도 이웃만 경계 띠 안에서 함께 평가한다. 축 방향은 이웃을 매번 중복 평가하지 않는다. 앵커 jitter, 줄기 기울기, 바람, 꽃머리 포괄구의 합이 L_z/2보다 작도록 배치해 각 축 ring에 단 하나의 소유자를 부여한다.
θ_local = θ − φ(k_z) − k_θΔθ
d_boundary = |p_xy−C(z_ring)| sin(Δθ/2−|θ_local|)
d_boundary < 0.76 이면
ρ_flower = max(ρ_kθ, ρ_kθ±1), D_flower = min(D_kθ, D_kθ±1)
|z_jitter| + |lean_z| + |wind_z| + r_head < L_z/2각도 경계 양쪽은 같은 두 후보를 반대 순서로 평가한다. 밀도 max, 거리 min, 물질 가중 평균은 모두 교환법칙을 따르므로 sector 평면에서 결과가 연속이다. 축 ring은 단일 후보만 평가했다.
개인적인 메모:실제로 shaderToy에서 보면 숙련된 쉐이더 프로그래머는 레이마칭을 자주쓴다. 이것은 그 응용의 첫단계이다. 셰이더 내부에서 절차적으로 정의하는 거리함수(SDF:Signed Distance Function) 및 도메인 반복 기법을 사용했다.
2. 거리만으로 결정되는 개화량
꽃 중심 c와 카메라 r_o 사이의 거리 d를 개화량 O로 변환한다.
P(d) = clamp((d_far − |c − r_o|)/(d_far − d_near), 0, 1)
P_η = clamp(P + 4ηP(1−P), 0, 1), η ∈ [−0.09,0.09]
O(d) = P_η²(3 − 2P_η)d ≥ d_far에서는 O=0, d ≤ d_near에서는 O=1이다. 시간은 줄기의 바람과 미세한 꽃잎 흔들림에만 사용한다. 따라서 카메라에 극단적으로 가까운 꽃이 다시 봉오리가 되는 상태 역전은 없다.
3. 타원형 봉오리에서 자유단부터 열리기
꽃잎의 길이 좌표를 u∈[0,1]로 둔다. u=0은 줄기에 붙은 기부이고 u=1은 자유단이다. 닫힌 꽃잎과 열린 꽃잎의 실제 cubic Bézier 제어점은 다음과 같다.
P₀ = (r_b, y_b)
P₁_closed = (r_b + 0.76b, y_b + 0.31h)
P₂_closed = (r_b + b, y_b + 0.72h)
P₃_closed = (r_b, y_b + h)
P₁_open = (r_b + 0.24R, y_b + 0.78A)
P₂_open = (r_b + 0.67R, y_b + 0.62A − 0.18G)
P₃_open = (r_b + R, y_b − G)h는 봉오리 높이, b는 봉오리 팽창, R은 열린 길이, A는 crown 높이, G는 꽃잎별 중력 처짐이다. 꽃잎마다 서로 다른 중간면을 혼합하지 않는다. 제어점 P₁,P₂,P₃을 각각 기부, 어깨, 자유단으로 두고 하나의 cubic Bézier 골격을 끝까지 유지한다. g(x)=x²(3−2x)일 때 세 제어점은 모두 O=0부터 움직이되 자유단이 가장 빠르고 어깨와 기부가 뒤따른다.
r = g(O)
r_tip = r
r_shoulder = r(0.58 + 0.42O)
r_hinge = r(0.28 + 0.72O)
P₁ = mix(P₁_closed, P₁_open, r_hinge)
P₂ = mix(P₂_closed, P₂_open, r_shoulder)
P₃ = mix(P₃_closed, P₃_open, r_tip)
c(u,O) = (1−u)³P₀ + 3(1−u)²uP₁ + 3(1−u)u²P₂ + u³P₃이 구조에서는 한 꽃잎 안에 닫힌 곡선 조각과 열린 곡선 조각이 동시에 생기지 않는다. 모든 release 함수의 값과 1차 기울기는 시작점에서 0이므로, 자유단만 먼저 움직이며 어깨에 순간적인 꺾임이 생기던 초기 edge pop도 없다. 이전 구현에서 개화 중 이중 화관이 재생성되던 원인이던 self-fold를 구조적으로 차단한다. 기본 거리 구간도 2.2→7.5 m에서 1.6→10.8 m로 넓혀 기본 속도에서 개화가 약 5초가 아니라 약 8.8초에 걸쳐 진행된다.
꽃마다 해시로 28,30,32,34장의 corona 중 하나를 선택한다. 전체 길이·폭·처짐·화심 크기와 개화 위상도 셀별로 다르고, 각 꽃잎 길이와 폭의 작은 개별 편차는 g(O)에 따라 0부터 연속적으로 자란다. 따라서 닫힌 외피는 갈라지지 않고 임계 개화량에서 형상이 갑자기 활성화되지도 않는다.
4. 얇은 꽃잎 밀도장
꽃잎을 직선 리본 구간으로 나누지 않는다. 현재 개화량의 연속 중심선 c(u,O)에서 샘플 q_xy와 가장 가까운 매개변수 u를 구한다. r=c(u,O)−q_xy일 때 목적함수는 F(u)=|r|²/2다.
F′(u) = dot(r,c′)
F″(u) = dot(c′,c′) + dot(r,c″)
δ_k = clamp(F′/safe(F″), −0.16, 0.16)
u_{k+1}= clamp(u_k − δ_k, 0, 1)
n = dot(q_xy − c(u,O), normalize(−c′_y,c′_x))미분은 유한 차분이 아니라 cubic Bézier의 해석적 1·2차 미분이다.
c′(u) = 3(1−u)²(P₁−P₀) + 6(1−u)u(P₂−P₁) + 3u²(P₃−P₂)
c″(u) = 6(1−u)(P₂−2P₁+P₀) + 6u(P₃−2P₂+P₁)봉오리 곡선은 반지름 방향으로 한 번 접혔다가 개화하며 단조 곡선이 된다. 그래서 높이나 반지름 하나만 역추정한 초기값은 중간 상태에서 잘못된 곡선 가지로 수렴할 수 있다. 구현은 [0,1]의 아홉 후보에서 전역 최소 basin을 고르고, 완전한 Newton 분모로 네 번 보정한다.
u_0 = argmin_{j∈{0,…,8}} |c(j/8,O)−q_xy|²
u ← Newton(F′,F″), 4 iterations이산 후보의 argmin은 거리값 자체를 바꾸지 않고 Newton의 basin만 선택한다. 실제 거리와 밀도는 보정된 곡선점에서 계산하므로, 개화 중 잘못된 가지가 긴 흰 파편으로 발산하던 경로를 막는다. 접선 잔차는 보수적인 sphere-tracing 거리로 사용하지 않는다. 설상화는 화심 근처에서 가늘고 중간에서 넓어진다. u=1에서 폭을 0으로 만들지 않고, 최근접점이 끝점에 고정된 뒤 중심선 바깥 거리까지 포함하는 M_end가 타원 cap을 만든다.
b(u) = smootherstep(clamp(u/0.13,0,1))
g(u) = mix(0.50,1,smootherstep(clamp((u−0.06)/0.44,0,1)))
t(u) = 1 − 0.12 smootherstep(clamp((u−0.70)/0.30,0,1))
w_max(O) = mix(0.023,0.0355,O) · scale_width
w(u,O) = w_max(O) · b(u) · g(u) · t(u)각도 반복은 한 sector에서 현재 꽃잎만 잘라 쓰지 않는다. 현재 꽃잎 i와 경계 방향의 이웃 i±1을 함께 평가하고, sector 중심에서는 0이며 경계에서 1인 gate χ로 이웃을 연속적으로 활성화한다.
χ = smootherstep(clamp((|φ_local|−0.14Δθ)/(0.36Δθ),0,1))
ρ_corona = max(ρ_i, χρ_{i±1})따라서 폭이 sector의 반폭보다 커도 꽃잎이 방사형 직선으로 잘리지 않고 실제 꽃처럼 서로 조금 겹칠 수 있다.
중간면의 가우시안 밀도와 가장자리 마스크를 곱해 얇은 sheet를 만든다.
ρ_p(q) = exp(−2.20(n/τ)²) · M_side(|q_z|/w) · M_end · M_nearest
τ ∈ [0.0088, 0.0102]봉오리의 초록 조직이 흰 설상화로 바뀌는 색 전이는 화관 전체의 O 하나로 켜지 않는다. 자유단 u=1에서 먼저 시작하고 기부 u=0으로 전파되는 별도 pigment field를 밀도장에 저장한다.
O_start(u) = mix(0.48,0.34,u)
W(u,O) = smootherstep(clamp((O−O_start(u))/0.34,0,1))
ρ_white = ρ_p · W따라서 아직 컵처럼 접힌 초기 화관에는 흰색이 나타나지 않는다. 꽃잎 blade가 실제로 분리된 뒤 자유단에서 흰색이 시작되어 기부 쪽으로 내려온다.
각도 반복의 셀 ID는 반드시 corona 개수로 정규화한다. atan의 반환값은 같은 방향에서 +π와 −π로 끊기므로 원시 정수를 해시에 쓰면 동일한 꽃 또는 꽃잎의 양쪽이 서로 다른 개체가 된다.
i_flower = mod(floor((θ−φ+Δθ/2)/Δθ), N_flower)
i_petal = mod(floor((ψ+δψ/2)/δψ), N_petal)이 정규화가 없던 버전에서는 branch cut을 가로지르는 꽃이 두 개의 독립적인 반쪽으로 평가되어 중앙이 잘리거나 흰 반원 조각이 생겼다.
열린 꽃잎은 횡방향 좌표의 제곱에 비례해 가장자리를 들어 올린다. 이 cupping은 단면을 두껍게 만들지 않고 중간면 자체를 휘게 한다. 중앙 잎맥 ρ_v, 평행한 미세 능선 ρ_r, 가장자리 조직 ρ_e는 u와 정규화 횡좌표에서 계산한다. 외부 텍스처가 아니라 꽃잎에 붙어 움직이는 절차적 표면 좌표이므로 개화 중 미끄러지지 않는다.
M_nearest는 Newton 결과의 접선 방향 잔차가 큰 샘플만 부드럽게 제거한다. 이 값은 가시 밀도의 안전장치일 뿐, 빈 공간을 크게 건너뛰는 거리의 근거로 쓰지 않는다.
5. 암벽 앵커와 꽃머리의 단일 좌표 소유자
동굴 벽의 앵커 b를 원점으로 하고 +Y=n_i인 로컬 프레임에서 줄기 끝점은 다음 하나다.
h = (lean_t, stemHeight, lean_a)
D_stem(q) = sdCapsule(q, 0, h, r)
c_flower = b + h_x t + h_y n_i + h_z a꽃머리 로컬 +Y축은 normalize(h)에 정렬된다.
q_head = (
dot(p−h, x̂),
dot(p−h, ŷ),
dot(p−h, ẑ)
)줄기 캡슐과 꽃머리가 같은 암벽 앵커와 프레임을 사용하므로 벽·천장에 있어도, 바람이 불어도 꽃받침과 꽃잎이 줄기에서 분리되지 않는다.
6. 바닥의 암석·이끼 표면장
바닥은 화면 아래에 얹은 2D 레이어가 아니다. 동굴 단면의 방사 벡터 r_xy가 아래를 향하는 정도로 floor mask를 만들고, 벽 좌표 (x,z)의 저주파 노이즈로 이끼와 가는 수분 맥을 배치한다.
M_floor = smootherstep(clamp((−r_y/|r|−0.24)/0.56,0,1))
ρ_moss = ρ_wall M_floor smoothstep(0.46,0.72,N_moss)
ρ_vein = ρ_wall M_floor (1−|2N_f−1|)^11 smoothstep(0.50,0.76,N_moss)낮은 자갈은 (x/0.58,z/0.76) 셀마다 위치·가로세로비·높이를 해시로 바꾼 타원 footprint다. 자갈 높이 h_s∈[0.030,0.095]만큼 wallGap을 안쪽으로 이동시켜 실제 볼륨의 실루엣과 가림에 참여시킨다. 이끼와 맥은 같은 벽 밀도를 착색하므로 공중에 뜨지 않는다.
7. 방출–흡수 볼륨 레이마칭
각 샘플의 소멸계수는 꽃잎, 잎맥, 줄기, 화심, 꽃받침 밀도의 가중합이다.
σ_t = ρ_p(112 + 14ρ_r + 4ρ_e) + 58ρ_s + 72ρ_c + 54ρ_k + 44ρ_w샘플 간격 Δs마다 Beer–Lambert 투과율과 산란광을 앞에서 뒤로 갱신한다.
T_step = exp(−σ_t Δs)
α = 1 − T_step
L_{k+1} = L_k + T_k · S_k · α
T_{k+1} = T_k · T_stepS_k는 재질색, 태양 방향의 밀도 미분, 전방·후방 산란 근사를 합친 source다. 꽃잎의 기본 소멸계수는 38→112로 높여 정면에서는 백색 조직이 거의 불투명하게 보인다. 단, 시트의 가우시안 꼬리와 얇은 가장자리는 T를 남겨 역광 투과를 보존한다.
8. 적응형 샘플 간격과 경계
줄기 캡슐, 꽃머리 포괄구, 꽃잎 평면, 동굴 벽까지의 보수적 거리 d_macro로 빈 공간을 건너뛴다. 꽃머리 포괄구는 외부에서만 가속 거리로 사용한다. 포괄구 내부의 음수는 실제 물질이 아니므로 내부에 진입한 뒤에는 꽃잎·코어·꽃받침 거리장이 소유한다. 픽셀은 선이 아니라 원뿔이므로, 그 폭보다 가는 스텝은 해상할 수 없는 것을 마칭하는 낭비다.
D_head⁺ = max(D_head,0)
외부: d_macro = min(D_stem,D_head⁺)
상세 모델 내부: d_macro = min(D_stem,D_core,D_calyx,D_petal)w_px = (2 / (H · 1.78)) · s 픽셀 원뿔 폭
Δs = clamp(max(0.60 d_macro, w_px), 0.007, 1.60)
σ_t > 0.025이면 Δs = clamp(0.35 / σ_t, 0.007, Δs)최대 128회 샘플링하고 T<0.012에서 조기 종료하며 20 m에서 끝낸다. 동굴 암벽도 같은 방출–흡수 적분에 포함되어 꽃 뒤의 가림이 별도 2D 레이어로 분리되지 않는다. 내부 렌더 해상도는 측정 FPS에 따라 화면의 34–100 % 범위에서 조절된다.
d_macro는 부호거리장이 아니라 근접 추정치다. 꽃잎 시트는 베지어 위 최근접점을 뉴턴법으로 근사해서 얻고, 화심·꽃받침은 타원체 근사식이며, 벽은 반경 방향 간격이다. 따라서 구면 추적이 d_macro를 전부 쓰면 표면 바깥에 남는다는 보장이 없다. 계수를 0.85에서 0.60으로 내린 이유이고, 물질 내부의 간격은 고정 하한이 아니라 광학 두께가 정한다. 두 값 모두 측정으로 골랐다 — 아래 「스텝이 꽃잎을 뛰어넘던 문제」 참고.
9. 카메라
카메라는 GLSL 동굴과 동일한 C(z)를 따라 이동하며 앞쪽 3.35 단위를 바라본다. 고정된 월드 위쪽 벡터 대신 진행 방향과 곡률에서 만든 국소 프레임을 사용하고, 두 저주파 롤을 더해 동굴을 실제로 탐험하는 듯한 방향 변화를 만든다.
r_o = path(z)
r_t = path(z+3.35)
f = normalize(r_t−r_o)
s = normalize(f × y_world)
u_0 = normalize(s × f)
roll(z) = 0.23sin(0.17z) + 0.09sin(0.43z+1.20)
u = u_0 cos(roll) + s sin(roll)드래그 입력은 이 국소 s,u 방향으로 look-ahead 목표만 움직인다. 카메라를 통과한 꽃은 전방 레이 범위에서 자연스럽게 빠지고, 뒤쪽 꽃을 별도 상태로 남기지 않는다.
수식 → 코드
수식/개념 | 구현 위치 | 역할 |
|---|---|---|
|
| 굽은 동굴 중심선과 암벽 경계 |
축–각도 반복 셀 |
| 벽·천장의 꽃 앵커 선택 |
upstream stem lean |
| 3시·9시 측벽 화관의 edge-on 투영 붕괴 방지 |
|
| sector 평면에서 꽃 전체가 잘리는 현상 방지; 축 ring은 배치 불변식으로 단일 소유 |
varied cubic |
| 느린 거리 개화와 셀별 위상 편차 |
continuous staged Bézier |
| 동시에 출발하고 자유단이 선행하는 영속 골격 |
설상화 |
| 가는 기부·넓은 blade·원호 자유단 |
global basin + analytic Newton |
| 접힌 봉오리에서도 잘못된 곡선 가지로 발산하지 않는 최근접점 |
neighbor-gated petal union |
| sector 절단 없이 겹치는 설상화 |
|
| 접선 잔차 경계를 포함한 얇은 sheet density |
|
| 중앙 잎맥·평행 섬유·가장자리 조직 |
|
| 자유단에서 기부로 진행하는 흰색 pigment 전이 |
angular corona repetition |
| 꽃마다 28–34장인 한 겹의 화관 |
canonical angular IDs |
|
|
암벽 국소 프레임과 |
| 벽 법선 정렬과 꽃머리 끝점 |
|
| 동굴 벽에 붙은 바닥 이끼·수분 맥·낮은 자갈 |
|
| Beer–Lambert 투과율 |
|
| front-to-back 산란 적분 |
adaptive |
| 빈 공간 가속과 얇은 꽃잎 보존 |
look-ahead + roll camera |
| 곡선 동굴 탐험 방향과 시선 정렬 |
- 수식/개념
C(z),R(z,θ)- 구현 위치
shaders/flower-model.glsl- 역할
굽은 동굴 중심선과 암벽 경계
- 수식/개념
축–각도 반복 셀
- 구현 위치
shaders/flower-model.glsl- 역할
벽·천장의 꽃 앵커 선택
- 수식/개념
upstream stem lean
ℓ_z- 구현 위치
shaders/flower-model.glsl- 역할
3시·9시 측벽 화관의 edge-on 투영 붕괴 방지
- 수식/개념
d_boundary, angular adjacent-cell union- 구현 위치
shaders/flower-model.glsl- 역할
sector 평면에서 꽃 전체가 잘리는 현상 방지; 축 ring은 배치 불변식으로 단일 소유
- 수식/개념
varied cubic
O(d)- 구현 위치
shaders/flower-model.glsl- 역할
느린 거리 개화와 셀별 위상 편차
- 수식/개념
continuous staged Bézier
c(u,O)- 구현 위치
shaders/flower-model.glsl- 역할
동시에 출발하고 자유단이 선행하는 영속 골격
- 수식/개념
설상화
w(u,O)- 구현 위치
shaders/flower-model.glsl- 역할
가는 기부·넓은 blade·원호 자유단
- 수식/개념
global basin + analytic Newton
u- 구현 위치
shaders/flower-model.glsl- 역할
접힌 봉오리에서도 잘못된 곡선 가지로 발산하지 않는 최근접점
- 수식/개념
neighbor-gated petal union
- 구현 위치
shaders/flower-model.glsl- 역할
sector 절단 없이 겹치는 설상화
- 수식/개념
ρ_p=exp(−2.20(n/τ)²)M- 구현 위치
shaders/flower-model.glsl- 역할
접선 잔차 경계를 포함한 얇은 sheet density
- 수식/개념
ρ_v,ρ_r,ρ_e- 구현 위치
shaders/flower-model.glsl- 역할
중앙 잎맥·평행 섬유·가장자리 조직
- 수식/개념
ρ_white=ρ_pW(u,O)- 구현 위치
shaders/flower-model.glsl- 역할
자유단에서 기부로 진행하는 흰색 pigment 전이
- 수식/개념
angular corona repetition
- 구현 위치
shaders/flower-model.glsl- 역할
꽃마다 28–34장인 한 겹의 화관
- 수식/개념
canonical angular IDs
mod(i,N)- 구현 위치
shaders/flower-model.glsl- 역할
atanbranch cut에서 꽃·꽃잎이 반으로 갈라지는 현상 방지
- 수식/개념
암벽 국소 프레임과
D_stem- 구현 위치
shaders/flower-model.glsl- 역할
벽 법선 정렬과 꽃머리 끝점
- 수식/개념
ρ_moss,ρ_vein,ρ_stone- 구현 위치
shaders/flower-model.glsl- 역할
동굴 벽에 붙은 바닥 이끼·수분 맥·낮은 자갈
- 수식/개념
T=exp(−σ_tΔs)- 구현 위치
shaders/render.frag- 역할
Beer–Lambert 투과율
- 수식/개념
L+=T·S·α- 구현 위치
shaders/render.frag- 역할
front-to-back 산란 적분
- 수식/개념
adaptive
Δs- 구현 위치
shaders/render.frag- 역할
빈 공간 가속과 얇은 꽃잎 보존
- 수식/개념
look-ahead + roll camera
- 구현 위치
main.js- 역할
곡선 동굴 탐험 방향과 시선 정렬
파일 구조
quad.vert
↓
common.glsl — hash / noise / primitive / tone mapping
↓
flower-model.glsl — tunnel frame / wall anchors / petal sheet density
↓
render.frag — flower + cave volume integration / scattering
↓
main.js — static manifest / camera / uniforms / adaptive qualitymain.js의 정적 SHADER_URLS manifest가 모든 GLSL 파일을 명시적으로 로드한다. 외부 이미지, 외부 텍스처, CPU 꽃 메쉬는 사용하지 않는다.
조작
패널은 기본적으로 숨겨져 있으며
H로 연다.Flight speed는 카메라 이동 속도다.Near bloom안의 꽃은 완전히 열린다.Far bud limit밖의 꽃은 봉오리다.캔버스를 드래그하면 진행 방향을 기준으로 시선을 조절한다.
New cavern은 꽃 위치와 형태 seed를 갱신한다.Space는 카메라와 바람을 일시정지한다.
구현하면서 발생했던 트러블 슈팅
지표는 픽셀 휘도의 시간 2차 차분이다. 부드럽게 움직이는 신호라면 2차 차분은 1차 차분보다 훨씬 작아야 한다. 크다면 값이 프레임마다 방향을 뒤집는다는 뜻이고, 그게 깜빡임이다. |2차 차분| > 12/255인 픽셀 비율을 popPct로 적는다.
출하본은 765×595에서 popPct 4.22 %, d2 2.583 > d1 1.706이었다.
원인이 아니었던 것 (전부 측정으로 배제)
실험 | popPct |
|---|---|
원본 | 4.22 % |
해상도 270 → 1800 px | 4.66 / 4.61 / 4.63 / 4.63 % |
스텝 수 84 → 320, 최대 스텝 0.26 → 0.05 | 4.61 % |
최소 스텝 7 mm → 1.5 mm | 4.49 % |
스텝당 광학깊이 0.35로 제한 | 4.60 % (비용 +40 %) |
꽃잎 개수 28–34 → 8–10 | 3.70 % |
셔터 2-tap 평균 | 6.11 % (악화) |
- 실험
원본
- popPct
4.22 %
- 실험
해상도 270 → 1800 px
- popPct
4.66 / 4.61 / 4.63 / 4.63 %
- 실험
스텝 수 84 → 320, 최대 스텝 0.26 → 0.05
- popPct
4.61 %
- 실험
최소 스텝 7 mm → 1.5 mm
- popPct
4.49 %
- 실험
스텝당 광학깊이 0.35로 제한
- popPct
4.60 % (비용 +40 %)
- 실험
꽃잎 개수 28–34 → 8–10
- popPct
3.70 %
- 실험
셔터 2-tap 평균
- popPct
6.11 % (악화)
해상도를 6.7배 올려도 그대로라는 것이 결정적이었다. 공간 앨리어싱이면 그럴 수 없다. 카메라를 세우면 (speed = 0) popPct가 0.03 %로 떨어졌으므로 시간 항(바람·개화)도 아니다.
실제 원인
dt를 줄이며 잰 결과다.
dt | d1 | d2 | d2/d1 | popPct |
|---|---|---|---|---|
1/60 | 1.87 | 2.90 | 1.55 | 4.73 % |
1/240 | 0.62 | 0.67 | 1.09 | 1.19 % |
1/960 | 0.185 | 0.177 | 0.96 | 0.22 % |
- dt
1/60
- d1
1.87
- d2
2.90
- d2/d1
1.55
- popPct
4.73 %
- dt
1/240
- d1
0.62
- d2
0.67
- d2/d1
1.09
- popPct
1.19 %
- dt
1/960
- d1
0.185
- d2
0.177
- d2/d1
0.96
- popPct
0.22 %
밀도장은 매끄럽다. 부족한 것은 프레임레이트였다. d1은 dt에 선형으로 줄지만 d2도 선형으로 줄어드는데, 이는 화면에서 단단한 경계가 픽셀을 가로지르는 신호의 특징이다. 기본 속도 0.82에서 가까운 꽃 하나가 프레임당 8.2픽셀을 지나가고, 그 실루엣은 거의 검은 동굴 위의 밝은 흰 경계다.
그러니 움직이는 게 아니라 스트로보가 된다. 밀도 항을 하나씩 지워보면 꽃잎 항만 지워도 popPct가 4.23 % → 0.67 %로 떨어졌다.
즉 이건 마칭 버그가 아니라 표본화 한계라고 이해된다. 완전한 해결 방법은 시간 누적(TAA)이거나 더 느린 카메라뿐인데 현실적으로는 별로였다.
그래서 한 것과 철회한 것
기본 속도 0.82 → 0.50: 이 지표에 선형에 가깝게 듣는 유일한 손잡이다.
765×595에서 popPct는 0.82에서 4.28 %, 0.55에서 3.49 %, 0.35에서 0.94 %. 슬라이더는 여전히 1.25까지 갔으나 시각적으로 별로였다.
픽셀당 셔터는 철회: 각 픽셀의 카메라 시점을 다음 프레임까지 서로 다르게
섞는 방식은 수치상 스트로브의 공간 일관성을 낮췄지만, 가까운 흰 꽃에서는 윤곽을 긴 흰 파편과 중앙 절단처럼 보이게 했다. 이 부분 버그는 조금 문제가 있었다. 현재는 한 프레임의 모든 픽셀이 동일한 카메라 변환을 사용하고, 광선 시작점의 작은 공간 디더만 유지한다.
즉 시간 누적 없이 움직임 깜빡임은 여전히 존재하나, 대신 형상을 깨뜨리는 가짜 모션 블러를 제거하고, 기본 속도로 표본화 오차를 완화하였다.
광선이 예산을 다 쓰고 죽던 문제
AI와 작업하다가 새로운 버그가 있었는데,
아래의 코드에서 발견됐다.
if (field.macroDistance < 0.020 || extinction > 0.025) stepSize = MIN_VOLUME_STEP;앞의 조건이 문제였는데, 꽃을 스치기만 하는 광선(대략 꽃봉오리에서 2cm 정도) 도 긴 구간 내내 macroDistance < 0.020을 만족해서, 아무 물질도 만나지 않은 채 7 mm씩 기어간다. 그러다 84스텝 예산을 소진하고 투과율이 1에 가까운 채로 빠져나오며, 픽셀은 배경으로 떨어진다.
당시 측정에서는 화면의 17.21 %가 그렇게 끝나고 있었고, 그 픽셀들의 지도는 모든 꽃의 테두리와 먼 통로 전체였다. 해당 조건을 제거한 뒤 그 검사값은 0.00 %가 됐지만, 그 검사는 포괄구의 음수 내부가 min을 통해 거리장을 계속 점유하는 두 번째 경로를 잡지 못했다.
GPT가 제시한 방식은 빈 공간은 petalDistance가 이미 macroDistance로 들어와 있으므로 구면 추적으로 건너뛰게 하고, 스텝이 픽셀 원뿔 폭보다 작아지지 않게 했다. 원뿔보다 가는 것은 어차피 해상할 수 없으니까, 그리고 MAX_VOLUME_DISTANCE를 26 → 20 m로 줄였다. 안개가 exp(-0.008 d²)이라 16 m에서 이미 0.87, 20 m에서 0.96이므로 그 너머는 무엇을 찾든 배경이었다.
부수 효과로 먼 통로의 봉오리들이 보이게 됐다.
두 번째 실패는 가까운 큰 꽃에서 방사형 검은색 쐐기 모양으로 나타났다. 지름 1.16 m인 headBound 내부를 7 mm로만 통과하려면 약 166스텝이 필요해 96스텝 예산보다 크다. D_head<0을 실제 내부 거리로 사용하지 않고, 클로드의 조언을 받아 위 식처럼 상세 거리장으로 교체했다, 빈 화관 내부에서는 다시 건너뛰고 실제 얇은 꽃잎 근처에서만 최소 스텝을 사용한다.
해상도 컨트롤러가 진동하던 문제
renderScale은 fps < 24면 내리고 > 52면 올렸는데, 내린 뒤 빨라지면 다시 올리고 올린 뒤 느려지면 다시 내린다. 임계 근처의 기기에서는 0.7초마다 해상도가 펄스 친다. 해상도 변화는 그 자체가 눈에 띄는 문제가 있었다.
내릴 때 천장도 같이 내리고(래칫), 올리려면 빠른 틱이 3회 연속 필요하게 했다. 내리는 쪽은 한 번으로 즉시 반응한다
스텝이 꽃잎을 뛰어넘던 문제
가까운 꽃이 디더 무늬로 갈려 보이고, 꽃머리에서 쐐기가 한 조각 잘려 나갔다. 확대해 보면 밝은 픽셀과 배경 픽셀이 체커처럼 섞여 있었다.
해결하기위한 트러블 슈팅:
시도 | 결과 |
|---|---|
스텝 예산 96 → 512 | 줄지만 남음 |
꽃잎 근접거리 | 변화 없음 |
| 변화 없음 |
각도 이웃 셀을 항상 평가 | 거의 변화 없음 |
축 방향 링을 둘 평가 | 변화 없음 |
봉오리 색 | 완전히 동일 |
- 시도
스텝 예산 96 → 512
- 결과
줄지만 남음
- 시도
꽃잎 근접거리
proximity× 0.5, × 0.25- 결과
변화 없음
- 시도
proximity를planeGap만으로 축소- 결과
변화 없음
- 시도
각도 이웃 셀을 항상 평가
- 결과
거의 변화 없음
- 시도
축 방향 링을 둘 평가
- 결과
변화 없음
- 시도
봉오리 색
pigmentReveal제거- 결과
완전히 동일
셰이딩을 의심해 어두운 픽셀과 밝은 픽셀의 마칭 통계를 갈라 재보니 구별되지 않았다 — 투과율 0.010 대 0.011, 물질 샘플 26.2개 대 24.5개. 둘 다 무언가에 완전히 흡수됐다. 어두운 픽셀은 가까운 꽃잎을 뛰어넘고 뒤쪽 벽에 도달한 광선이었다.
원인 1 — d_macro는 부호거리장이 아니다
구면 추적은 "이 반경 안에는 아무것도 없다"는 하한을 전제했다. 임의의 점에서 d_macro를 읽고, 임의의 방향으로 그 거리까지 64등분해 물질이 있는지 본다. 한 방향만 보므로 이 값은 위반율의 하한이다.
위반한 표본 | 최악 비율 | |
|---|---|---|
수정 전 | 8.06 % | 0.016 |
수정 후 | 0.29 % | — |
- #1
수정 전
- 위반한 표본
8.06 %
- 최악 비율
0.016
- #1
수정 후
- 위반한 표본
0.29 %
- 최악 비율
—
즉 "비어 있다"고 보고한 표본의 8 %에 물질이 들어 있었고, 최악의 경우 주장한 거리의 1.6 % 지점에 있었다.
벽 밀도는 표면이 아니라 그 안쪽 wallEdge = 0.105 + 0.035 fineRock에서부터 차오르는데, 마칭 거리로는 표면까지의 간격을 그대로 넘겨주고 있었다. fineRock이 만들 수 있는 최댓값(국소값이 아니라 — 스텝은 한 점이 아니라 구간을 지나므로)을 빼면 8.06 % → 0.30 %가 된다. 국소 wallEdge를 빼면 0.47 %로, 최댓값 쪽이 옳다는 것이 측정으로도 확인된다.
나머지 오차는 꽃잎·타원체 근사에 남아 있고 없앨 수 없다. 그래서 추적 계수를 측정된 포락선에서 골랐다: 0.85는 깨지고 0.60은 깨끗하며 프레임 시간이 같다(819×460에서 6.50 ms 대 6.52 ms). 더 내린 0.45는 더 느리고(7.22 ms) 예산 소진이 오히려 늘었다(5.23 % 대 1.83 %).
0.60은 그냥 내 눈에 좋아서 고른 값이다. 즉 증명된 안전값이 아니라 측정으로 고른 값이다. 이 장을 진짜로 증명 가능하게 만들려면 꽃잎 근접거리를 참 부호거리장으로 다시 써야 한다.
참고문헌과 실측값 대조
이 데모가 어느 문헌에서 온 값을 쓰고, 어디서 벗어나는지 한곳에 정리한다. 일치는 구현값이 문헌의 범위 안에 있다는 뜻이고, 벗어남은 의도적이거나 아직 고치지 않은 차이라는 뜻이다.
항목 | 문헌값 | 이 구현 | 판정 |
|---|---|---|---|
설상화가 두상화 반지름에서 차지하는 비 | 0.667 – 0.706 | 0.687 | 일치 |
설상화 개수 | 보통 13 – 34+ | 28 / 30 / 32 / 34 | 범위 상단, 항상 짝수 |
두상화 지름 | 34 – 60 mm | 0.805 m | 약 17배 (의도적 스케일) |
개화 시 원위/기부 전개율 비 | 2.5 | 3.57 → 1.00 (O에 따라) | 초기만 일치 |
화반 나선 팔 수 | 연속하는 피보나치 쌍 | 13 (한 계열만) | 부분 일치 |
톤매핑 계수 | a 2.51 · b 0.03 · c 2.43 · d 0.59 · e 0.14 | 동일 | 일치 |
구면 추적 거리 하한 |
| 위반 표본 0.29 % | 근사 |
- 항목
설상화가 두상화 반지름에서 차지하는 비
- 문헌값
0.667 – 0.706
- 이 구현
0.687
- 판정
일치
- 항목
설상화 개수
- 문헌값
보통 13 – 34+
- 이 구현
28 / 30 / 32 / 34
- 판정
범위 상단, 항상 짝수
- 항목
두상화 지름
- 문헌값
34 – 60 mm
- 이 구현
0.805 m
- 판정
약 17배 (의도적 스케일)
- 항목
개화 시 원위/기부 전개율 비
- 문헌값
2.5
- 이 구현
3.57 → 1.00 (O에 따라)
- 판정
초기만 일치
- 항목
화반 나선 팔 수
- 문헌값
연속하는 피보나치 쌍
- 이 구현
13 (한 계열만)
- 판정
부분 일치
- 항목
톤매핑 계수
- 문헌값
a 2.51 · b 0.03 · c 2.43 · d 0.59 · e 0.14
- 이 구현
동일
- 판정
일치
- 항목
구면 추적 거리 하한
- 문헌값
|f(x)| ≤ d(x, f⁻¹(0))- 이 구현
위반 표본 0.29 %
- 판정
근사
1. 개화 역학 — Liang & Mahadevan (2011)
꽃이 열리는 힘이 어디서 오는지에 대한 기준 논문이었다.
백합(Lilium casablanca)에서 수술적 조작과 정량 측정으로, 통설이던 중륵(midrib) 성장이나 향축/배축 차등 성장이 아니라 꽃잎 가장자리의 성장이 개화를 구동한다는 것을 보였다.
측정값: 중륵의 성장 변형률은 꽃잎·꽃받침 모두 약 10 %로 비교적 균일한 반면, 가장자리의 세로 방향 성장 변형률은 기부에서 20 % 이상, 원위로 갈수록 거의 50 %까지 증가한다. 즉 원위/기부 비가 약 2.5다.
이 구현은 응력을 풀지 않는다. 대신 하나의 3차 베지어 골격에서 끝(tip)이 어깨를, 어깨가 경첩(hinge)을 앞서게 해서 같은 방향의 기울기를 만든다.
float release = smootherStep(opening);
float tipRelease = release;
float shoulderRelease = release * (0.58 + 0.42 * opening);
float hingeRelease = release * (0.28 + 0.72 * opening);
| tip | shoulder | hinge | tip/hinge |
|---|---|---|---|---|
0.00 | 1.00 | 0.580 | 0.280 | 3.57 |
0.17 | 1.00 | 0.651 | 0.402 | 2.49 |
0.50 | 1.00 | 0.790 | 0.640 | 1.56 |
1.00 | 1.00 | 1.000 | 1.000 | 1.00 |
- O
0.00
- tip
1.00
- shoulder
0.580
- hinge
0.280
- tip/hinge
3.57
- O
0.17
- tip
1.00
- shoulder
0.651
- hinge
0.402
- tip/hinge
2.49
- O
0.50
- tip
1.00
- shoulder
0.790
- hinge
0.640
- tip/hinge
1.56
- O
1.00
- tip
1.00
- shoulder
1.000
- hinge
1.000
- tip/hinge
1.00
부호와 자릿수는 맞고, 시간 의존성은 다르다. 실제 백합의 원위/기부 기울기는 개화가 진행되는 동안 유지되지만, 이 구현의 기울기는 O=0.17 부근에서 문헌값 2.5를 지나고 이후 계속 줄어 완전 개화에서 1.00, 즉 기울기가 사라졌다. 봉오리에서 곡률 주름이 생기지 않게 세 제어점을 동시에 출발시킨 결과이며, 시각적 선택이지 측정에 맞춘 값이 아니다.
2. 형태 - Flora of North America, Leucanthemum vulgare
옥스아이 데이지가 가장 가까운 실물 대응이다. FNA 기재문의 값:
총포(involucre) 지름 12–20+ mm
설상화(ray florets) 보통 13–34+, 드물게 0
설편(laminae) 길이 12–20(–35+) mm
화반(disc) 지름 10–20 mm
이 구현의 대응값(난수 평균, 완전 개화):
baseRadius = 0.0261, reach = 0.3763 -> 꽃잎 끝 반지름 0.4024
coreRadius.x(개화) = 0.126 화반 반지름문헌 | 이 구현 | |
|---|---|---|
설편 / 두상화 반지름 | 0.667 – 0.706 | 0.687 |
두상화 지름 | 34 – 60 mm | 805 mm |
설상화 개수 | 13 – 34+ | 28 / 30 / 32 / 34 |
- #1
설편 / 두상화 반지름
- 문헌
0.667 – 0.706
- 이 구현
0.687
- #1
두상화 지름
- 문헌
34 – 60 mm
- 이 구현
805 mm
- #1
설상화 개수
- 문헌
13 – 34+
- 이 구현
28 / 30 / 32 / 34
비율은 맞고 절대 크기는 약 17배다. 동굴 반지름이 2.28 m인 세계에서 사람 크기의 꽃을 지나가는 것이 이 데모의 전제이므로 절대 크기는 의도적이다.
설상화 개수는 문헌 범위 안에 있기는 하다. 다만 28 + 2⌊4ξ⌋라서 항상 짝수이고, 13–34+ 중 상단 네 값에만 놓인다. 짝수만 나오는 것은 형태학적 근거가 없다. 상단 편중은 시각적 선택이다 — 꽃잎이 적으면 이 크기에서 성기게 보인다. (FNA가 범위를 13–34로 적고 양 끝이 피보나치 수인 것은 눈에 띄지만, 설상화 개수가 피보나치 수에 몰린다는 것을 뒷받침할 자료는 확인하지 못했다. 피보나치 편향이 문헌으로 확인되는 것은 아래 화반의 나선 개수 쪽이다.)
개인적인 메모: 오히려 꽃을 다양하게 할까했지만, 오히려 문제가 생길까봐 하지 못했다. 생각보다 너무 단순한 구현이다. 하지만 참고문헌이 없기때문에 실제 구현 도전에 써야할 횟수때문에 쉽사리 도전하지 못했다.
3. 화반 배열 — Vogel (1979)
두상화 중심의 관상화 배열에 대한 표준 모형이다.
θ_n = n · 137.50776° (황금각)
r_n = c √n (낱꽃당 면적이 일정)이 구현은 점집합을 배치하지 않고 나선 무늬를 텍스처로 그린다.
float phyllotaxis = 0.5 + 0.5 * cos(
76.0 * coreRadiusNormalized - 13.0 * coreAngle
);
float diskRings = 0.5 + 0.5 * cos(54.0 * coreRadiusNormalized + 2.0 * sin(coreAngle * 5.0));국화과 두상화에서는 시계·반시계로 감기는 두 나선(사엽선, parastichy) 계열이 동시에 보이고, 그 개수는 대개 연속한 피보나치 쌍이다 — 거베라 34/55, 해바라기 55/89. 예외도 보고되어 있어 한 방향만 뚜렷하거나 셀 수 없는 경우도 있다.
각도 항의 13은 피보나치 수이므로 그 계열에 속한다. 반면 반지름 항의 76과 54는 어떤 피보나치 쌍에도 대응하지 않고, 무엇보다 계열이 하나뿐이다. 이 구현이 그리는 것은 한 방향으로만 감기는 무늬이므로, 가까이서 보면 실제 화반의 교차 나선이 나타나지 않는다. 이 데모의 두상화 크기에 맞는 실제 쌍이 얼마인지는 확인하지 못했다.
4. 구면 추적의 전제 — Hart (1996)
「스텝이 꽃잎을 뛰어넘던 문제」에서 위반한 것이 바로 이 논문의 정의다.
Definition 2. A function
f: R³ → Ris a signed distance bound of its implicit surfacef⁻¹(0)if and only if|f(x)| ≤ d(x, f⁻¹(0)). (7)Theorem 1. Let
fbe Lipschitz with Lipschitz boundλ ≥ Lip f. Thenf/λis a signed distance bound of its implicit surface.
macroDistance는 식 (7)을 만족하지 않는다. 측정: 벽 항을 고치기 전 표본의 8.06 %가 주장한 반경 안에 물질을 가지고 있었고, 고친 뒤 0.29 %다.
그러면 추적 계수 0.60은 무엇인가. Theorem 1에 비추면 그것은 경험적으로 고른 Lipschitz 상한 λ ≈ 1/0.60 = 1.67이다. 즉 이 구현은 f를 λ로 나눠 거리 하한으로 바꾸는 정확히 그 절차를 쓰되, λ를 유도가 아니라 측정으로 정한다. 0.29 %가 남는 이유도 같다. 참 λ가 아니기 때문이다.
5. 볼륨 적분 — Max (1995)
앞에서 뒤로 누적하는 흡수–방출 모형은 Max의 분류 중 absorption plus emission이다. 이 구현의 T_step = exp(−σ_t Δs)와 L ← L + T·S·α가 그 이산화다. 산란은 단일 방향 미분과 전·후방 위상 근사로 대체했고, 다중 산란은 풀지 않는다.
6. 렌더링 상수
상수 | 출처 | 이 구현 |
|---|---|---|
| Jimenez (2014), interleaved gradient noise |
|
| Narkowicz (2016), ACES 필믹 곡선 근사 |
|
- 상수
52.9829189,0.06711056,0.00583715- 출처
Jimenez (2014), interleaved gradient noise
- 이 구현
interleavedGradientNoise()에 그대로
- 상수
a 2.51 · b 0.03 · c 2.43 · d 0.59 · e 0.14- 출처
Narkowicz (2016), ACES 필믹 곡선 근사
- 이 구현
acesToneMap()에 그대로
아직 근거가 없는 값
꽃잎 시트 두께의 실물 대조를 찾지 못해서 그냥 임의로 했다. 이 구현의 시트는 반치폭 기준 0.0099이고 꽃잎 길이 0.3763에 대해 길이/두께 = 38이다. 국화과 설상화 설편의 실제 두께를 기재한 자료를 확인하지 못해 이 비가 맞는지 판정할 수 없다. 두께는 광학(투과율)과 마칭(최소 스텝) 양쪽에 들어가는 값이므로, 출처를 찾으면 sheetThickness와 MIN_VOLUME_STEP을 함께 재검토해야 한다.
문헌
Growth, geometry, and mechanics of a blooming lily
Liang, H. & Mahadevan, L. (2011). PNAS 108(14), 5516–5521. https://doi.org/10.1073/pnas.1007808108
Leucanthemum vulgare Lamarck
Flora of North America Editorial Committee. FNA vol. 19. http://floranorthamerica.org/Leucanthemum_vulgare
A better way to construct the sunflower head
Vogel, H. (1979). Mathematical Biosciences 44(3–4), 179–189.
My favourite flowering image: a capitulum of Asteraceae
(국화과 두상화의 사엽선 쌍과 그 예외) Journal of Experimental Botany 70(21), e6496. https://academic.oup.com/jxb/article/70/21/e6496/2964617
Computer Graphics & Rendering
Sphere tracing: a geometric method for the antialiased ray tracing of implicit surfaces
Hart, J. C. (1996). The Visual Computer 12(10), 527–545.
Optical models for direct volume rendering
Max, N. (1995). IEEE Transactions on Visualization and Computer Graphics 1(2), 99–108.
Next generation post processing in Call of Duty: Advanced Warfare
Jimenez, J. (2014). SIGGRAPH Advances in Real-Time Rendering in Games. https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/
ACES filmic tone mapping curve
Narkowicz, K. (2016). https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/