기초 조명셰이더 난반사광





(게임속에서 직접광은 계산하지만 간접광은 수없이 

반사의 반사를 거치므로 표현하기 어렵다고 한다.)


간접광 : 반사되는 빛

직접광 : 광원으로 직접 받는 빛



난 반사광(Diffuse Light)


우리가 물체를 볼 수 있는 이유는 다른 물체가 비추는 빛이 이 물체를 표면에 반사되기 때문에 볼 수 있는 것이다. 이 것이 난 반사광이라고 한다.


물체를 어느 방향에서 보든 명암이나 색조가 크게 변해 보이지 않는 이유가 여러 방향으로 고르게 퍼지는 난 반사광 덕분이다.


(그림 : https://kblog.popekim.com/2011/12/04-part-1.html)


수학적으로 난 반사광을 어떻게 계산하려면?


lambert모델(게임에서 주로 사용됨)을 살펴보면 

입사광이 이루는 각의 코사인 값을 구하면 난 반사광의 양을 구 할 수 있다.


입사광과 법선의 두개의 각도에 따라 코사인 값은 아래와 같다.

0도 => 1

90도 => 0

90도를 넘는 순가 음수 값을 갖다가

180도에서 -1

음수 값은 아예 깜깜해지기 때문에 값 변경이 필요하다.


 

코사인값을 구하는 수학 공식을 보면

cosθ = (A ∙ B) ÷ (| A |ⅹ| B |);

두 개의 내적을 구한 뒤 두 벡터의 길이를 곱한 결과를 나눈 것과 같다. 


여기서 두 벡터를 1로 정규화한다. (두 벡터가 이루는 각이 중요할뿐 길이는 중요하지 않기 때문에) 

cosθ = (A'  B')


두 벡터가 이루는 각의 코사인 값은 두 벡터의 내적과 같다는 의미이다.


셰이더에서 난 반사광을 계산하려면?


물체 표면 벡터는 정점에 저장되어 있기 때문에 그대로 가져오면 되고 입사광의 벡터는 광원의 위치에서

현재 픽셀 위치까지 긋고 그 그것의 벡터를 가져와야한다.


선을 긋는 것을 벡터의 뺄셈이라고 하는데, 모든 변수의 공간을 일치 시킨 뒤 현재 위치에서 광원의 위치를 빼면 입사광의 벡터를 구할 수 있다. (표면의 법선도 같은 공간에 있어야 함)


난 반사광 범위는 0~1이니까 위에서 코사인값을 0이하의 값은 0으로 1이상의 값을 1로 맞춰 줘야한다.




학습참고 출처 https://kblog.popekim.com/2011/12/04-part-1.html

반응형

램버트 라이트


커스텀 라이트로 램버트 라이트 구현하기


알아 두어야 할 것


법선 벡터가 조명벡터를 마주볼 때 해당 버텍스는 가장 밝다.


두 벡터의 각도의 따른 내적 값 

0도이면 =>1

90도이면 =>0

180도이면 => -1






램버트 라이트란 


밸브에서 개발했으며 하프라이프에서 처음 사용되었다고 한다.

주변 및 방향 조명을 통합하여 3D장면의 개체를 음영 처리한다.


공식 Dot(L,N) L:라이트벡터, N:법선 벡터(노말 벡터)

법선 벡터가 곱해지면 표면에 굴곡진 라이팅을 생성하게 해주는 방법



float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten)

{

float4 ndot1 = dot(s.Normal, lightDir);

return ndot1;

}

dot 함수 : 노멀벡터와 라이트 벡터를 내적 연산


내적 연산한 값은 1~ -1까지 범위의 숫자가 나온다. 

즉, 그림에서 가장 밝은 곳은1, 어두워지는 경계선은 0 아예 어두운 곳은 -1일 것이다.



-1인 부분은 너무 어둡기 때문에 변경이 필요하다.


float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten)

{

float4 ndot1 = saturate(dot(s.Normal, lightDir));

return ndot1+0.5;

} 

saturate 함수 : 이 안의 값을 0~1사이로 잘라줌 0보다 작은 값은 0으로 1보다 큰값은 1로

+ 차이를 위해 결과 값에 +0.5를 해준다.



(NormalMap 추가했음)



(아까 더했던 0.5를 지우고)




학습참고 : 

1) https://www.slideshare.net/tartist/ss-56389186

2) 유니티쉐이더 스타트업 도서


반응형

서피스 툰 셰이더 #1

 

<툰 쉐이더 적용 전>


We're gonna set a custom lighting function

