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


