TECHARTNOMAD | TECHARTFLOWIO.COM

TECH.ART.FLOW.IO

[번역]물리적 기반 렌더링 알고리즘

jplee 2023. 9. 17. 20:12
역자 주.
원본 링크가 어디였는지 기록을 안해놔서 원문 링크를 추가 하지 않았습니다. 2019년도에 한글로 정리 해 놨었던 것이 노션에 남겨져 있었기에 리마인드 차원에서 포스팅 해 봅니다.

 

Unity3D에 대한 포괄적인 연구

물리적 기반 렌더링에 대해 이야기해 보겠습니다.

물리적 기반 렌더링(PBR)은 지난 몇 년 동안 매우 인기를 끌고 있습니다.

유니티 5, 언리얼 엔진 4, 프로스트바이트, 심지어 ThreeJS, 그리고 더 많은 게임 엔진이 그것을 사용합니다.

많은 3D 모델링 스튜디오가 Marmoset Toolbag 및 Allegorithmic Substance Suite와 같은 인기있는 도구에 따라 "PBR Pipeline"으로 전환하고 있습니다.

오늘날(2018년도...) 파이프 라인에 익숙하지 않은 아티스트를 찾기는 어렵지만 파이프 라인이 백그라운드에서 어떻게 작동하는지 잘 아는 엔지니어와 기술 아티스트를 찾는 것은 어려울 수 있습니다. 이 튜토리얼을 작성하여 PBR 쉐이딩 (shading)을 분해(분석)하여 가능한 PBR 렌더링에 익숙하지 않은 분들이 쉽게 이해할 수 있게 만들려고 했습니다.  시작해 보겠습니다.

 

PBR의 물리적 특성

지난 3 ~ 40 년 동안 우리 주변 세계에 대한 이해와 그것이 과학적으로 / 수학적으로 어떻게 작용했는지 많은 발전이 있었다.

또한 이러한 이해의 일부는 렌더링 기술 분야에서 엄청난 발전을 가져 왔습니다.

영민한 여러 연구가들은 빛, 시야, 표면의 정상적인 모습, 그리고이 세 가지가 서로 어떻게 상호 작용하는지에 관해 심각한 결론을 내릴 수있었습니다.

이러한 발전은 대부분은 BRDF (양방향 반사율 분포 함수)와 그 고유 한 에너지 보존이라는 아이디어를 중심으로 이루어집니다.

빛과 관측점이 서페이스와 어떻게 상호 작용하는지 이해하려면 먼저 서페이스 자체를 이해해야합니다.

빛이 완벽하게 매끄러운 표면에 비치면 거의 완벽하게 그 표면에서 반사됩니다.

빛이 우리가 거친 표면이라고 부르는 것과 상호 작용할 때, 그것은 비슷하게 반사되지 않을 것입니다. 이것은 미세표면 (microfacets)의 존재로 설명 할 수 있습니다.

 

너무나 흔하게 봐왔던 그거... M 으로 표기된 것은 Half vector 와 대응될 수 있는 개념.

우리가 물체를 볼 때, 우리는 그것의 표면이 완벽하게 매끄럽지 않고 매우 작은 작은면으로 이루어져야한다고 가정해야합니다. 각각의 물체는 완벽한 Specular reflection 입니다.

이 미세표면(microfacets)은 매끄러운 표면의 법선에 걸쳐 분포되는 법선을가집니다.

마이크로 페이스 노멀이 매끄러운 표면 노멀과 다른 정도는 표면의 거칠기에 의해 결정됩니다.

표면이 거칠수록 반사 하이라이트가 손상 될 가능성이 커집니다.

이 때문에 거친 표면은 더 크고 더 흐리게 보이는 반점을 가지고 있습니다.

매끄러운 표면은 빛이 이전보다 더 완벽하게 반사 될 때 반사 하이라이트를 압축 할 수 있습니다.

이제 BRDF로 돌아가서...

양방향 반사율 분포 함수 (Bridirectional Reflectance Distribution Function, BRDF)는 표면의 반사율을 설명하는 함수입니다.

여러 가지 BRDF 모델 / 알고리즘이 있으며 그 중 다수가 물리적 기반 알고리즘이 아닙니다.

물리적 기반으로 간주되는 BRDF의 경우 에너지 보존이 필요합니다.

'에너지 보존 (Energy Conservation)'은 지표에서 반사 된 총 광량이 지표가받은 총량보다 적다고 말합니다.

표면에서 반사 된 빛은 이전에 논의했던 모든 마이크로면과 상호 작용하기 전보다 더 강렬해서는 안됩니다.

BRDF 알고리즘은 다른 알고리즘보다 더 복잡한 쉐이딩 모델을 특징으로합니다.

이 쉐이딩 모델은 기술적으로 3 개의 단일 부분으로 구성됩니다 : 정규 분포 함수, 기하학적 음영 함수 및 프레 넬 함수.

이 알고리즘을 함께 사용하면 이해 할 수 있을 것입니다.

$$ f(l,v,h) = \frac{F(v,h)G(l,v,h)D(h)} {4(NdotL)(NdotV)} $$

BRDF를 이해하려면 BRDF를 구성하는 세 가지 기능을 이해하는 것이 매우 중요합니다.

각각 차례로 구현 해 보면서 우리에게 도움이 될 음영 모델을 만들어 보겠습니다.

PBR 셰이더 작성 : 너트, 볼트 및 매끄러운 표면

PBR 셰이더의 속성

대부분의 PBR 쉐이딩 모델에서 어떤 형식으로 몇 가지 동일한 속성이 영향을주는 것은 일반적입니다. 현대 PBR 접근법에서 가장 중요한 두 가지 속성은 부드러움 (Smoothness) 및 금속성 (Metallic)입니다.

이 두 값은 모두 0..1 사이 일 때 가장 잘 작동합니다. PBR 쉐이더를 작성하는 방법에는 여러 가지가 있으며, 그 중 일부는 디즈니의 PBR 파이프 라인과 같은 더 많은 효과를 위해 BRDF 모델을 사용할 수 있습니다. 각 효과는 특정 속성에 의해 유도됩니다.

Unity에서 Writing Shaders에 대한 내 페이지를 확인하지 않았다면 집중 해서 읽을 수 있는 좋은 시간이 될 것입니다.

 

Shader "Physically-Based-Lighting" {
    Properties { 
    _Color ("Main Color", Color) = (1,1,1,1)                    //diffuse Color
    _SpecularColor ("Specular Color", Color) = (1,1,1,1)        //Specular Color (Not Used)
    _Glossiness("Smoothness",Range(0,1)) = 1                    //My Smoothness
    _Metallic("Metalness",Range(0,1)) = 0                    //My Metal Value      

    }
    SubShader {
	Tags {
            "RenderType"="Opaque"  "Queue"="Geometry"
        } 
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDBASE
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #pragma multi_compile_fwdbase_fullshadows      
            #pragma target 3.0

float4 _Color;
float4 _SpecularColor;
float _Glossiness;
float _Metallic;

Unity Shader에서 public 변수를 정의했습니다.그것들은 나중에 추가하겠습니다.

속성 아래에는 셰이더의 초기화 구조가 있습니다.

나중에 더 많은 기능을 추가 할 때 #pragma 지시문을 나중에 참조 할 것입니다.

Vertex Program

Vertex 프로그램은 Unity에서 셰이더 쓰기에 대한 자습서에서 생성 된 것과 매우 유사합니다.

우리가 필요로하는 핵심 요소는 vertex에 대한 normal, tangent및 bitangent 정보입니다.

따라서 이것들을 Vertex Program에 꼭 포함 시켜야합니다.

struct VertexInput {
    float4 vertex : POSITION;       //local vertex position
    float3 normal : NORMAL;         //normal direction
    float4 tangent : TANGENT;       //tangent direction    
    float2 texcoord0 : TEXCOORD0;   //uv coordinates
    float2 texcoord1 : TEXCOORD1;   //lightmap uv coordinates
};

struct VertexOutput {
    float4 pos : SV_POSITION;              //screen clip space position and depth
    float2 uv0 : TEXCOORD0;                //uv coordinates
    float2 uv1 : TEXCOORD1;                //lightmap uv coordinates

//below we create our own variables with the texcoord semantic. 
    float3 normalDir : TEXCOORD3;          //normal direction   
    float3 posWorld : TEXCOORD4;          //normal direction   
    float3 tangentDir : TEXCOORD5;
    float3 bitangentDir : TEXCOORD6;
    LIGHTING_COORDS(7,8)                   //this initializes the unity lighting and shadow
    UNITY_FOG_COORDS(9)                    //this initializes the unity fog
};

VertexOutput vert (VertexInput v) {
     VertexOutput o = (VertexOutput)0;           
     o.uv0 = v.texcoord0;
     o.uv1 = v.texcoord1;
     o.normalDir = UnityObjectToWorldNormal(v.normal);
     o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz );
     o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
     o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
     o.posWorld = mul(_Object2World, v.vertex);
     UNITY_TRANSFER_FOG(o,o.pos);
     TRANSFER_VERTEX_TO_FRAGMENT(o)
     return o;
}

Fragment Program

프래그먼트 (fragment) 프로그램에서, 알고리즘에서 나중에 사용할 수있는 변수 세트를 정의 해야 합니다 :

float4 frag(VertexOutput i) : COLOR {

//normal direction calculations
     float3 normalDirection = normalize(i.normalDir);

     float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz
 - i.posWorld.xyz,_WorldSpaceLightPos0.w));

     float3 lightReflectDirection = reflect( -lightDirection, normalDirection );

     float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);

     float3 viewReflectDirection = normalize(reflect( -viewDirection, normalDirection ));

     float3 halfDirection = normalize(viewDirection+lightDirection); 

     float NdotL = max(0.0, dot( normalDirection, lightDirection ));

     float NdotH =  max(0.0,dot( normalDirection, halfDirection));

     float NdotV =  max(0.0,dot( normalDirection, viewDirection));

     float VdotH = max(0.0,dot( viewDirection, halfDirection));

     float LdotH =  max(0.0,dot(lightDirection, halfDirection));
 
     float LdotV = max(0.0,dot(lightDirection, viewDirection)); 

     float RdotV = max(0.0, dot( lightReflectDirection, viewDirection ));

     float attenuation = LIGHT_ATTENUATION(i);

     float3 attenColor = attenuation * _LightColor0.rgb;

