Unity Shader - HSV 和 RGB 的相互转换

本文深入探讨HSV和HSL色彩模型,解释其与RGB模型的区别,介绍色相、饱和度、明度的概念,并提供HSV与RGB之间的转换算法。通过Unity Shader实例,展示HSV模型在不同坐标系下的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

对于颜色值,RGB 可能是我们接触最多的颜色模型,图像中的任何颜色都是由红色(R)绿色(G)蓝色(B) 这三个通道合成的,这三种颜色可以组合成几乎所有的颜色。

然而,它并不直观,比如我随便说一个rgb值,你能猜到他是什么颜色吗?几乎不可能,所以,后面引入了HSVHSL等颜色模型。
HSV 相对于 RGB 来说 是一种更加直观的颜色模型,HSV更加符合我们人类视觉。

HSL和HSV 概念:

HSL 即色相、饱和度、亮度(英语:Hue, Saturation, Lightness)。

HSV 即色相、饱和度、明度(英语:Hue, Saturation, Value),又称HSB,其中B即英语:Brightness。

  • 色相(H)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。
  • 饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。
  • 明度(V),亮度(L),取0-100%

HSL和HSV色彩空间比较:

二者在数学上都是圆柱,但

HSV 在概念上可以被认为是颜色的倒圆锥体(黑点在下顶点,白色在上底面圆心);

HSL 在概念上表示了一个双圆锥体和圆球体(白色在上顶点,黑色在下顶点,最大横切面的圆心是半程灰色)。

image

在这里插入图片描述

HSV 和 RGB 之间的相互转换

以下函数由国外大神 Iñigo Quiles 提供
https://www.shadertoy.com/view/MsS3Wc

HSB/HSV 转 RGB

// Official HSV to RGB conversion 
vec3 hsv2rgb( in vec3 c )
{
    vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );

	return c.z * mix( vec3(1.0), rgb, c.y);
}
// Smooth HSV to RGB conversion 
// https://www.shadertoy.com/view/MsS3Wc
vec3 hsv2rgb_smooth( in vec3 c )
{
    vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );

	rgb = rgb*rgb*(3.0-2.0*rgb); // cubic smoothing	

	return c.z * mix( vec3(1.0), rgb, c.y);
}

ShaderLab版:

float3 hsb2rgb( float3 c ){
    float3 rgb = clamp( abs(fmod(c.x*6.0+float3(0.0,4.0,2.0),6)-3.0)-1.0, 0, 1);
    rgb = rgb*rgb*(3.0-2.0*rgb);
    return c.z * lerp( float3(1,1,1), rgb, c.y);
}

RGB 转 HSB/HSV

vec3 rgb2hsb( in vec3 c ){
    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    vec4 p = mix(vec4(c.bg, K.wz),vec4(c.gb, K.xy),step(c.b, c.g));
    vec4 q = mix(vec4(p.xyw, c.r),vec4(c.r, p.yzx),step(p.x, c.r));
    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)),d / (q.x + e),q.x);
}

ShaderLab版:

float3 RGB2HSV(float3 c)
{
    float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
	float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), step(c.b, c.g));
	float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), step(p.x, c.r));

	float d = q.x - min(q.w, q.y);
	float e = 1.0e-10;
	return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

实践

下面我们在Unity Shader 中来看看

在笛卡尔坐标系下的SHV

在这里插入图片描述
由上图我们可以清晰的看到

X 轴 决定 色相,Y 轴 决定 饱和度

代码如下:

Shader "lcl/shader2D/HSV"
{
   
    SubShader
    {
        Pass
        {
            CGPROGRAM
            // vert_img 是 UnityCG.cginc 内置的
            #pragma vertex vert_img 
            #pragma fragment frag

            #include "UnityCG.cginc"

            //  该函数由国外大神 Iñigo Quiles 提供
            //  https://www.shadertoy.com/view/MsS3Wc
            float3 hsb2rgb( float3 c ){
                float3 rgb = clamp( abs(fmod(c.x*6.0+float3(0.0,4.0,2.0),6)-3.0)-1.0, 0, 1);
                rgb = rgb*rgb*(3.0-2.0*rgb);
                return c.z * lerp( float3(1,1,1), rgb, c.y);
            }
            
            // ---------------------------【片元着色器】---------------------------
            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 col;
                // hsb 转换为 rgb
                // uv.x  决定 色相, 
                // uv.y  决定 亮度, 
                col.rgb = hsb2rgb(float3(i.uv.x, 1, 1-i.uv.y));
                return col;
            }
            ENDCG
        }
    }
}

