[Unity] URP 셰이더 HLSL 텍스쳐 적용

2023. 6. 9. 17:00Unity

728x90
반응형

HLSL의 변수

float4 testVar; //초기 값 없이 변수만 선언
float4 testVar = float4(1,2,3,4); // 초기 값과 함께 변수 선언

 

 

float2 newVar1 = testVar.xy;
float2 newVar2 = testVar.zw;
float3 newVar3 = testVar.zxy;

위와 같이 구성 성분을 조합해서 사용 가능하다.

 

 

testVar.xygb //Error
testVar.xx = float(1, 2); //Error (xx가 문제)

xyzw 방식과 rgba 방식을 혼용할 수 없다.

 

 

HLSL의 함수

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

위의 예시로 함수에 대해서 알아보자.

 

 

half4 frag(Varyings IN) : SV_Target
{
    half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
    color *= _BaseColor;
    
    half var = 0.4;
    color.rgb = color.rgb - var;
    color.rgb = saturate(color.rgb / (1 - var));
    
    return color;
}

세 줄의 코드가 추가되었다. var의 값을 선언한 후 기존 color.rgb의 그래프에서 x 절편으로 0.4만큼 이동시킨 후 기울기를 1 / 0.6 = 1.66 만큼 주었다.

 

 

위의 내용을 함수로 만들어보자.

half3 BlackOffset(half3 color, half var)
{
    half3 outColor = color.rgb - var;
    outColor.rgb = saturate(outColor.rgb / (1 - var));
    return outColor;
}

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

    color.rgb = BlackOffset(color.rgb, 0.4);

    return color;
}

frag 함수 안에서 함수를 정의할 수 없어서 밖에서 BlackOffset 함수를 정의했다. 바로 위에 추가된 세 줄을 함수로 표현한 것이다. BlackOffset 함수가 한 번만 사용되더라도 내용이 길다면 frag 함수의 가독성을 위해 위와 같이 분리하기도 한다.

인수 이름은 매개변수 이름과 달라도 되지만 순서와 개수가 같아야 한다.

 

 

half3 BlackOffset(in half3 color, in half var)

사실 매개변수 부분에 "in"이 생략되어 있었으며 생략하면 기본값으로 "in"이 적용된다.

  • in: 읽기 전용
  • out: 쓰기 전용
  • inout: 읽고 쓰기 가능, Pass by reference

매개변수를 읽기 전용으로만 사용하는 것은 함수의 입력 값에 절대 변화가 없고 출력 값과 분리된다는 확신을 줌으로써 함수를 사용할 때 안심하고 사용하게 된다. 하지만 반환 값이 하나만 가능하다는 것이 단점이고(구조체로 여러 값을 반환하기도 함) 때로는 입력 매개변수를 함수 내에서 수정하는 것이 효과적인 경우도 있어서 out이나 inout이 사용되는 경우도 많다.

 

 

void BlackOffset(inout half3 color, in half var)
{
    color = -= var;
    color = saturate(color / (1 - var));
}

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

    color.rgb = BlackOffset(color.rgb, _Var);

    return color;
}

inout에 의해 color 매개변수의 읽기와 쓰기를 모두 허용한 경우이다. 반환 값이 사라져서 void BlackOffset이라고 정의했다.

함수 호출에서 사용했던 0.4 였던 상수는 프로퍼티 변수인 "_Var"로 대체되었다.

 

 

HLSL의 구조체, 시멘틱

struct float4
{
    float x;
    float y;
    float z;
    float w;
};

위는 float4 자료형을 구조체 형태로 정의한 것이다. float4는 기본적으로 제공하는 자료형이라 정의하지 않아도 되긴 한다.

 

 

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

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

밑에서 사용할 텍스쳐를 적용하는 셰이더의 일부이다. 각각 Attributes와 Varyings 자료형을 정의한다. 이 구조체 이름들은 바꿔도 상관없다.(ex) struct Test / 이후 Varyings ver(Test IN)으로 사용) 하지만 관례적으로 사용하므로 그대로 사용하길 권장한다.

 

 

POSITION, TEXCOORD0, SV_POSITION 등 콜론 뒤에 오는 문자들은 시멘틱(Semantics)이다. 구조체의 멤버 변수가 렌더 파이프라인의 어떤 값을 의미하는지 구분할 필요가 있어서 시멘틱을 사용한다.

float2 uv는 렌더 파이프라인의 TEXCOORD0 시멘틱으로부터 전달받은 버텍스의 UV 좌표로 사용된다.

 

 


 

 

텍스쳐 적용

