본문 바로가기
카테고리 없음

Aurora

by 서나하 2025. 12. 25.

Volumetric rendering using Ray marching

 

Ray marching을 쓰는 이유

일반적인 렌더링: 표면(surface)을 렌더링, 픽셀 하나 = 표면 한 점

이거: 표면이 아닌 공간(volume)을 렌더링, 픽셀 하나 = 카메라 시선이 공간을 통과한 경로

 

밀도 개념

density = 카메라 시선이 어떤 공간을 통과하면서 누적한 발광 물질의 양

적분 결과 = 그 픽셀이 얼마나 밝게 보일지 (이걸 ray marching에서 +stepsize로 근사)

공간의 국소 밀도 * 거리 조각

 

한 번 샘플링 안하고 계속 더하는 이유

오로라/ 안개/ 연기/ 볼륨광은 표면 개념이 아니고 두께가 있고, 안쪽으로 갈수록 계속 빛을 내니까 박스 안에서 계속 누적되어야 하기 때문

density += BaseDensity * StepSize * height_factor * noise_value;

이 지점에서 이만큼의 빛을 더한다..~

 

Noise의 역할

공간 안에서 밀도가 균일하지 않게 만드는 것

noise = 공간 구조, density = 에너지 양, ray marching = 적분 도구

노이즈가 xy 평면에서 샘플링되니까 기둥처럼 보이게 만들 수 있음

 

Box가 오로라가 존재할 수 있는 공간을 정의한다면, height_factor는 고도에 따른 밀도 감소, noise는 자기장/플라즈마 요동, ray marching은 빛의 누적 이런 느낌.

-> 정리하면... 레이 마칭을 통해 카메라 시선이 공간 안에서 만난 발광 물질의 총량을 계산하고, 그 적분값인 density라는 결과를 낸다.


Dual Noise Sampling

같은 노이즈(simplex soft)를 약간 다른 위치에서 2번 샘플링하는 아이디어.

uv1은 오른쪽 위로, uv2는 왼쪽 아래로 움직이도록. 그래서 두 값을 빼면 노이즈의 경계선만 강조되는 효과

노이즈1: ████░░░░░░

노이즈2: ░░░░████░░

결과:      ████▓▓▓▓██

이걸로 커튼 효과를 만들겠다는거

 

옵션으로 줄 수 있는 값들이 있음

Contrast Enhancement

noise_value = (0.5 - abs(noise_value - 0.5)) * 2.0;
nosie_value = pow(noise_value, 16);

 

순서대로 첫줄은 중앙값 강조, 다음줄은 명암 대비 강화의 역할을 함

0.5에 가까운 값은 밝게, 0이나 1에 가까운 값은 어둡게 만드니까 결과적으로 중간 톤만 남길텐데

 

처음에 simplex soft noise texture가 구름 형태였던걸 생각하면

이 연산을 통해 노이즈 값의 분포가 극단값들을 제거해 0.5 근처 값만 밝게 남도록 함

like this.... 중앙이 밝고 가장자리가 부드러우니까 오로라 밴드를 나타낼 수 있는 것

 

Before:  ████░░░░████░░░░████

After:     ░░░▓████▓░░▓████▓░░░  

 

이게 노이즈 텍스처 상에서 대비를 이루는 부분들의 경계선이지 않을까 싶음

그리고 여기서 n승을 해주면 falloff 정도를 높여서 sharpening의 효과를 낼 수 있음

 

Before: ░░▓▓████▓▓░░ (흐릿한 그라데이션)

After:    ░░░░████░░░░ (선명한 빛줄기)

 

Distortion

vec2 uv_distort = texture(noise, uv).rr * distortion;
uv1 += uv_distort;
uv2 += uv_distort;

 

다음으로는 UV 좌표 자체를 노이즈로 흔들어줄거임

 

Before: | | | | | | | |  (직선 커튼)

