14

Part V · Pipeline Chapter 14 of 18 Build 2025.31550

POP geometry가 화면에 그려지기까지

“POP attribute → vertex shader input → fragment shader varying의 흐름이 raster pipeline 그 자체다.”

이 챕터에서 정복할 CG 개념

왜 이게 중요한가

지금까지 POP은 GPU 위의 attribute buffer를 만지는 도구였다. 그 buffer가 어디로 가느냐가 14장의 질문이다. POP attribute는 vertex shader의 in 변수, 정확히는 SSBO에서 끌어온 per-vertex 값으로 바인딩된다. fragment shader는 그 값을 varying으로 받아 픽셀 색을 결정한다. 이 흐름이 raster pipeline의 정의이며 OpenGL의 glDrawElements, Unity의 Material, Unreal의 PSO, WebGPU의 render pass에서 같은 모양으로 다시 등장한다. POP → Geometry COMP → Render TOP 경로는 그 파이프라인을 노드 그래프 위에서 직접 만지게 한다.

POP에서의 노출 지점

이론

raster pipeline의 최소 모델

POP SSBO → Vertex Shader (gl_Position) → Primitive Assembly + Clipping → Rasterizer (varying 보간) → Fragment Shader (fragColor) → Blend → Framebuffer (= Render TOP 출력).

vertex shader의 의무는 gl_Position을 clip space에서 채우는 것 하나, 그 외 out은 rasterizer가 fragment의 in으로 보간한다. fragment shader의 의무는 fragColor를 채우는 것 하나다.

TouchDesigner의 GLSL MAT은 이 최소 모델에 두 가지 편의를 얹는다. transform 매트릭스를 TDMatrix uTDMats[TD_NUM_CAMERAS] uniform으로 제공하고, TDDeform(P) · TDWorldToProj(P)가 모델→world→clip 변환을 한 줄로 끝낸다. fragment 측에서는 출력 텍스처 채널 순서를 맞추려 TDOutputSwizzle(curColor)를 거친다.

POP attribute → vertex shader input

POP의 attribute는 GPU 메모리 위의 SSBO다 (4장, 8장 참조). Geometry COMP가 그 SSBO를 vertex buffer로 인식하고 GLSL MAT의 vertex shader가 TDPos() 헬퍼로 현재 vertex의 P를 읽는다. custom attribute를 받으려면 GLSL MAT의 Attributes 페이지에 이름·타입을 등록 (예: vel, vec3)한 뒤 TDAttrib_vel()로 접근한다.

flowchart LR A["POP 그래프\n(P, N, Cd, vel, ...)"] --> B["Geometry COMP\n(render flag 있는 POP)"] B --> C["GLSL MAT\nvertex shader"] C --> D["rasterizer\n(varying 보간)"] D --> E["GLSL MAT\nfragment shader"] E --> F["Render TOP\n출력 텍스처"] G["Camera COMP"] --> F H["Light COMP"] --> F

노드 ≡ 셰이더 한 줄

노드 GLSL MAT
Phong MAT Diffuse fragColor = ambient + diffuse*N·L
Constant MAT Color fragColor = vec4(color, 1.0)
Geometry COMP Transform TDDeform(P)
Camera COMP View+Projection TDWorldToProj(world)

fixed pipeline MAT은 GLSL MAT을 TD가 자동 생성한 결과다.

손작업 (Hands-on)

시작 파일

연결: Sphere POP1 → Normal POP1 (in geo1); Camera COMP1, Light COMP1, Geometry COMP1 → Render TOP1.

단계 1 — 기본 렌더

Render TOP1의 Geometry 파라미터를 geo1로, Camera를 cam1로, Light를 light1로 지정한다. Phong MAT을 만들어 Geometry COMP의 Material 파라미터에 연결한다. 화면: 균일하게 음영이 들어간 구. 보게 될 것: POP이 만든 점·삼각형이 Geometry COMP 안에서 mesh로 해석되고, Phong MAT의 fixed pipeline 셰이더가 그 mesh를 그린다.

단계 2 — GLSL MAT로 같은 결과를 손으로

GLSL MAT1을 만들고 vertex/pixel shader DAT 두 개를 연결한다. Geometry COMP의 Material을 GLSL MAT1로 바꾼다.

vertex shader (vshader1):

// per-vertex: world space 위치 + 법선을 fragment로 보낸다
out Vertex {
    vec3 worldPos;
    vec3 worldNormal;
} oVert;

void main() {
    vec4 worldPos = TDDeform(TDPos());        // SOP/POP → world
    vec3 worldN   = TDDeformNorm(TDNormal()); // 법선의 world 변환
    oVert.worldPos    = worldPos.xyz;
    oVert.worldNormal = worldN;
    gl_Position = TDWorldToProj(worldPos);    // world → clip
}

