13. 아웃라인과 BRDF
Outline
Sobel Mask Filter
(Post Processing - Render Feature)
이미지 처리에서, 한 픽셀에 대해 주변 9가지 픽셀을 검사하여,
변위를 크기화 하여 외곽선을 찾아낼 수 있습니다.
즉 마스킹을 해서 처리하는 방식이라 볼 수 있는데, 그에 따른 마스크는 다음과 같습니다.
13/Untitled.png)
어느방향에 대한 경계선 성분을 검사할 것 인지에 대해 설정하는데,
저렇게 만들어진 필터를 사용해, 처리합니다.
어떤 점에 대해, 주변 픽셀의 광량을 모두 합산해 저장합니다.
(색상의 강도 혹은 원하는 변조된 값도 넣을 수 있겠죠. depth라던지)
그런데 한쪽 방향의 데이터와, 반대쪽 데이터의 부호가 반대이므로,
양 방향의 차이가 크다면 명확하게 큰 값(부호는 다를 수 있음)을 저장하게 됩니다.
이때 이제 구해진 부호있는 값의 부호를 삭제하기 위해 자기자신으로 내적한 후 제곱근 연산하여 크기를 반환합니다.
13/Untitled 1.png)
코드가 한국어보다 쉽습니다.
13/Untitled 2.png)
만약 어떤 성분에 대해 극단적으로 찾고싶다면,
13/Untitled 3.png)
필터를 이렇게 걸어도 됩니다.
13/Untitled 4.png)
BRDF(양방향반사도분포함수)
Cook-Torrance Light Model
Cook - Torrance 라이트 모델은 오브젝트의 표면이
매우 많은 미세면에 의해 구성되어있다고 가정합니다.
이 미세면이 각자 개별적으로 들어오는 빛을 반사할 때, 거칠기에 따라 반사패턴이 달라지죠.
k는 확산비율, NoL은 난반사, I(spec)은 Microfacet BRDF, 극소면 BRDF라고 합니다.
그 중 specular에 해당하는 항은 다음과 같이 구성되는데,
추후 여기적힌 π는 4로 나누는게 에너지 보존법칙에 더 적합하다며 4로 대체됩니다.
https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
F(fresnel) 프레넬,
G(geometrix Attenuation Factor), 기하학 감쇠요소 (거칠기에 따른 입사광차단, 자체 그림자)
혹은 V라는 약칭으로 불립니다.
D(NDF, Normal Distribution Fuction), 정규 분포 함수
입니다.
13/Untitled 5.png)
13/Untitled 6.png)
그중 살펴볼 것은 Diffuse 과 Specular(사진의 Glossy)입니다.
어떻게 빛을 확산하는지 알아봅시다.
Diffuse BRDF: Lambert Light model
램버트는 Diffuse만 처리하는 간단한 라이트 모델입니다.
노멀과 빛의 내적(N dot L)을 통해 음영을 표기합니다.
Shader "Hidden/Lambert"
{
Properties
{
_BaseColor ("Base Color", Color) = (1, 1, 1, 1)
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float3 normal : NORMAL;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float3 normal : NORMAL;
};
Varyings vert(Attributes IN)
{
Varyings OUT;
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
OUT.normal = TransformObjectToWorldNormal(IN.normal);
return OUT;
}
ENDHLSL
SubShader
{
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" }
HLSLINCLUDE
#pragma multi_compile _MAIN_LIGHT_SHADOWS
#pragma multi_compile _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _SHADOWS_SOFT
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
CBUFFER_END
ENDHLSL
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
half4 frag(Varyings IN) : SV_Target
{
Light light = GetMainLight();
float lambertian = saturate(dot(IN.normal, light.direction));
return _BaseColor * lambertian;
}
ENDHLSL
}
}
}
13/Untitled 7.png)
Diffuse BRDF: Half Lambert
하지만 램버트는 어두운면이 너무 어두워, 디테일이 모두 날라간다는 점이 있습니다.
그래서 절반을 곱하고 절반을 더한, 하프라이프 시절 만들어진 방식이 Half Lambert입니다.
13/Untitled 8.png)
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
half4 frag(Varyings IN) : SV_Target
{
Light light = GetMainLight();
float lambertian = saturate(dot(IN.normal, light.direction));
lambertian = lambertian * 0.5f + 0.5f;
return _BaseColor * lambertian;
}
ENDHLSL
}
13/Untitled 9.png)
Specular BRDF: Phong Light Model(Phong Reflection Model)
퐁 라이트 모델의 연산방법은 다음과 같습니다.
일단 노멀과 빛의 반사 빛 벡터를 구합니다.
이후 반사 빛 벡터를 시선벡터의 역과 내적하여 빛이 눈에 도달하는 정도를 구합니다.
빛의 방향은 표면으로 들어오는 방향,
반사되는 방향은 표면에서 나가는 방향, 시선벡터는 들어오는 방향이므로
시선벡터의 역을 반사벡터와 내적하거나 , 혹은 반사벡터의 역을 시선벡터와 내적해야 합니다.
같은 나가거나-들어오는 방향을 이룰 수 있게 말이죠.
13/Untitled 10.png)
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
half4 frag(Varyings IN) : SV_Target
{
IN.normal = normalize(IN.normal);
//Half lambert
Light light = GetMainLight();
float lambertian = saturate(dot(IN.normal, light.direction));
lambertian = lambertian * 0.5f + 0.5f;
//Phong
float3 view = GetWorldSpaceNormalizeViewDir(IN.PositionWS);
float3 reflectDirection = reflect(light.direction, IN.normal);
half spec = saturate(dot(reflectDirection, -view));
spec = pow(spec,5);
return _BaseColor * lambertian + spec;
}
ENDHLSL
}
13/Untitled 11.png)
Specular BRDF: Blinn - Phong Light Model
Bline - Phong 라이트 모델은 위 방법을 좀 더 계산에 최적화 했습니다.
표면 기준, 빛으로 나가는 방향과 시야로 나가는 방향을 더해, 하프벡터를 만듭니다.
이 하프 벡터의 방향과 표면의 방향을 내적한 값이 1에 가까울 때, 시야와 반사벡터가 같다는 뜻이죠.
13/Untitled 12.png)
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
half4 frag(Varyings IN) : SV_Target
{
IN.normal = normalize(IN.normal);
//lambert
Light light = GetMainLight();
float lambertian = saturate(dot(IN.normal, light.direction));
lambertian = lambertian * 0.5f + 0.5f;
//BPhong
float3 view = GetWorldSpaceNormalizeViewDir(IN.PositionWS); //ShaderVariablesFunctions.hlsl
float3 halfDirection = SafeNormalize(view + light.direction); //common.hlsl
half spec = saturate(dot(halfDirection, IN.normal));
spec = pow(spec,5);
return _BaseColor * lambertian + spec;
}
ENDHLSL
}
13/Untitled 13.png)
Specular BRDF: Minimalist Cook-Torrance BRDF
아까 위에서 확인했던
이 식을 근사합니다.
이때, 두 부분으로 분리하는데, D 와 G x F 입니다.
13/Untitled 14.png)
13/Untitled 15.png)
이렇게 나온 그래프는
13/Untitled 16.png)
이런 형태를 띄는데,
이를 이제 근사합니다.
13/Untitled 17.png)
최종적으로 이를 합쳐 처리합니다.
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
half4 frag(Varyings IN) : SV_Target
{
IN.normal = normalize(IN.normal);
//lambert
Light light = GetMainLight();
float lambertian = saturate(dot(IN.normal, light.direction));
lambertian = lambertian * 0.5f + 0.5f;
//BPhong
float3 view = GetWorldSpaceNormalizeViewDir(IN.PositionWS); //ShaderVariablesFunctions.hlsl
float3 halfDirection = SafeNormalize(view + light.direction); //common.hlsl
float NoH = saturate(dot(halfDirection, IN.normal));
half LoH = half(saturate(dot(light.direction, halfDirection)));
//approx GGX
float roughness2 = _Roughness * _Roughness; //Property
float d = NoH * NoH * (roughness2 - 1) + 1.00001f;
half d2 = half(d * d);
half LoH2 = LoH * LoH;
half specularTerm = roughness2 / (d2 * max(half(0.1), LoH2) * (roughness * 4.0 + 2.0));
return _BaseColor * lambertian + specularTerm;
}
ENDHLSL
}
13/Untitled 18.png)
Image Based Lighting
큐브맵 샘플링입니다.
13/Untitled 19.png)
https://learnopengl.com/Advanced-OpenGL/Cubemaps
사실 개념자체는 간단합니다. 큐브의 전개도를 펼쳐 각 이미지를 작성한 다음,
아까 Phong 모델에서 사용하던것과 같이 반사벡터를 구해
해당 반사벡터가 가르키는 큐브의 색상을 가져오면 됩니다.
Spherical Harmonics (SH, 구면조화)
라이트프로브 혹은 스카이박스로 부터 가져오는 방법입니다.
보통 L에 대해 **대역(Band)**이라고 말하며,
각 대역은 해당차수의 다항식과 동일(0대역 = 0차 = 상수)하며,
주어진 대역에는 2L+1개의 함수가 존재합니다.
라이트 정보는 L2 구형 고조파라고도 하는 3차 다항식을 이용해,
13/Untitled 20.png)
13/Untitled 21.png)
13/Untitled 22.png)
각 색상채널에 대해 9개, 총 27개의 부동소숫점값을 사용하여 저장합니다.
이를 큐브맵 시각화(붉은색 - 양수, 녹색 - 0, 파란색 - 음수)하면 다음과 같습니다.
13/Untitled 23.png)
유니티에서 사용하는 정보는 Vector4를 7개 사용합니다.
마지막 값을 제외한 27개 맞습니다.
13/Untitled 24.png)
해당데이터는
13/Untitled 25.png)
다음과 같이 샘플링되어 사용됩니다.
여기서 각 대역에 대해
13/Untitled 26.png)
와 같이 계산해서 던지고 있습니다.
대충 찾아봅시다.
13/Untitled 27.png)
SHA에는 각 R,G,B에 대한 x,y,z 기저 및 강도(w)를 설정하고 있습니다.
SH를 직접 가져와서 넣어봤습니다.
public void SetSH()
{
LightProbes.GetInterpolatedProbe(target.transform.position, target, out sh);
for (var i = 0; i < 3; i++)
SHA[i] = new Vector4(sh[i, 3], sh[i, 1], sh[i, 2], sh[i, 0] - sh[i, 6]);
for (var i = 0; i < 3; i++)
SHB[i] = new Vector4(sh[i, 4], sh[i, 5], sh[i, 6] * 3.0f, sh[i, 7]);
SHC = new Vector4(sh[0, 8], sh[1, 8], sh[2, 8], 1);
mat.SetVector("_SHAr", SHA[0]);
mat.SetVector("_SHAg", SHA[1]);
mat.SetVector("_SHAb", SHA[2]);
mat.SetVector("_SHBr", SHB[0]);
mat.SetVector("_SHBg", SHB[1]);
mat.SetVector("_SHBb", SHB[2]);
mat.SetVector("_SHC", SHC);
}
half4 frag(Varyings IN) : SV_Target
{
IN.normal = normalize(IN.normal);
//lambert
Light light = GetMainLight();
float lambertian = saturate(dot(IN.normal, light.direction));
lambertian = lambertian * 0.5f + 0.5f;
//BPhong
float3 view = GetWorldSpaceNormalizeViewDir(IN.PositionWS); //ShaderVariablesFunctions.hlsl
float3 halfDirection = SafeNormalize(view + light.direction); //common.hlsl
float NoH = saturate(dot(halfDirection, IN.normal));
half LoH = half(saturate(dot(light.direction, halfDirection)));
//GGX
float roughness2 = _Roughness * _Roughness;
float d = NoH * NoH * (roughness2 - 1) + 1.00001f;
half d2 = half(d * d);
half LoH2 = LoH * LoH;
half specularTerm = roughness2 / (d2 * max(half(0.1), LoH2) * (_Roughness * 4.0 + 2.0));
//SH
real4 SHCoefficients[7];
SHCoefficients[0] = _SHAr;
SHCoefficients[1] = _SHAg;
SHCoefficients[2] = _SHAb;
SHCoefficients[3] = _SHBr;
SHCoefficients[4] = _SHBg;
SHCoefficients[5] = _SHBb;
SHCoefficients[6] = _SHC;
half4 ambient = float4(SampleSH9(SHCoefficients, IN.normal), 1);
return _BaseColor * lambertian + specularTerm + ambient;
}
13/Untitled 28.png)
이제 더 알고싶다면 GDC 2008에 소개된 Stupid SH Tricks를 읽으면 됩니다.
13/Untitled 29.png)
참조
https://learnopengl.com/Lighting/Basic-Lighting
https://jcgt.org/published/0007/04/01/paper.pdf
http://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
- 해당 문서에는 일부 오류 수정사항이 적용되지 않았습니다.
아래 원본 문서를 확인해주세요
https://www.graphics.cornell.edu/~bjw/microfacetbsdf.pdf
13/Untitled 30.png)
13/Untitled 31.png)