유니티 쉐이더 튜토리얼의 설명에 따라 유니티가 제공 한 데이터로 컴파일 할 변수입니다.

이 변수는 BRDF 내부로 이동하면서 셰이더 전체에서 반복적으로 사용됩니다.

Roughness

아래의 접근 방식에서는 거칠기를 다시 처리합니다. 이런 처리 방식은 지극히 개인적 선호에 의한 것입니다.

아래에 다시 처리 된 거칠기가 시작적인 체감상 분명한 결과를 만들어내는 것을 발견했습니다.

float roughness = 1- (_Glossiness * _Glossiness);   // 1 - smoothness*smoothness
roughness = roughness * roughness;

Metallic

PBR 쉐이더에서 Metallic을 사용할 때 조심 해야 할 것이 많습니다.

알고리즘 중 어느 것도 그 알고리즘을 설명하지 않는다는 것을 알 수 있습니다. 그래서 우리는 다른 형식으로 완전히 포함 시켰습니다.

금속성은 재료가 유전체 재료 (비금속, 즉 금속 = 0) 또는 금속 (금속 = 1) 재료인지 여부를 결정하는 제어 값입니다.

따라서 우리의 메탈릭 값이 쉐이더에 올바른 방식으로 영향을 주도록하기 위해, 우리는 확산 된 색으로 그것을 연결하고 우리의 반사 색을 유도하게 될 것입니다.

금속은 확산 반사(Diffuse)를 보이지 않기 때문에 완전히 반사 된 Albedo를 가지며 실제 반사 색상은 물체의 표면을 반영하여 변경됩니다.

아래 참조 :

float3 diffuseColor = _Color.rgb * (1-_Metallic) ;
float3 specColor = lerp(_SpecularColor.rgb, _Color.rgb, _Metallic * 0.5);

우리 세이더 내부

아래는 우리가 구축 할 기본 셰이더 형식입니다.

의견을 적어 두어 코드를 삽입 할 위치를 구성하고 알려줍니다.

Shader "Physically-Based-Lighting" {
    Properties { 
    _Color ("Main Color", Color) = (1,1,1,1)                    //diffuse Color
    _SpecularColor ("Specular Color", Color) = (1,1,1,1)        //Specular Color (Not Used)
    _Glossiness("Smoothness",Range(0,1)) = 1                    //My Smoothness
    _Metallic("Metalness",Range(0,1)) = 0                    //My Metal Value      


// future shader properties will go here!! Will be referred to as Shader Property Section
    }

    SubShader {
	Tags {
            "RenderType"="Opaque"  "Queue"="Geometry"
        } 
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDBASE
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #pragma multi_compile_fwdbase_fullshadows  
            #pragma target 3.0

float4 _Color;
float4 _SpecularColor;
float _Glossiness;
float _Metallic;

//future public variables will go here! Public Variables Section

struct VertexInput {
    float4 vertex : POSITION;       //local vertex position
    float3 normal : NORMAL;         //normal direction
    float4 tangent : TANGENT;       //tangent direction    
    float2 texcoord0 : TEXCOORD0;   //uv coordinates
    float2 texcoord1 : TEXCOORD1;   //lightmap uv coordinates
};

struct VertexOutput {
    float4 pos : SV_POSITION;              //screen clip space position and depth
    float2 uv0 : TEXCOORD0;                //uv coordinates
    float2 uv1 : TEXCOORD1;                //lightmap uv coordinates

//below we create our own variables with the texcoord semantic. 
    float3 normalDir : TEXCOORD3;          //normal direction   
    float3 posWorld : TEXCOORD4;          //normal direction   
    float3 tangentDir : TEXCOORD5;
    float3 bitangentDir : TEXCOORD6;
    LIGHTING_COORDS(7,8)                   //this initializes the unity lighting and shadow
    UNITY_FOG_COORDS(9)                    //this initializes the unity fog
};

VertexOutput vert (VertexInput v) {
     VertexOutput o = (VertexOutput)0;           
     o.uv0 = v.texcoord0;
     o.uv1 = v.texcoord1;
     o.normalDir = UnityObjectToWorldNormal(v.normal);
     o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz );
     o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
     o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
     o.posWorld = mul(_Object2World, v.vertex);
     UNITY_TRANSFER_FOG(o,o.pos);
     TRANSFER_VERTEX_TO_FRAGMENT(o)
     return o;
}    

//helper functions will go here!!! Helper Function Section


//algorithms we build will be placed here!!! Algorithm Section



float4 frag(VertexOutput i) : COLOR {

//normal direction calculations
     float3 normalDirection = normalize(i.normalDir);

     float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w));

     float3 lightReflectDirection = reflect( -lightDirection, normalDirection );

     float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);

     float3 viewReflectDirection = normalize(reflect( -viewDirection, normalDirection ));

     float3 halfDirection = normalize(viewDirection+lightDirection); 

     float NdotL = max(0.0, dot( normalDirection, lightDirection ));

     float NdotH =  max(0.0,dot( normalDirection, halfDirection));

     float NdotV =  max(0.0,dot( normalDirection, viewDirection));

     float VdotH = max(0.0,dot( viewDirection, halfDirection));

     float LdotH =  max(0.0,dot(lightDirection, halfDirection));
 
     float LdotV = max(0.0,dot(lightDirection, viewDirection)); 

     float RdotV = max(0.0, dot( lightReflectDirection, viewDirection ));

     float attenuation = LIGHT_ATTENUATION(i);

     float3 attenColor = attenuation * _LightColor0.rgb;

     float roughness = 1- (_Glossiness * _Glossiness);   // 1 - smoothness*smoothness
     
     roughness = roughness * roughness;

     float3 diffuseColor = _Color.rgb * (1-_Metallic) ;
    
     float3 specColor = lerp(_SpecularColor.rgb, _Color.rgb, _Metallic * 0.5);
      
    //future code will go here!    Fragment Section

     return float4(1,1,1,1);
}
ENDCG
}
}
FallBack "Legacy Shaders/Diffuse"
}

Unity에서 머티리얼에 첨부 할 때 세이더 코드를 사용 해 흰색 객체를 만들어야합니다.

속성 션, 변수 섹션의 변수, Helper 함수 섹션의 Helper 함수, 알고리즘 섹션의 알고리즘 및 Fragments 섹션의 셰이더 코드 구현을 통해이 셰이더를 확장합니다.

PBR 쉐이더 구성하기 Pt.1 : 정규 분포 함수 (NDF : Specular D)

정규 분포 함수 란?

Normal Distribution Function은 BRDF 쉐이더를 구성하는 세 가지 핵심 요소 중 하나입니다.

NDF는 표면 상에 미세면 정상 분포를 통계적으로 설명합니다.

용도에 따라 NDF는 반사 (명암)의 밝기를 조정하는 가중 함수 역할을합니다.

NDF는 지표의 기하학적 특성으로 생각하는 것이 중요합니다.

셰이더에 알고리즘을 추가하기 시작하여 NDF가 생성하는 효과를 시각화 할 수 있습니다.

우리가 하고 싶은 첫 번째 일은 알고리즘을 만드는 것입니다.

알고리즘을 시각화하기 위해 Float 타입의 임의 값을 오버라이드 하여 반환 해 줍니다.

float3 SpecularDistribution = specColor;

//the algorithm implementations will go here

return float4(float3(1,1,1) * SpecularDistribution.rgb,1);

다음 섹션의 형식은 다음과 같습니다.

알고리즘 섹션에 알고리즘을 작성한 후에는 위에서 설명한 위치에 알고리즘을 구현합니다.

새 알고리즘을 구현할 때 위의 활성 알고리즘을 주석 처리하면 결과로 나오는 효과는 현재 주석 처리되지 않은 알고리즘에만 기반을 둡니다.

걱정하지 마시고, 우리는 이것을 나중에 정리하고 Unity 내에서 알고리즘 사이를 쉽게 전환 할 수있는 방법을 제공해 줄 것이며 더 이상의 주석 재생은 필요하지 않습니다.

간단한 Blinn-Phong 방식으로 시작하겠습니다.

 

Blinn-Phong NDF

Phong specularity의 Blinn 근사는 Phong Specular Model의 최적화로 만들어졌습니다.

Blinn(그래픽스 연구가 이름), 그는 매 프레임마다 빛의 반사 벡터를 계산하는 것보다 법선 벡터와 반 벡터의 내적을 만드는 것이 더 빠르다고 결정했습니다.

알고리즘은 Blinn이 Phong보다 부드럽고 다양한 결과를 산출합니다.

float BlinnPhongNormalDistribution(float NdotH, float specularpower, float speculargloss)
{
    float Distribution = pow(NdotH,speculargloss) * specularpower;
    Distribution *= (2+specularpower) / (2*3.1415926535);
    return Distribution;
}

Blinn-Phong은 물리적으로 올바른 알고리즘으로 간주되지 않지만 특정 게임의 표현 의도에 사용할 수있는 신뢰할 수있는 반사 하이라이트를 생성합니다. 알고리즘 섹션에 위의 알고리즘을 배치하고 조각 섹션에 아래 코드를 배치하십시오.

SpecularDistribution *=  BlinnPhongNormalDistribution(NdotH, _Glossiness,  max(1,_Glossiness * 40));

쉐이더에 Glossiness 값을 지정하면 객체가 일반 분포 (Specularity)를 묘사하는 흰색 강조 표시로 표시되고 나머지 객체는 검정색으로 표시됩니다. 쉐이더를 쉽게 테스트 할 수 있도록 하였는데 위의 구현에서 40은 높은 범위의 함수를 제공 할 수 있도록하기위한 것이지만 모든 사람에게 가장 적합한 값은 아닙니다.

 

Phong NDF

float PhongNormalDistribution(float RdotV, float specularpower, float speculargloss){
    float Distribution = pow(RdotV,speculargloss) * specularpower;
    Distribution *= (2+specularpower) / (2*3.1415926535);
    return Distribution;
}

Phong 알고리즘은 비 물리적 알고리즘이지만 위의 Blinn 근사보다 훨씬 더 정밀한 결과를 생성합니다. 아래는 구현 예입니다.

SpecularDistribution *=  PhongNormalDistribution(RdotV, _Glossiness, max(1,_Glossiness * 40));

Blinn-Phong 방식과 마찬가지로 * 40 은 테스트 용도로만 사용 하십시오.

 

 

