본문으로 건너뛰기
VFX Diary
VFX Diary
작업기록

Fresnel 셰이더 실험: 지수 1·3·8은 림의 분포를 어떻게 바꿀까

Fresnel의 지수를 올리면 가장자리 빛이 가늘어진다는 설명은 익숙합니다. 그런데 이때 구의 윤곽이 작아진 것인지, 같은 표면에서 밝게 남는 범위가 줄어든 것인지는 나누어 보고 싶었습니다. 제가 이번에 확인하려는 것은 보기 좋은 지수의 정답보다, 입력 방향이 같은 상태에서 지수만 바꿨을 때 계수가 어디에 남는가입니다.

이번 작은 셰이더 실험은 AI의 도움으로 작성한 WebGL2 코드를 브라우저에서 실행한 기록입니다. 운영자가 Unreal 프로젝트에서 작업한 경험을 옮긴 글은 아닙니다. 생성 일러스트 세 장은 발상을 설명하며, 별도로 삽입한 실제 비교 캡처와 실행 기록만 이번 실험의 근거로 사용합니다.

가장자리의 가는 빛과 넓은 표면의 대비를 표현한 Fresnel 림 개념 장면
AI 생성 개념 이미지 — 시선에 따른 외곽 강조를 떠올리기 위한 일러스트이며 실제 셰이더 출력이나 물리적 반사 측정이 아닙니다.

밝기 슬라이더보다 먼저 분포를 분리하기

Epic의 Utility Material Expressions는 Fresnel을 표면 법선과 카메라 방향의 내적으로 만드는 감쇠로 설명합니다. Exponent가 커지면 더 빠르게 감쇠한다는 것이 이번 비교의 출발점입니다. 이를 작은 식으로 떼어내, 정면을 향한 표면에서는 낮고 옆으로 눕는 표면에서는 높아지는 마스크를 만들었습니다.

여기서 법선 N은 표면이 향하는 방향, V는 표면에서 카메라를 향하는 방향입니다. 두 방향이 같으면 정규화한 벡터의 내적은 1이고, 직각이면 0입니다. 둘 다 같은 좌표계의 단위 벡터여야 이 각도 해석이 가능합니다. GLSL의 dot 함수 자체가 입력 길이나 좌표계를 맞춰주지는 않습니다.

이번 식은 Base Reflect Fraction을 0으로 놓은 예술적 림 마스크입니다. 굴절률, 금속성, 거칠기, 입사광을 넣어 실제 반사 에너지를 계산하지 않았습니다. 그래서 아래 계수를 물이나 유리의 물리적인 Fresnel 반사율로 읽지 않겠습니다. UE 노드 전체와 렌더링 경로를 그대로 재현했다는 주장도 하지 않습니다.

같은 분석 반구에서 지수만 바꾸기

실제 구 메시를 띄우는 대신, 화면을 덮는 사각형의 프래그먼트마다 반구의 법선을 계산했습니다. 화면 좌표를 고정 반지름으로 나눈 x, y에서 남은 z 성분을 구한 뒤 정규화합니다. 원 밖은 실루엣에서 제외합니다. 시선 V는 같은 분석 좌표계의 (0, 0, 1)로 고정했으므로, 정사영으로 반구를 바라보는 조건에 해당합니다.

vec3 N = normalize(vec3(x, y, sqrt(max(0.0, 1.0 - x*x - y*y))));
vec3 V = vec3(0.0, 0.0, 1.0);
float F = pow(1.0 - clamp(dot(N, V), 0.0, 1.0), p);

각 조건의 렌더 크기는 512×512이고, 원의 화면상 반지름은 정규화 좌표에서 0.82입니다. 시간은 0, 난수는 사용하지 않았습니다. 법선 계산, 시선, 원의 영역과 표현 조건을 유지한 채 p를 1, 3, 8로만 바꿨습니다. 카메라 위치나 노멀맵이 함께 변하면 지수의 영향만 읽기 어려워지므로, 이번에는 그 변화들을 제외했습니다.

추가로 Khronos GLSL ES 3.00 명세의 dot, normalize, clamp와 pow 정의를 확인했습니다. 특히 pow는 음수 밑이나, 밑이 0이면서 지수가 0 이하인 경우 결과가 정의되지 않습니다. 이번에는 밑을 0~1로 제한하고 양수 지수만 써서 그 범위를 피했습니다. 이는 입력 조건을 정한 이유이지 모든 구현의 오차가 사라진다는 뜻은 아닙니다.