pixel shader (pshader1):

// per-fragment: 단순 Lambert + ambient
in Vertex {
    vec3 worldPos;
    vec3 worldNormal;
} iVert;

out vec4 fragColor;

void main() {
    vec3 N = normalize(iVert.worldNormal);
    vec3 L = normalize(vec3(0.3, 1.0, 0.5));  // 임의 방향광
    float NdotL = max(dot(N, L), 0.0);
    vec3 col = vec3(0.1) + vec3(0.9, 0.7, 0.4) * NdotL;
    fragColor = TDOutputSwizzle(vec4(col, 1.0));
}

이 코드가 GPU에서 하는 일: 매 vertex마다 vertex shader가 gl_Position을 채운다. rasterizer가 삼각형을 픽셀로 자르고 iVert를 보간한다. 매 픽셀마다 fragment shader가 N·L 내적으로 명도를 정한다. TDOutputSwizzle은 출력 텍스처 채널 배치를 맞춘다 (wiki: "ensures the channels are in the correct place").

보게 될 것: Phong MAT과 거의 같은 음영의 구. 음영 식이 위 6줄에 다 있다.

단계 3 — custom attribute를 vertex → fragment로 흘려보내기

Sphere POP1과 Normal POP1 사이에 GLSL POP1을 끼우거나 Random POP1을 끼워 Cd (color) point attribute를 만든다. 또는 더 명시적으로, GLSL POP1에서 vel 같은 vec3 attribute를 생성한다 (Create Attribs 페이지에서 vel: vec3 선언, Output Attributes에 vel 추가).

GLSL POP1 셰이더 본문:

// 점마다 P를 입력 삼아 의사-velocity를 만든다
void main() {
    const uint id = TDIndex();
    if (id >= TDNumElements()) return;

    vec3 p = TDIn_P();
    vel[id] = vec3(sin(p.x*3.0), cos(p.y*3.0), sin(p.z*3.0));
}

이 코드가 GPU에서 하는 일: 한 thread가 한 점을 맡아 P를 읽고 sin/cos 조합으로 vec3을 만들어 vel SSBO에 쓴다. workgroup-round-up 때문에 마지막 워크그룹이 잉여 thread를 포함할 수 있고 if (id >= TDNumElements()) 가드가 그 쓰기를 막는다.

GLSL MAT1의 Attributes 페이지에 entry 추가: Name vel, Type vec3. 그러면 vertex shader에서 TDAttrib_vel()로 읽힌다. 단계 2의 vertex shader에 vec3 velVertex 블록에 추가하고 oVert.vel = TDAttrib_vel(); 한 줄을 더한다. pixel shader는 색 계산만 교체:

// pixel shader 본문 (Vertex 블록과 fragColor선언은 단계 2와 동일)
vec3 col = 0.5 + 0.5 * iVert.vel;   // -1..1 → 0..1 매핑
fragColor = TDOutputSwizzle(vec4(col, 1.0));

보게 될 것: 구 표면에 부드러운 RGB 그라데이션. 각 픽셀 색은 삼각형 세 vertex vel의 barycentric 보간이다. POP의 attribute가 vertex shader의 in이 되고 out을 거쳐 rasterizer가 fragment의 in으로 보간하는 흐름이 화면에 나타난다.

노드 ↔︎ GLSL 매핑

같은 결과 — Sphere POP을 회색 음영으로 렌더.

노드: Sphere POP → Normal POP → Geometry COMP. Material: Phong MAT (Diffuse RGB 0.6).

GLSL MAT: 단계 2의 vertex+pixel shader, Diffuse 색만 vec3(0.6). Phong MAT은 specular·ambient·rim·emit 항을 자동으로 더하므로 셰이더에서 그 항을 다 쓰면 동일해진다. Phong MAT은 GLSL MAT을 자동 작성한 결과물이다.

확인 질문 (Self-check)

  1. POP의 point attribute가 vertex shader의 어떤 인터페이스로 들어오는가? GLSL MAT의 어떤 페이지·함수가 그것을 노출하는가?
  2. gl_Position은 어느 좌표계의 값인가? TDWorldToProj는 어떤 변환을 합치는가?
  3. fragment shader의 in은 어떻게 vertex shader의 out으로부터 만들어지는가? 어떤 단계가 그 사이에 있는가?
  4. POP에서 만든 custom attribute vel을 GLSL MAT에서 받으려면 GLSL MAT 측에서 무엇을 선언해야 하는가?
  5. TDOutputSwizzle이 없으면 어떤 시각적 증상이 나타날 가능성이 있는가? 왜 그런가?

연결 고리

이 챕터의 한 줄 명제

POP attribute는 vertex shader의 SSBO 입력이고, GLSL MAT은 그 SSBO를 읽어 gl_PositionfragColor를 채우는 손글씨 셰이더다.