유리 나뭇잎이 떨어지는 풍경
하루종일 만들어서 도전해보았다.
Glass Leaf Fall
해당 글은 AI로 대부분의 코드가 작성됨을 알림.
글은 사람이 작성했으나, 수식 및 및 코드 대부분 AI가 작성함.
단풍잎의 형태 발생, N-BK7 유리 광학, 얇은 판의 준정상 낙하, 비압축 난류, 유리 미립자의 유한 Reynolds 수 항력을 한 화면 안에 결합한 WebGL2 연구 데모
AI의 성능이 놀랍다. 실제로 논문을 그대로 박아두니까 거의 그대로 나왔다.
이 작품은 “실제 유리 단풍잎을 그대로 계산했다”고 주장하지 않는다. 공기역학에는 실제 크기와 질량을 사용하지만, 눈에 보이는 굴절을 만들기 위한 광학 두께는 의도적으로 과장하는데, 잎맥은 식물 형태 모델에서 생성하고, 광학 표면은 그 맥을 유리 릿지로 해석한다.
1. 모델 범위와 사실의 소유자
층 | 이 데모가 계산하는 것 | 사실의 소유자 |
|---|---|---|
잎 윤곽 | 5개 갈래, 호길이로 배치한 톱니, 잎자루 |
|
잎맥 | 공간 식민화와 Murray 반지름 법칙으로 생성한 3개 망 |
|
유리 재료 | SCHOTT N-BK7 Sellmeier 굴절률과 RGB 분산 |
|
광학 표면 | 타원체 캡, 잎맥 릿지, 기울기와 Hessian |
|
낙하 | 2D 타원판의 부가질량, Munk 모멘트, 준정상 양력·항력 |
|
공기 | 유선함수로 만든 6옥타브 발산 없는 속도장 |
|
유리 입자 | Schiller–Naumann 보정 종단속도와 1차 응답 |
|
검증 | 실행 중인 GLSL의 128² 부동소수점 readback과 실제 body 통계 |
|
- 층
잎 윤곽
- 이 데모가 계산하는 것
5개 갈래, 호길이로 배치한 톱니, 잎자루
- 사실의 소유자
tools/leaf-model.mjs,shaders/leaf-outline.glsl
- 층
잎맥
- 이 데모가 계산하는 것
공간 식민화와 Murray 반지름 법칙으로 생성한 3개 망
- 사실의 소유자
tools/leaf-model.mjs→shaders/leaf-venation.glsl
- 층
유리 재료
- 이 데모가 계산하는 것
SCHOTT N-BK7 Sellmeier 굴절률과 RGB 분산
- 사실의 소유자
js/glass.js
- 층
광학 표면
- 이 데모가 계산하는 것
타원체 캡, 잎맥 릿지, 기울기와 Hessian
- 사실의 소유자
shaders/leaf-thickness.glsl
- 층
낙하
- 이 데모가 계산하는 것
2D 타원판의 부가질량, Munk 모멘트, 준정상 양력·항력
- 사실의 소유자
js/dynamics.js
- 층
공기
- 이 데모가 계산하는 것
유선함수로 만든 6옥타브 발산 없는 속도장
- 사실의 소유자
js/air.js
- 층
유리 입자
- 이 데모가 계산하는 것
Schiller–Naumann 보정 종단속도와 1차 응답
- 사실의 소유자
js/world.js
- 층
검증
- 이 데모가 계산하는 것
실행 중인 GLSL의 128² 부동소수점 readback과 실제 body 통계
- 사실의 소유자
js/renderer.js,js/controls.js
GLSL은 shaders/만이 소유.
실제로 AI와 작업할때 둘이 합쳐서 작업하는 경우가 있어서 이 경우 명확히 분리하는게 좋았다. leaf model의 경우에 비전 OCR을 통해서 윤곽을 찍은 채로 전달해서 mjs 형태로 구현하는 형식을 취했다.
JavaScript 안에 셰이더 문자열이나 저품질 대체 셰이더를 두지 않는다. js/shaders.js는 명시적 파일 URL을 fetch하고, #include "..."를 한 프로그램당 한 번만 합성하게 했다. 파일이나 확장이 없으면 조용히 폴백하지 않고 시작 단계에서 실패하게 했다
index.html
style.css
js/
main.js 부팅, 프레임 루프, 시스템 배선
shaders.js 명시적 GLSL 파일 로더와 include 해석기
glass.js N-BK7 Sellmeier와 파장별 굴절률
dynamics.js 얇은 타원판의 강체 운동과 RK4
air.js 발산 없는 난류 속도장
world.js 잎·파편·입자 개체군과 측정 통계
renderer.js WebGL2 패스, 인스턴싱, GPU 감사
controls.js UI와 감사표
shaders/
leaf-outline.glsl
leaf-venation.glsl 생성 파일
leaf-thickness.glsl
background.frag
leaf.frag
particle.frag
audit.frag
post.frag
tools/
leaf-model.mjs 윤곽·톱니·잎맥 생성기ES module과 셰이더
fetch를 사용하므로file://가 아니라 HTTP로 열게 해두었다.
런타임 자산 정책
이미지, 외부 텍스처, 오디오, CDN JavaScript는 사용하지 않음
배경, bokeh, 잎 표면과 유리 먼지는 모두 로컬 GLSL이 절차적으로 생성
외부 자동 요청은 UI용 Google Fonts뿐이며 렌더링 결과에는 관여하지 않는다.
모든
.vert,.frag,.glsl은js/shaders.js의 명시적new URL(..., import.meta.url)매니페스트에 등록되는데. 이는 Makonea Lab이 iframe CSP 안에서 프로젝트 파일을 정적으로 식별하도록 하기 위한 조건
실제로 이러한 런타임 자산 정책을 통해서 샌드박스 블록에 격리하는데 성공했다. 개인적으로 구현이라는 것은 결국 올리고자 하는 대상의 상태에 따라서 바뀌니까.
2. 기준 상수와 기본 상태
아래 값은 비슷해 보이는 숫자를 파일마다 다시 적지 않는다. 공기 밀도와 중력은 dynamics.js에서 export하며 잎과 입자가 같은 값을 import하는 것을 체크함.
항목 | 기본값 | 단위 / 의미 |
|---|---|---|
공기 밀도 | 1.204 | kg·m⁻³, 20 °C / 1 atm 모델값 |
중력가속도 | 9.80665 | m·s⁻² |
유리 밀도 | 2500 | kg·m⁻³, 동역학 모델값 |
공기 점도 | 1.81×10⁻⁵ | Pa·s |
잎 반폭 | 0.070 | m |
공력 반두께 | 100 | µm |
화면의 물리 높이 | 0.60 | m |
난류 RMS | 0.60 | m·s⁻¹ |
난류 길이 척도 | 0.370–0.0149 | m |
광학 전파거리 | 0.20 | 잎 좌표 |
광학 돔 높이 | 0.17 | 잎 좌표 |
잎맥 relief |
| 잎 좌표 |
주맥의 | 0.001 | 잎 좌표² |
주변 입자 반지름 | 18–72 | µm, 물리 계산용 |
파쇄 입자 반지름 | 28–143 | µm, 물리 계산용 |
- 항목
공기 밀도
ρ_air- 기본값
1.204
- 단위 / 의미
kg·m⁻³, 20 °C / 1 atm 모델값
- 항목
중력가속도
g- 기본값
9.80665
- 단위 / 의미
m·s⁻²
- 항목
유리 밀도
ρ_glass- 기본값
2500
- 단위 / 의미
kg·m⁻³, 동역학 모델값
- 항목
공기 점도
μ- 기본값
1.81×10⁻⁵
- 단위 / 의미
Pa·s
- 항목
잎 반폭
a- 기본값
0.070
- 단위 / 의미
m
- 항목
공력 반두께
b- 기본값
100
- 단위 / 의미
µm
- 항목
화면의 물리 높이
- 기본값
0.60
- 단위 / 의미
m
- 항목
난류 RMS
- 기본값
0.60
- 단위 / 의미
m·s⁻¹
- 항목
난류 길이 척도
- 기본값
0.370–0.0149
- 단위 / 의미
m
- 항목
광학 전파거리
d- 기본값
0.20
- 단위 / 의미
잎 좌표
- 항목
광학 돔 높이
- 기본값
0.17
- 단위 / 의미
잎 좌표
- 항목
잎맥 relief
- 기본값
0.55 × 0.008- 단위 / 의미
잎 좌표
- 항목
주맥의
1/e반폭 제곱- 기본값
0.001
- 단위 / 의미
잎 좌표²
- 항목
주변 입자 반지름
- 기본값
18–72
- 단위 / 의미
µm, 물리 계산용
- 항목
파쇄 입자 반지름
- 기본값
28–143
- 단위 / 의미
µm, 물리 계산용
광학 두께와 공력 두께는 같은 값이 아니다.
기본 잎은 총 공력 두께가 200 µm인 판으로 떨어지지만, 화면의 광학 두께장은 약 0.072–0.174 잎 좌표를 차지. 70 mm 반폭에 그대로 환산하면 수 mm급 유리입니다. 200 µm 판은 낙엽처럼 움직일 수 있지만 화면에서 강한 코스틱을 거의 만들지 못하고, 수 mm 유리는 아름답게 굴절하지만 낙엽처럼 떨어지지 않는다
3. 단풍잎: 윤곽과 잎맥을 하나의 구조로 만들기
3.1 갈래 프로파일
각 갈래는 축 c, 길이 l, 각 반폭 w를 가진 함수
넓은 어깨와 가는 첨두를 동시에 만들기 위해 갈래 하나당 두 프로파일의 최댓값을 사용합니다. 실제 코드도 식을 그대로 계산한다.
float lobe(float th, float c, float len, float w) {
return len * pow(max(0.0, 1.0 - abs(th - c) / w), LOBE_P);
}
float leafEnvelope(float th) {
float r = 0.0;
for (int i = 0; i < 5; i++) {
r = max(r, lobe(th, LOBE_AXIS[i], LOBE_BL[i], LOBE_BW[i]));
r = max(r, lobe(th, LOBE_AXIS[i], LOBE_SL[i], LOBE_SW[i]));
}
return r;
}raised cosine처럼 축에서 미분값이 0인 프로파일은 끝을 둥글고 넓게 만든다. 현재의 선형 테이퍼 거듭제곱은 축에 모서리를 남겨 단풍잎 특유의 첨두를 보존
3.2 톱니는 각도 간격이 아니라 호길이 간격
극좌표 윤곽 r(θ)의 호길이는 다음과 같다.
생성기는 이 s를 등간격으로 나눠 톱니 꼭짓점을 놓더라, 이러한 구현이 신기하다. 각도를 등분하면 갈래 끝에서 톱니가 벌어지고 만입에서 뭉치기 때문인데, 두 꼭짓점 사이의 반지름은
로 안쪽으로 파인다. q=TOOTH_SHARP>1이라 꼭짓점 사이에 V형 만입이 생기고, 갈래 축 가까이에서는 d를 0으로 보내 갈래 끝이 톱니 부채가 되는 것을 막는다.
float u = (th - t0) / (t1 - t0);
float base = mix(TOOTH_R[lo], TOOTH_R[lo + 1], u);
float notch = pow(sin(PI * u), TOOTH_SHARP);
r = min(base * (1.0 - dip * notch), env);
r = max(r, floorR);3.3 공간 식민화 잎맥
먼저 다섯 actinodromous 1차맥을 갈래 축에 놓는다. 그 뒤 잎몸 내부와 톱니 바로 안쪽에 공급원을 뿌린다 공급원은 영향반경 R_I=0.13 안에서 가장 가까운 마디를 끌어당기고, 새 마디가 R_K=0.045 안에 도달하면 소비, 성장 간격은 D=0.034 잎 좌표로 되어있다.
톱니 꼭짓점 자체가 공급원이므로 2차맥과 잔맥이 톱니를 향해 자람. 윤곽과 맥을 별개 장식으로 겹친 것이 아니라, craspedodromous 조건을 생성 과정에 넣은 것.
3.4 Murray 반지름 법칙
분기점의 맥 구경은 다음 보존식으로 뒤에서부터 계산
for (const i of order) {
if (children[i].length === 0) { w[i] = 1; continue; }
let s = 0;
for (const c of children[i]) s += Math.pow(w[c], 3);
w[i] = Math.pow(s, 1 / 3);
}여기까지의 w는 생물학적 맥의 정규화 반지름인데, 화면에 그리는 가우시안 릿지는 가느다란 맥이 서브픽셀로 사라지지 않도록 최소 반지름 ε=0.065를 둔다.
float radius = mix(VEIN_MIN_RADIUS, 1.0, w);
float widthSq = uVeinW * radius * radius;
float ridge = w * exp(-d * d / max(widthSq, 1e-7));반원 단면 릿지라면 높이와 반폭 모두 반지름에 비례하므로 높이 지수는 1로 해두었다. 그전에는 w^2.35로 구현했으나 가장 가는 가지를 Murray 구경이 요구하는 것보다 훨씬 납작하게 만들었다.(실제로 보기 좋지 않았다) 별도의 미학적 높이 법칙을 쓰려면 Murray라는 이름 아래 섞지 않고 독립 파라미터로 명시해야 할것이다.
여러 선분은 max나 평범한 합 대신 6차 Lp 결합을 사용했다.
float ridge2 = ridge * ridge;
union6 += ridge2 * ridge2 * ridge2;
return pow(max(union6, 1e-18), 1.0 / 6.0);max는 승자가 바뀌는 곳에 곡률 주름을 만들고, 단순 합은 분기점의 공유 선분을 중복해 밝은 구슬을 만들어준다. L6 결합에서는 같은 높이의 두 선분이 만나도 높이가 2^(1/6)=1.122배에 머뭅니다.
3.5 생성 결과
leaf-venation.glsl의 헤더는 생성기가 직접 측정한 결과
항목 | 생성 결과 |
|---|---|
톱니 | 25개 |
잎맥 변형 | 3개 |
변형별 선분 | 210 / 195 / 211 |
총 선분 | 616, 예산으로 잘린 선분 없음 |
톱니 도달 | 세 변형 모두 25 / 25 |
평균 도달 오차 | 0.0379 / 0.0393 / 0.0429 |
윤곽 좌우 대칭 오차 | 1.70×10⁻⁴ |
잎몸 W/L | 1.253 |
정규화 면적 | 0.6829 |
도심 x | 0.32594 |
- 항목
톱니
- 생성 결과
25개
- 항목
잎맥 변형
- 생성 결과
3개
- 항목
변형별 선분
- 생성 결과
210 / 195 / 211
- 항목
총 선분
- 생성 결과
616, 예산으로 잘린 선분 없음
- 항목
톱니 도달
- 생성 결과
세 변형 모두 25 / 25
- 항목
평균 도달 오차
- 생성 결과
0.0379 / 0.0393 / 0.0429
- 항목
윤곽 좌우 대칭 오차
- 생성 결과
1.70×10⁻⁴
- 항목
잎몸 W/L
- 생성 결과
1.253
- 항목
정규화 면적
- 생성 결과
0.6829
- 항목
도심 x
- 생성 결과
0.32594
각 잎은 세 망 중 하나와 y축 반전 중 하나를 골라 여섯 가지 잎맥을 가진다.윤곽은 같은 종의 형태를 공유하고, 맥만 개체마다 달라진다.
4. N-BK7 분산: 수식에서 실행 코드까지
4.1 Sellmeier 식
파장 λ를 µm로 넣음
const B = [1.03961212, 0.231792344, 1.01046945];
const C = [0.00600069867, 0.0200179144, 103.560653];
export function nBK7(um) {
const l2 = um * um;
let s = 1;
for (let i = 0; i < 3; i++) s += (B[i] * l2) / (l2 - C[i]);
return Math.sqrt(s);
}계수에서 도출되는 값은 다음과 같다.
항목 | 계산값 | 기준값 |
|---|---|---|
| 1.51680003 | 1.51680 |
Abbe 수 | 64.16734 | 64.17 |
수직입사 단일면 반사율 | 4.21646 % |
|
임계각 | 41.24519° |
|
- 항목
n_d, 587.5618 nm- 계산값
1.51680003
- 기준값
1.51680
- 항목
Abbe 수
V_d- 계산값
64.16734
- 기준값
64.17
- 항목
수직입사 단일면 반사율
- 계산값
4.21646 %
- 기준값
((n−1)/(n+1))²
- 항목
임계각
- 계산값
41.24519°
- 기준값
asin(1/n_d)
렌더러는 대표 RGB 파장 650, 550, 450 nm를 샘플한다.
채널 |
|
|
|---|---|---|
R, 650 nm | 1.51452031 | 0.992282 |
G, 550 nm | 1.51852239 | 1.000000 |
B, 450 nm | 1.52531950 | 1.013109 |
- 채널
R, 650 nm
- n(λ)
1.51452031
- (n−1)/(n_G−1)
0.992282
- 채널
G, 550 nm
- n(λ)
1.51852239
- (n−1)/(n_G−1)
1.000000
- 채널
B, 450 nm
- n(λ)
1.52531950
- (n−1)/(n_G−1)
1.013109
파장이 짧은 파랑이 가장 많이 꺾이고 빨강이 가장 적게 꺾이는데, 입자 셰이더도 같은 배율을 uniform으로 받아 사용한다.
vec3 bend = (uIndex - 1.0) / (uIndex.g - 1.0);
vec3 bent = vec3(
texture(uBg, vScreen + px * bend.r).r,
texture(uBg, vScreen + px * bend.g).g,
texture(uBg, vScreen + px * bend.b).b);이전 입자 값 1.15 / 1.00 / 0.84는 분산의 폭을 과장한 것뿐 아니라 순서도 뒤집어 빨강을 가장 많이 이동시킴. 현재는 잎과 입자가 동일한 N-BK7 분산을 공유한다.
5. 두께장과 얇은 소자 광학
5.1 윤곽과 두께를 분리
광학 몸체는 직교좌표의 타원체 캡과 잎맥 릿지의 합이다.
float thickness(vec2 p, int variant) {
float d = domeProfile(p);
return uThick * (DOME_FLOOR + (1.0 - DOME_FLOOR) * d)
+ uVein * veinField(p, variant) * (0.25 + 0.75 * d);
}두께장을 잎 윤곽으로 0에 잘라내지 않는다. 유한차분이 그 계단을 가로지르면 실제 표면 곡률이 아니라 잘라낸 경계의 무한 곡률을 읽기 때문이다. 윤곽은 마지막에 coverage와 스침각 Fresnel로 처리한다(이부분에 대해서는 왜 이렇게 했는지 아직도 모르겠다)
5.2 얇은 소자 사상
배경 평면까지의 전파거리를 d라 할 때, 얇은 두께장 t는 광선을
로 이동시킨다. 이 사상의 야코비안은
이고, 한 갈래 근사에서 밝기 배율은
이다. det J=0을 지나는 곳이 접힘과 코스틱 선으로 보인다.
5.3 기울기와 Hessian
중심, 축 방향 네 점, 대각선 네 점의 총 9개 표본으로 기울기와 Hessian을 함께 계산한다.
float txx = (px - 2.0 * t0 + mx) / (e * e);
float tyy = (py - 2.0 * t0 + my) / (e * e);
float txy = (pp - pm - mp + mm) / (4.0 * e * e);
j.grad = vec2(px - mx, py - my) / (2.0 * e);
j.detJ = (1.0 + k * txx) * (1.0 + k * tyy)
- (k * txy) * (k * txy);과거 구현은 생 2차 차분이 이미 포함한 e²를 다시 곱해 Hessian 효과를 e²=3.6×10⁻⁵만큼 줄였다. 반대로 윤곽 자체를 돔 반지름으로 사용한 시도는 톱니와 만입의 고주파 곡률을 모두 두께에 넣어 넓은 면적을 접었는데, 현재는 Hessian 단위와 두께장의 매끄러움을 각각 바로잡았다.
5.4 화면 표시용 코스틱
1/|det J|는 접힘에서 발산하고, 현재 렌더러는 여러 원상을 모아 적분하지 않는다. 그러므로 원시 determinant는 감사표에 보존하되 화면에는 로그 압축한 한 갈래 이득을 사용한다.
float logGain = -log(max(abs(detJ), 0.08));
return exp(0.58 * clamp(logGain, -0.72, 0.72));분모는 CPU에서 비슷하게 다시 계산한 값이 아니라. audit.frag가 같은 thicknessJet()을 128² RGBA32F target에 렌더한 뒤 readPixels로 측정한 잎 내부 평균이다.(계산을 좀 더 빠르게 하기위해서)
5.5 Fresnel, Beer–Lambert, 3D 자세
표면 법선은 앞면 z=t/2의 기울기로 만든다.
이 법선을 적분된 잎의 tilt와 화면 spin으로 회전하는데, 실루엣만 납작하게 만들고 법선을 정면에 두는 2D 트릭을 사용하지 않는다.
비편광 Fresnel은 s, p 편광 반사율의 평균이다.
투과광은 Beer–Lambert 형식 T=exp(−σL)을 사용한다.. 다만 uTint와 채널별 흡수 배율은 SCHOTT의 분광 흡수 데이터가 아니라 시각적 색조 파라미터이다.. 굴절률과 Fresnel은 재료 데이터에 묶여 있지만 흡수색까지 물성값이라고 주장하지 않는다.
6. 낙하: 얇은 타원판의 준정상 모델
6.1 질량, 관성, 부가질량
반폭 a, 반두께 b인 2D 타원판을 단위 깊이로 해석
기본 잎에서 I*=0.5824, broadside 힘평형 속도는 1.797 m/s이다.
6.2 힘과 Munk 모멘트
몸체 좌표의 상대속도를 (u,v), 받음각을 α=atan2(v,u)라 두었다.
Munk 모멘트의 부호가 중요하다.
const CD = P.CD0 + (P.CD90 - P.CD0) * sa * sa;
const CL = P.CL0 * Math.sin(2 * al);
const q = 0.5 * RHO_AIR * V * V * 2 * P.a;
const Fu = -q * (CD * ca - CL * sa);
const Fv = -q * (CD * sa + CL * ca);
const tau = -(P.m22 - P.m11) * u * v
+ P.kcp * P.a * ca * Fv
- P.Ctau * RHO_AIR * P.a ** 4 * Math.abs(w) * w;상태 s=[x,y,θ,u,v,ω]는 한 프레임을 네 하위 구간으로 나눠 RK4로 적분하는데, 화면의 out-of-plane tilt는 별도 사인파가 아니라 이 θ이다.
6.3 구현하지 못한것
준정상 C_L(α), C_D(α)에는 뒤에 와류를 구현하지 못했다. 이부분에 대해서는 추후 논문을 찾아서 추가로 도전해 볼 것이다.
7. 공기: 발산 없는 다중척도 속도장
2D 유선함수의 옥타브 합을
로 두고,
로 속도를 만든다. 따라서 ∂u/∂x+∂v/∂y=0이 구성상 성립한다. Kolmogorov 구조함수 δv∝ℓ^(1/3)에 맞춰 속도 진폭이 k^(−1/3), 유선함수 진폭이 k^(−4/3)이 되도록 두는데,
const c = Math.cos(g.kx * x + g.ky * y + g.om * t + g.ph) * g.amp;
u += c * g.ky;
v += -c * g.kx;6개 옥타브의 가장 큰 길이 척도는 0.3696 m, 가장 작은 척도는 0.01493 m이다. 0.60 m 화면보다 큰 단일 와류가 모든 잎을 같은 방향으로 미는 현상을 피하도록 적분척도를 0.37 m로 낮췄다. 무작위 위상 합은 진폭합이 아니라 RMS로 정규화하며, 실행 중 재측정값은 0.999이다.
8. 유리 입자와 파쇄 먼지
8.1 Stokes 범위 밖을 다루는 방법
구형 입자의 Stokes 종단속도는
이지만, 큰 파쇄 입자는 Re<1에 머물지 않는다. 현재는 속도를 임의 상한으로 자르지 않고 Schiller–Naumann 보정을 반복해 힘평형을 푼다.
Stokes 속도에 대한 보정형은
이고 Re가 v에 의존하므로 10회 고정점 반복한다.
for (let i = 0; i < 10; i++) {
reynolds = Math.max(1e-8,
(2 * RHO_AIR * radius * settle) / AIR_VISCOSITY);
correction = 1 + 0.15 * reynolds ** 0.687;
settle = vStokes / correction;
}현재 반지름 범위의 계산 결과는 다음과 같다.
반지름 | 보정 종단속도 | Reynolds 수 |
|---|---|---|
18 µm | 0.093 m/s | 0.22 |
72 µm | 0.931 m/s | 8.92 |
28 µm | 0.209 m/s | 0.78 |
143 µm | 2.121 m/s | 40.30 |
- 반지름
18 µm
- 보정 종단속도
0.093 m/s
- Reynolds 수
0.22
- 반지름
72 µm
- 보정 종단속도
0.931 m/s
- Reynolds 수
8.92
- 반지름
28 µm
- 보정 종단속도
0.209 m/s
- Reynolds 수
0.78
- 반지름
143 µm
- 보정 종단속도
2.121 m/s
- Reynolds 수
40.30
입자 속도는 dv/dt=(u_air−v)/τ의 한 프레임 정확해 1−exp(−Δt/τ)로 갱신하므로 τ<Δt인 작은 입자도 수치적으로 폭주하지 않는다
8.2 물리 크기와 보이는 크기
18–143 µm 입자는 0.60 m 화면에서 거의 보이지 않는다. 따라서 물리 반지름은 항력과 종단속도에만 사용하고, 렌더링 반크기는 주변 입자 0.45–1.45 mm, 파쇄 입자 0.70–3.10 mm로 확대한다.(그냥 눈에 보이는 것만 눈에 보이는 것만 작업한 것이지 실제 비례 크기는 아닌것으로 보인다)
입자 모양은 원 하나가 아니라 3차·5차 각주기 변형을 가진 작은 facet이며, 배경을 N-BK7 파장 비율로 다시 읽어 분산을 만든다. 잎 전체의 616개 선분 두께장을 작은 입자마다 평가하지 않는 별도 저비용 셰이더이다.
9. 파쇄 모델
유리 파편은 정갈한 삼각형이 아니라 잎 도심 주변에 뿌린 12개 seed의 Voronoi 셀로 만든다.
float shardMask(vec2 p, float seed, float which) {
float dm = length(p - shardSeed(which, seed));
float best = 1e9;
for (int i = 0; i < 12; i++) {
if (float(i) == which) continue;
best = min(best, length(p - shardSeed(float(i), seed)));
}
return best - dm;
}셀의 위치, 파쇄 시점, 파열 impulse는 시각 모델인데, 균열 선단, 응력확대계수, 파괴에너지나 충돌 접촉을 푸는 fracture mechanics는 아니다
10. 실행 중 감사
10.1 GPU 광학 감사
audit.frag는 렌더링 셰이더와 같은 leaf-thickness.glsl을 include합니다. 128² RGBA32F target에서 thicknessJet()을 직접 실행해 다음을 읽었다.
det(I+d(n−1)H)최솟값·최댓값det<0인 접힌 면적|det|<0.15인 코스틱 선 면적중앙 편향
d(n−1)|∇t|표시 이득의 잎 내부 평균
두께 최솟값·최댓값
2026-08-18, 기본 설정, 크롬 브라우저에서 측정한 값은 다음과 같은데, 이 표는 코드 상수가 아니라 회귀 확인을 위한 관측 기록이다.
항목 | 관측값 |
|---|---|
determinant 범위 | −1.04 … 6.32 |
접힌 잎 면적 | 9.9 % |
코스틱 선 면적 | 11.3 % |
중앙 편향 중앙값 | 0.0112 잎 좌표 |
정규화 전 표시 이득 | 1.059 |
적용 정규화 | ×0.944 |
두께 범위 | 0.072–0.174 잎 좌표 |
렌더링 | 60 FPS 관측, 콘솔 오류 없음 |
- 항목
determinant 범위
- 관측값
−1.04 … 6.32
- 항목
접힌 잎 면적
- 관측값
9.9 %
- 항목
코스틱 선 면적
- 관측값
11.3 %
- 항목
중앙 편향 중앙값
- 관측값
0.0112 잎 좌표
- 항목
정규화 전 표시 이득
- 관측값
1.059
- 항목
적용 정규화
- 관측값
×0.944
- 항목
두께 범위
- 관측값
0.072–0.174 잎 좌표
- 항목
렌더링
- 관측값
60 FPS 관측, 콘솔 오류 없음
FPS는 장치·창 크기에 따라 달라진다(따라서 반드시 이 값에 일치한다고 할 수 없다)
10.2 낙하 통계는 하드코딩하지 않음
각 잎의 실제 적분 속도를 월드 좌표로 되돌려 수직선과의 순간 각도를 측정하였다.
다만 이 부분은 추후에 하드코딩하는게 성능면에서 유리한것으로 보인다.
감사표는 최근 최대 2048개 표본의 중앙값을 보여준다. 동시에 통계 reset 이후 모든 잎의 순 변위를 합쳐
도 표시한다. gust 슬라이더를 바꾸면 이전 공기장에서 얻은 표본을 섞지 않도록 통계를 초기화한다.
11. 수정 이력: 잘못된 구현에서 현재 구현으로
문제 | 왜 틀렸는가 | 현재 구현 |
|---|---|---|
입자 RGB | 빨강을 더 꺾고 파랑을 덜 꺾어 N-BK7 분산 순서를 역전 |
|
Stokes 속도를 0.65/1.35 m/s에서 clamp | 그 속도에서 이미 | Schiller–Naumann을 속도와 함께 반복 계산 |
잎맥 높이 | 반원 릿지의 높이는 반지름과 선형; Murray 아래에 두면 오해 | 높이와 반폭 모두 |
| 잎과 먼지가 같은 화면에서 다른 공기를 통과 |
|
낙하 각도 두 줄이 상수이고 predicate가 | 난류척도 변경을 절대 검출하지 못함 | 실제 적분 body의 순간 중앙값과 순변위를 런타임 측정 |
Hessian에 | determinant가 거의 1이라 코스틱이 사실상 꺼짐 | 생 2차 차분을 정확히 |
윤곽으로 돔을 나눔 | 톱니·만입의 곡률이 두께장에 들어가 넓은 면적이 접힘 | 독립된 직교 타원체 캡 |
잎맥 결합에 | 교차선의 주름 또는 분기점의 밝은 구슬 | 매끄러운 L6 union |
잎좌표 편향을 UV에 바로 더함 | 창 크기에 따라 굴절 세기가 달라짐 | tilt → spin → scale → aspect 순으로 변환 |
실루엣과 법선의 자세가 분리 | edge-on 잎도 정면 Fresnel을 유지 | 적분 자세로 실루엣과 법선을 함께 회전 |
0.80 m 적분척도 | 0.60 m 화면에서 개체군이 한 덩어리로 이동 | 0.37 m부터 0.0149 m까지 6옥타브 |
- 문제
입자 RGB
1.15/1/0.84- 왜 틀렸는가
빨강을 더 꺾고 파랑을 덜 꺾어 N-BK7 분산 순서를 역전
- 현재 구현
n(λ)−1의 실제 비0.9923/1/1.0131
- 문제
Stokes 속도를 0.65/1.35 m/s에서 clamp
- 왜 틀렸는가
그 속도에서 이미
Re≈4–12; 저Re 법칙의 범위 밖- 현재 구현
Schiller–Naumann을 속도와 함께 반복 계산
- 문제
잎맥 높이
w^2.35- 왜 틀렸는가
반원 릿지의 높이는 반지름과 선형; Murray 아래에 두면 오해
- 현재 구현
높이와 반폭 모두
w에 선형, 가시성 최소폭은 별도 명시
- 문제
ρ,g가 파일마다 다름- 왜 틀렸는가
잎과 먼지가 같은 화면에서 다른 공기를 통과
- 현재 구현
dynamics.js의 상수를world.js가 import
- 문제
낙하 각도 두 줄이 상수이고 predicate가
true- 왜 틀렸는가
난류척도 변경을 절대 검출하지 못함
- 현재 구현
실제 적분 body의 순간 중앙값과 순변위를 런타임 측정
- 문제
Hessian에
e²가 두 번 반영- 왜 틀렸는가
determinant가 거의 1이라 코스틱이 사실상 꺼짐
- 현재 구현
생 2차 차분을 정확히
e²로 나눔
- 문제
윤곽으로 돔을 나눔
- 왜 틀렸는가
톱니·만입의 곡률이 두께장에 들어가 넓은 면적이 접힘
- 현재 구현
독립된 직교 타원체 캡
- 문제
잎맥 결합에
max또는 단순 합- 왜 틀렸는가
교차선의 주름 또는 분기점의 밝은 구슬
- 현재 구현
매끄러운 L6 union
- 문제
잎좌표 편향을 UV에 바로 더함
- 왜 틀렸는가
창 크기에 따라 굴절 세기가 달라짐
- 현재 구현
tilt → spin → scale → aspect 순으로 변환
- 문제
실루엣과 법선의 자세가 분리
- 왜 틀렸는가
edge-on 잎도 정면 Fresnel을 유지
- 현재 구현
적분 자세로 실루엣과 법선을 함께 회전
- 문제
0.80 m 적분척도
- 왜 틀렸는가
0.60 m 화면에서 개체군이 한 덩어리로 이동
- 현재 구현
0.37 m부터 0.0149 m까지 6옥타브
실제로 AI로 작업하면서 구현에서 게산이 실제로 틀린 경우가 있었다. 처음 한번에 구현을 못해서, 그 구현들에 대해서 잘못된 값을 측정해서 기록해두었다.
AI는 나보다 훨씬 똑똑하지만, 내가 원하는대로 한번에 하지는 못하는 것 같다. 내 생각에 논문을 넣고 수식을 넣었을때 중간 값이 증발하는 문제로 발생하는 것 같다.
12. 렌더링 파이프라인
background.frag → RGBA16F background
│
├─ copy.frag → RGBA16F scene
│
├─ leaf.frag
│ ├─ N-BK7 RGB 굴절
│ ├─ Fresnel + Beer–Lambert
│ └─ GPU 정규화 코스틱
│
├─ particle.frag → 유리 먼지와 파쇄 glint
│
└─ post.frag → exposure / tone mapping → 화면
audit.frag → RGBA32F 128² → readPixels → 감사표와 caustic 정규화잎과 파편은 instanced quad이다. 멀리 있는 작은 개체를 먼저 그린 뒤 가까운 개체가 덮는다. 배경은 화면공간 텍스처이므로 겹친 잎이 서로를 다시 굴절시키지는 않는다.
13. 조작
컨트롤 | 의미 |
|---|---|
Leaves | 잎 개체 수, 1–40 |
Leaf half-span | 실제 동역학 반폭, 20–120 mm |
Half-thickness | 공력 반두께, 60–1200 µm; |
Gust speed | RMS 난류 속도, 0–3.0 m/s; 변경 시 운동 통계 reset |
Propagation distance | 얇은 소자 전파거리 |
Caustic | 압축 코스틱 이득 혼합량 |
Dome | 광학 타원체 캡 높이 |
Vein relief | 생성 잎맥의 광학 높이 |
Glass motes | 주변 입자와 파쇄 먼지 밀도 |
Absorption | 예술적 Beer–Lambert 색 흡수량 |
Exposure | 최종 HDR 노출 |
Time | 관찰용 시간 배율, 0.03–2.0× |
- 컨트롤
Leaves
- 의미
잎 개체 수, 1–40
- 컨트롤
Leaf half-span
- 의미
실제 동역학 반폭, 20–120 mm
- 컨트롤
Half-thickness
- 의미
공력 반두께, 60–1200 µm;
I*와 운동영역 변경
- 컨트롤
Gust speed
- 의미
RMS 난류 속도, 0–3.0 m/s; 변경 시 운동 통계 reset
- 컨트롤
Propagation distance
- 의미
얇은 소자 전파거리
d, 0–1.5 잎 좌표
- 컨트롤
Caustic
- 의미
압축 코스틱 이득 혼합량
- 컨트롤
Dome
- 의미
광학 타원체 캡 높이
- 컨트롤
Vein relief
- 의미
생성 잎맥의 광학 높이
- 컨트롤
Glass motes
- 의미
주변 입자와 파쇄 먼지 밀도
- 컨트롤
Absorption
- 의미
예술적 Beer–Lambert 색 흡수량
- 컨트롤
Exposure
- 의미
최종 HDR 노출
- 컨트롤
Time
- 의미
관찰용 시간 배율, 0.03–2.0×
패널은 기본으로 접혀 있고 inert 상태로 두었다(makonea 썸네일 문제)버튼으로 열면 수식의 파라미터와 현재 감사 결과를 함께 볼 수 있다.
14. 알려진 한계
얇은 소자와 한 갈래 화면공간 광학입니다. 접힌 영역의 여러 원상을 경로추적하지 않는다.
유리 내부의 횡방향 이동, 다중 내부반사, 편광 간섭을 계산하지 않는다.
uTint흡수는 N-BK7의 측정 분광 투과율이 아니라 디자인 파라미터이다.겹친 잎은 서로를 굴절하지 않습니다. 모든 잎은 같은 원본 배경을 읽는다.
광학 두께와 공력 두께가 의도적으로 분리되어 있다.
공력은 2D 준정상 모델이며 비정상 후류, vortex shedding, 3D precession이 없다.
공기장은 발산 없는 합성장이지 Navier–Stokes DNS가 아니다.
Schiller–Naumann 입자는 구형·고립 입자 근사이며 유리 조각의 실제 비구형 항력이 아니다.
입자의 표시 크기는 픽셀 가시성을 위해 물리 크기보다 크다(실제로 입자를 좀 더 작게하니까 보기에 좋지 않았다)
파쇄는 Voronoi 시각 모델이고 응력 기반 파괴해석이 아니다.
잎맥은 고정 seed 세 개와 반전으로 여섯 종류뿐이므로 많은 잎에서는 반복된다.
공간 식민화는 형태 발생 알고리즘이지 auxin 수송 PDE의 직접 해가 아니다.
WebGL2와
EXT_color_buffer_float, HTTP 제공 환경이 필요하다
16. 참고문헌
SCHOTT, Optical Glass Datasheet — N-BK7.
L. J. Hickey, “Classification of the Architecture of Dicotyledonous Leaves,” American Journal of Botany 60 (1973), 17–33.
A. Runions et al., “Modeling and visualization of leaf venation patterns,” ACM Transactions on Graphics 24 (2005), 702–711, DOI: 10.1145/1073204.1073251.
C. D. Murray, “The Physiological Principle of Minimum Work,” Journal of General Physiology 9 (1926), 835–841.
M. Born and E. Wolf, Principles of Optics, 7th ed., Cambridge University Press, 1999.
H. Lamb, Hydrodynamics, 6th ed., Cambridge University Press, 1932, §71.
M. M. Munk, The Aerodynamic Forces on Airship Hulls, NACA Report 184 (1924).
A. Andersen, U. Pesavento and Z. J. Wang, “Unsteady aerodynamics of fluttering and tumbling plates,” Journal of Fluid Mechanics 541 (2005), 65–90.
S. B. Field et al., “Chaotic dynamics of falling disks,” Nature 388 (1997), 252–254.
L. Schiller and A. Naumann, “A drag coefficient correlation,” Zeitschrift des Vereines Deutscher Ingenieure 77 (1935), 318–320.
개인적인 메모: 세상 정말 좋아졌다. 그냥 비슷한 논문들 찾아서 검색한 후에 각각 논문들의 값만 넣어주기만 하면 애니메이션이 뽑힌다. 내가 원하는 것과 비슷하다. 잎맥쪽 부분에서 계산 공식을 좀 더 고치면 좋을것 같다.