법선과 시선을 고정한 표면에서 림의 범위를 비교한다는 발상을 표현한 개념 이미지
AI 생성 개념 이미지 — 표면을 따라 달라지는 법선 방향을 비유한 장면이며 실제 실험의 벡터나 각도를 재현하지 않습니다.

수식의 한 지점과 화면의 픽셀을 구별하기

먼저 내적을 0.5로 고정한 수식 계산 예를 적어보면 지수의 역할이 선명해집니다. 밑은 1−0.5, 즉 0.5이므로 다음과 같습니다. 이 값들은 화면의 특정 픽셀을 읽어 얻은 측정값이 아닙니다.

지수 p 고정 내적 0.5에서 계산한 F
1 0.5
3 0.125
8 0.00390625

0과 1 사이의 같은 밑을 거듭제곱하면 지수가 클수록 값이 작아집니다. 반면 수식의 두 끝점인 정면 방향의 0과 직각 방향의 1은 양수 지수에서 유지됩니다. 제가 여기서 구분하고 싶은 것은 모든 값을 같은 배수로 줄이는 강도 조절과, 중간 값의 분포를 바꾸는 지수 조절입니다. 지수만 올리는 일을 단순히 ‘빛을 약하게 했다’고 기록하면 이 차이가 흐려집니다.

실제로 그린 격자에서는 정확히 정면이나 접선에 해당하는 위치와 픽셀 중심이 일치하지 않을 수 있습니다. 따라서 이론의 끝점과 캡처의 가장 밝은 픽셀을 같은 것으로 가정하지 않겠습니다. 특히 가느다란 띠를 볼 때는 연속적인 식의 모양과 고정 해상도에서 읽히는 픽셀을 함께 살펴야 합니다.

같은 p를 쓰더라도 평평한 판과 굽은 표면에서는 화면에 분포하는 NdotV가 다릅니다. 지수는 그 입력을 바꿔주는 값이 아니므로, 이 반구에서 얻은 띠의 픽셀 수를 다른 메시의 기준으로 그대로 옮길 수는 없습니다.

실제 실행에서 같았던 입력과 달랐던 출력

아래 그림은 생성 일러스트가 아니라 이번 WebGL2 실행에서 보존한 비교 PNG입니다. 기록된 환경은 Chromium 151.0.7922.34, GLSL ES 3.00과 SwiftShader 소프트웨어 렌더러입니다. Unreal이나 특정 그래픽카드에서 얻은 결과가 아닙니다.

같은 분석 반구와 시선을 두고 Fresnel 지수 1·3·8을 비교한 실제 WebGL2 실행 결과
직접 작성한 WebGL2 코드의 실제 출력 — 같은 NdotV와 실루엣에서 지수만1·3·8로 바꿨습니다. SwiftShader 소프트웨어 렌더링이며 물리 반사율이나 Unreal 실행·GPU 성능 측정이 아닙니다.

실행 기록에서는 세 조건의 실루엣 마스크와 NdotV 진단 이미지 해시가 각각 같았습니다. 반면 계수 이미지와 최종 출력의 해시는 조건마다 달랐습니다. 이는 이번 구현에서 고정하려던 입력 필드가 같고, 출력은 달라졌는지 확인하는 진단입니다. 화면 전체의 모든 원인을 검증했거나 UE와 일치함을 증명하는 검사는 아닙니다.

지수 p 실루엣 안의 픽셀 수 계수 채널의 8비트 값이 128 이상인 픽셀 수
1 138,408 34,588
3 138,408 5,884
8 138,408 928

이 표는 512×512 출력에서 계수를 RGBA8로 읽고 정한 문턱값으로 센 결과입니다. 실루엣의 픽셀 수는 같지만 비교적 큰 계수가 남는 픽셀 수는 줄었습니다. 따라서 이번 조건에서 관찰한 차이는 물체의 외곽을 줄인 결과와 구별해서 기록할 수 있겠습니다. 다만 128이라는 문턱값을 바꾸면 집계도 달라지므로, 이를 림의 유일한 폭이라고 부르지는 않겠습니다.

같은 입력의 재렌더 결과도 해당 브라우저 세션 안에서는 일치했고 GL 오류 값은 0이었습니다. 여기서 확인한 재현 범위는 이 실행과 진단까지입니다. 새로고침 뒤나 다른 GPU, 브라우저 버전에서 바이트까지 같다고 확인한 것은 아닙니다.

계수와 색칠한 결과를 따로 보관한 이유

