네 번째 방향에 남아 있는 것
4차원 공간에 놓인 조각을 3차원으로 잘라 유리 상자 안에 보여 주기. 평면 회전, 클리퍼드 토러스의 거리 함수, 4차원 공의 단면, 단면 속 스피어 트레이싱, 모스 이론과 복셀 위상 추적, 4D 지평선을 따라 「아직 여기 있는 것들」을 처음부터 다시 만들어 봅니다.

보이지 않게 된 것은, 아직 다른 방향에 남아 있다.
유리 상자 안에 돌 하나가 놓여 있습니다. 청록빛 마노처럼 결이 겹겹이 쌓인 돌이고, 금빛 이음매가 표면을 가로지릅니다. 화면 아래의 선을 조금 움직이면 돌이 녹듯이 모양을 바꿉니다. 고리 하나가 가늘어지다 끊어지고, 떨어져 나간 조각은 점점 작아지다가 사라집니다. 반대쪽에서는 아무것도 없던 자리에 작은 구슬이 떠올라 몸체에 붙습니다. 상자는 한 번도 열리지 않았는데, 상자 안을 떠돌던 빛 하나가 사라졌다가 잠시 뒤 상자 바깥에 나타납니다.
플레이그라운드의 「아직 여기 있는 것들」은 네 개의 공간 좌표로 이루어진 조각입니다. 화면에 보이는 것은 그 조각을 3차원으로 자른 단면 하나이고, 선을 움직이는 일은 네 번째 방향을 따라 단면을 옮기는 일입니다. 돌이 녹고 갈라지는 것처럼 보이지만, 조각 자체는 처음부터 끝까지 한 번도 변하지 않습니다. 바뀌는 것은 우리가 서 있는 자리뿐입니다.
이 글에서는 작품을 만든 과정을 다음 순서로 따라갑니다.
- 4차원 물체를 '보는' 두 방법 중 왜 하필 자르는가 — 평면나라와 단면
- 네 번째 방향으로 기울이기 — 평면 회전과 그 순서
- 돌의 모양 — 두 원의 곱으로 만든 토러스와 거리 함수
- 상자를 나가지 않고 밖으로 가는 빛 — 4차원 공의 단면
- 단면을 픽셀로 — 4차원 거리로 하는 레이 마칭
- 조각이 갈라지는 순간을 알아채기 — 모스 이론과 복셀 위상 추적
- 숨은 방향을 색으로 읽게 하기 — 4D 지평선, 지연 단면, 4차원 재질 좌표
- 다른 방향에 남겨 둔 빛 — 흔적
그림 대부분은 직접 만져 볼 수 있습니다. 작품의 model.ts를 이 페이지에서 그대로 불러와 쓰기 때문에, 그림 속 회전과 거리와 조각 수는 작품이 계산하는 값과 같습니다.

