HLSL 스터디 (스위즐링, 출력 실습)

이미 셰이더 그래프에서 했던 내용이고 상당히 비슷하지만.. 그래프에서 안다뤘던 내용을 위주로 적을 것 같다. 중복해서 적기엔 좀 그러니까..?

 

 

스위즐링

 

셰이더 코드 내에서는 float, half 등등을 쓸 수 있다. 이 값들을 자유롭게 나누고 합치는 등 조작을 한다.

우선 준비된 코드를 보자.

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
        }
    }
}

출처: https://chulin28ho.tistory.com/666 [대충 살아가는 게임개발자:티스토리]

 

 

스위즐링에 대해 다룰거니까 아래쪽 half4 frag() { } 부분을 보자.

코드를 적용시켜보면 붉은색이 나온다.

 

 

여기에서 half4 (0.5, 0, 0, 1) 은

customColor.r == 0.5

customColor.g == 0

customColor.b == 0

customColor.a == 1

로도 나타낼 수 있다.

 

 

여기에서 그치지 않고

customColor.rg == float2(0.5, 0)

customColor.gb == float2(0, 0)

customColor.ba == float2(0, 1)

로 나타낼 수 있다.

이걸 보니 float1은 그냥 숫자만 쓰는건가보다

 

 

이제 이 요소들을 가지고 스위즐링을 해볼건데, 점(.)을 찍고 내부에 있는 요소들을 적어서 새로운 float를 만드는 것으로 시작해보자.

스위즐링 이름에 맞게 요소들을 뒤바꿔볼까

customColor.gbr == float3(0, 0, 0.5)

 

 

심지어 같은 요소를 반복하는것도 괜찮다고 한다.

customColor.rrr == float3(0.5, 0.5, 0.5)

 

이렇게 점을 찍는 것으로 내부 데이터를 뒤섞는 것을 스위즐링이라고 한다.

 

위에서 사용한 방식을 응용할 수도 있다.

각 float를 쪼개서 변수를 만들고, 다시 합치는 등으로..

 

우선 쪼개는 방법을 알아보자.

예를들어 다음과 같은 half4가 있다고 생각한다.

half4 customColor = half4(0.5, 0, 0, 1);

 

이 코드는 이렇게 나눠서 쓸 수도 있다.

half4 customColor = half4(0.5, 0, 0, 1);
half2 test2Vector = customColor.rg;
half2 test2Vector2 = customColor.ba;

임의의 변수를 만들어서 데이터를 나눠 저장했다.

여기서 크기에 맞게 변수를 저장하는 것이 중요하다. (half2를 사용함)

 

 

 

이렇게 분리한 데이터를 이번엔 반대로 합쳐보자

half4 finalColor = half4(test2Vector, test2Vector2);

 

이렇게 변수는 나눠서 각 데이터들을 숫자처럼 쓸 수 있다.

이걸 응용하면

half4 finalColor = half4(1, 0, test2Vector2.rg );

이와 같이 사용할 수도 있다.

 

 


 

 

출력실습

 

 

스위즐링으로 각 데이터들을 합치는 방법을 배웠다. 이것을 이용해서 각각의 데이터들을 입력하여 색을 출력해보자.

 

 

먼저 색을 출력하기 위한 인터페이스를 만든다.

코드의 properties{ } 부분에 float를 사용하여 인터페이스를 만들어주자.

아 참고로 나는 visual studio를 사용하는데 대마왕님이 작성하신 코드 그대로 써도 자꾸 빨간줄이 나타나서 뭐가 문제였나 싶었는데.. vsCode에서는 아무 문제 없이 잘 실행되고 심지어 vs에서도 빨간줄만 생길 뿐 실행 자체에는 문제가 없더라..

그냥 코드편집기 문제였던것을 1시간동안 머리싸매고 블로그나 gpt한테 엄청 물어봤다 ㅠㅠ

 

 

아무튼 적용해보면

오오 ㅋㅋㅋ 왠지 그래프로 만든것보다 코드가 훨씬 신기하다. 벌써 재미있다.

 

 

지금 상태는 인터페이스 창에 나타나기만 한 것이고 아직 우리가 원하는 출력과 연결은 안됐다.

연결하기 위해 우선