在极坐标系下的SHV

在这里插入图片描述
在极坐标系下,我们可以看到,角度 决定 色相, 半径 决定 饱和度, 亮度固定

Shader代码如下:

Shader "lcl/shader2D/HSVInPolarCoordinate"
{
    
    SubShader
    {
        Pass
        {
            CGPROGRAM
            // vert_img 是 UnityCG.cginc 内置的
            #pragma vertex vert_img 
            #pragma fragment frag

            #include "UnityCG.cginc"

            #define TWO_PI 6.28318530718

            //  该函数由国外大神 Iñigo Quiles 提供
            //  https://www.shadertoy.com/view/MsS3Wc
            float3 hsb2rgb( float3 c ){
                float3 rgb = clamp( abs(fmod(c.x*6.0+float3(0.0,4.0,2.0),6)-3.0)-1.0, 0, 1);
                rgb = rgb*rgb*(3.0-2.0*rgb);
                return c.z * lerp( float3(1,1,1), rgb, c.y);
            }
            
            // ---------------------------【片元着色器】---------------------------
            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 col;
                // 笛卡尔坐标系转换到极坐标系
                float2 toCenter = float2(0.5,0.5)-i.uv;
                float angle = atan2(toCenter.y,toCenter.x);
                float radius = length(toCenter)*2.0;

                // 将角度 从(-PI, PI) 映射到 (0,1)范围
                // 角度决定色相, 半径决定饱和度, 亮度固定
                col.rgb = hsb2rgb(float3((angle/TWO_PI)+0.5,radius,1.0));
                return col;
            }
            ENDCG
        }
    }
}

### Unity Shader Graph 中的颜色转换及数值操作 #### 1. 颜色空间转换UnityShader Graph 中,颜色通常表示为 RGBA 向量 (Vector4),其中 R, G, B 表示红绿蓝三个通道的强度,A 表示透明度。为了实现不同颜色空间之间的转换,可以利用内置节点完成线性伽马色彩空间间的切换。 对于从 RGB 到灰度图像的转换,可以通过 `Luminance` 节点来计算亮度值[^1]: ```csharp // 计算给定RGB颜色的亮度 float luminance = dot(rgbColor.rgb, float3(0.299f, 0.587f, 0.114f)); ``` 如果需要执行更复杂的颜色变换,比如 HSL 或 HSV 模型,则需手动编写 HLSL 函数并将其封装成自定义节点加入到 Shader Graph 当中[^2]。 #### 2. 数值运算与控制流 除了基础的颜色混合外,在 Shader Graph 内还可以通过多种方式处理浮点数其他数据类型的变量。常见的有加减乘除四则运算、取模(`Mod`)、绝对值(`Abs`)等基本函数支持;同时也提供了条件分支逻辑的支持,允许创建更加灵活的效果。 例如要实现基于时间变化而改变物体表面属性的功能时,可引入 Time 输入参数配合 Sine (`Sin`) 波形生成器共同作用于材质属性上: ```csharp // 使用正弦波随时间波动调整颜色 half timeFactor = sin(_Time.y * frequency); finalColor *= lerp(baseColor, highlightColor, saturate(timeFactor)); ``` 另外值得注意的是,当涉及到多层嵌套或者复杂表达式的构建时,合理运用 Grouping Node 来组织代码结构有助于提高可读性并减少错误发生几率。 #### 3. 实际应用案例展示 假设现在有一个需求是要在一个球体表面上模拟日出效果,即随着太阳位置升高逐渐由暗变亮直至完全照亮整个场景的过程。此时可以在 Shader Graph 中设置如下流程: - 添加 Directional Light 组件作为光源方向输入; - 构建 Dot Product 节点用于测量光照角度与法向量夹角大小; - 结合 Clamp Smoothstep 对结果做平滑过渡处理得到最终光照系数; - 将此系数应用于 Base Color 属性上来体现明暗差异。 ```csharp // 日照阴影效果简化版伪代码 half ndotl = max(dot(normalDirection, lightDirection), 0.0h); // 获取光线入射角余弦值 half shadowIntensity = smoothstep(shadowThresholdMin, shadowThresholdMax, ndotl); output.Albedo = baseAlbedo * shadowIntensity; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值