평면나라에서 온 편지
구 하나가 평면을 지나갈 때
1884년 에드윈 애벗의 소설 『평면나라(Flatland)』에서 2차원 세계의 주인공 '정사각형'은 3차원에서 온 구의 방문을 받습니다. 구가 평면을 지나가는 동안 정사각형이 보는 것은 점 하나가 원으로 커졌다가 다시 점으로 줄어들어 사라지는 모습뿐입니다. 정사각형에게 구는 '크기가 변하는 원'입니다. 구는 한 번도 변하지 않았는데도 그렇습니다.
이 작품의 관객이 정사각형의 자리에 있습니다. 우리는 3차원 공간에 살고, 조각은 4차원 공간에 있습니다. 둘이 만나는 곳은 3차원 단면입니다. 단면은 구보다 훨씬 재미있게 변할 수 있습니다. 아래 그림에서 평범한 도넛을 평면으로 잘라 보세요.
평면나라 사람은 이 모든 모습을 보고 나서야 '이것들이 모두 같은 물체'라고 추론할 수 있습니다. 단면 하나는 전체를 보여 주지 않습니다. 대신 단면들이 어떻게 이어지는지가 전체를 알려 줍니다. 고리 하나가 원 둘로 갈라지는 순간이 있다는 것, 그 순간이 어떤 기울기에서 오는지가 3차원의 모양을 담고 있습니다. 이 작품의 감상법도 같습니다.
투영이 아니라 절단
4차원 물체를 그리는 가장 흔한 방법은 투영입니다. 정팔포체(tesseract)가 '정육면체 안의 정육면체'로 돌아가는 영상은 4차원 꼭짓점을 원근법으로 3차원에 눌러 담은 그림입니다. 투영은 전체를 한 번에 보여 주지만, 모든 것이 겹쳐 보이고 무엇이 앞에 있는지가 불분명해집니다. 선으로만 그릴 수 있다는 한계도 있습니다.
절단은 반대입니다. 한 번에 일부만 보이지만, 보이는 것은 진짜 3차원 고체입니다. 빛을 받고, 그림자를 드리우고, 유리처럼 속이 비칩니다. 이 작품은 절단을 택했고, 코드의 주석이 그 선택을 분명하게 적어 둡니다.
/** The artwork lives in four spatial dimensions; time only moves the observer. */
/** Perspective projection is a separate view, never a substitute for slicing. */
export function project4(p: Vec4, d = 5): Vec3 | null { … }첫 줄도 중요합니다. 많은 4차원 시각화가 시간을 '네 번째 차원'으로 씁니다. 그러면 단면을 옮기는 일이 곧 애니메이션의 재생이 됩니다. 이 작품에서 네 번째 차원은 공간이고, 시간은 관객을 움직이고 빛을 깜빡이게 할 뿐입니다. 셰이더에도 같은 원칙이 적혀 있습니다. "시간은 조명만 바꾼다. 네 번째 좌표를 대신하지 않는다." 그래서 자동 감상을 끄고 W 선을 처음 자리로 되돌리면, 조각은 정확히 처음과 같은 모습으로 돌아옵니다.
축이 아니라 평면을 돈다
회전은 평면에서 일어난다
3차원에서는 '축을 중심으로 돈다'고 말합니다. 그런데 이 말을 다시 들여다보면, 실제로 움직이는 것은 와 좌표입니다. 그러니 이 회전은 평면 안의 회전입니다. 3차원에서는 평면 하나에 수직인 축이 정확히 하나 있어서 축으로 불러도 헷갈리지 않았을 뿐입니다.
4차원에서는 이 우연이 깨집니다. 평면에 수직인 방향은 와 두 개라서, '어느 축을 중심으로'라는 말이 성립하지 않습니다. 대신 좌표 두 개를 고르는 방법의 수만큼, 즉 개의 기본 회전 평면 가 있습니다.
이 가운데 우리 눈에 새로운 것은 가 들어간 세 평면입니다. 평면에서 돌리면 방향에 있던 것이 숨은 방향으로 기울어 들어가고, 숨은 방향에 있던 것이 방향으로 나옵니다. 작품에서 조각을 좌우로 끌면 , 위아래로 끌면 회전이 바뀌고, 는 0.28 라디안에 고정해 두었습니다.
순서가 결과를 바꾼다
3차원의 회전이 순서에 민감하다는 것은 잘 알려져 있습니다. 4차원에서는 여기에 흥미로운 예외가 생깁니다. 아래 그림의 정팔포체는 같은 두 회전을 순서만 바꿔 적용한 것입니다.
순서가 중요하므로 '되돌리기'도 정확히 거꾸로 해야 합니다. 작품은 순서로 돌리고(rotate4), 되돌릴 때는 순서로 각도를 뒤집어(unrotate4) 풉니다. 이 되돌리기가 이 글에서 가장 자주 나오는 연산입니다. 화면 속 3차원 점 와 단면 위치 가 주어지면, 그 점이 조각 자신의 좌표계에서 어디인지를 묻는 식이기 때문입니다.
셰이더의 originalAt이 이 식을 그대로 옮긴 것입니다. model.ts에는 같은 회전을 열 우선 4×4 행렬로 만드는 rotation4Elements도 있어서, 테스트가 그 행렬과 rotate4가 같은 답을 내는지 확인합니다.
vec4 originalAt(vec3 p, vec2 rotation, float slice) {
vec4 q = vec4(p, slice);
q.zw = turn(-0.28) * q.zw; // 마지막에 돌린 것부터
q.yw = turn(-rotation.y) * q.yw;
q.xw = turn(-rotation.x) * q.xw; // 처음에 돌린 것은 마지막에
return q;
}두 원의 곱으로 만든 돌
4차원의 토러스
평범한 도넛은 큰 원을 따라 작은 원을 돌려서 만듭니다. 4차원에는 더 대칭적인 방법이 있습니다. 를 평면과 평면 두 개로 나누면, 두 평면은 서로 완전히 직교합니다. 이제 평면에 반지름 인 원 하나, 평면에 반지름 인 원 하나를 그리고, 두 원의 점을 하나씩 골라 이어 붙인 점 를 모두 모읍니다.
이것이 두 원의 곱 , 곧 토러스입니다. 3차원 도넛과 달리 이 토러스는 휘어 있지 않습니다. 안쪽과 바깥쪽의 구분이 없고, 어느 점에서 봐도 같은 모양입니다. 이고 원점에서 거리가 1인 경우가 기하학자들이 클리퍼드 토러스라고 부르는 곡면입니다.
이 곡면까지의 거리는 놀랄 만큼 간단합니다. 두 평면이 직교하므로, 점 에서 곡면까지 가장 가까운 점을 찾는 문제는 ' 평면에서 원까지'와 ' 평면에서 원까지'라는 두 개의 독립된 2차원 문제로 갈라집니다. 두 거리를 피타고라스로 합치고 두께 를 빼면, 곡면 둘레의 4차원 고체에 대한 정확한 부호 거리 함수(SDF) 가 됩니다.
float torus4(vec4 q, vec3 parameters) {
return length(vec2(length(q.xy) - parameters.x, length(q.zw) - parameters.y)) - parameters.z;
}이 고체의 경계는 3차원 다양체이고, 모양으로는 원 세 개의 곱인 3-토러스입니다.
손으로 풀 수 있는 단면
회전 없이 단면 를 잡으면 라는 항이 남습니다. 작품의 첫 형태가 쓰는 값 , , 를 넣고 따라가 보겠습니다.
- : 를 만족하는 가 두 개라서 단면은 위아래로 겹쳐 놓인 도넛 두 개입니다.
- : 두 도넛이 에서 만나 하나가 됩니다.
- : 아무것도 남지 않습니다.
아래 그림은 이 단면을 평면으로 한 번 더 잘라서, 상자를 위에서 내려다보듯 보여 줍니다. 도넛 두 개는 이 평면에서 작은 원 네 개로, 합쳐진 도넛 하나는 원 두 개로 보입니다.
세 가지 돌
작품의 조각은 이런 토러스 여러 개를 섞어 만듭니다.
- 몸체: 평면 쌍의 토러스와, 축을 바꿔 평면 쌍에 놓은 토러스 하나를 부드러운 합집합으로 녹여 붙입니다.
- 가는 선: 와 평면 쌍에 머리카락 굵기(0.035, 0.045)의 토러스 두 개를 더 둡니다. 단면 속에서 겹겹의 윤곽선으로 나타납니다.
- 목걸이: 토러스 위의 토러스 매듭을 따라 4차원 공 아홉 개를 꿰었습니다. 구슬마다 W 위치가 달라서, 단면을 옮기면 구슬이 하나씩 떠올라 몸체에 붙었다가 떨어져 가라앉습니다. 크기는 황금비 수열로 흩어 규칙적으로 보이지 않게 했습니다.
const theta = 2 * Math.PI * k / BEAD_COUNT, psi = winding * theta + twist;
const size = radius * (0.78 + 0.44 * ((k * 0.618033988749895) % 1));
return { center: [orbit[0] * Math.cos(theta), orbit[1] * Math.cos(psi),
orbit[0] * Math.sin(theta), orbit[1] * Math.sin(psi)], radius: size };구슬 아홉 개의 거리를 매번 다 계산하면 비쌉니다. 그런데 구슬은 모두 궤도 토러스 위에 있으므로, 궤도 토러스까지의 거리에서 가장 큰 구슬 반지름을 뺀 값이 모든 구슬까지 거리의 하한이 됩니다. 이 하한조차 지금까지의 거리보다 멀면 구슬 루프를 통째로 건너뜁니다. 화면의 대부분 픽셀에서 이 지름길이 통합니다.
세 형태는 같은 구조에 반지름과 매듭의 감김 수(2, 3, −2)만 바꾼 것이고, 형태를 바꾸면 1.4초 동안 매개변수와 색이 함께 보간되어 녹아 넘어갑니다.