Beckman NDF

Beckman Normal Distribution 기능은 훨씬 고급 기능이며 조도 값을 고려합니다. 여기서 '조도' 란 공학적인 측면에서 '표면 조도(Surface Roughness)'를 뜻한다.

가공된 금속 표면에 생기는 주기가 짧고, 진폭이 비교적 작은 불규칙한 요철(凹凸)의 크기. 촉침식의 측정기로, 측정면에 수직인 단면에 나타나지는 윤곽을 세로방향 및 가로방향으로 확대 기록한 단면 곡선으로부터 표면 거칠기를 구한다.

 

💡 KS B 0161에서는 표면 거칠기를 다음 세 가지 방법으로 규정하고 있다.

  • 최대 높이 (Rmax)
  • 십점 평균 거칠기 (Rz)
  • 중심선 평균 거칠기(Ra)

거칠기뿐만 아니라 우리의 정상 방향과 반 방향 사이의 내적 (dot product)과 함께, 우리는 표면을 가로 지르는 법선의 분포를 정확하게 근사 할 수 있습니다.

float BeckmannNormalDistribution(float roughness, float NdotH)
{
    float roughnessSqr = roughness*roughness;
    float NdotHSqr = NdotH*NdotH;
    return max(0.000001,(1.0 / (3.1415926535*roughnessSqr*NdotHSqr*NdotHSqr)) * exp((NdotHSqr-1)/(roughnessSqr*NdotHSqr)));
}

이 알고리즘의 구현은 매우 간단합니다.

SpecularDistribution *=  BeckmannNormalDistribution(roughness, NdotH);

주목할 중요한 것은 Beckman 모델이 물체의 표면을 다루는 방법입니다. 위의 그림에서 알 수 있듯이 Beckman 모델은 부드럽게 값을 사용하여 Highlight가 극적으로 강화되는 특정 지점까지 변경되는 속도가 느립니다. 표면의 매끄러움이 증가하는 것처럼, Reflection Highlight를 함께 당겨서 예술적 관점에서 매우 기쁜듯한 부드러운 값을 만들어냅니다. 이 Behavior는 초기 거칠기 값에서 거친 금속에 매우 유리하며 부드러움 값이 증가함에 따라 플라스틱에도 상당히 좋습니다.

 

 

Gaussian NDF

Gaussian Normal Distribution 모델은 다른 모델보다 많이 사용되지는 않습니다. 더 높은 부드러움 값에서 원하는 것보다 훨씬 부드러운 경사면 하이라이트를 생성하는 경향이 있기 때문입니다. 예술적 관점에서 볼 때 이것은 바람직 할 수 있지만 실제 물리적 특성에 대한 논쟁이 있습니다.

float GaussianNormalDistribution(float roughness, float NdotH)
{
    float roughnessSqr = roughness*roughness;
	float thetaH = acos(NdotH);
    return exp(-thetaH*thetaH/roughnessSqr);
}

이 알고리즘의 구현은 표면의 거칠기와 일반 및 하프 벡터의 내적을 사용하는 다른 일반 분포 함수의 구현과 유사합니다.

SpecularDistribution *=  GaussianNormalDistribution(roughness, NdotH);

 

 

GGX NDF

GGX는 오늘날 널리 사용되는 것은 아니지만 인기있는 알고리즘 중 하나입니다. 현대 응용 프로그램의 대부분은 BRDF 기능 중 일부에 의존합니다. GGX는 Bruce Walter와 Kenneth Torrance가 개발했습니다. 이 논문에서 사용 된 많은 알고리즘은 사용되는 것 보다 보편적 인 알고리즘 중 일부입니다.

float GGXNormalDistribution(float roughness, float NdotH)
{
    float roughnessSqr = roughness*roughness;
    float NdotHSqr = NdotH*NdotH;
    float TanNdotHSqr = (1-NdotHSqr)/NdotHSqr;
    return (1.0/3.1415926535) * sqr(roughness/(NdotHSqr * (roughnessSqr + TanNdotHSqr)));
}

구현 방식은 다른 메서드와 유사합니다.

SpecularDistribution *=  GGXNormalDistribution(roughness, NdotH);

GGX 알고리즘의 반사 하이라이트는 매우 단단하고 뜨겁지만 볼 표면 전체에 걸쳐 부드러운 분포를 유지합니다. 이것은 왜 GGX 알고리즘이 금속 표면을 통해 반사의 왜곡을 복제하는 것이 바람직한 지 보여주는 좋은 예입니다.

 

 

Trowbridge-Reitz NDF

Trowbridge-Reitz 접근법은 GGX와 동일한 논문에서 개발되었으며 GGX 알고리즘과 현저하게 유사한 결과를 산출합니다. 눈에 띄는 차이점은 물체의 극단 가장자리가 GGX보다 더 매끄러운 하이라이트를 특징으로한다는 것입니다. GGX는 지표각 에서보다 거친 감쇠를 보입니다.

float TrowbridgeReitzNormalDistribution(float NdotH, float roughness){
    float roughnessSqr = roughness*roughness;
    float Distribution = NdotH*NdotH * (roughnessSqr-1.0) + 1.0;
    return roughnessSqr / (3.1415926535 * Distribution*Distribution);
}

보통 Trowbridge-Reitz 공식은 거칠기와 법선 벡터와 하프 벡터의 내적 (dot product)에 의존합니다.

SpecularDistribution *=  TrowbridgeReitzNormalDistribution(NdotH, roughness);

 

Ward Anisotropic NDF

Ward 접근 방식으로 비 등방성 BRDF는 Trowbridge-Reitz 방법과 크게 다른 결과를 산출합니다. Specular 하이라이트는 표면이 부드러워 질수록 훨씬 부드럽고 훨씬 빨리 사라집니다.

float WardAnisotropicNormalDistribution(float anisotropic, float NdotL, float NdotV, float NdotH, float HdotX, float HdotY)
{
    float aspect = sqrt(1.0h-anisotropic * 0.9h);
    float X = max(.001, sqr(1.0-_Glossiness)/aspect) * 5;
 	float Y = max(.001, sqr(1.0-_Glossiness)*aspect) * 5;
    float exponent = -(sqr(HdotX/X) + sqr(HdotY/Y)) / sqr(NdotH);
    float Distribution = 1.0 / (4.0 * 3.14159265 * X * Y * sqrt(NdotL * NdotV));
    Distribution *= exp(exponent);
    return Distribution;
}

Trowbridge-Reitz 방법과 마찬가지로 Ward Algorithm은 탄젠트 및 비트 탄젠트 데이터가 필요하지만 일반 및 광의 내적 뿐만 아니라 정상 및 우리 관점의 내적을 사용합니다.

SpecularDistribution *=  WardAnisotropicNormalDistribution(_Anisotropic,NdotL, NdotV, NdotH, dot(halfDirection, i.tangent

 

함께 결합하기 PBR 쉐이더 Pt.2 : GEOMETRIC SHADOWING FUNCTION

기하학적 그림자 알고리즘이란?

기하학적 그림자 기능은 마이크로 펫의 자체 음영 현상으로 인한 빛의 감쇠를 설명하는 데 사용됩니다.

이 근사값은 주어진 지점에서 미세면(microfacet)이 서로에 의해 가려 지거나 빛이 여러 개의 마이크로 영역에서 반사 될 확률을 모델링합니다.

이 확률 동안 빛은 시점에 도달하기 전에 에너지를 잃습니다.

GSF를 정확하게 생성하려면 거칠기를 샘플링하여 microfacet 분포를 결정해야합니다.

거칠기를 포함하지 않는 몇 가지 기능이 있으며 신뢰할 수있는 결과를 얻을 수는 있지만 샘플 거칠기 기능만큼 많은 경우에 맞지 않습니다.

Geometric Shadowing Function은 BRDF 에너지 보존에 필수적입니다.

GSF가 없으면 BRDF는받은 빛의 에너지보다 더 많은 빛 에너지를 반사 할 수 있습니다.

microfacet BRDF 방정식의 핵심 부분은 활성 표면적 (L에서 V까지의 빛 에너지를 반사하는 표면 영역에 의해 덮인 면적)과 미세 표면의 총 표면적 사이의 비율과 관련이 있습니다.

Shadowing 및 Masking 이 고려되지 않은 경우 활성 영역이 전체 영역을 초과 할 수 있으며, 이로 인해 BRDF가 에너지를 보존하지 못하고 경우에 따라 과다한 금액이 발생할 수 있습니다.

float GeometricShadow = 1;
//the algorithm implementations will go here
 return float4(float3(1,1,1) * GeometricShadow,1);

GSF 함수를 미리보기 위해 정규 분포 함수 위에 이 코드를 배치 해 봅시다. 형식은 NDF 기능을 구현 한 방법과 매우 유사하게 작동합니다.

 

Implicit GSF (함축적인 GSF)

함축적 GSF는 Geometric Shadowing의 기본 논리입니다.

float ImplicitGeometricShadowingFunction (float NdotL, float NdotV)
{
	float Gs =  (NdotL*NdotV);       
	return Gs;
}

법선과 빛의 내적을 곱하고 법선과 우리의 시점의 내적을 곱함으로써 우리는 빛이 우리의 견해에 기초한 물체의 표면에 어떻게 영향을 미치는지 정확하게 묘사합니다.

GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV);

 

Ashikhmin-Shirley GSF

이방성 정규 분포 함수와 함께 사용하도록 설계된 Ashikhmin-Shirley GSF는 이방성 효과에 대한 좋은 기반을 제공합니다.

float AshikhminShirleyGSF (float NdotL, float NdotV, float LdotH)
{
	float Gs = NdotL*NdotV/(LdotH*max(NdotL,NdotV));
	return  (Gs);
}

오른쪽에서 볼 수 있듯이,이 모델에 의해 생성 된 미세면(microfacet)의 그림자는 매우 미묘합니다.

GeometricShadow *= AshikhminShirleyGSF (NdotL, NdotV, LdotH);

 

Ashikhmin-Premoze GSF

Ashikhmin-Premoze GSF는 Ashikhmin-Shirley 방식과 달리 등방성 NDF와 함께 사용하도록 설계되었습니다. Ashikhmin-Shirley와 마찬가지로, 이것은 매우 미묘한 GSF입니다.

float AshikhminPremozeGeometricShadowingFunction (float NdotL, float NdotV)
{
	float Gs = NdotL*NdotV/(NdotL+NdotV - NdotL*NdotV);
	return  (Gs);
}
GeometricShadow *= AshikhminPremozeGeometricShadowingFunction (NdotL, NdotV);

 

Duer GSF

Duer는 아래에서 다룰 Ward GSF 함수에서 발견 된 specularity와 관련된 문제를 해결하기 위해 아래의 GSF 함수를 제안했습니다. Duer GSF는 위의 Ashikhmin-Shirley와 비슷한 결과를 나타내지 만 등방성 BRDF 또는 매우 약간 이방성 인 BRDF에 더 적합합니다.

float DuerGeometricShadowingFunction (float3 lightDirection,float3 viewDirection, 
float3 normalDirection,float NdotL, float NdotV)
{
    float3 LpV = lightDirection + viewDirection;
    float Gs = dot(LpV,LpV) * pow(dot(LpV,normalDirection),-4);
    return  (Gs);
}
GeometricShadow *= DuerGeometricShadowingFunction (lightDirection, viewDirection, normalDirection, NdotL, NdotV);

 

Neumann GSF

Kelemen GSF는 적절한 에너지 절약 GSF를 제공합니다. 이전 모델의 대부분과 달리 기하 그림자의 비율은 일정하지 않지만 시야각에 따라 다릅니다. 이것은 Cook-Torrance 기하학적 음영 함수의 극단적 인 근사입니다.

float KelemenGeometricShadowingFunction (float NdotL, float NdotV, float LdotV, float VdotH)
{
	float Gs = (NdotL*NdotV)/(VdotH * VdotH); 
	return   (Gs);
}
GeometricShadow *= KelemenGeometricShadowingFunction (NdotL, NdotV, LdotV,  VdotH);

 

 

Modified-Kelemen GSF

이것은 Kelemen Approximation of Cook-Torrance의 변형 된 형태입니다. 거칠게 분산 된 Kelemen GSF를 생산하도록 수정되었습니다.

float ModifiedKelemenGeometricShadowingFunction (float NdotV, float NdotL, float roughness)
{
	float c = 0.797884560802865;    // c = sqrt(2 / Pi)
	float k = roughness * roughness * c;
	float gH = NdotV  * k +(1-k);
	return (gH * gH * NdotL);
}
GeometricShadow *=  ModifiedKelemenGeometricShadowingFunction (NdotV, NdotL, roughness );

 

 

Cook-Torrance GSF

float CookTorrenceGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float NdotH)
{
	float Gs = min(1.0, min(2*NdotH*NdotV / VdotH, 2*NdotH*NdotL / VdotH));
	return  (Gs);
}
GeometricShadow *= CookTorrenceGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH);

 

 

