Decal 이란?
Decal은 게임에서 스프레이, 총탄 자국, 낙서, 피격 효과처럼 특정 표면에 덧씌우는 방식의 렌더링에 사용된다.
일반적으로 기존 메시에 텍스처를 직접 합성하지 않고, 별도의 투영(Projection)으로 덮어쓰는 방식이다.
원래 많은 영상을 찍어두었지만,, 파일이 날라가는 바람에 기본적인 구현영상만 존재합니다.. ㅠ
1-1. Multi Pass
decal은 Main Pass와 다른 Render State(Blend, Depth Write등) 를 요구한다.
그렇기 때문에 별도의 Decal Pass 추가했다. (Multi Pass)
// Frustum Culliung & Occulusion Culling
// Draw Actor
// 여기서 Decal Draw
2. Pruning
Level(World)에 Actor 가 적다면, Decal이 입혀지는 모든 Actor들을 순회할 수 있겠지만, Actor수가 많아진다면 이는 문제가 생길 것이다.
그래서 Decal이 입혀질 Actors를 추리는 작업을 해줘야 한다.
2-1. Broad Phase
Decal Volume과 겹칠 가능성이 있는 Actor들을 폭 넓게 추리는 단계.
Actor들의 AABB 박스와 Decal Volume의 AABB 박스 대상으로 SAT알고리즘을 사용했다.
World BVH와 Decal Volume이 겹치는 지 비교했고, 겹치지 않는다면 후보 대상에서 제외시켰다. (pruning)
코드는 아래와 같다.
if (BVH)
{
// World BVH를 통해 Decal AABB와 교차하는 Actor들만 빠르게 찾기
BVH->IntersectAABB(DecalAABB, BroadPhaseCandidates);
}
2-2. Narrow Phase
Decal Volume가 확실히 겹치는 Actor를 추리는 단계.
- Actor-Decal 간의 AABB-AABB 충돌검사를 해서 1차 검수를 한다.
- 이제 거의 근사가 완료되었으니, 추려진 Actor들을 순회하며 SAT 알고리즘을 사용해서 Acotr OBB - Decal OBB의 충돌 검사를 한다.
// Broad Phase를 통해 얻은 후보군에서 실제 영향받는 메시 찾기
for (AActor* Actor : BroadPhaseCandidates)
{
if (!Actor || Actor->GetActorHiddenInGame())
continue;
// Actor의 모든 컴포넌트 검사
const TSet<UActorComponent*>& Components = Actor->GetComponents();
for (UActorComponent* Component : Components)
{
if (!Component)
continue;
// Mesh가 없으면 스킵
// 1차: AABB vs AABB
if (!DecalAABB.IsIntersect(ComponentAABB))
continue;
// 2차: OBB vs OBB (SAT)
if (DecalOBB.Intersects(ComponentOBB))
{
AffectedMeshes.push_back(StaticMeshComp);
}
}
}
2-3 정리
Broad Phase: BVH를 사용해서 후보 대폭감소Narrow Phase : AABB간 교차 + SAT알고리즘으로 확실한 후보 추출
3. Draw Decal
Decal 을 그리기 위해서는 Render State가 변경 되어야 한다.
3.1 Decal Render State Setting
Blend(OMSetBlendState): 기존 색과 Decal 색을 섞어야 하는 경우가 생길 수 있다. (Fade In/Out)
OMSetBlendState를 On으로 변경해줘야한다.
Depth Test: LessEqual+ ReadOnly 설정을 사용 (깊이값을 참고만 함)
DepthWrite: Decal은 Depth를 따로 기록하지 않는다. Decal 자체가 Actor위에 Skin을 입히는 것이라고 생각혀면 Depth를 추가적으로 입히지 않아도 된다는게 이해가 될 것이다.
3.2 Decal Space
Model Space -> World Space 까지는 동일하지만, Decal은 여기서 Camera Space가 아닌 Decal Space로 변환해준다.
FTransform DecalXform = GetWorldTransform();
DecalView = DecalXform.ToMatrixWithScaleLocalXYZ().InverseAffine();
아래는 hlsl 코드이다.
VertexShader
Decal Volume은 알맞게 그려져야하니, 기존 MVP를 사용하면된다.

PixelShader
하지만 Pixel Shader를 조금 다르다.
- World Space -> Decal Clip Space
- NDC 계산
- Decal Volume 밖 픽셀은 Discard
- Blend

Decal View: Decal Space로 옮겨줘야한다.Decal Perspective: Decal의 종류에 따라 달라진다. Decal 기준의 Projection Matrix라고 생각하면 된다.
- Orthographic Decal
우리가 흔히 아는 Decal이라고 생각하면 된다. (위의 유튜브 링크도 Orthogrphic Decal Demo 영상이다.)
벽에 스프레이를 Decal로 칠한다고 가정했을 때, 거리에 따라 스프레이(decal)의 크기가 달라지지 않는다.
이것이 Orthographic Decal이고, 아래와 같이 구현했다.
FVector Scale = GetWorldScale();
const float OrthoWidth = Scale.Y;
const float OrthoHeight = Scale.Z;
const float NearZ = -0.5f * Scale.X;
const float FarZ = 0.5f * Scale.X;
DecalProj = FMatrix::OrthoLH(OrthoWidth, OrthoHeight, NearZ, FarZ);
- Perspective Decal
const float FarX = FMath::Max(Scale.X, 1e-3f);
const float NearX = 0.01f;
const float Aspect = Scale.Y / Scale.Z;
float tanHalfFov = Scale.Z / (2.0f * FarX);
const float FovRad = 2.0f * atanf(tanHalfFov);
DecalProj = FMatrix::PerspectiveFovLH(FovRad, Aspect, NearX, FarX);'ComputerGraphics > 자체엔진' 카테고리의 다른 글
| [자체 엔진] Exponent Height Fog (0) | 2026.03.09 |
|---|---|
| [자체 엔진] FXAA (0) | 2026.03.09 |
| [자체 엔진] Batch Line Rendering (0) | 2026.03.09 |
| [자체 엔진] Billboard (0) | 2026.03.09 |
| [자체 엔진] Features (0) | 2026.03.09 |