01 연결 · CONFLUENCE

02 잔향 · RESONANCE

03 개화 · EFFLORESCENCE
상자를 나가지 않고 밖으로
4차원 공의 단면
조각 주위를 떠도는 빛 세 개는 반지름 인 4차원 공입니다. 중심이 단면에서 W 방향으로 만큼 떨어져 있으면, 단면에 보이는 것은 반지름 인 3차원 공입니다. 피타고라스 정리 에서 다음을 얻습니다.
평면나라의 구가 점에서 원으로 커졌다 줄어드는 것과 똑같습니다. 다만 이 빛은 W 방향으로만 움직이지 않고, 3차원 방향과 W 방향이 섞인 기울어진 타원 위를 돕니다.
원 밖으로 점을 옮기는 법
평면 위에 원을 그리고 그 안에 점을 하나 놓았다고 합시다. 평면 안에서만 움직인다면 점은 원의 둘레를 넘지 않고는 밖으로 나갈 수 없습니다. 하지만 점을 평면 위로 들어 올렸다가 원 밖에 내려놓으면 둘레를 건드리지 않고도 밖으로 옮길 수 있습니다. 작품 노트에 적힌 이 비유가 떠도는 빛의 설계도입니다.
유리 상자는 관객의 3차원 세계에 놓여 있습니다. 빛은 상자 안에서 출발해 W 방향으로 높이 떠오른 다음 3차원으로는 유리 벽을 지나가고, 상자 밖에서 다시 단면으로 내려옵니다. 돌아올 때는 W의 반대편으로 비켜 갑니다.
여기에는 계산해야 할 제약이 하나 있습니다. 관객은 단면을 에서 사이 어디로든 옮길 수 있습니다. 따라서 빛이 3차원 좌표로 벽을 지나는 동안에는 W 위치가 이 범위에서 반지름 이상 떨어져 있어야 합니다. 작품은 타원의 W 진폭(lift)을 2.25–2.55로 잡아 이 조건을 만족시켰고, 테스트가 세 빛, 한 주기 1,500개 시점, 0.04 간격의 모든 단면에서 빛이 '완전히 안'이거나 '완전히 밖'인지 확인합니다.
시간에 따른 각도에도 작은 장치가 있습니다.
// Linger where the ellipse crosses the slice and hurry through the hidden half.
const base = phase + speed * time;
const angle = base - 0.42 * Math.sin(2 * base);이므로, 빛은 단면 근처()에서 평소의 0.16배 속도로 머물고, 보이지 않는 구간에서는 1.84배로 서두릅니다. 빛이 상자 안에 다시 나타나면 '유리 안, 다른 방향에서 빛이 들어옵니다', 안에 있던 빛이 상자 밖에 나타나면 '열린 적 없는 유리 밖에, 같은 빛이.'라는 문장이 떠오릅니다.
단면을 픽셀로 — 4차원 거리로 하는 레이 마칭
화면 전체가 셰이더 하나
작품에는 삼각형 메시가 없습니다. 화면을 덮는 사각형 하나에 프래그먼트 셰이더를 돌려서, 픽셀마다 눈에서 광선을 쏘고 그 광선이 단면 속 고체와 만나는 곳을 찾습니다. 메시로 만들려면 단면이 바뀔 때마다 4차원 고체를 3차원으로 잘라 등위면을 다시 뽑아야 하는데, 거리 함수를 쓰면 그럴 필요가 없습니다.
광선의 교점을 찾는 방법은 스피어 트레이싱(sphere tracing) 입니다. 현재 위치에서 거리 함수가 를 돌려주면, 반지름 인 구 안에는 표면이 없다는 뜻이므로 만큼 안전하게 전진할 수 있습니다. 표면에 가까워질수록 걸음이 작아지다가 가 아주 작아지면 멈춥니다.
4차원 거리는 3차원에서도 안전하다
여기서 질문이 하나 생깁니다. 셰이더가 계산하는 는 4차원 공간에서의 거리인데, 광선은 3차원 단면 안에서 움직입니다. 이 거리를 걸음 크기로 써도 되는가?
됩니다. 단면 위의 점 에서 단면 속 표면까지 가는 모든 경로는 4차원 공간 속의 경로이기도 합니다. 그러니 단면 안에서 잰 가장 짧은 거리는 4차원 전체에서 잰 가장 짧은 거리보다 작을 수 없습니다.
따라서 만큼 걸으면 단면 속 표면을 결코 지나치지 않습니다. 대신 가 실제 단면 거리보다 훨씬 작을 수 있어서, 단면 밖 W 방향에 물질이 가까이 있는 곳에서는 광선이 괜히 조심스럽게 기어갑니다. 아래 그림에서 원들이 아무 경계에도 닿지 않고 작아지는 구간이 그런 곳입니다.
부드러운 합집합의 대가
토러스들을 녹여 붙이는 부드러운 최솟값도 이 안전성을 지켜야 합니다. 작품이 쓰는 이차 다항식 형태는 다음과 같습니다.
이 식의 기울기는 로, 두 기울기의 볼록 결합입니다. 그래서 와 가 1-립시츠(기울기의 크기가 1 이하)이면 결과도 1-립시츠이고, 거리 함수로 걸어도 안전합니다. 테스트는 세 형태 각각 100개 점에서 이 성질을 직접 확인합니다.
대신 합친 표면은 원래 표면보다 최대 만큼 부풀 수 있습니다. 이것은 광선이 처음에 들어가는 경계 구의 반지름을 정할 때 중요합니다. 셰이더는 광선과 경계 구의 교점부터 걷기 시작하는데, 경계 구가 조금이라도 작으면 조각의 가장자리가 잘려 나갑니다. fieldBounds는 모든 토러스, 모든 구슬, 관객이 남긴 흔적에 씩의 여유를 더해 이 반지름을 구합니다. 흔적이 사슬처럼 이어지면 여유도 흔적 수만큼 커집니다.
float b = dot(ro, rd), c = dot(ro, ro) - uTraceRadius * uTraceRadius, h = b * b - c;
if (h < 0.0) return -1.0; // 경계 구를 빗나감
h = sqrt(h);
float t = max(0.0, -b - h), end = -b + h;
for (int i = 0; i < 112; i++) {
float d = field(ro + rd * t);
if (d < 0.0012 * (1.0 + t * 0.05)) return t; // 멀수록 너그럽게
t += max(d * 0.86, 0.001);
if (t > end) break;
}0.86은 이론상 필요 없는 안전 여유입니다. 법선을 구하는 유한 차분, float 정밀도, 반복 횟수 제한이 겹치는 가장자리에서 생기는 작은 구멍을 막아 줍니다. 법선은 축마다 앞뒤로 재는 6번 대신 사면체 방향 4번의 거리 평가로 구합니다.
조각이 갈라지는 순간
단면의 위상은 언제 바뀌는가
작품을 보다 보면 모양보다 개수가 눈에 들어옵니다. 하나였던 것이 둘로 갈라지고, 없던 조각이 생겨나고, 떨어져 있던 둘이 다시 이어집니다. 이런 일은 언제 일어날까요?
모스 이론이 답을 줍니다. 4차원 고체 과 높이 함수 = '회전한 뒤의 W 좌표'를 생각합시다. 단면 의 위상은 를 연속으로 움직이는 동안 대부분 그대로이고, 가 경계 위에서 의 임계점을 지날 때만 바뀝니다. 임계점은 경계의 접공간이 단면과 나란해지는 점, 곧 4차원 법선이 정확히 W 방향을 향하는 점입니다.
- 극소점과 극대점: 경계가 단면에 아래나 위에서 살짝 닿는 점입니다. 새 조각이 점에서 태어나거나, 조각이 점으로 줄어 사라집니다. FIG. 1에서 수평 평면이 도넛 위를 스칠 때와 같습니다.
- 안장점: 두 조각이 목을 맞대고 합쳐지거나, 하나가 잘록해지다 갈라집니다. 같은 종류의 안장점에서 조각 수는 그대로이고 구멍만 생기거나 없어지는 경우도 있습니다.
조각 수는 이 사건들 사이에서는 절대 바뀌지 않습니다. 그래서 W 축 전체를 훑는 동안의 조각 수 그래프는 계단 모양이 되고, 계단의 모서리마다 임계점이 하나씩 있습니다. 이 그래프를 사건이 어떻게 이어지는지까지 그리면 리브 그래프(Reeb graph) 가 됩니다.
복셀로 세기
임계점을 해석적으로 찾기는 어렵습니다. 토러스 여러 개를 부드럽게 섞었기 때문입니다. 작품은 정공법 대신 세는 방법을 씁니다.
- 현재 단면을 30×30×30 복셀 격자(모바일 24)로 덮고, 거리 함수가 음수인 칸을 채웁니다. 이때 칸 크기의 0.45배만큼 팽창시킵니다. 격자보다 가는 목이 끊겨 보이지 않게 하려는 것입니다.
- 면을 맞댄 칸끼리 깊이 우선 탐색으로 묶어 연결 성분에 번호를 붙입니다. 4칸 미만의 성분은 잡음으로 보고 버립니다.
- 단면이 조금 움직이면 다시 번호를 붙이고, 같은 칸에서 이전 번호와 새 번호가 겹치는 관계를 봅니다.
| 겹침 관계 | 사건 |
|---|---|
| 새 성분이 이전 성분과 하나도 겹치지 않음 | 탄생 |
| 새 성분이 이전 성분 여럿과 겹침 | 합체 |
| 이전 성분이 새 성분과 하나도 겹치지 않음 | 소멸 |
| 이전 성분이 새 성분 여럿과 겹침 | 분열 |
for (let index = 0; index < next.labels.length; index++) {
const a = previous.labels[index], b = next.labels[index];
if (a >= 0 && b >= 0) { forward[a].add(b); backward[b].add(a); }
}
backward.forEach((sources, label) => {
if (sources.size === 0) events.push({ kind: "birth", position: next.components[label].centroid });
else if (sources.size > 1) events.push({ kind: "join", position: midpointOfClosest(previous.components, sources) });
});사건의 위치는 성분의 무게중심으로 잡습니다. 합체와 분열은 관련된 성분 가운데 가장 가까운 두 무게중심의 중점에 둡니다. 작품은 이 자리에서 퍼지는 빛의 고리를 그리고, '하나였던 것이 둘로 갈라집니다' 같은 문장을 띄우고, 소리를 켰다면 사건마다 다른 음을 울립니다. 30³ 격자 하나를 세는 데 약 7 ms가 걸리므로, 단면이나 회전이 바뀌었을 때만 0.1초에 한 번씩 셉니다.
훑기 결과에는 간단한 검산이 있습니다. 조각 수는 0에서 시작해 0으로 끝나고, 사건 하나가 조각 수를 정확히 하나씩 바꾸므로 탄생 + 분열 = 소멸 + 합체여야 합니다. 기본 회전에서 세 형태 모두 세 해상도에서 이 등식이 맞습니다. 예를 들어 01 연결을 24³로 훑으면 탄생 4, 분열 4, 소멸 5, 합체 3입니다.
숨은 방향을 색으로 읽기
단면 하나만 보면, 지금 보이는 표면이 W 방향으로 어느 쪽을 향하는지 알 수 없습니다. 곧 사라질 얇은 막인지, 계속 이어질 두꺼운 몸통인지 구분할 수 없습니다. 평면나라 사람이 원 하나를 보고 그것이 구의 적도인지 극 근처인지 모르는 것과 같습니다. 작품은 이 정보를 세 가지 색으로 표면에 칠합니다.
4D 지평선
거리 함수의 W 방향 기울기를 단면 앞뒤로 조금씩 옮겨 재면, 그 점의 4차원 법선의 W 성분 를 얻습니다. 정확한 거리 함수는 기울기의 크기가 1이므로, 법선의 3차원 성분 크기는 입니다.
// The 4D horizon: surfaces leaning into W are about to leave or join this slice.
float ahead = fieldQ(originalAt(p, uRotation, uSlice + 0.025));
float behind = fieldQ(originalAt(p, uRotation, uSlice - 0.025));
float horizon = pow(clamp(abs(ahead - behind) / 0.05, 0.0, 1.0), 5.0);이 1에 가까운 곳은 표면이 단면과 거의 나란하게 W 방향으로 누운 곳입니다. 단면을 조금만 옮겨도 이 근처의 모양이 크게 바뀝니다. 그리고 앞 절의 임계점은 정확히 인 점입니다. 그래서 가장자리의 빛은 곧 조각이 태어나거나 사라지거나 갈라질 자리를 미리 밝혀 줍니다. 5제곱은 이 신호를 임계점 근처로만 좁히는 역할을 합니다.
방금 들어온 면, 떠나는 면
두 번째 단서는 시간입니다. 작품은 실제 단면 위치를 따라 늦게 움직이는 지연 단면 uLagSlice를 하나 더 둡니다. 초당 5의 비율로 지수적으로 따라가므로, 대략 0.2초 전의 단면입니다. 지금 표면 위의 점을 지연 단면에서 평가했을 때 그 점이 밖이었다면 방금 W 방향에서 들어온 물질이고, 안이었다면 표면이 이 점 안쪽으로 물러나고 있는 것입니다.
float before = fieldQ(originalAt(p, uLagRotation, uLagSlice));
float born = smoothstep(0.003, 0.06, before); // 방금 도착 → 달아오르는 금빛
float leaving = smoothstep(0.003, 0.06, -before); // 떠나는 중 → 식어 가는 불씨
float front = pow(0.5 + 0.5 * cos(before * 48.0 - uTime * 2.0), 8.0);front는 before 값의 등고선을 시간에 따라 흘려서, 새로 들어온 면 위로 열의 전선이 지나가는 것처럼 보이게 합니다. 단면을 멈추면 지연 단면이 따라잡으면서 금빛과 불씨가 함께 꺼집니다.
4차원 재질 좌표
세 번째 단서는 돌의 결입니다. 셰이더는 표면 안쪽으로 굴절된 광선을 따라 11–24걸음 들어가며 빛을 쌓는데, 이때 마노의 띠를 3차원 깊이가 아니라 4차원 거리 -coreQ(q)로 칠합니다. 금빛 이음매는 주 토러스의 두 각도 atan(q.y, q.x), atan(q.w, q.z)로, 속의 결정 반짝임은 의 4차원 격자로 정합니다.
모든 무늬가 조각 자신의 좌표 에 묶여 있다는 점이 중요합니다. 관객이 회전하거나 단면을 옮겨도 무늬가 표면 위에서 미끄러지지 않습니다. 대신 단면이 돌의 다른 깊이를 지나가므로 결이 흘러갑니다. 3차원 깊이로 칠했다면 단면이 바뀌어도 띠는 늘 표면과 나란한 같은 모양이었을 것입니다. 4차원 거리로 칠했기 때문에, 띠가 표면에 비스듬히 잘려 드러나는 모습이 지금 이 표면이 W 방향으로 얼마나 기울었는지를 보여 줍니다.