Ward GSF

Ward GSF는 강화 된 암시적 GSF입니다. Ward는이 방법을 사용하여 정규 분포 함수를 강화합니다. 그것은 다양한 각도에서 표면의 이방성 밴드를 강조하는 데 특히 효과적입니다.

float WardGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float NdotH)
{
	float Gs = pow( NdotL * NdotV, 0.5);
	return  (Gs);
}
GeometricShadow *= WardGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH);

 

 

Smith-Based Geometric Shadowing Functions

$$ GSF(l,v,h) = GSF(L) * GSF(V) $$

스미스 기반 GSF는 다른 GSF보다 더 정확하다고 널리 인식되며 정규 분포의 거칠기와 모양을 고려합니다. 이 함수는 GSF를 계산하기 위해 두 조각을 처리해야합니다.

 

 

Walter et all. GSF

GGX GSF의 일반적인 형태 인 Walter et al. NDF와 함께 사용할 이 함수를 만들었습니다. Walter et al. GSF는 "지표각 근처 나 거친 표면을 제외하고는 BSDF (양방향 산란 분포 함수)의 형상에 비교적 작은 영향을 미치지만 에너지 보존을 유지하는 데 필요하다"고 느꼈다. 이를 염두에두고 GSF의 원동력으로 Roughness를 사용하여 이 원칙을 존중하는 GSF를 만들었습니다.

float WalterEtAlGeometricShadowingFunction (float NdotL, float NdotV, float alpha)
{
    float alphaSqr = alpha*alpha;
    float NdotLSqr = NdotL*NdotL;
    float NdotVSqr = NdotV*NdotV;
    
    float SmithL = 2/(1 + sqrt(1 + alphaSqr * (1-NdotLSqr)/(NdotLSqr)));
    float SmithV = 2/(1 + sqrt(1 + alphaSqr * (1-NdotVSqr)/(NdotVSqr)));

	float Gs =  (SmithL * SmithV);
	return Gs;
}
GeometricShadow *= WalterEtAlGeometricShadowingFunction (NdotL, NdotV, roughness);

 

 

Smith-Beckman GSF

원래 Beckman NDF와 함께 사용하도록 제작 된 Walter et al. Phong NDF와 함께 사용하기에 적합한 GSF라고 추측했다.

float BeckmanGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
    float roughnessSqr = roughness*roughness;
    float NdotLSqr = NdotL*NdotL;
    float NdotVSqr = NdotV*NdotV;

    float calulationL = (NdotL)/(roughnessSqr * sqrt(1- NdotLSqr));
    float calulationV = (NdotV)/(roughnessSqr * sqrt(1- NdotVSqr));

    float SmithL = calulationL < 1.6 ? (((3.535 * calulationL) + (2.181 * calulationL * calulationL))/(1 + (2.276 * calulationL) + (2.577 * calulationL * calulationL))) : 1.0;
    float SmithV = calulationV < 1.6 ? (((3.535 * calulationV) + (2.181 * calulationV * calulationV))/(1 + (2.276 * calulationV) + (2.577 * calulationV * calulationV))) : 1.0;

	float Gs =  (SmithL * SmithV);
	return Gs;
}
GeometricShadow *= BeckmanGeometricShadowingFunction (NdotL, NdotV, roughness);

 

 

Schlick GSF

Schlick은 다른 Smith GSF에도 적용 할 수있는 Smith GSF의 근사치를 몇 개 만들었습니다. 이것은 Smith GSF의 Schlick 근사값입니다.

 

float SchlickGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
    float roughnessSqr = roughness*roughness;
	float SmithL = (NdotL)/(NdotL * (1-roughnessSqr) + roughnessSqr);
	float SmithV = (NdotV)/(NdotV * (1-roughnessSqr) + roughnessSqr);
	return (SmithL * SmithV);
GeometricShadow *= SchlickGeometricShadowingFunction (NdotL, NdotV, roughness);

 

Schlick-Beckman GSF

이것은 Beckman Function의 Schlick 근사입니다. 이것은 조도에 2 / PI의 제곱근을 곱하여 작동합니다. 대신에 우리는 0.797884 ..... 사용.

float SchlickBeckmanGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
    float roughnessSqr = roughness*roughness;
    float k = roughnessSqr * 0.797884560802865;

    float SmithL = (NdotL)/ (NdotL * (1- k) + k);
    float SmithV = (NdotV)/ (NdotV * (1- k) + k);

	float Gs =  (SmithL * SmithV);
	return Gs;
}
GeometricShadow *= SchlickBeckmanGeometricShadowingFunction (NdotL, NdotV, roughness);

 

 

Schlick-GGX GSF

GGX의 Schlick Approximation은 우리의 조도 값을 단순히 2로 나눕니다.

float SchlickGGXGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
    float k = roughness / 2;

    float SmithL = (NdotL)/ (NdotL * (1- k) + k);
    float SmithV = (NdotV)/ (NdotV * (1- k) + k);

	float Gs =  (SmithL * SmithV);
	return Gs;
}
GeometricShadow *= SchlickGGXGeometricShadowingFunction (NdotL, NdotV, roughness);

 

 

PBR 쉐이더를 함께 사용하기 Pt.3 : 프레넬 기능.

프레넬 효과는 프랑스의 물리학자인 Augustin-Jean Fresnel의 이름을 따서지었습니다.

이 효과는 표면에서의 반사 강도가 시점에 따라 달라진다는 것을 나타냅니다.

반사 각도는 지표각도로 볼 때 증가합니다. Fresnel 효과를 우리의 쉐이더에 포함시키기 위해서는 여러 곳에서 사용해야 합니다.

먼저 우리는 확산 복고 반사를 고려해야 하며 그런 다음 BRDF 프레넬 효과를 고려해야 합니다.

Fresnel을 적절하게 계산하기 위해서는 수직 입사각과 지표각을 고려해야 합니다. 아래의 거칠기를 사용하여 Fresnel기능에 전달할 수 있는 확산 Fresnel 반사 발생률을 계산합니다. 이를 계산하기 위해 Schlick 근사값 인 Fresnel을 사용합니다. Fresnel의 Schlick 근사는 다음과 같이 구성됩니다.

schlick = x + (1-x) * pow(1-dotProduct,5);

이 함수는 다음과 같이 더 근사 될 수 있습니다.

mix(x,1,pow(1-dotProduct,5));

이 근사값은 일부 GPU에서 더 빠를 수 있습니다. 위의 x와 1을 전환하여 근사값을 반대로 할 수 있습니다. 여기에서 우리는 Diffuse를 계산하기 위해 아래에서 할 것입니다.

float MixFunction(float i, float j, float x) 
{
	 return  j * x + i * (1.0 - x);
}

float SchlickFresnel(float i)
{
    float x = clamp(1.0-i, 0.0, 1.0);
    float x2 = x*x;
    return x2*x2*x;
}

//normal incidence reflection calculation
float F0 (float NdotL, float NdotV, float LdotH, float roughness)
{
    float FresnelLight = SchlickFresnel(NdotL); 
    float FresnelView = SchlickFresnel(NdotV);
    float FresnelDiffuse90 = 0.5 + 2.0 * LdotH*LdotH * roughness;
    return  MixFunction(1, FresnelDiffuse90, FresnelLight) * MixFunction(1, FresnelDiffuse90, FresnelView);
}

 

Schlick Fresnel

프레넬 방정식에 대한 Schlick의 근사는 그의 가장 유명한 근사 중 하나 일 수 있습니다. 프레넬 효과의 근사는 지표각에서 반사율을 계산할 수 있게 해줍니다.

