URP_Unlit_Grayscale / Hue Shader – HLSL

Grayscale_URP_Unlit.shader

Shader "Unlit/lycos/Grayscale_URP_Unlit"
{
    Properties
    {
        [MainTexture] _BaseMap("Texture", 2D) = "white" {}
        _GrayscaleIntensity("Grayscale Intensity", Range(0, 1)) = 1.0 // 흑백 효과 강도 (0: 원본 색상, 1: 완전 흑백)
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" "Queue" = "Geometry" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            Name  "Grayscale_URP_Unlit"
            Tags {"LightMode" = "SRPDefaultUnlit"} // 이 Pass는 Unlit(조명이 없는) 모드로 작동함을 선언

            HLSLPROGRAM
            #pragma target 4.5 // Shader 모델 4.5를 대상
            #pragma vertex vert // vertex shader 선언 (vert 함수)
            #pragma fragment frag // fragment shader 선언 (frag 함수)
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            // Constant Buffer 
            // Unity의 각 Material 상수를 정의
            CBUFFER_START(UnityPerMaterial) 
                float4 _BaseMap_ST; // TEXTURE UV 좌표 변환에 사용되는 정보 (offset, tiling)
                float _GrayscaleIntensity; // 흑백 효과 강도
            CBUFFER_END
            TEXTURE2D(_BaseMap);  // TEXTURE2D 선언
            SAMPLER(sampler_BaseMap); // SAMPLER 선언
            // vertex shader의 입력 구조체 
            struct Attributes
            {
                float4 positionOS : POSITION;  // 모델 공간에서의 정점 위치 (float3, float4)
                float2 uv : TEXCOORD0;   // 첫 번째 UV 텍스처 좌표 (float2, float3, float4)
                float4 color : COLOR;  // 정점 별 색상  (float4)
            };
            // Varyings 구조체 정의
            // 출력 구조체로, fragment Shader에서 사용할 데이터(예: 변환된 위치, UV 좌표 등)를 포함
            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 color : COLOR;
            };
            // vertex shader가 Attributes를 사용
            Varyings vert(Attributes input)
            {
                // 출력 변수를 정의
                Varyings output = (Varyings)0; // Varyings 초기화, 모든 필드에 기본값을 할당
                float4 positionOS = input.positionOS; // 모델 공간에서의 정점 위치를 float4 형태로 저장
                float3 positionWS = TransformObjectToWorld(positionOS.xyz); // 모델 공간에서 월드 공간으로의 변환을 수행
                float4 positionCS = TransformWorldToHClip(positionWS); // 월드 공간에서 클립 공간으로의 변환을 수행
                output.positionCS = positionCS; // 변환된 클립 공간 위치를 출력 구조체 output에 저장
                output.uv = input.uv; // 입력으로 받은 UV 좌표를 출력 구조체 output에 저장
                output.color = input.color;
                return output;
            }
            // 프래그먼트 셰이더(fragment shader) 
            // 픽셀 단위로 호출되며, 각 픽셀의 최종 색상을 계산해 화면에 출력
            // 기본적으로 입력된 UV 좌표를 이용해 텍스처 색상을 샘플링하고, 
            // 최종적으로 머티리얼의 색상과 결합하여 픽셀의 색상을 반환하는 역할
            float4 frag(Varyings input) : SV_Target
            {
                // UV 좌표 변환을 위한 계산
                float2 baseMapUV = input.uv.xy * _BaseMap_ST.xy + _BaseMap_ST.zw;
                // SAMPLE_TEXTURE2D 함수를 사용하여 텍스처를 샘플링합니다.
                float4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, baseMapUV);
                // Grayscale 변환
                float gray = dot(texColor.rgb, float3(0.299, 0.587, 0.114));
                // 흑백 효과의 강도를 적용
                // lerp 함수를 사용하여 texColor와 변환된 흑백 색상 사이에서 _GrayscaleIntensity 값을 기반으로 색상을 보간합니다.
                float4 finalColor = lerp(texColor, float4(gray, gray, gray, texColor.a), _GrayscaleIntensity);

                // 최종 계산된 색상을 출력합니다. SV_Target은 이 값이 픽셀 셰이더의 출력으로 화면에 렌더링된다는 것을 의미합니다.
                return finalColor;
            }
            ENDHLSL
        }
    }
}

Grayscale 변환 원리 :

주어진 코드에서 색상을 흑백으로 변환하는 원리는 색상의 밝기(휘도, Luminance)를 계산하는 것에 기반합니다.

인간의 눈은 색상마다 민감도가 다르기 때문에, 각 RGB 채널에 가중치를 부여하여 밝기를 계산합니다.