1) 우리가 설정 가능한 조명기능을 설정해주도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Shader "Custom/ToonShaderPratice"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
 
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0
 
        sampler2D _MainTex;
        half4 _Color;
 
        half4 LightingToonLighting(SurfaceOutput s, half3 lightDir, half atten) 
        {
 
        }
        struct Input
        {
            float2 uv_MainTex;
        };
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Shader "Custom/ToonShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf ToonLighting
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
        sampler2D _MainTex;
        half4 _Color;
 
        half4 LightingToonLighting(SurfaceOutput s, half3 lightDir ,half atten)
        {
            float NdotL = saturate(dot(s.Normal, lightDir))* atten;
            float toonL = step(0.5, NdotL);
            return half4(toonL,toonL,toonL,1);
 
        }
        
        struct Input
        {
            float2 uv_MainTex;
        };
 
 
        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex)* _Color;
            //o.Albedo = c.rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Shader "Custom/ToonShader"
{
    Properties
    {
        _LitOffset("Lit Offset",Range(0,1))=0.25
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf ToonLighting
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
        sampler2D _MainTex;
        half4 _Color;
        half _LitOffset;
 
        half4 LightingToonLighting(SurfaceOutput s, half3 lightDir ,half atten)
        {
            float NdotL = saturate(dot(s.Normal, lightDir))* atten;
            float toonL = step(_LitOffset, NdotL);
            return half4(s.Albedo * toonL,1);
 
        }
        
        struct Input
        {
            float2 uv_MainTex;
        };
 
 
        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex)* _Color;
            o.Albedo = c.rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs


우리의 음영을 향상시키기 위해 우리는 표면에서 완전히 어두운 부분을 원하지 않는다.

_SSColor 은 "피부" 아래 인물의 색감이라고 생각하다. 그리고 그늘진 부분에서 우리는 알베도를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
Shader "Custom/ToonShader"
{
    Properties
    {
        _LitOffset("Lit Offset",Range(0,1))=0.25
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _SSSTex("SSS Map",2D) = "black" {}
        _SSSColor("SSS Tint",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf ToonLighting
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
        sampler2D _MainTex;
        sampler2D _SSSTex;
        half4 _Color;
        half _LitOffset;
        half _SSSColor;
 
        struct CustomSurfaceOutput {
            half3 Albedo;
            half3 Normal;
            half3 Emission;
            half Alpha;
            half3 SSS;
        };
 
        half4 LightingToonLighting(CustomSurfaceOutput s, half3 lightDir ,half atten)
        {
            float NdotL = saturate(dot(s.Normal, lightDir))* atten;
            float toonL = step(_LitOffset, NdotL);
            half3 albedoColor = lerp(s.Albedo * s.SSS, s.Albedo *toonL, toonL);
            return half4(albedoColor,1);
 
        }
        
        struct Input
        {
            float2 uv_MainTex;
        };
 
 
        void surf(Input IN, inout CustomSurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex)* _Color;
            o.Albedo = c.rgb;
            o.SSS = tex2D(_SSSTex, IN.uv_MainTex) * _SSSColor;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
 
cs

당신이 보는 것처럼 우리는 우리가 질감으로 더럽힐 수 있는 그림자에 단정한 어두운 색을 가지고 있다.

그러나 문제는 있다 .만약 우리가 라이트 색을 바꾼다면 그것은 머테리얼에 영향을 미치지 않을 것이다.

간단한 해결책: 단지 밝은 색과 우리의 밝은 알베도를 곱하면 된다.




next problem : look at that It's totally flat even when it should have shadows between the plates

다음 문제: 저것 좀 봐. 접시 사이에 그림자가 있어야 하는데도 완전히 평평해.



We'll paint the vertices of our model to mark the should shaded parts and read that colors in our shader

우리는 우리 모델의 정점을 칠해서 음영처리 된 부분을 표시하고 그 색을 우리의 음영처리 된 부분에 읽을 것이다.



This is how our model looks like when we map the vertx color

vertx 색상의 지도를 만들 때 우리의 모델은 이렇게 보인다.



To fix it we use a fancy rick: Vertex color

그것을 고치기 위해 우리는 화려한 릭을 사용한다


Then mult our lighting by the occlussion given by the vertex and done

그 다음 버텍스에 의해 주어지는 오쿨루젼으로 우리의 조명을 여러 번 혼합하여 완성한다.


To improve it even more, we'll add a third map: Combined Map

한층 더 향상시키려면, 3개의 맵: 결합 맵을 추가한다


We'll map if a pixel it's glossy or not in it's Red channel

우리는 픽셀에 광택이 나는지 아닌지를 빨간 채널에서 매핑할 것이다.


We'll use again the dot product and get the specular lights

우리는 다시 도트 제품을 사용할 것이고, 투시 조명을 받을 것이다.


We'll need the view direction(facing of the camera)

우리는 전망 방향이 필요할 것이다.


With the pow function we'll control the size of our reflection

파워 기능으로 우리는 반사되는 크기를 조절할 것이다.

We add a prop to control the Specular size

우리는 시편 크기를 조절하기 위해 받침대를 추가한다.



Then we apply the light color and the light attenuation to our specular component and mult the Glossy from the CombMap

그리고 나서 우리는 밝은 색과 빛 감쇠를 우리의 시야에 있는 성분과 콤비맵의 여러 가지 광택에 바른다.



Caution: darker it's bigger. The smaller the exponent, the bigger the highlight

주의: 더 어둡다. 지수가 작을수록 하이라이트는 커진다.


Finally, to not waste any memory, let's put at use the Green and Alpha channels of the CombMap

마지막으로, 어떤 기억도 낭비하지 않기 위해 콤비맵의 그린과 알파 채널을 사용하자.


We'll use Green channel to add extra shadows.(Caution: if you don't make this texture correctly shadows could pixelate)

우리는 그린 채널을 사용하여 그림자를 더 추가할 것이다.

(주의: 이 질감을 정확하게 만들지 않으면 그림자가 화소화될 수 있음)


Now, the Alpha channel will be used as an InnerLine mask. Alpha 0 it's a line

이제 알파 채널은 이너라인 마스크로 사용될 것이다. 알파 0 선이다.


COOL TRICK: if you structure a surface shader like this, you can mix regular vertex/fragment passes with surface passes

COOL TRICK: 만약 당신이 표면 하드어를 이렇게 구조화한다면, 당신은 일반 정점/약점 패스와 표면 패스를 혼합할 수 있다.


Very easy trick. We get the normal, and offset the vertex position along it a little bit.. Fragment just returns black

매우 쉬운 속임수 우리는 정상과 정점 위치를 약간 상쇄한다. 파편이 그냥 검은색으로 돌아온다.


PRO TIP : Renormalize the normal, some models have normals whith! =1 magnitide. This could result in inconsistent outlines.

PRO TIP: 정상화를 다시 하고, 어떤 모델들은 표준적인 기동을 가지고 있다! =1자석 이렇게 되면 윤곽이 일관되지 않을 수 있다.


To fix it just cull the front faces. We'll render only the back of the surface

그것을 고치기 위해서 앞면만 도려낸다. 우리는 표면만 다듬을 것이다.



All left to do it's add some props to control the outline color and thickness

남은 것은 윤곽의 색과 두께를 조절하기 위한 소품 몇 가지를 더하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
Shader "Custom/ToonShader"
{
    Properties
    {
        _LitOffset("Lit Offset",Range(0,1))=0.25
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _SSSTex("SSS Map",2D) = "black" {}
        _SSSColor("SSS Tint",Color) = (1,1,1,1)
        _CombMap("Comb Map",2D) = "white" {}
        _SpecPower("Specular Power",Range(0,100)) =20.0
        _SpecScale("Specular Scale",Range(0,10)) = 1.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf ToonLighting
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
        sampler2D _MainTex;
        sampler2D _SSSTex;
        sampler2D _CombMap;
        half4 _Color;
        half4 _SSSColor;
        half _LitOffset;
        half _SpecPower;
        half _SpecScale;
 
    
 
 
        struct CustomSurfaceOutput {
            half3 Albedo;
            half3 Normal;
            half3 Emission;
            half Alpha;
            half3 SSS;
            half vertexOc;//
            half Glossy;
            half Glossiness;
            half Shadow;
            half InnerLine;
        };
 
        half4 LightingToonLighting(CustomSurfaceOutput s, half3 lightDir, half viewDir ,half atten)
        {
            float oc = step(0.9, s.vertexOc);
            float NdotL = saturate(dot(s.Normal, lightDir))* atten;
            float toonL = step(_LitOffset, NdotL)*s.Shadow * oc;
            half3 albedoColor = lerp(s.Albedo * s.SSS, s.Albedo* _LightColor0 *toonL, toonL);
            half3 specularColor = saturate(pow(dot(reflect(-lightDir, s.Normal), viewDir),s.Glossiness* _SpecPower))
                                * toonL * _LightColor0 * s.Glossy * _SpecScale;
 
            //return half4(s.Glossy, s.Glossy, s.Glossy);
            return half4( (albedoColor + specularColor)*s.InnerLine,1);
 
        }
        
        struct Input
        {
            float2 uv_MainTex;
            float4 vertColor : COLOR;
        };
 
 
        void surf(Input IN, inout CustomSurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex)* _Color;
            half4 comb = tex2D(_CombMap, IN.uv_MainTex);// *_SSSColor;
            o.Albedo = c.rgb;
            o.SSS = tex2D(_SSSTex, IN.uv_MainTex) * _SSSColor;
            o.vertexOc = IN.vertColor.r; //
            o.Glossy = comb.r;
            o.Glossiness = comb.b;
            o.Shadow = comb.g;
            o.InnerLine = comb.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs




학습참고 https://www.youtube.com/watch?v=pMq--FUR5VE

반응형

서피스 셰이더



서피스 셰이더 동작원리


먼저 3D모델이 3D모델 지오메트리를 변경할 수 있는 어떤 함수로 넘겨진다. 

그런 다음, 몇몇 직관적인 속성을 사용하여 겉모습을 정의하는 어떤 함수로 넘겨진다.


마지막으로, 이 속성들은 지오메트리가 근처의 광원들에 의해 어떻게 영향을 받을지를 결정하는 라이팅 모델에 의해 사용된다. (각 모델의 픽셀들의 RGB색상으로 나타내짐)


서피스 함수


서피스 함수는 3D모델의 데이터를 인풋으로 가져와서, 결과값으로 렌더링 프로퍼티들을 반환한다. 

아래의 서피스 셰이더는 오브젝트를 흰색 diffuse를 갖는 오브젝트로 렌더링한다.



#pragma surface : 해당 셰이더를 위한 서피스 함수이름은 surf이고 라이팅 모델로는 램버시안 모델을 사용할 것이다 라는 뜻이다.


o.Albedo = 1; : 머티리얼의 알베도 즉, 재질 기본색상이 흰색이라는 것을 나타낸다.


최초의 3D모델로부터의 어떠한 데이터도 사용하지 않지만,

그럼에도 인풋 구조체를 정의하는 형식이 필요하다.




서피스 아웃풋


SurfaceOutput구조체 안에는 머티리얼의 최종 모습을 결정하기 위해 사용되는 여러가지 프로퍼티들을 포함하고 있다.


fixed3 Albedo : 기본색상

fixed3 Normal : 반사각을 결정하는 면의 방향

fixed3 Emission : 이 오브젝트가 스스로 생성하는 빛의 양

half Specular : 머티리얼이 빛을 반사하는 정도(0~1)

fixed Gloss : 스펙큘러 반사가 퍼지는 정도

fixed Alpha : 머티리얼의 투명한 정도


Cg/HLSL은 전통적인 float타입을 지원한다. 하지만 보통 32비트의 정밀도는 거의 필요하지 않을 것이고,

보통 16비트 정도면 충분하기 떄문에 half타입을 주로 사용하게 될 것이다.



텍스처 샘플링


텍스처를 붙이는것을 배우기전에 텍스처 매핑이 어떻게 일어나는지 알아보자

텍스처를 입힐 모델은 여러 개의 삼각형들로 만들어져있고, 각각의 삼각형은 3개의 버텍스로 구성된다.

데이터(UV와 색상)는 이러한 각 버텍스들 안에 저장된다.


삼각형의 버텍스들을 통해 텍스처 상에 맵핑되는 0에서 1사이의 정규값을 가져 올 수 있는데 이것이 텍스처 UV좌표이다.

즉, 3D오브젝트에 텍스처를 매핑하려면, 각 버텍스의 UV좌표가 필요하다.




_MainTex라는 이름으로 텍스처 프로퍼티를 선언했는데 이 프로퍼티는 머티리얼

인스펙터를 통해 접근가능하도록 처리가 되도록 한 것이다.

<쉐이더를 적용한 머티리얼 인스펙터창>


현재 픽셀의 UV데이터는 float2 uv_MainTex에서 추출된다.


tex2D는 텍스처와 UV값을 넘기면, 이에 해당하는 RGB색상으로 반환한다.

tex2D함수는 텍스처를 임포팅할 때, Unity에 의해 직접 설정될 수 있는 다른 파라미터들을

계산에 고려하게 된다.



서피스 인풋

서피스 인풋인 Input의 필드들을 Unity가 계산 할 값들로 채울 수 있다.

예를 들어, float3 worldPos를 추가함으로써 surf에서의 해당 점에 대한 월드 포지션을 가지고 초기화 되는데

이것은 특정 점으로부터의 거리에 의존적인 이펙트를 생성하는데 사용된다.


그 밖의 다른 인풋들

- float3 viewDor : 카메라의 방향(뷰 방향)

- float4 name : COLOR : name변수에 버텍스 색상을 포함한다.

- float4 screenPos : 스크린 좌표 상의 해당 픽셀의 위치

- float3 worldPos : 해당 픽셀에 대한 월드 좌표 상의 위치



버텍스 함수


surf함수로 보내기 전에 이들을 수정/변경해준다. 

surf함수가 RGBA공간 안에서 색상을 다루는 반면, 공간에의 3D점을 제어하기 위해서는 버텍스 모디파이어 사용방법을 알아야한다. 


3d모델을 원래의 모습보다 좀 더 통통하게 만드는 셰이더를 예제로 만들어보자

이렇게 만드려면 모델의 삼각형을 삼각형면이 향하는 방향을 따라서 확장시키면 된다. 

삼각형면이 향하는 방향은 결국 노멀값으로 나타내며, 삼각형 자신의 포면에 수직인 단위 벡터이다.

그렇다면, 이제 다음과 같이 노멀 방향으로 버텍스를 확장하려면 다음과 같은 수식을 사용할 수 있다.



#pragma surface surf Lambert vertex:vert : vert라는 이름을 갖는 버텍스 모디파이어를 설정한다.


실제 vert함수 코드를 보면 ,(v.vertex.xyz += v.normal * _Amount; )

버텍스 위치를 받아서 노멀을 따라 해당 버텍스를 밀어낸다.


appdata_full 구조체는 해당 현재 버텍스의 모든 데이터를 포함하고 있다. 


<결과 화면>



눈 쉐이더


surf함수와 vert함수 이 두가지를 사용하는 snow이펙트가 있다.

어떤 모델의 삼각형 위에 눈이 쌓이는 것을 시뮬레이션하는 것이다.

처음에는 _SnowDirection을 직접 마주하는 삼각형들에만 적용되다가, _Snow가 증가함에 따라서

하늘을 향하지 않는 삼각형들도 결국엔 영향을 받게 되는 방식이다.


우선, 삼각형이 하늘을 향해 있다는 의미를 파악해보자

_SnowDirection은 눈이 내리는 방향을 의미하며 단위벡터의 형태이다.

그것들이 어떻게 정렬되어 있는지를 확인하는 방법중 가장 쉬운 방법은 눈 방향으로 법선(노멀)을 투사하는 것인데,

두 벡터 모두 크기는 1이므로, 결과값은 +1(같은 방향)에 -1(반대 방향)사이가 될 것이다.

이것은 내적과 같다는 것을 Cos세타와 동일하다는 것만 알면된다.

특정 _Snow값보다 도트 연산이 커지게 된다는 것은


  • cos (\ theta) \ geq +1 두 방향이 같은 경우에만 참입니다.
  • cos (\ theta) \ geq 0\ theta90도 미만일 때 true입니다 .
  • cos (\ theta) \ geq -1 항상 사실입니다.

<결과 화면>


벡터 사이의 코사인 값을 수동으로 계산하는 대신,

Cg는 dot라는 이름의 도트 연산 구현을 포함하고 있다.

float4 sn = mul(_SnowDirection, _World2Object);

if (dot(v.normal, sn.xyz) >= _Snow)

v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;

이 부분은 노멀을 월드좌표로 바꾸기 위해서 조금 다른 메소드를 활용하고 있다.

WorldNormalVector함수는 버텍스 모디파이어 안에서는 사실상 이용할 수 없기 때문이다.



http://www.alanzucconi.com/2015/06/17/surface-shaders-in-unity3d/

https://jinhomang.tistory.com/137

반응형

유니티) 셰이더 #1




3D모델은 버텍스라고 불리우는 3D 좌표들의 모음이다.

그것들은 삼각형을 이루기 위해서 서로간에 연결이 되어 있다.

 각 버텍스에는 몇가지 다른 정보들을 포함할 수 있다.

색상값, 방향(노말) 그리고 UV값이라고 불리는 텍스처 매핑을 위한 좌표들이 있다.





모델은 머터리얼없이는 렌더링이 불가능하고, 머터리얼은 셰이더와 그 셰이더 속성값을 포함한 일종의 덮개?이다.



셰이더구조


유니티는 서피스 셰이더와 버텍스버텍스 와 프래그먼트 셰이더 두 가지 타입의 셰이더를 지원한다. + 픽스드 함수 셰이더도 있다.


먼저 셰이더 기본 구조를 알아보자 어떤 셰이더든 구조는 모두 아래와 동일하다.



SubShader섹션은 여러개가 될 수 있다. 여기에 실질적인 GPU에 대한 명령을 담겨 있는데, 유니티 엔진은 그래픽 카드에 호환되는 것을 찾을 때까지 이것들을 순서대로 실행을 시도한다. 

여러 플랫폼을 고려하여 여러개 섹션을 나누는것이다.



프로퍼티

셰이더 프로퍼티는 머터리얼의 인스펙터 안에서 볼 수 있다. 따라서 인스펙터를 통해 그 값을 변경할 수도 있는 것이다. (C#의 public 필드 느낌)


스크립트를 사용하는 것과 다르게 머터리얼은 Asset이라서 프로퍼티 변경값이 그대로 남아있는다. 따라서 멈춘 후에도, 머티리얼에서 변경헀던 부분의 남아있는 것을 확인할 수 있다.



 첫번재와 두번째 프로퍼티에 사용된 2D 타입은 파라미터가 텍스처라는 것을 뜻한다. 

이들은 white, black, gray로 초기화 될 수 있다. 

또한 텍스처가 노멀맵으로 사용될 것을 의미하는 bump를 사용 할 수도있다.


Vector와 Color는 각각 X,Y,Z,W와 R,G,B,A이렇게 항상 4가지 요소를 가진다.


프로퍼티는 유니티에 의해 셰이더 내의 숨겨진 변수에 액세스 할 수 있도록 할 뿐이지 실제로 적용되지는 않는 것이다

그렇기 때문에 SubShader 본문을 정의 해야한다.



렌더링오더

 SubShader섹션은 셰이더의 실제 코드를 담고 있다. 이 코드는 C와 비슷한 Cg / HLSL로 작성된다.

대략적으로, 셰이더의 바디느느 이미지의 각각의 픽셀을 위해 실행된다. 


셰이더 안에서 수행할 수 있는 명령의 수가 제한 되지만, 계산을 여러 패스(pass)로 나누어 이런 것을 피할 수 도 있다. 



Tag는 우리가 작성 중인 셰이더의 특정 프로퍼티에 대해 Unity에게 얘기하는 수단이다.


여기서 말하는 특정 프로퍼티를 예를 들자면, 렌더링이 되어야 하는 순서를 뜻하는 Queue

모델을 어떻게 렌더링이 되어야 하는지를 뜻하는 RenderType같은 것들이 있다.


쉽게 Queue는 머터리얼의 렌더링 순서를 제어하기 위해 Queue태그를 지정할 수 있는 것이다.

(숫자가 작을 수록 먼저 그림)


Background(1000) : 배경과 스카이박스르 위해 사용

Geometry(2000) : 대부분의 불투명 오브젝트를 위해 사용되는 기본 레이블

Transparent(3000) : 투명 프로퍼티를 가지는 머티리얼(유리, 불, 파티클, 물 등등)에서 사용된다.

Overlay(4000) : 렌즈 플레어같은 이펙트, GUI, 텍스트와 같은 것을 그릴때 사용



서피스 셰이더


머티얼이 매 순간 빛에 의해 영향 받으며 현실감 넘치게 시뮬레이션하고 싶은 경우

라이트(빛)가 반영되는 방법에 대한 계산은 내부에 숨기고, surf라는 함수 안에서 알베도, 노멀, 반사율등 과 같은 "직관적인" 속성만 정해주는것이다.



예제코드를 보면 sampler2D _MainTex라인을 사용하여 입력되는 것을 알 수 있다. 

이것은 surf함수에서 머티리얼의 Albedo속성으로 설정된다.



버텍스/프래그먼트 셰이더


버텍스/프래그먼트 셰이더는 거의 GPU가 삼각형을 렌더링하는 방법대로 동작하게된다.

그리고 라이트와 어떻게 작용하는지에 대해 특별히 내장되거나 정해진 컨셉이없다. 

모델의 지오메트리는 먼저 버텍스를 변경할 수 있는 vert라는 함수로 전달된다. 

그 후에, 각 픽셀에 대해서 최종 RGB색상을 결정하는 frag라는 함수로 전달된다.

보통 이 유형의 셰이더는 2D이펙트, 후처리 또는 서피스 셰이더로 표현되기 어려운 복잡한 3D특스 이펙트를 구현하는 유용하다. 


<적용>

vert함수 안을 보면, 버텍스들을 그 원래의 3D공간에서 스크린 상의 최종 2D포지션으로 변환하는 코드가 있다. 

유니티는 이 변환에 대한 수학적인 내용을 감추기 위해서 UNITY_MATRIX_MVP를 사용하여 단순화 하였다. 이 후, frag함수를 반환하면 모든 픽셀이 빨간색으로 칠해진다.

https://unity3d.com/kr/learn/tutorials/topics/graphics/gentle-introduction-shaders

반응형

버텍스 및 프래그먼트 기초 셰이더 #2


월드 공간에 메시 노멀을 표시하는 셰이더


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    // no Properties block this time!
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // include file that contains UnityObjectToWorldNormal helper function
            #include "UnityCG.cginc"
 
            struct v2f {
                // we'll output world space normal as one of regular ("texcoord") interpolators
                half3 worldNormal : TEXCOORD0;
                float4 pos : SV_POSITION;
            };
 
            // vertex shader: takes object space normal as input too
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // UnityCG.cginc file contains function to transform
                // normal from object to world space, use that
                o.worldNormal = UnityObjectToWorldNormal(normal);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 c = 0;
                // normal is a 3D vector with xyz components; in -1..1
                // range. To display it as color, bring the range into 0..1
                // and put into red, green, blue components
                c.rgb = i.worldNormal*0.5+0.5;
                return c;
            }
            ENDCG
        }
    }
}
cs

노멀은 컬러를 만들어내는 것 외에도 모든 종류의 그래픽스 이펙트(조명,반사,실루엣 등등)에 사용된다.


#include "UnityCG.cgin" : UnityObjectToWorldNormal을 쓰기 위한 include


TEXCOORD0, TEXCOORD1 등은 텍스처 좌표 및 포지션과 같은 임의의 고정밀도 데이터를 나타낼 때 사용


UnityObjectToClipPos 오브젝트 공간의 버텍스를 화면으로 변환하는 유틸리티 함수

UnityObjectToWorldNormal :  객체 공간에서 화면으로 정점을 변형시키는 유틸리티 함수 


정규화된 벡터를 컬러로 시각화(-1.0~ +1.0 범위)

절반을 곱하고 나서 절반을 +


<적용 후>


월드 - 공간 노멀을 사용한 환경 반사


씬에서 반사 소스로 스카이박스가 사용 될 때 이 스카이박스 데이터를 담고 있는 

"디폴트"반사 프로브가 생성된다. (반사 프로브 : 내부적으로는 큐브맵 텍스처이다.)


큐브맵

__Cubemap__은 환경에 대한 반사를 나타내는 여섯 개의 사각형 텍스처 컬렉션입니다. 여섯 개의 사각형은 오브젝트를 둘러싸는 가상 큐브면을 형성합니다. 각각의 면은 월드 축의 방향을 따른 뷰를 나타냅니다 (위, 아래, 좌, 우, 앞, 뒤).

큐브맵은 오브젝트의 반사나 “주변 환경”을 캡처하는 데 사용됩니다. 예를 들어, skyboxes  environment reflections는 주로 큐브맵을 사용합니다.

유니티 도큐 : https://docs.unity3d.com/kr/current/Manual/class-Cubemap.html


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
 
            struct v2f {
                half3 worldRefl : TEXCOORD0;
                float4 pos : SV_POSITION;
            };
 
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // compute world space position of the vertex
                float3 worldPos = mul(_Object2World, vertex).xyz;
                // compute world space view direction
                float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                // world space normal
                float3 worldNormal = UnityObjectToWorldNormal(normal);
                // world space reflection vector
                o.worldRefl = reflect(-worldViewDir, worldNormal);
                return o;
            }
        
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the default reflection cubemap, using the reflection vector
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.worldRefl);
                // decode cubemap data into actual color
                half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
                // output it!
                fixed4 c = 0;
                c.rgb = skyColor;
                return c;
            }
            ENDCG
        }
    }
}
cs