float3 SchlickFresnelFunction(float3 SpecularColor,float LdotH)
{
    return SpecularColor + (1 - SpecularColor)* SchlickFresnel(LdotH);
}
FresnelFunction *=  SchlickFresnelFunction(specColor, LdotH);

이 다음 알고리즘은 반사 색상 대신 전달되는 특정 값을 사용합니다. 이 새로운 값은 굴절 지수입니다. IOR은 빛이 표면을 통과하는 속도를 나타내는 데 사용되는 무 차원 숫자입니다. 이 기능을 사용하려면 셰이더에 새 속성 및 변수를 추가해야 합니다.

_Ior("Ior",  Range(1,4)) = 1.5
float SchlickIORFresnelFunction(float ior ,float LdotH)
{
    float f0 = pow(ior-1,2)/pow(ior+1, 2);
    return f0 + (1-f0) * SchlickFresnel(LdotH);
}
FresnelFunction *=  SchlickIORFresnelFunction(_Ior, LdotH);

 

Spherical-Gaussian Fresnel

Spherical-Gaussian Fresnel 함수는 Schlick 's Approximation과 매우 유사한 결과를 산출합니다.

유일한 차이점은 Spherical Gaussian 계산에서 파생 된 것입니다.

float SphericalGaussianFresnelFunction(float LdotH,float SpecularColor)
{	
  float power = ((-5.55473 * LdotH) - 6.98316) * LdotH;
  return SpecularColor + (1 - SpecularColor) * pow(2,power);
}
FresnelFunction *= SphericalGaussianFresnelFunction(LdotH, specColor);

 

 

PBR Shader를 함께하기 Pt.4 : 함께 가져 오기

알고리즘 결합하기.

이제 NDF, GSF 및 Fresnel Function의 여러 버전이 있으므로이를 결합하여 결과 BRDF PBR Shader를 실제로 볼 수 있습니다. 이러한 알고리즘을 결합하는 형식은 매우 간단합니다. 알고리즘을 서로 곱한 다음 4 * NdotL * NdotV로 나눕니다.

float3 specularity = (SpecularDistribution * FresnelFunction * GeometricShadow) / (4 * (  NdotL * NdotV));

알고리즘을 결합한 후에는 확산 된 색상에 그 값을 간단하게 추가 할 수 있습니다. PBR 실행.

float3 lightingModel = (diffuseColor + specularity);
lightingModel *= NdotL;
float4 finalDiffuse = float4(lightingModel * attenColor,1);
return finalDiffuse;

 

 

Unity 조명 정보에 추가하기.

환경을 샘플링하려면 Unity와 쉐이더에서 몇 가지 중요한 단계를 수행해야합니다. 먼저, Reflection Probe 을 장면에 추가하고 구워냅니다. 그런 다음이 함수를 셰이더에 추가해야합니다.

 

UnityGI GetUnityGI(float3 lightColor, float3 lightDirection, float3 normalDirection,float3 viewDirection, float3 viewReflectDirection, float attenuation, float roughness, float3 worldPos)
{
 //Unity light Setup ::
    UnityLight light;
    light.color = lightColor;
    light.dir = lightDirection;
    light.ndotl = max(0.0h,dot( normalDirection, lightDirection));
    UnityGIInput d;
    d.light = light;
    d.worldPos = worldPos;
    d.worldViewDir = viewDirection;
    d.atten = attenuation;
    d.ambient = 0.0h;
    d.boxMax[0] = unity_SpecCube0_BoxMax;
    d.boxMin[0] = unity_SpecCube0_BoxMin;
    d.probePosition[0] = unity_SpecCube0_ProbePosition;
    d.probeHDR[0] = unity_SpecCube0_HDR;
    d.boxMax[1] = unity_SpecCube1_BoxMax;
    d.boxMin[1] = unity_SpecCube1_BoxMin;
    d.probePosition[1] = unity_SpecCube1_ProbePosition;
    d.probeHDR[1] = unity_SpecCube1_HDR;
    Unity_GlossyEnvironmentData ugls_en_data;
    ugls_en_data.roughness = roughness;
    ugls_en_data.reflUVW = viewReflectDirection;
    UnityGI gi = UnityGlobalIllumination(d, 1.0h, normalDirection, ugls_en_data );
    return gi;
}

프래그먼트 프로그램 내부에서이 함수를 호출하면 환경 샘플링에 사용할 수있는 적절한 데이터가 생성됩니다.

UnityGI gi =  GetUnityGI(_LightColor0.rgb, lightDirection, normalDirection, viewDirection, viewReflectDirection, attenuation, 1- _Glossiness, i.posWorld.xyz);
    float3 indirectDiffuse = gi.indirect.diffuse.rgb ;
    float3 indirectSpecular = gi.indirect.specular.rgb;

이 값들을 사용하기 위해서, 우리는 우리가 이미 우리의 쉐이더에 놓았던 한 줄 또는 두 줄을 대체하여 아래에 보이는 것처럼 최종 결과물에 그것들을 추가하기를 원할 것입니다. 이렇게하면 환경 데이터를 볼 수 있는 방식으로 속성을 설정할 때 환경을 샘플링 할 수 있습니다.

float grazingTerm = saturate(roughness + _Metallic);
float3 unityIndirectSpecularity =  indirectSpecular * FresnelLerp(specColor,grazingTerm,NdotV) * max(0.15,_Metallic) * (1-roughness*roughness* roughness);

float3 lightingModel = (diffuseColor + specularity 
+ (unityIndirectSpecularity *_UnityLightingContribution));

 

 

알고리즘 디버깅하기

우리의 알고리즘을 디버깅하기 위해 우리는 셰이더 내에서 재료 속성에 토글을 추가하는 Unity의 기능을 활용할 수 있습니다.

[Toggle] _ENABLE_NDF ("Normal Distribution Enabled?", Float) = 0
[Toggle] _ENABLE_G ("Geometric Shadow Enabled?", Float) = 0
[Toggle] _ENABLE_F ("Fresnel Enabled?", Float) = 0
[Toggle] _ENABLE_D ("Diffuse Enabled?", Float) = 0

이 토글은 셰이더 키워드를 활성화하거나 비활성화합니다.

#ifdef _ENABLE_NDF_ON
 	 return float4(float3(1,1,1)* SpecularDistribution,1);
#endif
#ifdef _ENABLE_G_ON 
 	 return float4(float3(1,1,1) * GeometricShadow,1) ;
#endif
#ifdef _ENABLE_F_ON 
 	 return float4(float3(1,1,1)* FresnelFunction,1);
#endif
#ifdef _ENABLE_D_ON 
 	 return float4(float3(1,1,1)* diffuseColor,1);
#endif

이 설정을 사용하면 셰이더의 여러 효과를 서로 전환하여 효과를 디버깅 할 수 있습니다.

 

 

알고리즘을 선택할 수있는 셰이더 만들기.

Keyword Enums의 강력한 기능을 사용하면 주석 처리 된 모든 코드 조작없이 알고리즘 사이를 쉽게 전환 할 수 있습니다. 키워드 열거에는 9 개의 키워드 제한이 있으므로 전환 구조를 수립 할 때 주의 해야 합니다.

[KeywordEnum(BlinnPhong,Phong,Beckmann,Gaussian,GGX,TrowbridgeReitz,TrowbridgeReitzAnisotropic, Ward)] _NormalDistModel("Normal Distribution Model;", Float) = 0
[KeywordEnum(AshikhminShirley,AshikhminPremoze,Duer,Neumann,Kelemen,ModifiedKelemen,Cook,Ward,Kurt)]_GeoShadowModel("Geometric Shadow Model;", Float) = 0

[KeywordEnum(None,Walter,Beckman,GGX,Schlick,SchlickBeckman,SchlickGGX, Implicit)]_SmithGeoShadowModel("Smith Geometric Shadow Model; None if above is Used;", Float) = 0

