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 값입니다.