unity的渐变材质没有现成的,百度了一下基本上都是两色渐变,使用lerp()做线性插值。但是我要实现色轮,红-黄-绿-蓝这种光谱的渐变,想了一下使用HSV颜色模型最适合了。
这里要先介绍一下HSV的颜色模型, 我们最熟悉的是RGB(red,green,blue)颜色空间,各个分量的数值越小,亮度越低。数值越大,亮度越高;如:(0,0,0)表示黑色,(255,255,255)表示白色,计算机中最基本的控制也是基于RGB原理,我们的显示器每一个像素都由三个颜色的物理亮点组成,只需要控制三个分量的明暗程度,就能呈现不同的颜色。
和RGB不同的HSV三个分量分别是色相、饱和度、明度(英语:Hue, Saturation, Value),我要的结果是仅仅改变H的值,一般H值得范围是0-359,也就是园的一周。所有实现的思路,是从任何一个初始颜色开始,H值做偏移,经过360度以后,再次回到初始值颜色也就是可见光谱从蓝到红。unity里面Material输入的值是RGB的,所以需要转换成HSV以后做偏移,再换回RGB。
Shader "Tornado/ColorGradationByUV_HSV" {
Properties{
_MainColor("MainColor", color) = (1,0,0,1) //第一种颜色:绿
_SecondColor("SecondColor", color) = (1,0,0,1) //第二种颜色:红
//贴图
_MainTex("MainTex (RGB)", 2D) = "white" {}
//Hue的值范围为0-359. 其他两个为0-1 ,这里我们设置到3,因为乘以3后 都不一定能到超过.
_Hue("Hue", Range(0,359)) = 0
_Saturation("Saturation", Range(0,3.0)) = 1.0
_Value("Value", Range(0,3.0)) = 1.0
_YMinimum("Y-Minimum", range(-180, 180.0)) = 0.0
_YMaximum("Y-Maximum", range(-180, 180.0)) = 0.0
}
SubShader{
Pass {
Tags { "RenderType" = "Opaque" }
LOD 200
Lighting Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
half _Hue;
half _Saturation;
half _Value;
float _YMinimum;
float _YMaximum;
fixed4 _MainColor;
struct v2f {
float4 pos:POSITION;
float3 uv : TEXCOORD0;
};
//RGB to HSV
float3 RGBConvertToHSV(float3 rgb)
{
float R = rgb.x,G = rgb.y,B = rgb.z;
float3 hsv;
float max1 = max(R,max(G,B));
float min1 = min(R,min(G,B));
if (R == max1)
{
hsv.x = (G - B) / (max1 - min1);
}
if (G == max1)
{
hsv.x = 2 + (B - R) / (max1 - min1);
}
if (B == max1)
{
hsv.x = 4 + (R - G) / (max1 - min1);
}
hsv.x = hsv.x * 60.0;
if (hsv.x < 0)
hsv.x = hsv.x + 360;
hsv.z = max1;
hsv.y = (max1 - min1) / max1;
return hsv;
}
//HSV to RGB
float3 HSVConvertToRGB(float3 hsv)
{
float R,G,B;
//float3 rgb;
if (hsv.y == 0)
{
R = G = B = hsv.z;
}
else
{
hsv.x = hsv.x / 60.0;
int i = (int)hsv.x;
float f = hsv.x - (float)i;
float a = hsv.z * (1 - hsv.y);
float b = hsv.z * (1 - hsv.y * f);
float c = hsv.z * (1 - hsv.y * (1 - f));
switch (i)
{
case 0: R = hsv.z; G = c; B = a;
break;
case 1: R = b; G = hsv.z; B = a;
break;
case 2: R = a; G = hsv.z; B = c;
break;
case 3: R = a; G = b; B = hsv.z;
break;
case 4: R = c; G = a; B = hsv.z;
break;
default: R = hsv.z; G = a; B = b;
break;
}
}
return float3(R,G,B);
}
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag(v2f IN) : COLOR//SV_Target
{
float y = IN.uv.y;
float g = lerp(_YMaximum, _YMinimum, y);
float3 colorHSV;
float4 original;
original = _MainColor;
colorHSV.xyz = RGBConvertToHSV(original.xyz); //转换为HSV
colorHSV.x = (g + _Hue) % 360; //调整偏移Hue值
//超过360的值从0开始
colorHSV.y *= _Saturation; //调整饱和度
colorHSV.z = _Value;
original.xyz = HSVConvertToRGB(colorHSV.xyz); //将调整后的HSV,转换为RGB颜色
return original;
}
ENDCG
}
}
FallBack "Diffuse"
}
shader在vertex部分读取了v.texcoord的uv信息,然后输入给fragment 去着色,要是是使用uv的v分量(IN.uv.y)插值得到0-360,把输入颜色转换成HSV,去做偏移,就形成了uv的v方向上的颜色变化。开始我想用物体顶点的Y值直接做驱动,但是发现模型有时候很大,有时候很小,值范围需要去做标准化,也很不方便,还是一键给个UV拉倒了。
(总结:现在的GPU都很厉害,GPU方面的一些禁忌在很多情况下貌似也不突出了,但是像在GPU上执行大量%运算,逻辑判断,应该还是要去避免)