[KeywordEnum(Schlick,SchlickIOR, SphericalGaussian)]_FresnelModel("Normal Distribution Model;", Float) = 0
//Normal Distribution Function/Specular Distribution-----------------------------------------------------	      

           
	#ifdef _NORMALDISTMODEL_BLINNPHONG 
		 SpecularDistribution *=  BlinnPhongNormalDistribution(NdotH, _Glossiness,  max(1,_Glossiness * 40));
 	#elif _NORMALDISTMODEL_PHONG
		 SpecularDistribution *=  PhongNormalDistribution(RdotV, _Glossiness, max(1,_Glossiness * 40));
 	#elif _NORMALDISTMODEL_BECKMANN
		 SpecularDistribution *=  BeckmannNormalDistribution(roughness, NdotH);
 	#elif _NORMALDISTMODEL_GAUSSIAN
		 SpecularDistribution *=  GaussianNormalDistribution(roughness, NdotH);
 	#elif _NORMALDISTMODEL_GGX
		 SpecularDistribution *=  GGXNormalDistribution(roughness, NdotH);
 	#elif _NORMALDISTMODEL_TROWBRIDGEREITZ
		 SpecularDistribution *=  TrowbridgeReitzNormalDistribution(NdotH, roughness);
 	#elif _NORMALDISTMODEL_TROWBRIDGEREITZANISOTROPIC
		 SpecularDistribution *=  TrowbridgeReitzAnisotropicNormalDistribution(_Anisotropic,NdotH, dot(halfDirection, i.tangentDir), dot(halfDirection,  i.bitangentDir));
	#elif _NORMALDISTMODEL_WARD
	 	 SpecularDistribution *=  WardAnisotropicNormalDistribution(_Anisotropic,NdotL, NdotV, NdotH, dot(halfDirection, i.tangentDir), dot(halfDirection,  i.bitangentDir));
	#else
		SpecularDistribution *=  GGXNormalDistribution(roughness, NdotH);
	#endif


	 //Geometric Shadowing term----------------------------------------------------------------------------------
	#ifdef _SMITHGEOSHADOWMODEL_NONE
	 	#ifdef _GEOSHADOWMODEL_ASHIKHMINSHIRLEY
			GeometricShadow *= AshikhminShirleyGeometricShadowingFunction (NdotL, NdotV, LdotH);
	 	#elif _GEOSHADOWMODEL_ASHIKHMINPREMOZE
			GeometricShadow *= AshikhminPremozeGeometricShadowingFunction (NdotL, NdotV);
	 	#elif _GEOSHADOWMODEL_DUER
			GeometricShadow *= DuerGeometricShadowingFunction (lightDirection, viewDirection, normalDirection, NdotL, NdotV);
	 	#elif _GEOSHADOWMODEL_NEUMANN
			GeometricShadow *= NeumannGeometricShadowingFunction (NdotL, NdotV);
	 	#elif _GEOSHADOWMODEL_KELEMAN
			GeometricShadow *= KelemenGeometricShadowingFunction (NdotL, NdotV, LdotH,  VdotH);
	 	#elif _GEOSHADOWMODEL_MODIFIEDKELEMEN
			GeometricShadow *=  ModifiedKelemenGeometricShadowingFunction (NdotV, NdotL, roughness);
	 	#elif _GEOSHADOWMODEL_COOK
			GeometricShadow *= CookTorrenceGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH);
	 	#elif _GEOSHADOWMODEL_WARD
			GeometricShadow *= WardGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH);
	 	#elif _GEOSHADOWMODEL_KURT
			GeometricShadow *= KurtGeometricShadowingFunction (NdotL, NdotV, VdotH, roughness);
	 	#else 			
 			GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV);
 		#endif
	////SmithModelsBelow
	////Gs = F(NdotL) * F(NdotV);
  	#elif _SMITHGEOSHADOWMODEL_WALTER
		GeometricShadow *= WalterEtAlGeometricShadowingFunction (NdotL, NdotV, roughness);
	#elif _SMITHGEOSHADOWMODEL_BECKMAN
		GeometricShadow *= BeckmanGeometricShadowingFunction (NdotL, NdotV, roughness);
 	#elif _SMITHGEOSHADOWMODEL_GGX
		GeometricShadow *= GGXGeometricShadowingFunction (NdotL, NdotV, roughness);
	#elif _SMITHGEOSHADOWMODEL_SCHLICK
		GeometricShadow *= SchlickGeometricShadowingFunction (NdotL, NdotV, roughness);
 	#elif _SMITHGEOSHADOWMODEL_SCHLICKBECKMAN
		GeometricShadow *= SchlickBeckmanGeometricShadowingFunction (NdotL, NdotV, roughness);
 	#elif _SMITHGEOSHADOWMODEL_SCHLICKGGX
		GeometricShadow *= SchlickGGXGeometricShadowingFunction (NdotL, NdotV, roughness);
	#elif _SMITHGEOSHADOWMODEL_IMPLICIT
		GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV);
	#else
		GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV);
 	#endif
	 //Fresnel Function-------------------------------------------------------------------------------------------------

	#ifdef _FRESNELMODEL_SCHLICK
		FresnelFunction *=  SchlickFresnelFunction(specColor, LdotH);
	#elif _FRESNELMODEL_SCHLICKIOR
		FresnelFunction *=  SchlickIORFresnelFunction(_Ior, LdotH);
	#elif _FRESNELMODEL_SPHERICALGAUSSIAN
		FresnelFunction *= SphericalGaussianFresnelFunction(LdotH, specColor);
 	#else
		FresnelFunction *=  SchlickIORFresnelFunction(_Ior, LdotH);	
 	#endif

이제 Unity의 드롭 다운에서 키워드를 선택하면 활성 알고리즘이 변경됩니다. 이것으로 쉐이더는 잘되어야합니다. 연습용 코드가 지저분한 경우에 대비하여 완전한 쉐이더를 완벽하게 볼 수 있도록 쉐이더 코드를 아래에 준비 했다.

 

 

The Complete Shader