// 이것은 주석입니다. (Comment)
Shader "Custom/ShaderLab HLSL/CustomUnlitColorTexture"
{
    Properties
    {
        [MainTexture] _BaseMap("Base Map", 2D) = "white" {}
        [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1)
    }
    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;
                half4 _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 *= _BaseColor;
                return color;
            }

            ENDHLSL
        }
    }
}

 

 

        [MainTexture] _BaseMap("Base Map", 2D) = "white" {}

텍스쳐 프로퍼티를 선언했다. _BaseMap 프로퍼티 하나만 있으면 중괄호가 없어도 되지만 다음 줄에 _BaseColor 프로퍼티가 이어지는 경우 중괄호가 없다면 에러가 발생한다. 2D는 Texture 2D 타입을 말한다. "white"는 (1, 1, 1, 1) 대신 사용하는 예약어 키워드이다. 키워드를 비우거나 잘못된 값을 입력하면 "gray"로 처리된다.

  • "black" (RGBA: 0,0,0,1)
  • "gray" (RGBA: 0.5,0.5,0.5,1)
  • "bump" (RGBA: 0.5,0.5,1,0.5)
  • "red" (RGBA: 1,0,0,1)

 

 

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

텍스쳐만 샘플링 하지 않고 마지막에 _BaseColor를 곱해서 반환한다.

 

 

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

렌더 파이프라인은 vert 함수가 버텍스 셰이더라는 것을 알고 입력 매개변수인 Attributes IN 변수에 POSITION 정보와 TEXCOORD0 정보를 담아서 vert 함수를 호출하는 것이다.

IN 변수는 TRANSFORM_TEX 함수의 인수로 사용된다. Attributes 구조체의 멤버 변수인 float2 in을 구성 성분으로 사용해서 IN.uv가 되었다.

TRANSFORM_TEX는 문자열을 치환하는 매크로이다. 전 처리 지시어라서 셰이더 컴파일하기 전에 정해진 문자열로 자동으로 치환된다. 관례적으로 대문자를 사용한다.

 

OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); //매크로

OUT.uv = (IN.uv.xy) * _BaseMap_ST.xy + _BaseMap_ST.zw; //실제

매크로를 풀어서 쓰면 아래줄과 같다.

 

 

            TEXTURE2D(_BaseMap); //매크로
            SAMPLER(sampler_BaseMap); //매크로

프로퍼티에서 정의한 2D 텍스쳐를 HLSL에서도 사용한다고 선언하는 부분이다. TEXTURE2D와 SAMPLER 역시 매크로이다. 각각 Texture2D textureName, SamplerState samplerName으로 치환된다. 실제는 아래와 같다.

            Texture2D _BaseMap; //실제
            SamplerState sampler_BaseMap; //실제

 

이름 규칙에 의해 sampler를 레퍼런스 이름 앞에 추가해야한다.

 

 

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

위에서 선언한 텍스쳐와 샘플러는 위와 같이 사용한다. SAMPLE_TEXTURE2D 역시 매크로이다. 대문자로 알아볼 수 있다.

half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv); //매크로

half4 color = _BaseMap.Sample(sampler_BaseMap, IN.uv); //실제

 

 

텍스쳐와 샘플러(https://docs.unity3d.com/kr/current/Manual/SL-SamplerStates.html)

Texture2D _BaseMap;
SamplerState sampler_BaseMap;

half4 color = _BaseMap.Sample(sampler_BaseMap, IN.uv);

텍스쳐와 샘플러를 분리해서 사용하는 방식이다. 매크로 없이 치환해서 표기한 것이다. 분리의 장점은 Direct3D 11의 경우 한 셰이더에 최대 128개의 텍스쳐와 16개의 샘플러를 사용할 수 있다. 텍스쳐가 달라져도 샘플러는 공유가 가능한데 연결된 방식으로 16개 이상의 텍스쳐를 사용하려면 샘플러 숫자 제약에 의해 문제가 발생할 수 있다.

 

 

sampler2D _BaseMap;

half4 color = tex2D(_BaseMap, IN.uv);

텍스쳐와 샘플러가 연결된 방식이다.

 

 


참고자료

 

아티스트를 위한 유니티 URP 셰이더 입문 - YES24

셰이더(Shader)를 HLSL(High Level Shader Language)로 다루고 싶은 아티스트를 위한 입문서다. 아티스트들의 눈높이에 맞추어 기초 그래픽스 이론과 셰이더 개념을 설명하고, 이를 유니티 엔진에서 활용할

www.yes24.com

 

728x90
반응형