1. Pass {  } 안에 properties에서 선언한 변수와 같은 이름을 가진 변수를 선언한다.

2. 그리고 이렇게 선언한 변수를 출력과 연결한다. 어디서? 마지막에 있는 half4 frag() 에서

좋다. 잘 작동하고 있다.

 

 

밝기 조절하기

이미 그래프에서 보았듯이, 색은 더해줄수록 밝아진다. 밝기 조절 기능 역시 위에서 선언한 색깔들을 모조리 더해주는 기능의 프로퍼티를 새로 만들면 될 것 같다.

finalColor.rgb += _Brightness;

 

만약 float의 값이 1을 넘어가거나 0보다 작아지면 어떻게 되는건가? 에 대한 답은 이미 셰이더 그래프쪽에서 공부했다. 어차피 지금 당장 모니터 상에선 별 문제없이 나올 것이지만, 나중에 더 복잡한 연산을 하게되면 반드시 문제를 일으키므로 범위를 제한해주는 함수 saturate와 clamp를 배웠다.

이 함수들의 코드로의 표현은

return saturate(finalColor); 와 return clamp(finalColor, 0, 1); 로 작성한다.

 

clamp 함수의 사용법은 아래와 같다.

clamp(값 , 값이 이보다 작으면 이 숫자로 고정, 값이 이보다 크면 이 숫자로 고정)
saturate(값)

 

 

 

노드와 달리 코드에서 새로 배우는 것이 있다.

SRP Batching // Scriptable Render Pipeline (SRP)

이 기능 덕분에 URP가 기존 레거시보다 훨씬 빨라졌다고 한다.

(노드는 자동으로 배치함)

 

SRP batcher에 대한 유니티 원문

https://blog.unity.com/kr/engine-platform/srp-batcher-speed-up-your-rendering

 

SRP Batcher로 렌더링 속도 개선 | Unity Blog

왼쪽은 기본 SRP 렌더링 루프이며, 오른쪽은 SRP Batcher 루프입니다. SRP Batcher의 컨텍스트에서 “배치”는 “바인드”, “드로우”, “바인드”, “드로우”… GPU 커맨드의 시퀀스에 불과합니다.

blog.unity.com

 

 

요약하자면, '셰이더의 변수를 GPU 메모리에 올려놓고 처리하기 때문에, 셰이더만 같으면 한번에 처리 가능한 기술'이다.

바꿔말하면 ' 비슷한 속성을 가진 여러 개체를 하나의 배치로 결합하여 GPU에 대한 호출 수를 줄이는 기술'로 말할 수도 있다.

일반적으로 게임에서 렌더링될 때, 비슷한 속성을 가진 개체들은 그룹화되어 렌더링된다. 예를 들어, 동일한 재질을 사용하는 여러 개의 개체가 있다면, SRP batcher는 이러한 개체들을 하나의 배치로 결합하여 GPU 호출 수를 줄이고 렌더링 성능을 최적화한다.

 

이 말은

일반적으로 Unity의 SRP에서는 재질(Material)이 동일해야만 배칭 처리가 가능했지만, URP의 SRP Batcher에서는 동일한 셰이더를 사용하는 경우에도 배칭 처리가 가능하다는 뜻이다.

즉, 셰이더가 동일하다면 메터리얼 속성이 다르더라도 셰이더가 요구하는 속성에 대한 정보만으로 배칭 처리를 수행할 수 있다는 말이다.

 

근데 이게 또 전부 배치하는게 아니라 전체 프로세스 중 무거운 부분인 렌더 상태 (Render state)만 GPU안에서 배칭시켜서 처리한다.

 

 

그래서 대체 어디에서 실행시키는건데..

우리가 만든 셰이더를 선택해보면 인스펙터 창에 이런 표시를 볼 수 있다.

사용할 수 없단다.

 

솔직히 개념 부분도 그렇고 좀 어려웠는데 갑자기 not compatible 하니까 쫄았다.

근데 생각보다 별 거 아니었다.

그냥 아까 Pass(){ } 부분에서 선언했던 변수들을 C Buffer로 감싸주면 끝이다.

 

그럼 유니티의 SRP Batcher 부분이 compatible로 바뀐 것을 볼 수 있다.