After   |~|~|~|~|~ (물결치는 커튼)

 

전체 파이프라인

기본 UV 좌표 — 노이즈로 UV 왜곡 — 2개의 다른 시간/오프셋으로 노이즈 샘플링 — 두 노이즈를 빼서 경계선 추출 — SMOOTHSTEP으로 부드럽게 — 중앙값 강조(0.5 근처만 살리기) — POW로 밝은 부분 극대화 — 끝

OFFSET 두 노이즈의 간격 클수록 커튼 주름이 굵어짐
SMOOTHNESS 경계 부드러움 클수록 그라데이션이 부드러움
DISTORTION UV 왜곡 강도 클수록 커튼이 더 주름지게
SCALE 노이즈 크기 클수록 패턴이 작고 촘촘해짐
POW 명암 대비 클수록 빛줄기가 가늘고 선명

 


Swirl Pattern

Swirl Transformation을 통해 이 패턴을 생성할 것임

다음과 같은 로직을 적용.

 

극좌표 변환

각 픽셀 (x, y)에서:
- 중심으로부터 거리: dist = sqrt((x-center_x)² + (y-center_y)²)
- 각도: angle = atan2(y-center_y, x-center_x)

 

각 픽셀에서 중심으로부터의 거리, 각도를 구해줌

 

거리 기반 회전

 

if (dist < radius):
    falloff = 1.0 - (dist / radius)  // 중심:1.0, 외곽:0.0
    rotation = strength * falloff
    new_angle = angle + rotation

 

그래서 중심에 가까울수록 많이 회전할 수 있도록!

 

new_x = center_x + cos(new_angle) * dist
new_y = center_y + sin(new_angle) * dist

 

그리고나서 다시 직교좌표로 변환해줌

 

역방향 매핑 (Inverse Mapping)

 

원본:        Swirl 적용 후:
  ●            ◐ ← 중심부: 많이 회전
  ●            ◑
  ●            ● ← 외곽부: 조금만 회전

 

지금까지의 과정이 목적지 픽셀(x,y)에서 시작해

이 위치의 색을 어디서 가져올지를 역으로 계산하는거임

그래서 swirl을 역으로 적용해서 소스 위치를 찾을 수 있고

그거슨 원본 텍스처에서 샘플링!

===

Inverse mapping

변형된 UV로 텍스처 샘플

회전된 좌표에서 값을 가져와서 현재 픽셀에 그리는 개념

 

Seamless 유지

Radius 밖은 변환하지 않고 original position을 리턴하는 방식을 통해 유지하는데 (원본 텍스처가 seamless라는 가정하에)

그럼 추가로 radius 안에 작은 radius를 하나 더 정의해서 거기부터 real radius까지는 변환되는 그 정도를 그라데이션으로 하면 될듯

 

* 요약하면 다음과 같음

Swirl Transformation (Inverse mapping)

1. 극좌표 변환: 각 픽셀을 중심으로부터의 거리/각도로 표현

2. 회전 변형: 중심에 가까울수록 많이 회전 (각도에 적용)

3. 직교 좌표 변환: xy 좌표로 되돌려 해당 좌표를 샘플링할 uv로 사용

그래서 변형된 UV로 텍스처에서 샘플링, 결국 회전된 좌표에서 값을 가져와서 현재 픽셀에 그리는 개념이라 역방향 매핑..~


어떤 오로라를 제공할지에 대한 고민

종류(커튼, 광선, 소용돌이)는 늘리기 나름인데

* 이렇게 형태에 따른 분류가 되는 경우에는 보간 옵션을 잘 지원할 수 있게 UX적인 고민..이 더 중요할듯

추가로 어떤 형태든 VOLUMETRIC 형태로의 변환이 가능하도록 지원하는게 좋을거같음 

그리고 스티브, SAR ARC 이런건 오히려 SPLINE으로 아주 잘 폴리싱된 버전을 만들어서 제공하는거.. 이상의 방법이 떠오르지 않음