mul(x,y)

: 두 행렬의 곱을 계산한다.

worldPos = mul(unity_ObjectToWorld, vertex).xyz;

월드공간 관측 방향 계산

worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); 

: 지정된 객체 공간 정점 위치에서 카메라 방향으로 월드 공간 방향을 계산하고 정규화 함

worldNormal = UnityObjectToWorldNormal(normal);

: 월드 공간을 노멀로 변환

reflect(-worldViewDir, worldNormal);

: 정반사광의 방향 벡터를 구하는 벡터반사 함수.

첫 번째 인자로 입사광의 방향벡터를 두 번째 인자로 반사면의 법선을 받는다.


UNITY_SAMPLE_TEXCUBE

 : 큐브맵을 샘플링하기 위한 빌트인 매크로

__unity_SpecCube0

: (셰이더 변수)사용중인 반사 프로브의 데이터를 포함하고있다

DecodeHDR 

: 반사 프로브 데이터에서 실제 컬러를 얻기 위함,반사 프로브 큐브맵을 특별하게 인코딩된 방식으로 저장


<적용 후>



노멀 맵을 사용한 환경 반영


노멀맵(Normal Maps)은 오브젝트들에 추가 지오메트리 생성 없이 추가 디테일을 생성하기 위해 종종 사용된다. 

