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

(HLSL)아티스트를 위한 URP 셰이더 Shader #10 - UV 를 이용한 불 만들기 실습

by 대마왕J 2021. 10. 1.

 

안내 
여기 나오는 내용은 유니티 쉐이더 스타트업에 나오는 예제를 최신 URP에 맞추어 예제를 번역한 내용입니다. 
때문에 이론적 내용이 상당히 간략하거나 불친절하며, 예제에 대한 설명도 축약되어 있기 때문에
책과 같이 보시는 것을 추천하는 바입니다.
시작하는 기본 코드 - 텍스쳐 한 장만 출력되는 코드 - 의 다운로드는 이것입니다.  

 

일단 아래 두 이미지가 있다고 해 둡시다 .. 이건 책에 나오는 예제인데.. 새로 만들기 귀찮아서 계속 쓰고 있는데...

역시 이 이미지를 다운로드 받으시려면, 이전 강의 문서에서 받으실 수 있습니다. 
https://chulin28ho.tistory.com/677

자 이걸로 UV 를 이용한 예제를 만들어 보도록 하죠. 불 만들기예요 불. 책 보신 분은 첨에 볼 수 있는 그거. 
책 보신 분을 기준으로 설명하는거라고 계속 말해 왔으니까, 이론 설명은 최대한 줄일께요. 이론은 책에 있는 그대로입니다.


1. 기본적인 불 만들기 

일단 텍스쳐를 불러들여보지요. 이건 뭐 쉽지요 이젠?
두 텍스쳐를 다 불러봤습니다.

물론 Properties에다가만 추가한 것이기 때문에 첫 번째 맵만 출력되고 있는 것이지요. 

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

기억을 되살려, 나머지를 다 완성해 봅시다. 아 물론 여전히 출력은 첫 번째 맵만 나와도 돼요

아 지금 벌써 10편인데 이 정도는 솔직히 해야지 않수 내가 언제까지 떠먹일 순 없잖아요 

흠 근데 사실 이 첫 번째 텍스쳐에 알파가 있거든요, 근데 가동이 안되죠? 아직 이르지만 알파를 가동시켜 보도록 하죠. 
알파 단원은 한 - 참 멀었지만 뭐 ... 지금 해도 문제는 .. 없겠죠

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

흠...? 근데 코드에서도 보니까, float4 color를 리턴 시키고 있네요?
float4 라... 이러면 RGBA 가 리턴되는 거니까 이미 알파도 출력 되고 있었다는건데..?
이렇게 이미 알파를 출력하고 있는데 결과는 안되고 있죠??

 

자 당황하지 말고 그럴때는 이렇게. 

 

일단 알파를 활성화 해 줍시다 

셰이더의 Tag 부분을 보면, 
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
RenderType이  Opaque 라고 되어 있는 것을 알 수 있습니다. 
Opaque는 일단 알파가 전 - 혀 먹지 않는 모드예요. 이래서 알파가 없었던 거죠 . 알파를 아무리 넣어도 무시되는 모드. 

여기를..  알파가 먹는 Transparent로 바꾸어 줍시다. 그리고 Queue도 설정해 줘야 해요.  
여기는 사실 요 부분만 설명하려면 한참 설명해야 하고, 책에서도 맨 마지막 단원에서 설명하는 부분이지만 
지금은 그냥 몰라도 따라해 보도록 합시다. (정말 궁금하면 책에서의 맨 마지막 Alpha 단원을 봅시다 )

저 Tag 부분을 이렇게 바꿔 써주세요. 대소문자 주의, 철자 틀리지 않게 주의!! 

