본문 바로가기
튜터리얼_스터디

(HLSL)아티스트를 위한 URP 셰이더 Shader #8 - 텍스쳐 출력 / 조작하기

by 대마왕J 2021. 9. 3.

자 그럼 각설하고, 이번에는 HLSL로도 텍스쳐를 출력해 보겠습니다. 

시작은 역시! 6편에 있던 그거죠 없으신 분은 6편에서 받아오시면 돼요. 다운로드 다 걸어 놨으니까... 
이걸 적용하면, 붉은 오브젝트가 나와야 정상입니다. 

뭐야 귀찮다고요? 

허허 나만큼 게으른 사람들이었잖아? 할 수 없군 이번만이예요. 

NewUnlitShader.shader
0.00MB

 


Shader "Example/URPUnlitShaderBasic"
{
    Properties
    { }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS   : POSITION;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                return OUT;
            }

            half4 frag() : SV_Target
            {
                half4 customColor = half4(0.5, 0, 0, 1);
                return customColor;
            }
            ENDHLSL
        }
    }
}

텍스쳐 출력하기 / 버텍스 셰이더  

일단 바로 텍스쳐 출력하기로 시작해 보겠습니다.
텍스쳐 출력하기는 일단 프로퍼티스에서  텍스쳐 하나 입력받고 시작합니다. 

    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
    }

이것만 하면 일단 인터페이스 구현은 끝났구요 

이제 진짜 데이터가 필요한데.. 

텍스쳐가 출력될려면 텍스쳐 오브젝트 , 샘플러, UV  3개가 필요하다는건 이미 상식이죠?? 뭐야 아니라고

1. 텍스쳐 오브젝트는 말그대로 텍스쳐,
2. 샘플러는 이 텍스쳐를 어떻게 불러들이겠냐라는 설정 같은것,
3. UV는 3D 하시는 분들이 다 알고 있는 그거 (...)

요 3개가 조합이 되어야 텍스쳐가 나오는 거예요. 
쪼아요 이 세 개를 넣어보죠 


텍스쳐 오브젝트와 샘플러 부르기 

그럼 일단 텍스쳐 오브젝트와 샘플러를 불러보겠습니다. 
부르는 마법의 주문은 다음과 같아요 

일단 연성진을 그리고요 (아님)

TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);

요렇게. 프로퍼티스에서 만들었던 변수와 같은 이름으로 불러줘요. 샘플러는 sampler라는 이름만 앞에 붙이면 되고요. 

그리고 C 버퍼 선언해주고 그 안에 변수를 불러줘야 합니다. 변수는 역시 프로퍼티 텍스쳐 이름과 그 뒤에 _ST를 붙이면 되고요  

CBUFFER_START(UnityPerMaterial)
          float4 _BaseMap_ST;
CBUFFER_END

_ST 는 S,T 좌표계를 의미하는 이름이랍니다. 흔히 UV라고도 불리는, 즉 X,Y 랑 비슷한거죠

흐음 그럼 안에 들어가 있는 저 float4 _BaseMap_ST 란 무엇일까요?

요겁니다. 타일링과 옵셋. 총 4개의 숫자죠? 그래서 float4 이고, 여기의 숫자가 _ST 라는 이름이 붙어서 셰이더로 넘어온답니다. 

 

자 그럼 일단 여기까지 정리하면 아래 모습처럼 되겠지요.  이렇게 텍스쳐 오브젝트와 샘플러, ST까지 불러 보았습니다. 

Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS   : POSITION;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
            };

            //텍스쳐 오브젝트와 샘플러를 부릅니다. 
            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            //c 버퍼에 _ST 를 불러준다
            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                return OUT;
            }

            half4 frag() : SV_Target
            {
                half4 customColor = half4(0.5, 0, 0, 1);
                return customColor;
            }
            ENDHLSL
        }
    }
}

 


UV 불러보기 

이제  UV만 부르면 되겠군요. 
근데 UV는 어디있죠? 텍스쳐야 프로퍼티스에서 받아서 가지고 오는거니 오브젝트와 샘플러를 위해서처럼 받아왔는데, UV는 우리가 3D 프로그램에서 Unwrap 작업을 해서 UV를 펴서 모델링을 가지고 왔기 때문에 '이미 버텍스가 가지고 있는 상태' 입니다. (역시 자세한 내용은 책을 참고하세요) 