Shader "Physically-Based-Lighting" 
{
    Properties 
    {
	_Color ("Main Color", Color) = (1,1,1,1)
	_SpecularColor ("Specular Color", Color) = (1,1,1,1)
	_SpecularPower("Specular Power", Range(0,1)) = 1
	_SpecularRange("Specular Gloss",  Range(1,40)) = 0
	_Glossiness("Smoothness",Range(0,1)) = 1
	_Metallic("Metallicness",Range(0,1)) = 0
	_Anisotropic("Anisotropic",  Range(-20,1)) = 0
	_Ior("Ior",  Range(1,4)) = 1.5
	_UnityLightingContribution("Unity Reflection Contribution", Range(0,1)) = 1
	[KeywordEnum(BlinnPhong,Phong,Beckmann,Gaussian,GGX,TrowbridgeReitz,TrowbridgeReitzAnisotropic, Ward)] _NormalDistModel("Normal Distribution Model;", Float) = 0
	[KeywordEnum(AshikhminShirley,AshikhminPremoze,Duer,Neumann,Kelemen,ModifiedKelemen,Cook,Ward,Kurt)]_GeoShadowModel("Geometric Shadow Model;", Float) = 0
	[KeywordEnum(None,Walter,Beckman,GGX,Schlick,SchlickBeckman,SchlickGGX, Implicit)]_SmithGeoShadowModel("Smith Geometric Shadow Model; None if above is Used;", Float) = 0
	[KeywordEnum(Schlick,SchlickIOR, SphericalGaussian)]_FresnelModel("Normal Distribution Model;", Float) = 0
	[Toggle] _ENABLE_NDF ("Normal Distribution Enabled?", Float) = 0
	[Toggle] _ENABLE_G ("Geometric Shadow Enabled?", Float) = 0
	[Toggle] _ENABLE_F ("Fresnel Enabled?", Float) = 0
	[Toggle] _ENABLE_D ("Diffuse Enabled?", Float) = 0
    }
    SubShader {
	Tags {
            "RenderType"="Opaque"  "Queue"="Geometry"
        } 
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDBASE
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma multi_compile _NORMALDISTMODEL_BLINNPHONG _NORMALDISTMODEL_PHONG _NORMALDISTMODEL_BECKMANN _NORMALDISTMODEL_GAUSSIAN _NORMALDISTMODEL_GGX _NORMALDISTMODEL_TROWBRIDGEREITZ _NORMALDISTMODEL_TROWBRIDGEREITZANISOTROPIC _NORMALDISTMODEL_WARD
            #pragma multi_compile _GEOSHADOWMODEL_ASHIKHMINSHIRLEY _GEOSHADOWMODEL_ASHIKHMINPREMOZE _GEOSHADOWMODEL_DUER_GEOSHADOWMODEL_NEUMANN _GEOSHADOWMODEL_KELEMAN _GEOSHADOWMODEL_MODIFIEDKELEMEN _GEOSHADOWMODEL_COOK _GEOSHADOWMODEL_WARD _GEOSHADOWMODEL_KURT 
            #pragma multi_compile _SMITHGEOSHADOWMODEL_NONE _SMITHGEOSHADOWMODEL_WALTER _SMITHGEOSHADOWMODEL_BECKMAN _SMITHGEOSHADOWMODEL_GGX _SMITHGEOSHADOWMODEL_SCHLICK _SMITHGEOSHADOWMODEL_SCHLICKBECKMAN _SMITHGEOSHADOWMODEL_SCHLICKGGX _SMITHGEOSHADOWMODEL_IMPLICIT
            #pragma multi_compile _FRESNELMODEL_SCHLICK _FRESNELMODEL_SCHLICKIOR _FRESNELMODEL_SPHERICALGAUSSIAN
            #pragma multi_compile  _ENABLE_NDF_OFF _ENABLE_NDF_ON
            #pragma multi_compile  _ENABLE_G_OFF _ENABLE_G_ON
            #pragma multi_compile  _ENABLE_F_OFF _ENABLE_F_ON
            #pragma multi_compile  _ENABLE_D_OFF _ENABLE_D_ON
            #pragma target 3.0
            
float4 _Color;
float4 _SpecularColor;
float _SpecularPower;
float _SpecularRange;
float _Glossiness;
float _Metallic;
float _Anisotropic;
float _Ior;
float _NormalDistModel;
float _GeoShadowModel;
float _FresnelModel;
float _UnityLightingContribution;

struct VertexInput 
{
    float4 vertex : POSITION;       //local vertex position
    float3 normal : NORMAL;         //normal direction
    float4 tangent : TANGENT;       //tangent direction    
    float2 texcoord0 : TEXCOORD0;   //uv coordinates
    float2 texcoord1 : TEXCOORD1;   //lightmap uv coordinates
};

struct VertexOutput 
{
    float4 pos : SV_POSITION;              //screen clip space position and depth
    float2 uv0 : TEXCOORD0;                //uv coordinates
    float2 uv1 : TEXCOORD1;                //lightmap uv coordinates

//below we create our own variables with the texcoord semantic. 
    float3 normalDir : TEXCOORD3;          //normal direction   
    float3 posWorld : TEXCOORD4;          //normal direction   
    float3 tangentDir : TEXCOORD5;
    float3 bitangentDir : TEXCOORD6;
    LIGHTING_COORDS(7,8)                   //this initializes the unity lighting and shadow
    UNITY_FOG_COORDS(9)                    //this initializes the unity fog
};

VertexOutput vert (VertexInput v) 
{
     VertexOutput o = (VertexOutput)0;           
     o.uv0 = v.texcoord0;
     o.uv1 = v.texcoord1;
     o.normalDir = UnityObjectToWorldNormal(v.normal);
     o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz );
     o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
     o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
     o.posWorld = mul(_Object2World, v.vertex);
     UNITY_TRANSFER_FOG(o,o.pos);
     TRANSFER_VERTEX_TO_FRAGMENT(o)
     return o;
}

UnityGI GetUnityGI(float3 lightColor, float3 lightDirection, float3 normalDirection,float3 viewDirection, float3 viewReflectDirection, float attenuation, float roughness, float3 worldPos)
{
 //Unity light Setup ::
    UnityLight light;
    light.color = lightColor;
    light.dir = lightDirection;
    light.ndotl = max(0.0h,dot( normalDirection, lightDirection));
    UnityGIInput d;
    d.light = light;
    d.worldPos = worldPos;
    d.worldViewDir = viewDirection;
    d.atten = attenuation;
    d.ambient = 0.0h;
    d.boxMax[0] = unity_SpecCube0_BoxMax;
    d.boxMin[0] = unity_SpecCube0_BoxMin;
    d.probePosition[0] = unity_SpecCube0_ProbePosition;
    d.probeHDR[0] = unity_SpecCube0_HDR;
    d.boxMax[1] = unity_SpecCube1_BoxMax;
    d.boxMin[1] = unity_SpecCube1_BoxMin;
    d.probePosition[1] = unity_SpecCube1_ProbePosition;
    d.probeHDR[1] = unity_SpecCube1_HDR;
    Unity_GlossyEnvironmentData ugls_en_data;
    ugls_en_data.roughness = roughness;
    ugls_en_data.reflUVW = viewReflectDirection;
    UnityGI gi = UnityGlobalIllumination(d, 1.0h, normalDirection, ugls_en_data );
    return gi;
}


//---------------------------
//helper functions
float MixFunction(float i, float j, float x){
	 return  j * x + i * (1.0 - x);
}
 
float2 MixFunction(float2 i, float2 j, float x){
	 return  j * x + i * (1.0h - x);
}
   
float3 MixFunction(float3 i, float3 j, float x){
	 return  j * x + i * (1.0h - x);
}   

float MixFunction(float4 i, float4 j, float x){
	 return  j * x + i * (1.0h - x);
} 

float sqr(float x){ return x*x; }
//------------------------------


//------------------------------------------------
//schlick functions
float SchlickFresnel(float i){
    float x = clamp(1.0-i, 0.0, 1.0);
    float x2 = x*x;
    return x2*x2*x;
}
float3 FresnelLerp (float3 x, float3 y, float d)
{
	float t = SchlickFresnel(d);	
	return lerp (x, y, t);
}

float3 SchlickFresnelFunction(float3 SpecularColor,float LdotH){
    return SpecularColor + (1 - SpecularColor)* SchlickFresnel(LdotH);
}

float SchlickIORFresnelFunction(float ior,float LdotH){
    float f0 = pow((ior-1)/(ior+1),2);
    return f0 +  (1 - f0) * SchlickFresnel(LdotH);
}
float SphericalGaussianFresnelFunction(float LdotH,float SpecularColor)
{	
	float power = ((-5.55473 * LdotH) - 6.98316) * LdotH;
    return SpecularColor + (1 - SpecularColor)  * pow(2,power);
}

//-----------------------------------------------



//-----------------------------------------------
//normal incidence reflection calculation
float F0 (float NdotL, float NdotV, float LdotH, float roughness){
// Diffuse fresnel
    float FresnelLight = SchlickFresnel(NdotL); 
    float FresnelView = SchlickFresnel(NdotV);
    float FresnelDiffuse90 = 0.5 + 2.0 * LdotH*LdotH * roughness;
   return  MixFunction(1, FresnelDiffuse90, FresnelLight) * MixFunction(1, FresnelDiffuse90, FresnelView);
}

//-----------------------------------------------




//-----------------------------------------------
//Normal Distribution Functions

float BlinnPhongNormalDistribution(float NdotH, float specularpower, float speculargloss){
    float Distribution = pow(NdotH,speculargloss) * specularpower;
    Distribution *= (2+specularpower) / (2*3.1415926535);
    return Distribution;
}
float PhongNormalDistribution(float RdotV, float specularpower, float speculargloss){
    float Distribution = pow(RdotV,speculargloss) * specularpower;
    Distribution *= (2+specularpower) / (2*3.1415926535);
    return Distribution;
}

float BeckmannNormalDistribution(float roughness, float NdotH)
{
    float roughnessSqr = roughness*roughness;
    float NdotHSqr = NdotH*NdotH;
    return max(0.000001,(1.0 / (3.1415926535*roughnessSqr*NdotHSqr*NdotHSqr))* exp((NdotHSqr-1)/(roughnessSqr*NdotHSqr)));
}

float GaussianNormalDistribution(float roughness, float NdotH)
{
    float roughnessSqr = roughness*roughness;
	float thetaH = acos(NdotH);
    return exp(-thetaH*thetaH/roughnessSqr);
}

float GGXNormalDistribution(float roughness, float NdotH)
{
    float roughnessSqr = roughness*roughness;
    float NdotHSqr = NdotH*NdotH;
    float TanNdotHSqr = (1-NdotHSqr)/NdotHSqr;
    return (1.0/3.1415926535) * sqr(roughness/(NdotHSqr * (roughnessSqr + TanNdotHSqr)));
//    float denom = NdotHSqr * (roughnessSqr-1)

}

float TrowbridgeReitzNormalDistribution(float NdotH, float roughness){
    float roughnessSqr = roughness*roughness;
    float Distribution = NdotH*NdotH * (roughnessSqr-1.0) + 1.0;
    return roughnessSqr / (3.1415926535 * Distribution*Distribution);
}

float TrowbridgeReitzAnisotropicNormalDistribution(float anisotropic, float NdotH, float HdotX, float HdotY){
	float aspect = sqrt(1.0h-anisotropic * 0.9h);
	float X = max(.001, sqr(1.0-_Glossiness)/aspect) * 5;
 	float Y = max(.001, sqr(1.0-_Glossiness)*aspect) * 5;
    return 1.0 / (3.1415926535 * X*Y * sqr(sqr(HdotX/X) + sqr(HdotY/Y) + NdotH*NdotH));
}

float WardAnisotropicNormalDistribution(float anisotropic, float NdotL, float NdotV, float NdotH, float HdotX, float HdotY){
    float aspect = sqrt(1.0h-anisotropic * 0.9h);
    float X = max(.001, sqr(1.0-_Glossiness)/aspect) * 5;
 	float Y = max(.001, sqr(1.0-_Glossiness)*aspect) * 5;
    float exponent = -(sqr(HdotX/X) + sqr(HdotY/Y)) / sqr(NdotH);
    float Distribution = 1.0 / ( 3.14159265 * X * Y * sqrt(NdotL * NdotV));
    Distribution *= exp(exponent);
    return Distribution;
}
//--------------------------



//-----------------------------------------------
//Geometric Shadowing Functions

float ImplicitGeometricShadowingFunction (float NdotL, float NdotV){
	float Gs =  (NdotL*NdotV);       
	return Gs;
}

float AshikhminShirleyGeometricShadowingFunction (float NdotL, float NdotV, float LdotH){
	float Gs = NdotL*NdotV/(LdotH*max(NdotL,NdotV));
	return  (Gs);
}

float AshikhminPremozeGeometricShadowingFunction (float NdotL, float NdotV){
	float Gs = NdotL*NdotV/(NdotL+NdotV - NdotL*NdotV);
	return  (Gs);
}

float DuerGeometricShadowingFunction (float3 lightDirection,float3 viewDirection, float3 normalDirection,float NdotL, float NdotV){
    float3 LpV = lightDirection + viewDirection;
    float Gs = dot(LpV,LpV) * pow(dot(LpV,normalDirection),-4);
    return  (Gs);
}

float NeumannGeometricShadowingFunction (float NdotL, float NdotV){
	float Gs = (NdotL*NdotV)/max(NdotL, NdotV);       
	return  (Gs);
}

float KelemenGeometricShadowingFunction (float NdotL, float NdotV, float LdotH, float VdotH){
//	float Gs = (NdotL*NdotV)/ (LdotH * LdotH);           //this
	float Gs = (NdotL*NdotV)/(VdotH * VdotH);       //or this?
	return   (Gs);
}

float ModifiedKelemenGeometricShadowingFunction (float NdotV, float NdotL, float roughness)
{
	float c = 0.797884560802865; // c = sqrt(2 / Pi)
	float k = roughness * roughness * c;
	float gH = NdotV  * k +(1-k);
	return (gH * gH * NdotL);
}

float CookTorrenceGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float NdotH){
	float Gs = min(1.0, min(2*NdotH*NdotV / VdotH, 2*NdotH*NdotL / VdotH));
	return  (Gs);
}

float WardGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float NdotH){
	float Gs = pow( NdotL * NdotV, 0.5);
	return  (Gs);
}

float KurtGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float alpha){
	float Gs =  (VdotH*pow(NdotL*NdotV, alpha))/ NdotL * NdotV;
	return  (Gs);
}

//SmithModelsBelow
//Gs = F(NdotL) * F(NdotV);

float WalterEtAlGeometricShadowingFunction (float NdotL, float NdotV, float alpha){
    float alphaSqr = alpha*alpha;
    float NdotLSqr = NdotL*NdotL;
    float NdotVSqr = NdotV*NdotV;
    float SmithL = 2/(1 + sqrt(1 + alphaSqr * (1-NdotLSqr)/(NdotLSqr)));
    float SmithV = 2/(1 + sqrt(1 + alphaSqr * (1-NdotVSqr)/(NdotVSqr)));
	float Gs =  (SmithL * SmithV);
	return Gs;
}

float BeckmanGeometricShadowingFunction (float NdotL, float NdotV, float roughness){
    float roughnessSqr = roughness*roughness;
    float NdotLSqr = NdotL*NdotL;
    float NdotVSqr = NdotV*NdotV;
    float calulationL = (NdotL)/(roughnessSqr * sqrt(1- NdotLSqr));
    float calulationV = (NdotV)/(roughnessSqr * sqrt(1- NdotVSqr));
    float SmithL = calulationL < 1.6 ? (((3.535 * calulationL) + (2.181 * calulationL * calulationL))/(1 + (2.276 * calulationL) + (2.577 * calulationL * calulationL))) : 1.0;
    float SmithV = calulationV < 1.6 ? (((3.535 * calulationV) + (2.181 * calulationV * calulationV))/(1 + (2.276 * calulationV) + (2.577 * calulationV * calulationV))) : 1.0;
	float Gs =  (SmithL * SmithV);
	return Gs;
}

float GGXGeometricShadowingFunction (float NdotL, float NdotV, float roughness){
    float roughnessSqr = roughness*roughness;
    float NdotLSqr = NdotL*NdotL;
    float NdotVSqr = NdotV*NdotV;
    float SmithL = (2 * NdotL)/ (NdotL + sqrt(roughnessSqr + ( 1-roughnessSqr) * NdotLSqr));
    float SmithV = (2 * NdotV)/ (NdotV + sqrt(roughnessSqr + ( 1-roughnessSqr) * NdotVSqr));
	float Gs =  (SmithL * SmithV) ;
	return Gs;
}

float SchlickGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
    float roughnessSqr = roughness*roughness;
	float SmithL = (NdotL)/(NdotL * (1-roughnessSqr) + roughnessSqr);
	float SmithV = (NdotV)/(NdotV * (1-roughnessSqr) + roughnessSqr);
	return (SmithL * SmithV); 
}


