렌더링 파이프라인과 별개로 GPU에서 범용 연산을 수행한다. 파티클, 물리 시뮬레이션, AI 연산에 활용된다
렌더링 파이프라인 밖의 GPU
기존 셰이더 (렌더링 파이프라인 안)
- 버텍스 셰이더 -> 래스터화 -> 픽셀 셰이더
- 3D를 2D 화면으로 변환하는 것이 목적이다.
- 입력 = 버텍스 데이터, 출력 = 픽셀 색상. 입출력이 정해져있다.
컴퓨트 셰이더 (렌더링 파이프라인 밖)
- 렌더링과 무관하게 GPU에서 실행한다
- 입출력이 자유롭다. -> GPU의 병렬 연산 능력만 빌려 쓰는 것이다
GPU = 수천 개의 코어가 동시에 같은 연산을 하는 기계
컴퓨트 셰이더 = 이 병렬성을 렌더링 외에도 활용
워크그룹과 스레드 : 컴퓨트 셰이더의 실행 단위
컴퓨트 셰이더 실행 구조
Dispatch (CPU에서 GPU에 실행 명령) : 몇 개의 워크그룹을 실행할지 지정
WorkGroup : GPU의 실행 블록 단위로 같은 워크그룹 내 스레드는 공유 메모리 사용 가능하다
Thread ( 스레드 = Invocation ) : 가장 작은 실행 단위로 실제로 셰이더 코드를 실행하는 것이다.
ex) 1024 x 1024 텍스처 처리
Dispatch(64, 64, 1) -> 64 x 64 = 4096 워크그룹
[numthreads(16, 16, 1)] -> 워크그룹당 16x16 = 256 스레드
총 스레드 수: 4096 x 256 = 1048576개 -> 픽셀 하나당 스레드 하나
스레드 ID
SV_GroupThreadID : 워크그룹 내 스레드 번호 (0~15, 0~15)
SV_GroupID : 워크그룹 번호 (0~63, 0~63)
SV_DispatchThreadID : 전체에서 고유한 스레드 번호
HLSL 컴퓨트 셰이더 기본 구조
// 파티클 시뮬레이션 컴퓨트 셰이더 (HLSL)
// 파티클 데이터 구조
struct Particle {
float3 position;
float3 velocity;
float lifetime;
float age;
};
// GPU 버퍼 - 읽기 / 쓰기 가능
RWStructuredBuffer<Particle> particles : register(u0);
// CPU에서 전달하는 상수
cbuffer SimConstants : register(b0) {
float deltaTime;
float3 gravity;
int particleCount;
};
// 워크그룹 크기 선언
[numthreads(64, 1, 1)]
void CSMain(
uint3 dispatchID : SV_DispatchThreadID // 전체에서 고유 ID
){
uint id = dispatchID.x;
// 범위 체크 - 스레드가 파티클 수 초과하면 종류
if(id >= (uint)particleCount) return;
Particle p = particles[id];
// 각 스레드가 자신의 파티클만 독립적으로 처리
p.velocity += gravity * deltaTime;
p.position += p.velocity * deltaTime;
p.age += deltaTime;
// 수명 다하면 리셋
if(p.age >= p.lifetime) {
p.position = float3(0, 0, 0);
p.veloctiy = float3((float(id % 7) - 3.0) * 0.5, 5.0, (float(id % 5) - 2.0) * 0.5);
p.age = 0.0;
}
// 결과 저장
particles[id] = p;
}
// CPU에서 디스패치
// int groups = (particleCount + 63) / 64;
// commandList->Dispatch(groups, 1, 1);
GLSL 컴퓨트 셰이더 ( OpenGL/Vulkan )
#version 430 // OpenGL ES 3.1 / OpenGL 4.3 이상
// 워크그룹 크기
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
// 이미지 읽기/쓰기 (이미지 프로세싱)
layout(rgba8, binding = 0)
uniform readonly image2D inputImage;
layout(rgba8, binding = 1)
uniform writeonly image2D outputImage;
void main() {
// 현재 스레드가 처리할 픽셀 좌표
ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
// 이미지 크기 체크
ivec2 size = imageSize(inputImage);
if(coord.x >= size.x || coord.y >= size.y) return;
// 블러 처리 (3x3 평균)
vec4 sum = vec4(0.0);
for(int dy = -1; dy <= 1; dy++)
{
for(int dx = -1; dx <= 1; dx++)
{
ivec2 sampleCoord = coord + ivec2(dx, dy);
sampleCoord = clamp(sampleCoord, ivec2(0), size - 1);
sum += imageLoad(inputImage, sampleCoord);
}
}
// 평균값 출력
imageStore(outputImage, coord, sum / 9.0);
}
컴퓨트 셰이더의 실제 활용 사례
파티클 시스템
- UE5 niagara : 컴퓨트 셰이더로 수백만 파티클 병렬 시뮬레이션
- CPU로 하면 초당 수천 개 한계
- GPU로 하면 초당 수백만 개 가능
물리 시뮬레이션
- 천 시뮬레이션 (Cloth) : 각 버텍스를 스레드로
- 유체 시뮬레이션 (Fluid) : 격자 셀을 스레드로
- 군중 AI (Crowd) : 각 에이전트를 스레드로
컬링 (GPU Driven Rendering)
- 수만 개 오브젝트 중 화면에 보이는 것을 CPU가 아닌 GPU에서 직접 판별
- UE5 Nanite의 핵심 기술
포스트 프로세싱
- 블룸, 블러, SSR, SSAO
- 픽셀 셰이더 대신 컴퓨트로 처리하면 더 효율적인 경우가 많음
스키닝 (Vertex Skinning)
- 캐릭터 애니메이션의 본 변환 계산
- 수천 개 버텍스를 병렬 처리
머신러닝 / AI
- TensorFlow, PyTorch의 GPU 연산 기반
- WebGPU 컴퓨트 셰이더로 브라우저 AI 추론
공유 메모리 (워크그룹 내 최적화)
// 공유 메모리는 같은 워크그룹 스레드들이 공유하는 빠른 메모리
// GPU 온칩 메모리 -> 글로벌 메모리보다 훨씬 빠르다
[numthreads(16, 16, 1)]
void BlurCS(uint8 groupID : SV_GroupID,
uint8 threadID : SV_GroupThreadID,
uint8 dispatchID : SV_DispatchThreadID)
{
// 공유 메모리 선언 (16 + 2) x (16 + 2) = 경계 포함
groupshared float4 sharedPixels[18][18];
// 각 스레드가 자신 담당 픽셀을 공유 메모리에 로드
// (글로벌 메모리 -> 공유 메모리: 한 번만)
ivec2 coord = dispatchID.xy;
sharedPixels[threadID.y+1][threadID.x+1] = inputTexture[coord];
// 모든 스레드 로드 완료 대기 (동기화)
GroupMemoryBarrierWithGroupSync();
// 공유 메모리에서 빠르게 읽기
float4 sum = float4(0,0,0,0);
for(int dy = -1; dy <= 1; dy++)
for(int dx = -1; dx <= 1; dx++)
sum += sharedPixels[threadID.y+1+dy][threadID.x+1+dx];
outputTexture[coord] = sum / 9.0;
// 글로벌 메모리 접근 : 로드 1번 + 저장 1번
// 공유 메모리 없으면 : 로드 9번 필요
}
UE5에서 컴퓨트 셰이더 직접 작성하기
// UE5 커스텀 컴퓨트 셰이더 C++ 선언
class FMyComputeShader : public FGlobalShader {
DECLARE_GLOBAL_SHADER(FMyComputeShader);
SHADER_USE_PARAMETER_STRUCT(FMyComputeShader,
FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(
RWStructuredBuffer<FParticle>,
ParticleBuffer)
SHADER_PARAMETER(float, DeltaTime)
SHADER_PARAMETER(int32, ParticleCount)
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(
const FGlobalShaderPermutationParameters& Params) {
return IsFeatureLevelSupported(
Params.Platform, ERHIFeatureLevel::SM5);
}
};
// HLSL 파일과 연결
IMPLEMENT_GLOBAL_SHADER(FMyComputeShader,
"/Project/Shaders/MyComputeShader.usf",
"CSMain",
SF_Compute);
// 실행 (RDG — Render Dependency Graph)
void RunComputeShader(FRDGBuilder& GraphBuilder,
int32 ParticleCount) {
FMyComputeShader::FParameters* PassParams =
GraphBuilder.AllocParameters
FMyComputeShader::FParameters>();
PassParams->DeltaTime = GetWorld()->DeltaTimeSeconds;
PassParams->ParticleCount = ParticleCount;
TShaderMapRef<FMyComputeShader> ComputeShader(
GetGlobalShaderMap(GMaxRHIFeatureLevel));
// 디스패치
int32 GroupCount = FMath::DivideAndRoundUp(
ParticleCount, 64);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("MyComputePass"),
ComputeShader,
PassParams,
FIntVector(GroupCount, 1, 1));
}
연결되는 단어들
GPGPU : General Purpose GPU. GPU를 그래픽스 외 범용 연산에 사용하는 개념. CUDA, OpenCL, 컴퓨트 셰이더가 모두 이 범주
Wrap/Wavefront : GPU의 최소 실행 단위. NVIDIA: Wrap(32스레드), AMD: Wavefront(64스레드). 워크그룹 크기를 이 배수로 맞춰야 효율적이다.
UAV (Unordered Access View) : DX12에서 컴퓨트 셰이더가 읽기/쓰기 가능한 버퍼. GLSL의 SSBO에 대응. RWStructuredBuffer가 UAV.
RDG (Render Dependency Graph) : UE5의 렌더링 의존성 관리 시스템. 컴퓨트 셰이더 패스를 추가할 때 사용. 자동으로 리소스 배리어와 패스 순서를 관리한다.
'Word' 카테고리의 다른 글
| UV 매핑 (UV Mapping) (0) | 2026.07.22 |
|---|---|
| 노멀 맵 (Normal Map) (0) | 2026.07.21 |
| HLSL/GLSL (0) | 2026.07.16 |
| WebGL / WebGPU (0) | 2026.07.14 |
| OpenGL ES ( Embedded Systems ) (0) | 2026.07.13 |