그러니 우리는 지금 UV를 '버텍스에서 받아와야 하는' 상태입니다. 
엔진보고 버텍스를 내놓으라고 해서 버텍스를 공짜로 받아와 보죠 !! 얌마 그래픽에서 얼마나열심히 UV를 폈는줄 아냐!! 

일단, 버텍스 셰이더 입력 구조체인 Attributes 와 출력 구조체인 Varyings 에 float2 의 UV를 각각 추가해 줍니다. 
레지스터는 TEXCOORD 0번을 사용합니다. 

 struct Attributes
 {
      float4 positionOS   : POSITION;
      float2 uv           : TEXCOORD0;
  };

 struct Varyings
 {
      float4 positionHCS  : SV_POSITION;
      float2 uv           : TEXCOORD0;
 };

입력이야 정상적으로 받아올테니 문제없겠지만, 출력을 저대로 두면 에러가 나겠지요. 
버텍스 셰이더에서 입력을 출력으로 빼 봅시다 

 Varyings vert(Attributes IN)
 {
     Varyings OUT;
     OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
     OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
     return OUT;
 }

TRANSFORM_TEX 함수에 uv와 텍스쳐를 넣어줍시다. 
이렇게 해주면 _ST도 같이 연산됩니다. 

#define TRANSFORM_TEX(tex, name) ((tex.xy) * name##_ST.xy + name##_ST.zw)

그래서 OUT.uv로 , Varyings 구조체이 있는 uv에 ST 연산이 들어가 있는 uv를 전달해 주게 됩니다. 

자 여기까지 하면 버텍스 셰이더는 끝 

여기를 대충 그림으로 그려보면 이런 느낌이예요 

 


텍스쳐 출력하기 / 픽셀셰이더 

이제 버텍스에서 uv 변환도 끝났고,  텍스쳐 오브젝트와 샘플러도 준비되었으니 픽셀셰이더에서 본격적으로 텍스쳐를 연산해서 출력해 봅시다. 

쓰는 함수는 SAMPLE_TEXTURE2D ( 텍스쳐, 샘플러, UV) 입니다 
그리고 Varings에서 uv 를 받아와서 픽셀셰이더로 넘겨야 하기 때문에, 픽셀셰이더 함수 입력에도 frag(Varyings IN) 으로 추가해 주도록 합시다. 

half4 frag(Varyings IN) : SV_Target
{
    //half4 customColor = half4(0.5, 0, 0, 1);
    half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
    return color;
}

그럼 짜잔

이제 여기의 풀 코드는 다음과 같습니다. 

Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
            };

            //텍스쳐 오브젝트와 샘플러를 부릅니다. 
            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            //c 버퍼에 _ST 를 불러준다
            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                //half4 customColor = half4(0.5, 0, 0, 1);
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
                return color;
            }
            ENDHLSL
        }
    }
}

텍스쳐의 연산과 스위즐링 

이건 뭐 더 설명할 것도 없지요? 
간단하게 복습겸 연습해 봅시다 . 이전에 다 한 것들의 복습이라는 생각으로요. 

칼라 프로퍼티를 만들어 더해서 텍스쳐를 밝게 만들어 보기 

칼라를 프로퍼티에 만들고, 텍스쳐와 Add 해줍니다. 그러면 당연히 더 밝아지겠지요?
이것은 포토샵에서의 Linear Dodge 와 같은 공식입니다. 그냥 심플한 더하기예요
위 코드에서 간단히 추가해 보았습니다. 추가한 부분만 주석이 달려 있어요. 

Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
        //프로퍼티를 추가합니다. 
        _BaseColor("BaseColor",Color) = (0,0,0,0)
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                //C버퍼 안에 변수를 추가합니다. 이제 웬만한 변수는 다 여기 추가 
                float4 _BaseColor;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
                //color에 더해준다 
                color += _BaseColor;
                return color;
            }
            ENDHLSL
        }
    }
}

 

칼라 프로퍼티를 만들어 곱해서 어둡게 만들어 보기 

네 뭐 이번엔 곱하기입니다. 곱하기는 포토샵의 multiply 와 같은 공식이니까, 어둡게 작동한다는것 알고 계시죠?

이건 너무 간단해서 뭐.. 이런게 들어가면 노드보다 간단합니다. 
위 코드에서 
//color에 곱해준다 
 color *= _BaseColor;

이렇게만 해주면 끝. 

반전 (Invert)