노멀 맵 텍스처를 가지고 환경을 반사하는 셰이더를 만드는 예제


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
 
            struct v2f {
                half3 worldRefl : TEXCOORD0;
                float4 pos : SV_POSITION;
            };
 
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // compute world space position of the vertex
                float3 worldPos = mul(_Object2World, vertex).xyz;
                // compute world space view direction
                float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                // world space normal
                float3 worldNormal = UnityObjectToWorldNormal(normal);
                // world space reflection vector
                o.worldRefl = reflect(-worldViewDir, worldNormal);
                return o;
            }
        
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the default reflection cubemap, using the reflection vector
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.worldRefl);
                // decode cubemap data into actual color
                half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
                // output it!
                fixed4 c = 0;
                c.rgb = skyColor;
                return c;
            }
            ENDCG
        }
    }
}
cs


노멀맵을 사용하기 시작하면 표면 노멀 자체가 피셀당 기준으로 계산되어야한다. 

즉 환경이 픽셀마다 어떻게 반사되는지도 계산해야한다.

이전 쉐이더 예제코드와 크게 변한 것은 없다. 다른 점은 이 셰이더는 모델의 각 버텍스에 대해서가 아니라 화면의 모든 픽셀 각각 대해 계산을 한다. 


노멀 맵 텍스처는 대부분의 경우 좌표 공간에서 표현되는데 이는 모델의 "표면을 따르는"것으로 생각할 수 있다. 지금 만들려는 셰이더는 탄젠트 공간 기반 벡터를 알아야 하고 텍스처로부터 노멀벡터를 읽어야하고, 이 벡터를 월드 공간으로 변환하고 그 후 위 셰이더에서 모든 수학적 계산을 해야한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
    Properties{
        // normal map texture on the material,
        // default to dummy "flat surface" normalmap
        _BumpMap("Normal Map", 2D) = "bump" {}
    }
        SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
 
            struct v2f {
                float3 worldPos : TEXCOORD0;
                // these three vectors will hold a 3x3 rotation matrix
                // that transforms from tangent to world space
                half3 tspace0 : TEXCOORD1; // tangent.x, bitangent.x, normal.x
                half3 tspace1 : TEXCOORD2; // tangent.y, bitangent.y, normal.y
                half3 tspace2 : TEXCOORD3; // tangent.z, bitangent.z, normal.z
                // texture coordinate for the normal map
                float2 uv : TEXCOORD4;
                float4 pos : SV_POSITION;
            };
 
    // vertex shader now also needs a per-vertex tangent vector.
    // in Unity tangents are 4D vectors, with the .w component used to
    // indicate direction of the bitangent vector.
    // we also need the texture coordinate.
    v2f vert(float4 vertex : POSITION, float3 normal : NORMAL, float4 tangent : TANGENT, float2 uv : TEXCOORD0)
    {
        v2f o;
        o.pos = UnityObjectToClipPos(vertex);
        o.worldPos = mul(_Object2World, vertex).xyz;
        half3 wNormal = UnityObjectToWorldNormal(normal);
        half3 wTangent = UnityObjectToWorldDir(tangent.xyz);
        // compute bitangent from cross product of normal and tangent
        half tangentSign = tangent.w * unity_WorldTransformParams.w;
        half3 wBitangent = cross(wNormal, wTangent) * tangentSign;
        // output the tangent space matrix
        o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x);
        o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y);
        o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z);
        o.uv = uv;
        return o;
    }
 
    // normal map texture from shader properties
    sampler2D _BumpMap;
 
    fixed4 frag(v2f i) : SV_Target
    {
        // sample the normal map, and decode from the Unity encoding
        half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv));
        // transform normal from tangent to world space
        half3 worldNormal;
        worldNormal.x = dot(i.tspace0, tnormal);
        worldNormal.y = dot(i.tspace1, tnormal);
        worldNormal.z = dot(i.tspace2, tnormal);
 
        // rest the same as in previous shader
        half3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
        half3 worldRefl = reflect(-worldViewDir, worldNormal);
        half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, worldRefl);
        half3 skyColor = DecodeHDR(skyData, unity_SpecCube0_HDR);
        fixed4 c = 0;
        c.rgb = skyColor;
        return c;
    }
    ENDCG
}
    }
}
cs