Tags { "RenderType" = "Transparent"Queue" = "Transparent"  "RenderPipeline" = "UniversalPipeline" }

흠 네 여기까지만 쓰면 사실 아무것도 일어나지 않고요, Tag 아래에 좀 더 설정을 적어줘야 할 게 있습니다. 
아직 이유는 잘 몰라도 괜찮으니 이것까지 써주세요

ZWrite Off
blend SrcAlpha OneMinusSrcAlpha

그럼 이렇게 되겠죠. 

Tags { "RenderType" = "Opaque" "Queue" = "Transparent" "RenderPipeline" = "UniversalPipeline" }
        ZWrite Off
        blend SrcAlpha OneMinusSrcAlpha

제대로 됐다면, 알파가 뙇 하고 보일 겁니다. 

가끔 이럴때 '저기다 막 저런걸 적어주는 이유.. 왜 그래요?' 라고 생각하는 아티스트분들이 있을 수 있는데요, 알아요 저도 그랬으니까. "왜 저런 이름을 적어야 반투명이 가동되는 것인지" 이해가 안돼서 진도가 안나가곤 했었으니까. 후후후. 이거 설명해 주는 분도 이거 질문 들어오면 뭘 어떻게 설명해야 할지 난감해 하죠. 저도 그 때 뭘 궁금했었는지, 답변해 주는 사람이 왜 난감해 했었는지 이제는 이해할 수 있습니다. 

이게 꼬꼬마때 제가 원하던 답변이었어요
누가 나한테도 그 때 이렇게 말해줬었다면 그렇게 힘들지 않았을텐데. 
"그렇게 누군가가 정한 거예요" 

놀랍게도 이게 제가 원하던 답변이었답니다. 프로그래밍을 아예 몰랐을때, 프로그램은 모두 1+1 =2 처럼 다 이유가 있는줄 알았어요. 근데 살아보니까, 1+1 =2 이것도 누군가가 정해 놓은 약속일 뿐이더라고요 (...) 

 

좋아요 알파는 동작하게 되었으니 마찬가지 방법으로 , 첫 번째 텍스쳐가 아닌 두 번째 텍스쳐가 나오도록 해 줘 봅시다. 그냥 리턴을 color 에서 color2 로 바꿔주면 되는 거니까요 (제 기준)

자 이때 쯤 해서  풀코드가 궁금하신 분을 위해 중간공개. 
사실 여기까지는 부담없이 하실 수 있어야 해요. 다음에는 이렇게 중간공개 슬슬 안해줄거양!!

하다가 모르겠다 싶으신 부분은 덧글달아 주시면 그 부분은 업뎃할게요

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

    SubShader
    {
        Tags { "RenderType" = "Opaque" "Queue" = "Transparent" "RenderPipeline" = "UniversalPipeline" }
        ZWrite Off
        blend SrcAlpha OneMinusSrcAlpha

        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);
            TEXTURE2D(_BaseMap2);
            SAMPLER(sampler_BaseMap);
            SAMPLER(sampler_BaseMap2);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                float4 _BaseMap2_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 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
                half4 color2 = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, IN.uv);
                return color2;
            }
            ENDHLSL
        }
    }
}

 

Tiling과 Offset 연결

아, 이전에도 설명했지만, 

이 부분에서 각각 가지고 있는 Tiling 과 Offset을 각각 적용되게 해 주려면 _ST 연산을 각각 해줘야 한다고 해줬습니다. 
즉 위 코드에서 버텍스 셰이더와 픽셀 셰이더를 아래처럼 바꿔줘야 해요 
흠 다음부터는 이것이 적용된 상태를 기본 코드로 만들어 놔야겠다.. 

CBUFFER_START(UnityPerMaterial)
  //ST 값을 받아온 다음에
  float4 _BaseMap_ST;
  float4 _BaseMap2_ST;
  CBUFFER_END

  Varyings vert(Attributes IN)
  {
  Varyings OUT;
  OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
  //버텍스 셰이더에서는 uv를 그냥 픽셀셰이더로 넘기고
  OUT.uv = IN.uv;
  return OUT;
  }

  half4 frag(Varyings IN) : SV_Target
  {
  //픽셀셰이더의 uv 부분을 TRANSFORM_TEX 함수로 바꿔줍니다. 
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
  half4 color2 = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
  return color2;
  }

그래야 이게 움직입니다. 

좋아요 이렇게 하면 두 번째 텍스쳐도 알파가 잘 나오네요

2 Side를 켜 줍시다

아차참, 2side도 켜주는게 좋을거예요. 뒷면이 안보이니까! 
cull off 를 시켜주면 뒷면이 보일거예요

 Tags { "RenderType" = "Opaque" "Queue" = "Transparent" "RenderPipeline" = "UniversalPipeline" }
   ZWrite Off
   blend SrcAlpha OneMinusSrcAlpha
   //2side 는 off, 기본은 back, 뒷면만 보이게 하려면 front
   cull Off

요렇게 하면 앞면 뒷면 다 칠해주죠 

 

 

흘러가게 해 봅시다. 

그럼, 두 번째 텍스쳐는 위로로 흘러가게 해 줍시다. 저게, 흘러가도 자연스럽게 연결되는 텍스쳐거든요. 
위로.. 니까 -Y 쪽이죠? 해보세요 . 요건 퀴즈니까 일단 숨김. 아 미리 보지말고 좀 해봐요. 고민해 봐야 는다고 코딩은. 

 

더보기
당신 먼저 본 거 아니지?!?
Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
        _BaseMap2("BaseMap2",2D) = "white"{}
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "Queue" = "Transparent" "RenderPipeline" = "UniversalPipeline" }
        ZWrite Off
        blend SrcAlpha OneMinusSrcAlpha
        cull Off

        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);
            TEXTURE2D(_BaseMap2);
            SAMPLER(sampler_BaseMap);
            SAMPLER(sampler_BaseMap2);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                float4 _BaseMap2_ST;
            CBUFFER_END

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

            half4 frag(Varyings IN) : SV_Target
            {
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                //UV의 V, 즉 y 값에 _Time.y 를 더해줍니다. 그냥 더하면 아래로 내려가기 때문에 -_Time.y 를 더해줍니다. 
                half4 color2 = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2) + float2(0, -_Time.y));
                return color2;
            }
            ENDHLSL
        }
    }
}

