Screen Space Reflections(SSR)로 바닥 반사 연출하기: 노이즈·누락을 줄이는 실전 트릭
SSR의 화면 공간 추적 원리부터 바닥 반사가 끊기거나 흔들리는 이유, 러프니스·마스크·평면 반사·큐브맵을 조합해 안정적인 반사 품질과 성능을 확보하는 방법을 정리합니다.

SSR(Screen Space Reflections)은 현재 화면에 렌더링된 깊이와 색 정보를 따라 반사 광선을 추적하는 실시간 반사 기법이다. 따라서 화면 밖 오브젝트는 반사할 수 없고 바닥 반사는 러프니스 제어·반사 마스크·대체 환경 반사를 함께 설계해야 안정적이다. 핵심은 SSR을 ‘정확한 거울’이 아니라 근거리 디테일을 보강하는 레이어로 사용하고 누락 구간은 평면 반사나 Reflection Probe·큐브맵으로 자연스럽게 숨기는 것이다.
SSR은 무엇이며 바닥 반사에 왜 잘 어울릴까?
SSR은 카메라가 이미 본 장면의 Scene Color와 Scene Depth를 이용한다. 화면의 한 픽셀에서 반사 방향을 계산한 뒤 그 방향으로 화면 공간을 조금씩 이동하며 깊이 버퍼와 교차하는 지점을 찾는다. 교차 지점의 화면 색을 샘플링해 현재 표면의 반사색으로 사용한다.
바닥은 넓고 시선과 가까우며 화면 안의 캐릭터·기둥·조명이 많이 비치기 때문에 SSR의 장점이 잘 드러난다. 특히 젖은 아스팔트, 광택 타일, 우주선 복도, 얕은 물웅덩이처럼 반사가 연출의 일부인 장면에 효과적이다.
반사 벡터는 표면 법선 과 카메라에서 표면으로 향하는 입사 방향 로 계산한다.
실제 구현에서는 이 반사 방향을 뷰 공간으로 변환한 뒤 깊이 버퍼에서 레이 마칭한다. 레이의 깊이와 깊이 버퍼의 깊이가 충분히 가까워지면 충돌로 판단한다.

