이 챕터에서 정복할 CG 개념
- 같은 mesh를 N번 그리는 일은
glDrawElementsInstanced한 줄, 곧 한 draw call이다. - per-instance 데이터는 instance attribute (vertex attribute의 divisor=1 형태)로 GPU에 흐른다.
- vertex shader는
gl_InstanceID로 인스턴스를 구별하고 instance transform을 곱한다. - TouchDesigner에서 Geometry COMP의 Instance 페이지는 그 per-instance buffer의 소스를 지정하는 노드 인터페이스다. POP의 P attribute는 정확히 그 자리에 꽂힌다.
왜 이게 중요한가
CPU가 객체를 하나씩 그리라고 GPU에 명령하면 1000개일 때 1000번의 driver 왕복이 일어난다. 한 번 수 마이크로초의 비용이 모이면 vertex 처리 시간을 가볍게 추월한다. instancing은 "한 mesh, 다른 transform N번" 패턴을 단일 명령으로 압축한다. 14장의 raster pipeline 위에서 vertex shader가 N번 시작되되 instance attribute가 인스턴스마다 갱신된다. POP 측에서는 attribute 한 컬럼이 인스턴스의 위치·회전·스케일·색을 정한다. 결과는 같지만 비용 곡선이 다르다.
POP에서의 노출 지점
- Geometry COMP Instance 페이지의
instancing토글.instanceop은 per-instance 소스 OP — CHOP, DAT, 그리고 POP을 받는다 (wiki: "Number of samples/rows in this CHOP or DAT determines the number of instances"). POP일 때는 element 수가 인스턴스 수다. - 변환 매핑:
instancetx/y/z(Translate),instancerx/y/z(Rotate),instancesx/y/z(Scale),instancepx/y/z(Pivot). Color:instancecolormode+instancer/g/b/a. Texture:instancetexindex+instancetexs. - Custom attribute: Instance 3 페이지의
instance0customop/instance0customx..w— GLSL MAT에서TDInstanceCustomAttrib0()~TDInstanceCustomAttrib3()로 읽음 (wiki: "Custom attributes allow arbitrary attributes to be assigned to instances, usable in a GLSL MAT."). - GLSL MAT instance 함수:
int TDInstanceID()("should always be used, not gl_InstanceID directly"),mat4 TDInstanceMat(),vec4 TDInstanceColor(vec4),vec4 TDInstanceCustomAttrib0..3(),uint TDInstanceTextureIndex(),vec4 TDDeform(int instanceID, vec3 pos)(instance+skinning+world 합성).
이론
per-vertex vs per-instance
vertex attribute는 보통 vertex마다 흐른다 (divisor = 0). 같은 buffer를 divisor = 1로 바인딩하면 instance 단위로 값이 바뀐다. instance 100, mesh당 vertex 24라면 per-vertex는 2400번, per-instance는 100번 fetch된다. draw call은 한 번, vertex shader는 instance × vertex 번 실행된다.
TouchDesigner의 instance attribute 데이터 경로
Geometry COMP는 두 입력을 받는다. 컴포넌트 안의 mesh (Box POP)와 Instance 페이지가 가리키는 외부 OP (Grid POP). 후자의 element 수가 인스턴스 수, 후자의 attribute가 per-instance 데이터다.
vertex shader 안에서
// 의사 코드 (TD가 내부에서 합성하는 것의 모양)
vec4 worldPos = TDInstanceMat(TDInstanceID()) * vec4(TDPos(), 1.0);
gl_Position = TDWorldToProj(worldPos);
TDInstanceMat()은 Instance 페이지의 translate/rotate/scale/pivot 파라미터를 모아 4×4 transform으로 만든 결과다. TDDeform(P)을 그대로 쓰면 skinning + instance + world 변환을 한 번에 처리한다.
직관과 어긋나는 부분
instancing은 draw call을 줄이지 vertex 작업을 줄이지 않는다. 인스턴스 100개라면 vertex shader는 여전히 vertex수 × 100번 실행된다 — 16장에서 그 함의를 다룬다. per-instance attribute가 vertex 메모리 대역폭을 줄여 주지도 않는다. 줄어드는 것은 CPU→GPU 호출 횟수이고 부수적으로 fetch 패턴이 일관되어 캐시 효율이 오른다.
손작업 (Hands-on)
시작 파일
- Box POP1 — 인스턴스될 mesh. (기본 파라미터)
- Grid POP1 — Rows 32, Columns 32, Size 8 × 8 (가로 세로 8 단위, 1024 점).
- Geometry COMP1 — 내부에 Box POP1 (render flag on), 외부에서 Grid POP1을 instanceop으로 참조.
- Camera COMP1, Light COMP1, Render TOP1.
연결: Box POP1을 Geometry COMP의 자식으로 둔다. Grid POP1은 root에 둔다.
단계 1 — instancing 활성화
Geometry COMP1의 Instance 페이지:
instancing: Oninstancecountmode:oplength(자동, Grid POP의 점 수만큼 인스턴스)instanceop:/grid1(Grid POP1 경로)instancetx:P(0)instancety:P(1)instancetz:P(2)
보게 될 것: 32×32=1024개의 박스가 grid 형태로 정렬된 모습. 모두 같은 mesh의 인스턴스다.
단계 2 — Phong MAT으로 단순 셰이딩
Geometry COMP의 Material에 Phong MAT을 붙인다. Phong MAT은 instancing-aware다 — TD가 자동 생성하는 셰이더가 내부에서 TDDeform을 거쳐 instance transform을 적용한다.
보게 될 것: 1024 회색 박스가 균일하게 음영을 받는다. Copy POP으로 1024번 복제한 mesh와 비교하면 cook 비용·메모리 사용이 크게 다르다. wiki 미확정 — Geometry COMP의 instancing path가 어느 시점에 GPU 명령으로 변환되는지의 내부 메커니즘은 wiki에 미노출. 실험 필요.
단계 3 — GLSL MAT으로 같은 결과를 손으로
GLSL MAT1을 만들어 Material을 교체한다.
vertex shader:
out Vertex { vec3 worldPos; vec3 worldNormal; int instID; } oVert;
void main() {
vec4 worldPos = TDDeform(TDPos()); // instance + world 합성
oVert.worldPos = worldPos.xyz;
oVert.worldNormal = TDDeformNorm(TDNormal());
oVert.instID = TDInstanceID(); // gl_InstanceID 직접 쓰지 않음
gl_Position = TDWorldToProj(worldPos);
}
pixel shader:
in Vertex { vec3 worldPos; vec3 worldNormal; flat int instID; } 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);
// instance ID로 hue를 흔든다
float h = fract(float(iVert.instID) * 0.0123);
vec3 hue = 0.5 + 0.5*cos(6.2831*h + vec3(0.0, 2.0, 4.0));
vec3 col = 0.1 + 0.9 * NdotL * hue;
fragColor = TDOutputSwizzle(vec4(col, 1.0));
}
이 코드가 GPU에서 하는 일: vertex shader가 instance × vertex = 1024 × 8 = 8192번 호출된다. 매번 TDInstanceID()는 0..1023 중 하나를 반환하고 TDDeform이 그 인스턴스의 transform을 곱해 world position을 만든다. fragment shader는 instance ID를 flat으로 받아 (보간하지 말라는 의미) hue로 변환한다. draw call은 여전히 하나다.
보게 될 것: 1024 박스가 각자 다른 색조로 음영을 받는다. 모두 한 번의 draw call에서 나왔다.
단계 4 — POP의 custom attribute를 instance color로
Grid POP과 Geometry COMP 사이에 Random POP을 끼워 Cd (vec4) attribute를 만든다. Geometry COMP Instance 2 페이지: instancecolormode=replace, instancecolorop=색 POP 경로, instancer/g/b/a=Cd(0..3). pixel shader의 색을 instance color로 곱한다.
vec4 baseColor = vec4(col, 1.0);
baseColor = TDInstanceColor(baseColor); // instance color 적용
fragColor = TDOutputSwizzle(baseColor);
보게 될 것: POP attribute가 직접 인스턴스 색을 결정한다. POP 측에서 색을 바꾸면 그래프 재컴파일 없이 갱신된다.
단계 5 — draw call 수 확인
Performance Monitor (Dialogs → Performance Monitor) 또는 GPU Info에서 Geometry COMP cook 시 draw call 카운트가 1 (또는 카메라당 1)에 머무는지 확인한다. wiki 미확정 — 정확한 라벨은 wiki에 명시 없음. 실험 필요. Copy POP으로 1024번 복제한 mesh와 비교하면 GPU 메모리 사용량이 mesh × 1024로 커진다.
노드 ↔︎ GLSL 매핑
같은 결과 — 1024 박스를 grid에 배치, 인스턴스마다 색.
노드: Box POP → Geometry COMP. Grid POP → Random POP(Cd) → Geometry COMP의 instanceop / instancecolorop. Material: Phong MAT.
GLSL MAT: 같은 그래프, Material을 GLSL MAT으로. vertex shader 한 줄 TDDeform(TDPos()), pixel shader 한 줄 TDInstanceColor(baseColor).
차이는 색의 출처가 노드 파라미터(instancecolormode=replace)냐 셰이더 코드냐다. 동작은 동일. Phong MAT은 위 GLSL MAT의 자동 생성판이다. 두 경로 모두 gl_InstanceID가 아니라 TDInstanceID()를 거쳐야 한다 (wiki: "should always be used, not gl_InstanceID directly") — TouchDesigner가 multi-camera 렌더에서 instance index를 재해석할 수 있기 때문이다.
확인 질문 (Self-check)
- 인스턴스 N개를 그릴 때 draw call은 몇 번 발생하는가? vertex shader 호출은 몇 번 발생하는가?
- Geometry COMP의
instanceop에 POP을 꽂으면 인스턴스 수는 어떻게 결정되는가? TDInstanceID()를 쓰는 이유는 무엇인가?gl_InstanceID를 직접 쓰면 어떤 상황에서 문제가 되는가?- POP의 P attribute가 어떤 파라미터 경로를 통해 인스턴스의 translate로 매핑되는가? color는?
- instancing이 vertex 처리 비용을 줄이는가 아닌가? 그 답이 16장의 LOD 논의를 어떻게 견인하는가?
연결 고리
- LearnOpenGL — "Instancing" (https://learnopengl.com/Advanced-OpenGL/Instancing): "Instanced arrays" 절의
glVertexAttribDivisor(2, 1)이 정확히 Geometry COMP의 instance attribute 설정과 같은 자리. - GPU Gems 2 Ch.3 "Inside Geometry Instancing" (Carucci, Lionhead): per-instance constant vs per-instance vertex stream의 trade-off 네 가지.
- GPU Gems 3 Ch.2 "Animated Crowd Rendering" (Dudash): instance + vertex texture fetch. POP attribute가 vertex texture fetch의 자리에 정확히 대응.
- Real-Time Rendering 4ed §18 "Pipeline Optimization": draw call의 비용 구조와 instancing의 자리.
이 챕터의 한 줄 명제
Instancing은 한 mesh와 N개의 attribute 행을 단 한 번의 draw call로 묶는 약속이다. POP의 attribute는 그 행을 채우는 가장 자연스러운 데이터원이다.