최종 화면만 보면 노란빛이 줄어든 것과 마스크가 달라진 것을 혼동하기 쉽습니다. 이번 코드는 계산한 F로 고정된 바탕색과 림 색을 mix해 결과를 표시하고, 별도 진단 모드에서는 F 자체를 회색으로 출력했습니다. 픽셀 집계는 뒤의 계수 출력에서 읽었습니다. 최종 색상의 밝기를 재서 반사율을 추정하거나, 바탕색까지 포함한 평균을 림의 강도라고 부르지 않기 위한 구분입니다.

여기에는 광원이 표면을 비추는 계산이나 HDR 발광, 블룸이 없습니다. 따라서 금빛 가장자리처럼 보이는 출력도 ‘조명을 잘 받았다’는 증거는 아닙니다. 실제 효과에 적용할 때는 마스크를 어디에 연결했는지에 따라 또 다른 비교가 필요하겠습니다. 같은 F를 색 혼합에 쓰는 것과 발광 강도에 곱하는 것을 이번 결과 하나로 평가하지 않겠습니다.

진단에서 정한 128이라는 기준도 절대적인 경계는 아닙니다. 다음에는 다른 문턱값에서도 경향이 유지되는지 확인할 수 있지만, 이번 글에는 실행하지 않은 집계값을 덧붙이지 않았습니다. 중요한 것은 비교 기준과 해상도를 함께 남겨서, 나중에 설정을 바꿨을 때 같은 질문으로 다시 읽을 수 있게 하는 일이라고 생각합니다.

넓게 퍼지는 가장자리의 빛과 좁게 남는 띠를 대비해 표현한 림 분포 개념 이미지
AI 생성 개념 이미지 — 같은 실루엣 안에서 강조 영역이 달라지는 발상을 표현했으며 실제 p별 출력이나 픽셀 수 비교는 아닙니다.

노멀을 바꾸면 같은 지수도 다른 질문이 된다

결과를 정리하다 보니 다음에는 지수보다 입력 법선을 먼저 확인해야 할 때도 있겠다는 생각이 들었습니다. 그래서 Epic의 Fresnel 사용 가이드를 추가로 열었습니다. 이 자료는 노멀맵이 실루엣 안쪽의 Fresnel 분포도 바꿀 수 있음을 보여주며, 해당 설정에서 노멀맵을 연결할 때 Tangent Space에서 World Space로 변환하도록 설명합니다.

이번 분석 반구에는 그런 세부 노멀이나 좌표계 변환 문제가 들어 있지 않습니다. 따라서 실제 에셋에서 림이 끊겨 보이거나 표면 안쪽까지 번졌다면, 이 표의 p만 그대로 가져와 답을 찾기보다 어떤 N과 V가 내적에 들어갔는지부터 살펴보겠습니다. 화면상 범위를 맞추기 위해 지수를 바꾼 것인지, 다른 법선 필드를 사용한 것인지 기록을 나누고 싶습니다.

또 p=8처럼 높은 계수가 남는 영역이 좁아지면 고정 픽셀 격자와 문턱값의 영향이 커질 수 있습니다. 이번 집계를 센티미터 단위의 실제 두께나, 안티앨리어싱의 품질, 최적화 이득으로 환산할 수 없는 이유입니다. 블룸, 노출, 실제 조명을 추가했을 때의 보기 좋은 범위도 이번 실험에서는 검증하지 않았습니다.

이번에 남길 작은 판단은 지수와 강도를 다른 손잡이로 기록하자는 것입니다. 넓은 면까지 영향을 남기고 싶은지, 가장자리 가까이만 남기고 싶은지 정한 뒤 지수를 비교하고, 그다음 출력 강도를 별도로 보겠습니다. 다음 비교에서는 법선을 고정한 상태에서 카메라 조건만 바꾸어, 지금 읽은 분포가 어떤 입력에 기대고 있는지 더 살펴볼 수 있겠습니다.

다른 실제 실행 기록은 작업기록에서 이어서 볼 수 있습니다.

참고 자료와 확인 범위

확인일은 2026년 10월 29일입니다. Epic 페이지는 UE 5.8 문서로 표시됐고 별도 작성·수정일은 확인하지 못했습니다. Khronos 명세의 표지 날짜는 2016년 1월 29일이며, 이를 새 기능 발표로 소개하는 글은 아닙니다. 실제 실행은 브라우저의 예술적 마스크 비교에 한정하고 물리 반사율, 엔진 성능, 운영자의 사용 경험은 주장하지 않습니다.

작업 이야기와 댓글

이 글을 보고 떠오른 질문이나 경험을 남겨 주세요. 서로의 시도와 관찰을 나누는 공간입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다