// Grayscale 변환
float gray = dot(texColor.rgb, float3(0.299, 0.587, 0.114));

RGB를 Grayscale로 변환하는 원리

인간의 눈은 각각의 색상 성분(R, G, B)에 대해 감도가 다르다.

일반적으로 녹색(G) 성분에 가장 민감하고, 빨간색(R) 성분에 중간 정도로 민감하며, 파란색(B) 성분에 가장 덜 민감하다.

따라서, 각 색상 성분에 적절한 가중치를 부여하여 이를 반영한 값을 계산해야 더 자연스러운 Grayscale 변환이 된다.

그래서 RGB 값에서 Grayscale 값을 구하는 공식은 각 성분의 가중치를 적용한 합으로 나타낸다.

이 가중치는 인간의 시각적 감도를 반영한 표준 가중치라고 할 수 있다.

내적(Dot Product) 사용 이유

내적은 두 벡터 간의 대응되는 성분을 곱한 후 더하는 연산입니다.

RGB 값을 각각의 가중치와 곱한 후 더하는 이 과정을 내적을 통해 쉽게 계산할 수 있습니다.

float gray = dot(texColor.rgb, float3(0.299, 0.587, 0.114));

이 공식을 내적 연산을 사용하여 간결하게 표현한 것입니다.

HueAdjustment_URP_Unlit.shader

Shader "Unlit/lycos/HueAdjustment_URP_Unlit"
{
    Properties
    {
        [MainTexture] _BaseMap("Texture", 2D) = "white" {} // 기본 텍스처
        _HueAdjustment("Hue Adjustment", Range(-1, 1)) = 0.0 // 색조 조정 값 (-1: -180도, 1: 180도)
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" "Queue" = "Geometry" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            Name  "HueAdjustment_URP_Unlit"
            Tags {"LightMode" = "SRPDefaultUnlit"} // 이 Pass는 Unlit(조명이 없는) 모드로 작동함을 선언

            HLSLPROGRAM
            #pragma target 4.5 // Shader 모델 4.5를 대상으로 함
            #pragma vertex vert // vertex shader 선언
            #pragma fragment frag // fragment shader 선언
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            // Constant Buffer
            // Unity의 각 Material 상수를 정의
            CBUFFER_START(UnityPerMaterial) 
                float4 _BaseMap_ST; // 텍스처의 UV 좌표 변환 정보 (offset, tiling)
                float _HueAdjustment; // 색조 조정 값
            CBUFFER_END

            TEXTURE2D(_BaseMap);  // 텍스처 선언
            SAMPLER(sampler_BaseMap); // 샘플러 선언

            // Vertex Shader의 입력 구조체
            struct Attributes
            {
                float4 positionOS : POSITION;  // 모델 공간에서의 정점 위치
                float2 uv : TEXCOORD0;   // UV 텍스처 좌표
                float4 color : COLOR;  // 정점 색상
            };

            // Varyings 구조체 정의
            // Fragment Shader에서 사용할 데이터(예: 변환된 위치, UV 좌표 등)를 포함
            struct Varyings
            {
                float4 positionCS : SV_POSITION; // 클립 공간에서의 정점 위치
                float2 uv : TEXCOORD0; // UV 좌표
                float4 color : COLOR; // 색상
            };

            // Vertex Shader
            Varyings vert(Attributes input)
            {
                Varyings output = (Varyings)0; // Varyings 초기화
                float4 positionOS = input.positionOS; // 모델 공간에서의 정점 위치 저장
                float3 positionWS = TransformObjectToWorld(positionOS.xyz); // 모델 공간에서 월드 공간으로 변환
                float4 positionCS = TransformWorldToHClip(positionWS); // 월드 공간에서 클립 공간으로 변환
                output.positionCS = positionCS; // 변환된 클립 공간 위치 저장
                output.uv = input.uv; // UV 좌표 저장
                output.color = input.color; // 색상 저장
                return output;
            }

            float3 ApplyHue(float3 col, float hueAdjust)
            {
                // RGB 색상을 HSV 색상 공간으로 변환
                float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
                float4 P = lerp(float4(col.bg, K.wz), float4(col.gb, K.xy), step(col.b, col.g));
                float4 Q = lerp(float4(P.xyw, col.r), float4(col.r, P.yzx), step(P.x, col.r));
                float D = Q.x - min(Q.w, Q.y);
                float E = 1e-10; // 작은 값 추가
                float3 hsv = float3(abs(Q.z + (Q.w - Q.y)/(6.0 * D + E)), D / (Q.x + E), Q.x);

                // 색조 조정
                float hue = hsv.x + hueAdjust;
                hsv.x = (hue < 0)
                        ? hue + 1
                        : (hue > 1)
                            ? hue - 1
                            : hue;

                // HSV 색상을 RGB 색상으로 변환
                float4 K2 = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
                float3 P2 = abs(frac(hsv.xxx + K2.xyz) * 6.0 - K2.www);
                float3 result = hsv.z * lerp(K2.xxx, saturate(P2 - K2.xxx), hsv.y);
                return result;
            }

            // Fragment Shader
            float4 frag(Varyings input) : SV_Target
            {
                // UV 좌표 변환
                float2 baseMapUV = input.uv.xy * _BaseMap_ST.xy + _BaseMap_ST.zw;
                // 텍스처 샘플링
                float4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, baseMapUV);
                // 색조 조정
                float3 hueAdjustedColor = ApplyHue(texColor.rgb, _HueAdjustment);
                // 최종 색상 반환
                float4 finalColor = float4(hueAdjustedColor, texColor.a);

                // 최종 계산된 색상을 출력
                return finalColor;
            }
            ENDHLSL
        }
    }
}