W = −0.30

W = +0.30

W = +0.95
이 밖에도 발광 강도를 알파 채널에 따로 적어 두고 그 빛만 0.45초 시상수로 잔상을 남기는 후처리 패스가 있습니다. 떠도는 빛과 사건의 고리가 장노출 사진처럼 꼬리를 끕니다.
다른 방향에 남겨 둔 빛
작품의 마지막 장치가 제목을 설명합니다. 조각을 두 번 클릭하면 그 자리에 작은 빛, 흔적이 남습니다. 흔적은 화면 좌표가 아니라 조각의 좌표계에 저장됩니다.
/** Store memories in the artwork's frame, so rotation never moves their home. */
export function seedFromSlice(point: Vec3, settings: StillHereSettings, id: number): MemorySeed {
return { position: unrotate4([point[0], point[1], point[2], settings.slice], settings.rotationX, settings.rotationY), … };
}
/** Signed distance along W: a seed is visible only while its 4D sphere meets the slice. */
export function sliceDistance(seed: MemorySeed, settings: StillHereSettings): number {
return rotate4(seed.position, settings.rotationX, settings.rotationY)[3] - settings.slice;
}흔적은 반지름 0.13인 4차원 공이고, 부드러운 합집합으로 조각에 녹아 붙습니다. 단면을 옮기면 흔적은 떠도는 빛처럼 작아지다 사라집니다. 하지만 지워진 것이 아닙니다. 다시 그 W로 돌아오면, 회전을 바꿨더라도 흔적은 조각의 같은 자리에서 다시 떠오르고 '남겨 둔 빛을 다시 만났습니다'라는 문장이 나타납니다.
평면나라의 정사각형이 원 하나가 사라지는 것을 보고 '구가 없어졌다'고 말한다면 틀린 말입니다. 구는 평면 위 어딘가에 아직 있습니다. 작품의 흔적도 마찬가지입니다. 보이지 않게 된 것은 사라진 것이 아니라 다른 방향에 있습니다.
펼쳐 보기 — 투영은 따로
절단을 고집하면 관객은 전체를 한 번에 볼 수 없습니다. 그래서 작품은 전체를 엿볼 수 있는 창을 하나 따로 둡니다. '펼쳐 보기'를 누르면 네 토러스의 중심 곡면을 두 방향으로 감는 고리 176개와 목걸이의 매듭이 원근 투영으로 겹쳐 나타납니다.
vec3 q = 5.0 * p.xyz / (5.0 - p.w); // W가 클수록 크게: 4D 원근
vDepth = abs(p.w - uSlice);
// fragment: alpha = uReveal * (0.085 + 0.25 * exp(-vDepth * 2.5))선의 밝기는 지금 단면까지의 W 거리에 따라 줄어듭니다. 단면 근처의 선이 가장 밝고, 숨은 방향 멀리 있는 선은 희미하게 남습니다. 투영은 절단을 대신하지 않고, 지금 보는 단면이 전체 가운데 어디쯤인지를 알려 주는 지도 역할만 합니다.

