Fresnel
Summary A common effect people use in shaders in a fresnel effect. With a fresnel you can darken, lighten or color the outline of your objects, increasing the sense of depth. For this tutorial we will make a surface shader, so if you follow it directly you
www.ronja-tutorials.com
Ronja 님의 허락을 받고 번역한 튜토리얼입니다. 원문은 위 링크에서 확인하실 수 있습니다.
몇몇 부분은 생략·추가하였습니다.
의역과 오역이 넘쳐날 수 있으니 편하게 봐주시고 잘못된 부분은 알려주시면 감사하겠습니다!
목차
- 개요
- 모델의 한쪽 면 하이라이팅하기
- 외곽 부분 하이라이팅하기
- 프레넬 색상와 강도 추가하기
개요
셰이더에서 자주 사용되는 효과 중 하나는 프레넬 효과입니다. 프레넬 효과를 사용하면 객체의 외곽선을 밝거나 어둡게 하거나, 색상을 추가할 수 있어서 깊이감을 키울 수 있습니다.
이번 튜토리얼에서는 서피스 셰이더를 만드려고 합니다. 따라서 튜토리얼을 직접 따라하기 위해서는 서피스 셰이더의 기본 개념을 알고 있어야 합니다. 이 글에서 서피스 셰이더에 대한 설명을 확인 수 있습니다. 하지만 프레넬 효과는 언릿(Unlit) 셰이더에도 사용할 수 있으며, 비용이 높은 라이팅 연산 없이도 객체에 부드러움과 실체감을 줄 수 있습니다.
모델의 한쪽 면 하이라이팅하기
기본 서피스 셰이더를 수정해 프레넬 효과를 나타나게 하겠습니다. 프레넬 효과는 객체의 노말 값을 사용하여 효과의 강도를 결정합니다. 셰이더에서 월드 공간의 노말을 구하기 위해서, 입력 구조체에 worldNormal 속성과 내부 데이터 매크로를 추가해줍니다. 우리가 내부 데이터와 직접적으로 상호작용하지는 않지만, 유니티가 월드 공간의 노말을 생성하기 위해서 내부 데이터가 필요합니다.
서피스 셰이더가 아닌 셰이더에서는 간단한 행렬 곱셈을 통해 월드 공간의 노말을 생성할 수 있습니다. 이에 대해서는 트라이플래너 매핑 튜토리얼에서 설명하고 있습니다.
// 유니티에 의해 자동으로 채워지는 입력 구조체
struct Input {
float2 uv_MainTex;
float3 worldNormal;
INTERNAL_DATA
};
그라디언트를 얻기 위해서, 다른 정규화된 벡터와의 내적을 계산합니다. 두 개의 정규화된 벡터의 내적을 계산하면, 두 벡터가 얼마나 일치하는지를 나타내는 값을 얻을 수 있습니다. 만약 두 벡터가 같은 방향을 가리킨다면, 내적 값은 1이 되고, 서로 수직이라면 0을, 반대 방향을 가리킨다면 -1이 됩니다.
먼저 노말 벡터와 정적인 벡터 하나의 내적 연산을 통해 연산이 어떻게 동작하는지 확인해 보겠습니다. 그런 다음 그 결과를 이미션(Emission) 채널에 적용해 나타내도록 하겠습니다.
// 라이팅 함수에서 사용하는 매개변수를 설정하는 서피스 셰이더 함수
void surf (Input i, inout SurfaceOutputStandard o) {
//...
// 아래를 가리키는 방향 벡터와 노말 벡터간의 내적을 구함
float fresnel = dot(i.worldNormal, float3(0, 1, 0));
// 이미션 값에 프레넬 값을 적용
o.Emission = _Emission + fresnel;
}
이제 표면이 위쪽을 향할수록 더 밝아지고, 아래를 향할수록 더 어두워지는 것을 확인할 수 있습니다. 이미션 값이 음수가 되어 이상한 결과가 나오는 것을 방지하기 위해서는, 프레넬 값을 사용하기 전에 0과 1 사이로 클램프(clamp)해야 합니다. 이를 위해 saturate() 메소드를 사용할 것입니다. 이 메소드는 0과 1로 clamp하는 것과 동일하지만, 일부 GPU에서는 더 빠르게 동작합니다.
이러한 변경을 통해서 프레넬 효과가 객체의 윗부분에만 영향을 주는 것을 확인할 수 있습니다.
// 라이팅 함수에서 사용하는 매개변수를 설정하는 서피스 셰이더 함수
void surf (Input i, inout SurfaceOutputStandard o) {
//...
// 아래를 가리키는 방향 벡터와 노말 벡터간의 내적을 구함
float fresnel = dot(i.worldNormal, float3(0, 1, 0));
// 값을 0과 1사이로 클램프해 뒷면에 어두운 아티팩트가 생기지 않게 함
fresnel = saturate(fresnel);
// 이미션 값에 프레넬 값을 적용
o.Emission = _Emission + fresnel;
}
외곽 부분 하이라이팅하기
다음 단계는 고정된 방향이 아닌 우리가 보는 시점 방향에 따라 동작하도록 만드는 것입니다. 서피스 셰이더에서는 입력 구조체에 시점(View) 벡터를 추가하는 것으로 간단히 값을 가져올 수 있습니다.
만약 언릿(Unlit) 프레넬 셰이더를 만들고 있다면, 버텍스의 월드 좌표에서 카메라의 좌표를 빼서 시점 벡터를 얻을 수 있습니다. (월드 공간의 좌표를 얻는 방법에 대해서는 플래너 매핑 튜토리얼에서 설명하고 있습니다. 카메라 위치는 빌트인 변수인 _WorldSpaceCameraPos 를 통해 가져올 수 있는데, 이 변수는 추가 설정 없이 코드에 추가하는 것으로 바로 사용할 수 있습니다.)
// 유니티에서 자동으로 채워주는 입력 구조체
struct Input {
float2 uv_MainTex;
float3 worldNormal;
float3 viewDir;
INTERNAL_DATA
};
// 라이팅 함수에서 사용하는 매개변수를 설정하는 서피스 함수
void surf (Input i, inout SurfaceOutputStandard o) {
//...
// 금속성과 매끄러움 값을 변수 값 그대로 적용
o.Metallic = _Metallic;
o.Smoothness = _Smoothness;
// 시점 벡터와 노말 벡터간의 내적을 구함
float fresnel = dot(i.worldNormal, i.viewDir);
// 값을 0과 1사이로 클램프해 뒷면에 어두운 아티팩트가 생기지 않게 함
fresnel = saturate(fresnel);
// 이미션 값에 프레넬 값을 적용
o.Emission = _Emission + fresnel;
}
이 상태로도 이미 잘 동작하고 있지만, 메테리얼의 외곽 부분이 아닌 중심 부분이 밝게 표시되고 있습니다. 이를 반전하기 위해서는 단순히 1에서 해당 값을 빼주면 됩니다. 이렇게 하면 밝게 표시되던 부분이 반전되어 외곽 부분이 하이라이팅되게 됩니다.
// 라이팅 함수에서 사용하는 매개변수를 설정하는 서피스 함수
void surf (Input i, inout SurfaceOutputStandard o) {
//...
// 시점 벡터와 노말 벡터간의 내적을 구함
float fresnel = dot(i.worldNormal, i.viewDir);
// 프레넬 값을 반전해 외곽 부분이 큰 값이 되도록 함
fresnel = saturate(1 - fresnel);
// 이미션 값에 프레넬 값을 적용
o.Emission = _Emission + fresnel;
}
프레넬 색상과 강도 추가하기
이 셰이더를 완성하기 위해 몇 가지의 커스터마이징 옵션을 추가하려고 합니다. 먼저 프레넬 색상을 추가하겠습니다. 이를 위해 색상을 설정할 프로퍼티와 값을 정의한 뒤, 프레넬 값에 해당 색상값을 곱해주면 됩니다.
//...
_FresnelColor ("Fresnel Color", Color) = (1,1,1,1)
//...
float3 _FresnelColor;
//...
// 라이팅 함수에서 사용하는 매개변수를 설정하는 서피스 함수
void surf (Input i, inout SurfaceOutputStandard o) {
//...
// 금속성과 매끄러움 값을 변수 값 그대로 적용
o.Metallic = _Metallic;
o.Smoothness = _Smoothness;
// 시점 벡터와 노말 벡터간의 내적을 구함
float fresnel = dot(i.worldNormal, i.viewDir);
// 프레넬 값을 반전해 외곽 부분이 큰 값이 되도록 함
fresnel = saturate(1 - fresnel);
// 프레넬 값과 색상 값을 혼합
float3 fresnelColor = fresnel * _FresnelColor;
// 이미션 값에 프레넬 값을 적용
o.Emission = _Emission + fresnelColor;
}
다음으로, 지수(Exponent)를 추가하여 프레넬 효과를 강하게 또는 약하게 조절할 수 있는 기능을 추가하겠습니다. 지수 프로퍼티에 powerslider 속성을 추가합니다. 이렇게 하면 0에 가까운 값들이 더 넓은 범위를 차지하게 되어, 더 정밀하게 조정할 수 있습니다. (이 예제에서는 슬라이더의 0.25~1의 구간이 1~4의 구간만큼 넓게 표시됩니다.)
지수 연산은 처리 비용이 꽤 많이 드는 편입니다. 따라서 프레넬 효과를 조절하는데 적절한 다른 방법을 찾았다면, 그 방법으로 바꾸는 것이 더 나을 수 있습니다. 하지만 지수 연산은 쉽고 사용하기 편리하다는 장점도 있습니다.
//...
[PowerSlider(4)] _FresnelExponent ("Fresnel Exponent", Range(0.25, 4)) = 1
//...
float _FresnelExponent;
//...
// 라이팅 함수에서 사용하는 매개변수를 설정하는 서피스 함수
void surf (Input i, inout SurfaceOutputStandard o) {
//...
// 금속성과 매끄러움 값을 변수 값 그대로 적용
o.Metallic = _Metallic;
o.Smoothness = _Smoothness;
// 시점 방향 벡터와 노말 벡터간의 내적을 구함
float fresnel = dot(i.worldNormal, i.viewDir);
// 프레넬 값을 반전해 외곽 부분이 큰 값이 되도록 함
fresnel = saturate(1 - fresnel);
// 프레넬 값을 지수만큼 제곱하여 조절할 수 있도록 함
fresnel = pow(fresnel, _FresnelExponent);
// 프레넬 값과 색상 값을 혼합
float3 fresnelColor = fresnel * _FresnelColor;
// 이미션 값에 프레넬 값을 적용
o.Emission = _Emission + fresnelColor;
}
프레넬 효과는 텍스처나 기타 효과들을 천천히 사라지게 하는 등의 다양한 용도로 사용할 수 있습니다. 이러한 사용법들은 아마도 다른 튜토리얼에서 다루거나, 아니면 직접 탐구해도 됩니다.
Shader "Tutorial/012_Fresnel" {
// 인스펙터에서 조작할 수 있는 값들이 보여집니다.
Properties {
_Color ("Tint", Color) = (0, 0, 0, 1)
_MainTex ("Texture", 2D) = "white" {}
_Smoothness ("Smoothness", Range(0, 1)) = 0
_Metallic ("Metalness", Range(0, 1)) = 0
[HDR] _Emission ("Emission", color) = (0,0,0)
_FresnelColor ("Fresnel Color", Color) = (1,1,1,1)
[PowerSlider(4)] _FresnelExponent ("Fresnel Exponent", Range(0.25, 4)) = 1
}
SubShader {
// 메테리얼은 완전히 불투명하고, 다른 불투명 지오메트리와 같은 타이밍에 렌더됩니다.
Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
CGPROGRAM
// 이 셰이더는 서피스 셰이더로,
// 백그라운드에서 유니티가 고급 조명 및 기타 기능을 추가하여 확장합니다.
// 우리의 서피스 셰이더 함수는 'surf'라고 불리며,
// PBR 조명인 표준(standard) 조명 모델을 사용합니다.
// fullfowardshadows 는 셰이더에 필요한 그림자 패스를 유니티가 추가하도록 합니다.
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
sampler2D _MainTex;
fixed4 _Color;
half _Smoothness;
half _Metallic;
half3 _Emission;
float3 _FresnelColor;
float _FresnelExponent;
// 유니티에서 자동으로 채워주는 입력 구조체
struct Input {
float2 uv_MainTex;
float3 worldNormal;
float3 viewDir;
INTERNAL_DATA
};
// 라이팅 함수에서 사용하는 매개변수를 설정하는 서피스 함수
void surf (Input i, inout SurfaceOutputStandard o) {
// sample and tint albedo texture
fixed4 col = tex2D(_MainTex, i.uv_MainTex);
col *= _Color;
o.Albedo = col.rgb;
// 금속성과 매끄러움 값을 변수 값 그대로 적용
o.Metallic = _Metallic;
o.Smoothness = _Smoothness;
// 시점 방향 벡터와 노말 벡터간의 내적을 구함
float fresnel = dot(i.worldNormal, i.viewDir);
// 프레넬 값을 반전해 외곽 부분이 큰 값이 되도록 함
fresnel = saturate(1 - fresnel);
// 프레넬 값을 지수만큼 제곱하여 조절할 수 있도록 함
fresnel = pow(fresnel, _FresnelExponent);
// 프레넬 값과 색상 값을 혼합
float3 fresnelColor = fresnel * _FresnelColor;
// 이미션 값에 프레넬 값을 적용
o.Emission = _Emission + fresnelColor;
}
ENDCG
}
FallBack "Standard"
}
'Graphics > Ronja's Unity Shader tutorials' 카테고리의 다른 글
체크 무늬 만들기 (0) | 2024.08.19 |
---|---|
트라이플래너 매핑 (Triplanar Mapping) (0) | 2024.07.08 |
색상 보간 (0) | 2024.06.15 |
평면 매핑 (Planar Mapping) (0) | 2024.05.05 |
스프라이트 셰이더 (0) | 2024.03.03 |