1. RGB to HSV 변환 (RGB 색상을 HSV 색상 공간으로 변환)

float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
float4 P = lerp(float4(col.bg, K.wz), float4(col.gb, K.xy), step(col.b, col.g));
float4 Q = lerp(float4(P.xyw, col.r), float4(col.r, P.yzx), step(P.x, col.r));
float D = Q.x - min(Q.w, Q.y);
float E = 1e-10; // 작은 값 추가
float3 hsv = float3(abs(Q.z + (Q.w - Q.y) / (6.0 * D + E)), D / (Q.x + E), Q.x);

입력 변수: col은 RGB 색상을 나타내는 float3 값입니다. 이 색상을 HSV로 변환하는 과정입니다.

K: 색상 변환에 필요한 상수 값입니다. 다양한 색상 조합을 비교할 때 사용됩니다.

P, Q: lerp(선형 보간) 함수와 step 함수를 사용하여 색상 성분을 비교하고 보간합니다. 이를 통해 RGB에서 가장 큰 값과 작은 값을 찾고, 색상 성분을 적절히 섞어 색상 차이 D와 관련된 값을 계산합니다.

D: RGB 색상의 최댓값과 최솟값의 차이를 나타내며, 이것은 채도(Saturation)를 결정하는 값입니다.

hsv.x: 색상(Hue)을 계산하기 위한 값입니다. RGB에서 Hue는 특정 범위의 계산을 통해 구해지며, 이 값은 색상 톤을 나타냅니다.

hsv.y: 채도(Saturation)를 계산한 값입니다.

D를 RGB에서의 최댓값과 나누어 채도를 구합니다.

hsv.z: 명도(Value)로, RGB 색상에서의 최댓값이 됩니다.

2. 색조 조정 (Hue Shift)

float hue = hsv.x + hueAdjust;
hsv.x = (hue < 0) ? hue + 1 : (hue > 1) ? hue - 1 : hue;

hueAdjust: 함수의 인자로 전달된 색조 변경 값입니다.

hsv.x + hueAdjust: 기존의 색조에 새로운 색조 조정 값을 더해 색조를 변경합니다.

hsv.x: Hue는 0에서 1 사이의 값으로 표현되므로, hue 값이 0보다 작으면 1을 더하고, 1보다 크면 1을 빼서 순환시킵니다. 이는 색조의 값을 항상 0~1 범위 내에 유지시키기 위한 작업입니다.

3. HSV to RGB 변환 (HSV를 다시 RGB로 변환)

float4 K2 = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 P2 = abs(frac(hsv.xxx + K2.xyz) * 6.0 - K2.www);
float3 result = hsv.z * lerp(K2.xxx, saturate(P2 - K2.xxx), hsv.y);

K2: HSV에서 RGB로 변환할 때 사용되는 상수 값입니다.

frac(hsv.xxx + K2.xyz): 이 계산을 통해 hsv.x에 기반한 색상 조합을 생성합니다.

P2: 색상을 변환할 때 필요한 중간 계산으로, 색조(Hue) 정보를 바탕으로 RGB로 변환하는데 필요한 보간을 수행합니다.

lerp: 보간을 사용하여 최종 RGB 값에 대한 보정을 수행합니다. hsv.y는 채도, hsv.z는 명도에 해당하는 값을 적용하여 최종적인 색상 값을 구합니다.

result: 변환된 최종 RGB 값입니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