화면에 오래 머물기 위해
조각 하나를 그리는 데 픽셀마다 최대 112걸음의 레이 마칭, 굴절된 내부를 따라가는 24걸음, 그림자 18걸음, 주변광 차폐 4번, 4D 지평선과 지연 단면을 위한 거리 평가 3번이 들어갑니다. 오래 켜 두는 작품이라 몇 가지를 아낍니다.
- 바뀔 때만 그립니다. 회전, 단면, 형태 보간, 사건의 고리, 지연 단면 가운데 무엇도 움직이지 않으면 프레임을 건너뜁니다. '동작 줄이기'를 켠 관객에게는 빛도 멈춥니다.
- 느리면 해상도를 줄입니다. 프레임 간격이 40–200 ms인 프레임이 70번 쌓이면 렌더 배율을 0.82배씩 낮춥니다. 배율이 0.85 아래로 내려가거나 모바일이면 그림자와 결정 반짝임을 끄고 내부 걸음을 11로 줄입니다.
- 위상은 가끔 셉니다. 복셀 세기는 단면이 0.006 이상 바뀌었을 때만, 0.1초(모바일 0.16초) 간격으로 돌립니다.
얼마나 맞는가
아름다운 그림은 틀려도 아름답습니다. 그래서 pnpm test:still-here의 테스트 22개가 모델의 약속을 확인합니다. 몇 가지만 옮기면 다음과 같습니다.
| 약속 | 확인하는 방법 |
|---|---|
| 회전은 길이를 보존하고 정확히 되돌아간다 | 4방향 전부에서 unrotate4(rotate4(p)) = p |
| GPU 행렬과 CPU 회전이 같다 | rotation4Elements의 열과 rotate4의 기저 벡터 비교 |
| 토러스 거리 함수가 기하적으로 맞다 | 중심 곡면 위에서 , 튜브 표면에서 0 |
| 거리 함수가 레이 마칭에 안전하다 | 세 형태 × 100점에서 1-립시츠 |
| 떠도는 빛은 4차원 공이다 | |
| 빛은 유리에 닿지 않는다 | 세 빛 × 한 주기 × 모든 단면에서 '완전히 안' 또는 '완전히 밖' |
| 모든 단면이 상자 안에 들어간다 | 세 형태, 여러 회전, 단면 범위 전체 |
| 흔적은 제자리에 있다 | 단면을 떠났다가 돌아오면 같은 4D 위치에서 다시 만남 |
| 자동 감상 경로에서 사건이 일어난다 | 세 형태 모두 30% 이상의 시간 동안 여러 조각, 탄생·소멸·분열 또는 합체 발생 |
마지막 항목은 기술이 아니라 감상에 대한 약속입니다. 자동 감상만 켜 두고 지켜보는 관객도 조각이 떠오르고 갈라지고 가라앉는 것을 볼 수 있어야 합니다.
마치며
이 작품에서 한 장면이 만들어지는 과정을 다시 따라가면 이렇습니다.
관객의 3차원 픽셀 → 광선 → 경계 구 → 점 와 단면 → 거꾸로 돌린 4차원 좌표 → 클리퍼드 토러스들의 부드러운 합집합 → 4차원 거리로 하는 스피어 트레이싱 → 법선과 → 4차원 재질 좌표의 결과 이음매 → 지연 단면과의 비교 → 픽셀
사슬의 고리 대부분은 4차원이 아니어도 쓰이는 기법입니다. 거리 함수 레이 마칭은 데모씬의 오랜 도구이고, 모스 이론은 지형의 등고선에서도 같은 말을 합니다. 이 작품에서 그 기법들은 모두 관객이 볼 수 없는 방향을 짐작하게 하는 데 쓰입니다. 들어오는 면을 데우고, 떠나는 면을 식히고, 사라질 자리를 미리 밝히고, 남긴 것을 기억합니다.
작품을 열고 자동 감상을 끈 다음, W 선을 아주 천천히 움직여 보세요. 조각이 갈라지기 직전에 가장자리가 먼저 밝아지는 것을 볼 수 있습니다. 그리고 흔적 하나를 남긴 뒤 멀리 떠났다가 다시 돌아와 보세요. 흔적은 그 자리에 남아 있습니다.
참고 자료
- E. A. Abbott, Flatland: A Romance of Many Dimensions (1884) — 차원 사이의 만남과 단면.
- T. F. Banchoff, Beyond the Third Dimension (1990) — 4차원 회전, 클리퍼드 토러스, 투영과 단면.
- J. C. Hart, "Sphere Tracing: A Geometric Method for the Antialiased Ray Tracing of Implicit Surfaces", The Visual Computer (1996).
- I. Quílez, "Smooth minimum" — 다항식 부드러운 합집합과 그 오차 .
- J. Milnor, Morse Theory (1963) — 높이 함수의 임계점과 위상의 변화.
- H. Edelsbrunner, J. Harer, Computational Topology (2010) — 리브 그래프와 베티 수.
- M. ten Bosch, 4D Toys (2017) — 4차원 물체를 단면으로 만지는 인터랙션.