학습참고

1) https://docs.unity3d.com/kr/current/Manual/SL-VertexFragmentShaderExamples.html

반응형

버텍스 및 프래그먼트 셰이더

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Shader "Unlit/ToonVertx"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
 
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
 
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}
 
cs


프로퍼티


머티리얼의 일부로 저장되고 머티리얼 인스펙터에 나타나게 될 셰이더 변수(텍스처, 컬러 등)를 포함하고 있다.

언릿 셰이더 템플릿에서는 하나의 텍스처 프로퍼티가 선언되어있따.



SubShader



하나의 셰이더는 하나 이상의 서브셰이더를 포함할 수 있고 서브 셰이더는 일차적으로 다른 GPU성능에 맞는 셰이더를 구현하기 위해 사용된다.



Pass

각 서브셰이더는 여러 개의 패스로 구성되며 각 패스는 해당 셰이더의 머티리얼과 함께 렌더링될 동일 오브젝트용 버텍스 및 프래그먼트 코드의 실행을 나타낸다.

단순한 쉐이더는 하나의 패스만 사용하는 경우가 많지만 조명과 상호작용하는 셰이더의 경우 더 많은 패스를 필요로 한다.


CGPROGRAM .. ENDCG

버텍스 및 프래그먼트 셰이더 내의 HLSL코드 부분을 이 곳 안에 작성한다.