1- x 하면 뒤집기가 됩니다! 
그도 그럴것이 , 예를 들어 1 - (1,0,1) = (0,1,0) 이 되거든요.
1에서 특정 색을 빼면, 뒤집어진 색이 나오게 됩니다. 포토샵의 Invert 공식도 이 공식입니다. 

공식이 이러니, 회색을 반전시키면 아무것도 일어나지 않게 됩니다.  

이건 뭐.. 역시 간단합니다. 
입력이 없으니 입력은 다 지워도 되고, 
픽셀셰이더를 이렇게 바꿔만 주면 끝나요. 

일정 수준 넘어가면, 코드가 노드보다 간단해집니다. 

half4 frag(Varyings IN) : SV_Target
{
    half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
	//반전
	color = 1 - color;
	return color;
}


텍스쳐를 흑백으로 만들기 

자 이렇게 텍스쳐를 가지고 노는 걸 배워봤는데요, 그럼 이번에는 이런 것들을 가지고 텍스쳐를 흑백으로 만드는 것을 어떻게 구현할 수 있을까 고민해 봅시다. 

흑백이라.. 생각해 본 적이 별로 없지요? 그냥 그래픽 툴에서 saturate 내려 버리거나 Grayscale로 바꿔 버렸지. 

제일 간단한 방법

일단 제일 간단한 법은 이거입니다. 
모니터는 RGB 세 개의 서브픽셀이 모여 하나의 픽셀을 이루고 있으니, 이 3 색의 평균을 내는 방법이죠. 
(R+G+B) / 3 을 하는 방법. 

half4 frag(Varyings IN) : SV_Target
{
    half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
    //평균으로 흑백을 만든다
    color =  (color.r + color.g + color.b)/3;
    return color;
}

루미넌스에 맞춘 방법

그리고 이것보다 좀 더 인간의 눈의 민감도에 맞춘 방법도 있습니다. 
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=catllage&logNo=221347509658

 

[번역] 7가지 그레이스케일 변환 알고리즘 (Tenner Helland)

상세히 번역하기에는 시간이 부족한 관계로 기술적인 부분만 떼서 요약적으로 남겨두었습니다. 전체 아티클...

blog.naver.com

RGB를 사실 보면, 같은 밝기로 보이지 않죠.

위 3개의 색을 보세요. 어떤게 가장 밝아 보이나요? 녹색이 가장 밝아 보이고 그 다음 빨강. 그 다음 파랑이 제일 어두워 보이지 않나요? 

요렇게 말이지요. 그러므로 사실 이걸 가지고 공평하게 평균을 내면, 녹색이 매우 서운할 겁니다. 지분이 다르잖여 지분이!!! 

 

그래서 이 비율을 곱해준 흑백 만드는 공식이, (R*0.3 + G*0.59 + B*0.11) 이라는 공식입니다. 
여기에는 다양한 방식에 의한 수치가 있어서, 유니티에서 쓰는 공식은 (R*0.2126729 + G*0.7151522+ B*0.0721750) 이네요. .. 근데 사실 결과물 보면 잘 차이를 모르겠음. 데헷.  
어쨌건 이걸 구현해 주면 좀 더 정확한 그레이스케일을 만들수 있.. 다고 합니다. 

확실히 좀 깊이감이 달라 보이지 않습니까?

...아니라고요? 그 , 그럴리가 없어요... (사실 나도 그래요) 

(color.r + color.g + color.b)/3
(color.r * 0.3 + color.g * 0.59 + color.b * 0.11)

 

그리고 이렇게 단순히 각 요소들을 각각 곱해주고 그걸 다 더하는 공식이라면, 아직 좀 설명하긴 이르지만 Dot 공식과도 똑같기 때문에 이렇게 해도 결과는 같습니다. 뭔 소린지 모르겠으면 이건 넘어가도 돼요

half4 frag(Varyings IN) : SV_Target
{
    half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
    //루미넌스 수치를 이용한 그레이스케일
    color =  (color.r * 0.3 + color.g * 0.59 + color.b * 0.11);
    //혹은 아래처럼 해도 동일하다. 모르면 그런게 있나보다 하면 됨. 
    // color = dot(color, float3(0.3, 0.59, 0.11));
    return color;
}

자 그럼 오늘은 텍스쳐 출력이랑 가지고 노는 것 까지 알아보았습니다. 
다음 시간에는 뭘하지?!??!? 

블로그 주인장에게 커피값을 후원할 수 있습니다! 

 

반응형

댓글