SSR 바닥 반사가 끊기거나 사라지는 이유는 무엇인가?
SSR의 문제는 대부분 알고리즘 오류가 아니라 입력 정보의 한계에서 나온다. 화면에 없는 정보는 레이 마칭을 아무리 정교하게 해도 찾을 수 없다.
| 증상 | 직접 원인 | 바닥 반사에서 보이는 모습 | 우선 대응 |
|---|---|---|---|
| 반사 대상이 갑자기 사라짐 | 대상이 카메라 프레임 밖에 있음 | 캐릭터가 화면 가장자리로 갈수록 반사가 잘림 | 큐브맵 또는 평면 반사로 폴백 |
| 반사가 화면 가장자리에서 늘어남 | 레이가 화면 경계를 넘음 | 바닥 끝에 색이 번지거나 검게 비침 | Edge Fade 적용 |
| 노이즈와 깜빡임 | 낮은 스텝 수, TAA 누적 부족, 깊이 불연속 | 물결이나 거친 바닥에서 반사가 흔들림 | Temporal Accumulation, Denoise |
| 반사가 떠 보임 | 두꺼운 깊이 판정 또는 부정확한 법선 | 오브젝트 발밑 반사가 원본과 어긋남 | Thickness와 Normal Bias 조정 |
| 거친 바닥이 거울처럼 보임 | 러프니스 기반 블러·페이드 부족 | 콘크리트에도 선명한 상이 남음 | Roughness Fade와 Mip 샘플링 |
화면 공간의 한계는 왜 완전히 제거할 수 없을까?
SSR은 보통 G-buffer 또는 카메라 깊이 버퍼만 참조한다. 카메라 뒤쪽의 오브젝트, 다른 오브젝트에 가려진 면, 컬링된 오브젝트는 버퍼에 존재하지 않는다. 이 때문에 화면 밖 캐릭터가 바닥에 비쳐야 하는 장면에서는 SSR만으로 요구사항을 충족할 수 없다.
이 제약은 품질 옵션을 올려 해결하는 문제가 아니다. 스텝 수를 늘리면 화면 안에서의 충돌 정확도는 좋아지지만 없는 데이터를 만들지는 못한다. 따라서 연출 단계에서 SSR의 담당 범위를 분명히 정하는 편이 중요하다.
바닥 SSR은 어떻게 구현하고 조정할까?
엔진마다 설정 이름은 다르지만 구현 순서는 같다. 아래 과정은 Unreal Engine 5의 Lumen Reflections, Unity HDRP의 Screen Space Reflection, 커스텀 URP 렌더 패스에 공통으로 적용할 수 있다.
1. 반사할 바닥 재질을 먼저 제한한다
모든 바닥에 SSR을 적용하면 비용과 시각적 잡음이 함께 늘어난다. 머티리얼의 러프니스, 마스크 텍스처, 버텍스 컬러 또는 데칼 마스크로 반사 영역을 좁힌다.
- 마른 콘크리트: 높은 러프니스로 SSR 기여도를 거의 제거한다.
- 젖은 통로: 물이 고인 부분만 마스크로 남기고 러프니스를 낮춘다.
- 타일 바닥: 타일 중앙은 반사를 유지하고 줄눈은 러프니스와 노멀을 높인다.
- 게임플레이 구역: 플레이어 발밑이나 카메라가 자주 보는 동선에 예산을 집중한다.
머티리얼에서 사용할 수 있는 단순한 반사 가중치 예시는 다음과 같다. 는 0~1 범위의 젖음 마스크다.
float wetness = saturate(wetMask);
float roughness = lerp(0.82, 0.12, wetness);
float ssrWeight = wetness * (1.0 - roughness);
// SSR 결과와 환경 반사 결과를 합성할 때 사용하는 가중치
float3 reflection = lerp(environmentReflection, ssrReflection, ssrWeight);
이 방식은 아티스트가 마스크 한 장으로 반사 강도와 선명도를 함께 제어할 수 있게 한다. 단, 실제 PBR 파이프라인에서는 반사 에너지가 과도해지지 않도록 엔진의 Fresnel, Specular, IBL 합성 규칙을 우회하지 않는 것이 좋다.
2. 레이 마칭 품질은 거리보다 화면 해상도를 기준으로 잡는다
SSR은 월드 단위 거리보다 화면 픽셀을 따라 계산되는 비용의 영향을 크게 받는다. 레이 마칭 스텝 수를 무작정 올리기보다 최대 거리, 스텝 수, 두께를 함께 제한한다.
| 파라미터 | 역할 | 바닥 반사 권장 방향 |
|---|---|---|
| Max Ray Distance | 추적 가능한 반사 거리 | 실내는 짧게 제한해 누락과 비용을 줄인다 |
| Max Steps | 레이 마칭 반복 횟수 | 우선 32~64에서 시작해 장면별로 측정한다 |
| Thickness | 깊이 교차 허용 폭 | 너무 크면 반사가 떠 보이고 너무 작으면 끊긴다 |
| Stride | 한 스텝의 화면 이동량 | 빠른 품질 모드는 크게, 고품질 모드는 작게 둔다 |
| Binary Search Steps | 충돌 지점 미세 보정 | 접촉부 어긋남이 보일 때만 추가한다 |
| Temporal Weight | 이전 프레임 누적 비율 | 카메라 이동 시 잔상과 노이즈 사이에서 조절한다 |
의사 코드 수준의 기본 레이 마칭 구조는 다음과 같다.
float3 RayMarchSSR(float3 viewPos, float3 viewNormal)
{
float3 viewDir = normalize(viewPos);
float3 rayDir = normalize(reflect(viewDir, viewNormal));
float3 rayPos = viewPos + viewNormal * _NormalBias;
for (int stepIndex = 0; stepIndex < _MaxSteps; ++stepIndex)
{
rayPos += rayDir * _StepLength;
float4 clipPos = mul(_ProjectionMatrix, float4(rayPos, 1.0));
float2 uv = clipPos.xy / clipPos.w * 0.5 + 0.5;
if (any(uv <= 0.0) || any(uv >= 1.0))
break;
float sceneDepth = SampleSceneDepth(uv);
float rayDepth = ViewDepth(rayPos);
if (abs(sceneDepth - rayDepth) < _Thickness)
return SampleSceneColor(uv);
}
return SampleEnvironmentReflection(viewNormal, viewDir);
}
커스텀 구현에서는 카메라의 투영 방식, 깊이 버퍼가 역 Z인지 여부, 플랫폼별 깊이 범위를 반드시 확인해야 한다. 위 코드는 구조를 설명하기 위한 예시이며 엔진이 제공하는 깊이 변환 함수와 좌표계 규칙에 맞춰 바꿔야 한다.
3. 화면 경계와 누락 구간을 의도적으로 페이드한다
SSR의 실패를 숨기는 가장 효율적인 트릭은 경계에서 반사 기여도를 부드럽게 낮추는 것이다. 화면 테두리와 가까울수록 반사값을 환경 반사로 전환하면 갑자기 끊기는 대신 자연스럽게 흐려진다.
여기서 와 는 화면 UV이고 는 페이드 폭이다. 실제 적용에서는 화면 경계 페이드에 더해 레이 길이, 러프니스, 깊이 불연속 기반 페이드를 곱한다.
float edgeDistance = min(min(uv.x, uv.y), min(1.0 - uv.x, 1.0 - uv.y));
float edgeFade = smoothstep(0.0, _EdgeFadeWidth, edgeDistance);
float roughnessFade = 1.0 - smoothstep(_RoughnessStart, 1.0, roughness);
float finalSSRWeight = edgeFade * roughnessFade * hitConfidence;
float3 finalReflection = lerp(environmentReflection, ssrReflection, finalSSRWeight);