버텍스 셰이더


3D모델의 각 버텍스에서 실행되는 프로그램이다.

여기서 버텍스 위치를 오브젝트 공간에서 이른바 "클립공간"으로 변환한다.

GPU가 오브젝트를 화면에 래스터화하기 위해 클립 공간을 사용한다.

프래그먼트 셰이더에서 텍스처를 샘플링하기 위해 필요로한다.


래스터화 :  백터 그래픽 이미지를 비디오 디스플레이나 프린터 등의 래스터 디바이스에 출력하기 위해 래스터 이미지로 변환하는 것



프래그먼트 셰이더



오브젝트가 화면에 차지하고 있는 모든 픽셀마다 실행되는 프로그램이며 보통 각 픽셀의 컬러를 계산하고 출력하기 위해 사용된다.

화면에 보통 수백만 개의 픽셀이 있으며 프래그먼트 셰이더는 이 모든 픽셀에 대해 실행된다.


일부 변수 또는 함수 정의 뒤에는 기호가 붙는다. 이 기호에 대해서도 정리 해야겠다.

(예: POSITION 또는 :SV_Target)이 시맨틱은 이 변수의 "의미"를 GPU에 알려준다.


<셰이더를 적용>



모델을 한 가지 색으로 만드는 셰이더 예제


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 Properties
    {
        // Color property for material inspector, default to white
        _Color ("Main Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            // vertex shader
            // this time instead of using "appdata" struct, just spell inputs manually,
            // and instead of returning v2f struct, also just return a single output
            // float4 clip position
            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return mul(UNITY_MATRIX_MVP, vertex);
            }
            
            // color from the material
            fixed4 _Color;
 
            // pixel shader, no inputs needed
            fixed4 frag () : SV_Target
            {
                return _Color; // just return it
            }
            ENDCG
        }
    }
}
cs

이 구조는 입력(appdata)및 출력(v2f)용으로 구조체를 사용하지 않고 셰이더 함수는 입력을 수동으로 판독하는 구조이다.


#pragma vertex vert : 프로젝션, 컬러, 텍스쳐 나 다른 fragment쉐이더를 coordinate하기 위해 사용 

#pragma fragment frag : 렌더링 창에서 이미지 속성을 수정하기 위한 쉐이더 프로그램, 각 화소마다 실행


POSITION : 변형 된 정점 위치

SV_POSITION : 픽셀 위치를 설명

UnityObjectToClipPos : 오브젝트 공간의 한 점을 동질적인 좌표에 있는 카메라의 클립공간으로 변환(World, View, Projection 행렬 연산을 쉽게 해주는 유니티 도우미 함수)


SV_Target : 결과를 어떤 렌더에게 보낼지 설정


<적용 후>

(얼굴은 적용하지 않아서 색이 남아있음)


학습참고

1) https://docs.unity3d.com/kr/current/Manual/SL-VertexFragmentShaderExamples.html

2) https://haewoneee.tistory.com/27


반응형

+ Recent posts