오히려 한줄.. 이렇게 나오는건 노이즈를 더 SPARSE하게 하는거랑 다를까? 싶음

 

특히 색깔옵션이 중요할거같음. 사용자가 살짝만 깔짝대도 괜찮은 그런 색조합이 나와야함 


Swirl Distortion

왜곡에도 여러 옵션을 줄 수 있는데

특히 다른 패턴과 보간될 때는 어떤 기법을 써야 제일 자연스러울지 판단에 활용

 

fall off

일반적인 선형 감쇠라면 다음과 같이 거리기반으로 감쇄 정도가 들어갈 것임

1.0 0.75 0.5 0.25 0.0

float falloff = 1.0 - (dist / radius);

 

근데 중심 근처는 강하게 유지하다가 가장자리로 갈수록 급격히 약해지게,

그래서 전체적으로 왜곡을 중심에 집중시키고 싶다면 다음과 같은 비선형 커브 감쇠를 써주면 됨

float falloff = 1.0 - smoothstep(0.0, swirlRadius, dist);

 

근데 이게 결과적으로 왜곡을 약하게 만들어서 같은 swirl strength를 썼을 때 소용돌이 왜곡이 훨씬 줄어들었음

 

morph cycle

float currentSwirlStrength = morphCycle * MaxSwirlStrength;

 

이렇게 정의해주는 부분이 있는데, 여기서 morphCycle이라는게 '자동 Shape Morphing 애니메이션'의 역할을 함

morphCycle = sin(Time * MorphSpeed) * 0.5 + 0.5; 이라서 0—1—0—1이 반복되도록 하는거

근데 이 사이클이 왜곡 정도를 시간에 따라 변화시켜서 애니메이션이 없을 때와 다른 결과를 만들어내버림

 

swirl로 갈 때.

morphCycle = smoothstep(0.0, 1.0, progress);

 

그냥 선형적인 왜곡을 표현할 수도 있고 가속을 줄 수도 있음

가속을 준다면 옵션이 끝도 한도 없어지긴한데

예를 들면 Exponential ease-in

morphCycle = pow(progress, 3.5); 이렇게 ease-in 정도를 power로 줄 수도 있음

 

 

Ray to Swirl, Swirl to Ray

소용돌이로 갈 때는 swirl distortion의 과정이 반영된 변화가 자연스러운데

원래 상태로 돌아올 때 이걸 그대로 역으로 써버리면 먼가 풀리는 느낌이 들면서 굉장히 어색함

그래서 먼가 먼가 풀어지는 듯하게 만들어주는 새로운 방법이 필요함 (이것도 비교적 아주 느리게 적용)

 

Bottom-Up Dissolve

████████████ ← 위 (소용돌이)

████████████

▓▓▓▓▓▓▓▓▓▓▓▓ ← 경계선 (부드러움)

░░░░░░░░░░░░ ← 아래 (커튼)

░░░░░░░░░░░░

 

Fragmentation

██░█░██░░█░█ ← 노이즈 패턴으로

█░░███░█░░██ ← 불규칙하게

░██░█░░███░█ ← 파편처럼

░█░░░██░█░░░ ← 사라짐

 

1+2

████████████ ← 위 (소용돌이 유지)

███░█░██░███ ← 경계가 파편화

░██░░█░░█░██ ← 아래부터 + 불규칙

░░█░░░░░░█░░ ← 커튼으로

░░░░░░░░░░░░

 

snapshot

Time = 0~2초 (Phase 1): curtainUV = uv + Time * Speed (이동 중)

Time = 2초: Phase 2 시작, curtainUV = uv + 2 * Speed 상태

소용돌이 계산은 uv 기준 → 갑자기 uv + 2 * Speed → uv로 점프!

@ 원하는 거: Phase 2 시작 시점의 curtainUV를 "고정"하고 그 위에서 회전/왜곡

댓글