SSR 트릭으로 바닥 반사를 더 설득력 있게 만드는 방법
젖음 마스크는 반사 마스크보다 많은 정보를 담는다
흰색과 검은색만 있는 단순한 반사 마스크는 경계가 너무 인위적으로 보이기 쉽다. 젖음 마스크에는 다음 정보를 함께 넣는 편이 좋다.
- 큰 물 고임의 형태: 반사가 보이는 주 영역을 결정한다.
- 중간 크기 노이즈: 물이 얕아지는 경계를 분해한다.
- 미세 노멀: 반사를 흐트러뜨려 표면 재질감을 만든다.
- 흐름 방향: 빗물이나 청소 흔적처럼 환경의 맥락을 만든다.
같은 러프니스 값이라도 노멀 강도가 높으면 반사상이 깨지고 러프니스가 높으면 반사상 자체가 넓게 퍼진다. ‘얕은 물’은 보통 낮은 러프니스와 약한 물결 노멀의 조합이 자연스럽다. 반대로 거친 젖은 아스팔트는 중간 러프니스와 비교적 강한 미세 노멀을 사용해 하이라이트만 남기는 편이 낫다.
반사 전용 라이트와 Emissive를 이용해 읽기 쉬운 장면을 만든다
SSR은 화면 색을 반사하므로 반사 대상이 어둡거나 색 대비가 낮으면 계산이 정상이어도 존재감이 약하다. 이때 바닥 반사를 더 밝게 증폭하기보다 반사될 피사체를 설계한다.
- 복도 벽면에 세로형 발광 패널을 배치해 바닥에 리듬을 만든다.
- 플레이어 경로 주변의 키 라이트를 반사 가능한 각도로 둔다.
- 네온 사인과 경고등에는 고명도 Emissive를 사용하되 블룸만 과도하게 키우지 않는다.
- 캐릭터 실루엣이 바닥에 비치도록 카메라 높이와 바닥 재질 구간을 함께 조정한다.
이 접근은 SSR 품질을 올리지 않고도 장면의 반사 인상을 높인다. 다만 HDR 톤 매핑 이후의 밝기만 보고 판단하면 결과가 달라질 수 있으므로 조명·Emissive·블룸을 함께 검수해야 한다.
디더링과 시간 누적으로 거친 반사를 만든다
러프한 표면의 반사는 넓은 영역을 적분해야 하므로 한 번의 화면 샘플로는 노이즈가 생긴다. 매 프레임 블루 노이즈 또는 해시 기반 지터로 레이 방향·시작 위치를 조금 바꾸고 모션 벡터를 이용해 이전 프레임 결과를 재투영한 뒤 누적하면 품질을 높일 수 있다.
하지만 시간 누적은 빠르게 움직이는 캐릭터와 카메라에서 잔상을 만들 수 있다. 다음 경우에는 히스토리 가중치를 낮추거나 리셋한다.
- 깊이 또는 법선 변화가 큰 픽셀
- 모션 벡터가 큰 픽셀
- SSR 히트 여부가 프레임 사이에 바뀐 픽셀
- 카메라 컷 또는 텔레포트 직후
SSR, 평면 반사, Reflection Probe는 어떻게 조합할까?
현실적인 제작 환경에서는 하나의 반사 기법만으로 모든 샷을 처리하지 않는다. 각 기법의 역할을 나누면 품질 목표와 성능 예산을 동시에 관리하기 쉬워진다.
| 기법 | 강점 | 약점 | 적합한 바닥 연출 |
|---|---|---|---|
| SSR | 화면 안의 동적 오브젝트를 즉시 반영 | 화면 밖 정보 누락, 경계 아티팩트 | 캐릭터·이펙트가 비치는 젖은 바닥 |
| Planar Reflection | 평면에서 정확하고 안정적인 반사 | 별도 렌더링 비용, 평면 제약 | 로비 타일, 거울 같은 얕은 수면 |
| Reflection Probe / 큐브맵 | 화면 밖 환경을 안정적으로 제공 | 동적 물체 반영이 제한적 | 먼 배경, SSR 폴백 |
| Ray Traced Reflection | 오프스크린과 복잡한 반사에 강함 | 높은 GPU 비용과 하드웨어 요구 | 고사양 모드, 시네마틱 |
권장 조합은 큐브맵 또는 Reflection Probe를 기본 반사로 두고 SSR을 근거리 동적 디테일에만 합성하는 방식이다. 플레이어가 자주 보는 완전한 평면 바닥이 있고 정확한 반사가 게임플레이나 연출의 핵심이라면 그 구역만 Planar Reflection을 추가한다.
성능을 지키면서 SSR 품질을 올리는 체크리스트
- 반사 영역을 줄인다. 재질 마스크와 러프니스 컷오프로 SSR이 의미 있는 바닥만 남긴다.
- 해상도를 낮춰 측정한다. 하프 해상도 SSR부터 시작하고 업샘플링 경계가 눈에 띄는 구역만 풀 해상도로 검토한다.
- 스텝 수보다 실패 처리를 먼저 다듬는다. Edge Fade, Roughness Fade, 환경 반사 폴백이 갖춰진 뒤 Max Steps를 올린다.
- 카메라 동선에서 확인한다. 정지 화면뿐 아니라 전진·회전·대시·카메라 컷에서 깜빡임과 잔상을 본다.
- GPU 캡처로 비용을 확인한다. Unreal Insights, RenderDoc, Unity Frame Debugger·Profiler 등으로 SSR 패스의 해상도와 GPU 시간을 기록한다.
품질 프리셋을 나눌 때는 일반적으로 SSR 해상도와 최대 스텝을 먼저 조정한다. 아트 방향을 무너뜨리는 반사 거리나 페이드 폭은 가능하면 모든 프리셋에서 유지하고 저사양에서는 반사 마스크 범위를 줄이는 방식이 예측 가능하다.
자주 묻는 질문 (FAQ)
SSR만으로 거울 같은 바닥을 만들 수 있나요?
가능한 화면 구도도 있지만 안정적이지 않다. 화면 밖 대상이 빠지고 경계 아티팩트가 드러나므로 항상 정확해야 하는 평면 거울 바닥에는 Planar Reflection이나 레이 트레이싱 반사가 더 적합하다.
러프니스를 낮췄는데 반사가 보이지 않는 이유는 무엇인가요?
반사할 대상이 화면에 없거나 SSR 최대 거리·스텝 수·두께가 부족할 수 있다. 먼저 Scene Color에 반사 대상이 실제로 보이는지 확인하고 그다음 SSR 디버그 뷰에서 히트 마스크를 점검한다.
젖은 바닥의 반사가 너무 지저분할 때 무엇부터 조정해야 하나요?
노멀 강도와 젖음 마스크 경계를 먼저 낮춘다. 그다음 러프니스 값을 약간 올리고 시간 누적 또는 디노이즈를 검토한다. 스텝 수 증가는 마지막 수단으로 두는 편이 비용 대비 효율적이다.
정리
SSR 바닥 반사의 품질은 레이 마칭 설정 하나보다 실패를 다루는 설계에서 결정된다. 화면 안의 동적 반사는 SSR로 살리고 화면 밖 환경은 Reflection Probe·큐브맵으로 보완하며 중요한 평면 구역만 Planar Reflection으로 강화하자. 여기에 젖음 마스크, 러프니스, 미세 노멀, 화면 경계 페이드를 조합하면 과도한 GPU 비용 없이도 바닥이 장면의 깊이와 분위기를 만드는 연출 요소가 된다.