합쳐봅시다 

이제 안 흘러가는 A와 흘러가는 B를 곱해서 불을 완성해 봅시다 
곱하면 되는거라 간단. 둘을 곱하면 RGB는 RGB끼리 곱해지고, A 는 A 끼리 곱해집니다.
이건 책에 자세한 내용을 적었으니 그 부분을 참고하시고요 

half4 frag(Varyings IN) : SV_Target
  {
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
  half4 color2 = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2) + float2(0, -_Time.y));
  //곱해줍니다
  return color * color2;
  }

 

fire1.shader
0.00MB

이러면 저렴한 첫 번째 불이 되었습니다.

알아요 퀄리티는 그냥 그렇죠. 그래도 작게 보면 괜찮아요. 빌보드로 만들어서 해도 좋고. 후후후 

 

 


2. 불 만들기 두 번째

 

이번에는 두 번째 불만들기 방법입니다. 책에 나와있는, UV를 구겨서 불을 만드는 방법을 셰이더그래프로 해 봅시다 
이 방식은 불 뿐만 아니라 다양한 이펙트에서 쓰기 좋은 기본적인 효과죠 

여기서 필요한 파일은 여기 있습니다. 

https://blog.kakaocdn.net/dn/1FWv8/btrgi2hb63o/SrXKdAoHySelfDkwCbL9V0/fireTest.tga?attach=1&knm=tfile.tga 

https://blog.kakaocdn.net/dn/duYKrV/btrginToVdd/Tl7JcSKzvmu8RTm3nd5if0/noise1.png?attach=1&knm=img.png 

 

자 이걸로 , 일단 텍스쳐를 불러들이는 것 부터 시작해 보죠.

일단 불 이미지가 알파로 나오게 만들었습니다. 물론 움직이진 않지요. 사실상 아까와 같습니다. 텍스쳐 두 개, 알파가 나오고, 2side 가 되고, 첫 번째 텍스쳐만 출력되는 상태 말이죠. 

이제 움직이게 해 줄건데, 첫 번째로는 두 번째 텍스쳐로 첫 번째 텍스쳐를 구겨줄 겁니다. 
... 뭐 근데 역시 자세한 이론은 책을 참고하세요 ㅋ

너무 책 내용을 다 써 버리면 출판사 사장님한테 잡혀감

half4 frag(Varyings IN) : SV_Target
  {
  half4 color2 = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
  //두 번째 텍스쳐의 r 채널을 UV에 더해서 UV를 구겨줍니다. 
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap) + color2.r) ;

  return color;
  }

자 일단 이렇게 구겨줍니다. UV에 텍스쳐를 더해준거예요. 0~1 까지 구겨주니 굉장히 많이 구겨졌네요. 너무 많이 구겨진듯. (이런 이펙트를 만들고 싶었다면 몰라도) 자 그럼 좀 줄여줍시다. 1/4 정도면 어떨까요? 0.25를 곱해주면 되겠죠 

half4 frag(Varyings IN) : SV_Target
  {
  half4 color2 = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
  //두 번째 텍스쳐의 r 채널을 UV에 더해서 UV를 구겨줍니다. 
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap) + color2.r * 0.25) ;

  return color;
  }

 

 

 

 

확실히 좀 나아졌습니다.

이제 저 노이즈를 위로 흘러가게 하면, 불을 구기면서 위로 올라갈 겁니다. 
이렇게 말이죠 

half4 frag(Varyings IN) : SV_Target
  {
  //노이즈를 흘러가게 해 줍니다.
  half4 color2 = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2) + float2(0, -_Time.y));
  //두 번째 텍스쳐의 r 채널을 UV에 더해서 UV를 구겨줍니다. 
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap) + color2.r * 0.25) ;

  return color;
  }

짜잔.

 

위에 보면 불이 힐끗힐끗 넘어가서 보이는 느낌으로 보이는 것을 알 수 있습니다. 뭔가 불 찌꺼기? 
이게 왜 나타나는지 이유를 생각해 보시고, 해결책을 제시하거나 직접 해결해 보세요! 과제임 
위에서는 설명하지 않았지만, 그리고 사실 큰 문제는 없지만, 감마 리니어 설정이 틀린게 있습니다. 뭘까요? 알아 맞추면 크게 칭찬해 드리겠습니다. :) 

 

일단 여기까지의 셰이더 파일은 이거입니다 .  잘 안되시는 분은 이 코드를 가지고 응용해 보시길. 

fire2.shader
0.00MB

자 오늘도 힘내서 화이팅!!

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

 

반응형

댓글