float SchlickBeckmanGeometricShadowingFunction (float NdotL, float NdotV, float roughness){
    float roughnessSqr = roughness*roughness;
    float k = roughnessSqr * 0.797884560802865;
    float SmithL = (NdotL)/ (NdotL * (1- k) + k);
    float SmithV = (NdotV)/ (NdotV * (1- k) + k);
	float Gs =  (SmithL * SmithV);
	return Gs;
}

float SchlickGGXGeometricShadowingFunction (float NdotL, float NdotV, float roughness){
    float k = roughness / 2;
    float SmithL = (NdotL)/ (NdotL * (1- k) + k);
    float SmithV = (NdotV)/ (NdotV * (1- k) + k);
	float Gs =  (SmithL * SmithV);
	return Gs;
}

//--------------------------


float4 frag(VertexOutput i) : COLOR {

//normal direction calculations
     float3 normalDirection = normalize(i.normalDir);
	 float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
     float shiftAmount = dot(i.normalDir, viewDirection);
	 normalDirection = shiftAmount < 0.0f ? normalDirection + viewDirection * (-shiftAmount + 1e-5f) : normalDirection;

//light calculations
	 float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w));
	 float3 lightReflectDirection = reflect( -lightDirection, normalDirection );
	 float3 viewReflectDirection = normalize(reflect( -viewDirection, normalDirection ));
     float NdotL = max(0.0, dot( normalDirection, lightDirection ));
     float3 halfDirection = normalize(viewDirection+lightDirection); 
     float NdotH =  max(0.0,dot( normalDirection, halfDirection));
     float NdotV =  max(0.0,dot( normalDirection, viewDirection));
     float VdotH = max(0.0,dot( viewDirection, halfDirection));
     float LdotH =  max(0.0,dot(lightDirection, halfDirection)); 
     float LdotV = max(0.0,dot(lightDirection, viewDirection)); 
     float RdotV = max(0.0, dot( lightReflectDirection, viewDirection ));
     float attenuation = LIGHT_ATTENUATION(i);
     float3 attenColor = attenuation * _LightColor0.rgb;
     
     //get Unity Scene lighting data
     UnityGI gi =  GetUnityGI(_LightColor0.rgb, lightDirection, normalDirection, viewDirection, viewReflectDirection, attenuation, 1- _Glossiness, i.posWorld.xyz);
     float3 indirectDiffuse = gi.indirect.diffuse.rgb ;
	 float3 indirectSpecular = gi.indirect.specular.rgb;

	 //diffuse color calculations
	 float roughness = 1-(_Glossiness * _Glossiness);
	 roughness = roughness * roughness;
     float3 diffuseColor = _Color.rgb * (1.0 - _Metallic) ;
 	 float f0 = F0(NdotL, NdotV, LdotH, roughness);
	 diffuseColor *= f0;
	 diffuseColor+=indirectDiffuse;
	 


	//Specular calculations


	 float3 specColor = lerp(_SpecularColor.rgb, _Color.rgb, _Metallic * 0.5);

	 float3 SpecularDistribution = specColor;
	 float GeometricShadow = 1;
	 float3 FresnelFunction = specColor;

	 //Normal Distribution Function/Specular Distribution-----------------------------------------------------	      

           
	#ifdef _NORMALDISTMODEL_BLINNPHONG 
		 SpecularDistribution *=  BlinnPhongNormalDistribution(NdotH, _Glossiness,  max(1,_Glossiness * 40));
 	#elif _NORMALDISTMODEL_PHONG
		 SpecularDistribution *=  PhongNormalDistribution(RdotV, _Glossiness, max(1,_Glossiness * 40));
 	#elif _NORMALDISTMODEL_BECKMANN
		 SpecularDistribution *=  BeckmannNormalDistribution(roughness, NdotH);
 	#elif _NORMALDISTMODEL_GAUSSIAN
		 SpecularDistribution *=  GaussianNormalDistribution(roughness, NdotH);
 	#elif _NORMALDISTMODEL_GGX
		 SpecularDistribution *=  GGXNormalDistribution(roughness, NdotH);
 	#elif _NORMALDISTMODEL_TROWBRIDGEREITZ
		 SpecularDistribution *=  TrowbridgeReitzNormalDistribution(NdotH, roughness);
 	#elif _NORMALDISTMODEL_TROWBRIDGEREITZANISOTROPIC
		 SpecularDistribution *=  TrowbridgeReitzAnisotropicNormalDistribution(_Anisotropic,NdotH, dot(halfDirection, i.tangentDir), dot(halfDirection,  i.bitangentDir));
	#elif _NORMALDISTMODEL_WARD
	 	 SpecularDistribution *=  WardAnisotropicNormalDistribution(_Anisotropic,NdotL, NdotV, NdotH, dot(halfDirection, i.tangentDir), dot(halfDirection,  i.bitangentDir));
	#else
		SpecularDistribution *=  GGXNormalDistribution(roughness, NdotH);
	#endif


	 //Geometric Shadowing term----------------------------------------------------------------------------------
	#ifdef _SMITHGEOSHADOWMODEL_NONE
	 	#ifdef _GEOSHADOWMODEL_ASHIKHMINSHIRLEY
			GeometricShadow *= AshikhminShirleyGeometricShadowingFunction (NdotL, NdotV, LdotH);
	 	#elif _GEOSHADOWMODEL_ASHIKHMINPREMOZE
			GeometricShadow *= AshikhminPremozeGeometricShadowingFunction (NdotL, NdotV);
	 	#elif _GEOSHADOWMODEL_DUER
			GeometricShadow *= DuerGeometricShadowingFunction (lightDirection, viewDirection, normalDirection, NdotL, NdotV);
	 	#elif _GEOSHADOWMODEL_NEUMANN
			GeometricShadow *= NeumannGeometricShadowingFunction (NdotL, NdotV);
	 	#elif _GEOSHADOWMODEL_KELEMAN
			GeometricShadow *= KelemenGeometricShadowingFunction (NdotL, NdotV, LdotH,  VdotH);
	 	#elif _GEOSHADOWMODEL_MODIFIEDKELEMEN
			GeometricShadow *=  ModifiedKelemenGeometricShadowingFunction (NdotV, NdotL, roughness);
	 	#elif _GEOSHADOWMODEL_COOK
			GeometricShadow *= CookTorrenceGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH);
	 	#elif _GEOSHADOWMODEL_WARD
			GeometricShadow *= WardGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH);
	 	#elif _GEOSHADOWMODEL_KURT
			GeometricShadow *= KurtGeometricShadowingFunction (NdotL, NdotV, VdotH, roughness);
	 	#else 			
 			GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV);
 		#endif
	////SmithModelsBelow
	////Gs = F(NdotL) * F(NdotV);
  	#elif _SMITHGEOSHADOWMODEL_WALTER
		GeometricShadow *= WalterEtAlGeometricShadowingFunction (NdotL, NdotV, roughness);
	#elif _SMITHGEOSHADOWMODEL_BECKMAN
		GeometricShadow *= BeckmanGeometricShadowingFunction (NdotL, NdotV, roughness);
 	#elif _SMITHGEOSHADOWMODEL_GGX
		GeometricShadow *= GGXGeometricShadowingFunction (NdotL, NdotV, roughness);
	#elif _SMITHGEOSHADOWMODEL_SCHLICK
		GeometricShadow *= SchlickGeometricShadowingFunction (NdotL, NdotV, roughness);
 	#elif _SMITHGEOSHADOWMODEL_SCHLICKBECKMAN
		GeometricShadow *= SchlickBeckmanGeometricShadowingFunction (NdotL, NdotV, roughness);
 	#elif _SMITHGEOSHADOWMODEL_SCHLICKGGX
		GeometricShadow *= SchlickGGXGeometricShadowingFunction (NdotL, NdotV, roughness);
	#elif _SMITHGEOSHADOWMODEL_IMPLICIT
		GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV);
	#else
		GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV);
 	#endif
	 //Fresnel Function-------------------------------------------------------------------------------------------------

	#ifdef _FRESNELMODEL_SCHLICK
		FresnelFunction *=  SchlickFresnelFunction(specColor, LdotH);
	#elif _FRESNELMODEL_SCHLICKIOR
		FresnelFunction *=  SchlickIORFresnelFunction(_Ior, LdotH);
	#elif _FRESNELMODEL_SPHERICALGAUSSIAN
		FresnelFunction *= SphericalGaussianFresnelFunction(LdotH, specColor);
 	#else
		FresnelFunction *=  SchlickIORFresnelFunction(_Ior, LdotH);	
 	#endif


 	#ifdef _ENABLE_NDF_ON
 	 return float4(float3(1,1,1)* SpecularDistribution,1);
    #endif
    #ifdef _ENABLE_G_ON 
 	 return float4(float3(1,1,1) * GeometricShadow,1) ;
    #endif
    #ifdef _ENABLE_F_ON 
 	 return float4(float3(1,1,1)* FresnelFunction,1);
    #endif
	#ifdef _ENABLE_D_ON 
 	 return float4(float3(1,1,1)* diffuseColor,1);
    #endif

	 //PBR
	 float3 specularity = (SpecularDistribution * FresnelFunction * GeometricShadow) / (4 * (  NdotL * NdotV));
     float grazingTerm = saturate(roughness + _Metallic);
	 float3 unityIndirectSpecularity =  indirectSpecular * FresnelLerp(specColor,grazingTerm,NdotV) * max(0.15,_Metallic) * (1-roughness*roughness* roughness);

     float3 lightingModel = ((diffuseColor) + specularity + (unityIndirectSpecularity *_UnityLightingContribution));
     lightingModel *= NdotL;
     float4 finalDiffuse = float4(lightingModel * attenColor,1);
     UNITY_APPLY_FOG(i.fogCoord, finalDiffuse);
     return finalDiffuse;
}
ENDCG
}
}
FallBack "Legacy Shaders/Diffuse"
}

 

역자 후기.
사실 이 문서의 목적이 BRDF 를 구성하는 여러가지 요소들의 또 다른 형식들에 대해서 살펴 보는 것인데요... 2023년이 된 시점에서 거의 표준화가 되었습니다. 개인적으로 관심이 있는 분들은 가볍게 살펴 볼만 하겠습니다. 또한 이 전에 시그라프 발표자료 중 일부에서 언급 되었던 Chan Diffuse 같은 GSF 처럼 앞으로 더 발표될 것이며 꾸준히 관심을 갖고 살펴봐야 할 것입니다.