Unity Shader之色彩变换

发表于2018-10-05
评论7 1.32w浏览
先来看一个特效:
这个效果是几年前我在Shadertoy看到的一位大神写的Shader特效,里面没有用到任何额外的纹理贴图,纯用Shader编写出来的。我把他翻译到了Unity中,想研究的同学可以点击连接获取完成的Unity工程 Github

在开始本文具体案例之前下面先做些基础的科普,便于还没入坑的同学对Shader编程有个全局的了解。

为什么出现了shader编程?这个要从渲染管线开始说起:

渲染管线

一般分为固定渲染管线和可编程渲染管线。固定管线因为功能固定,无法在程序上对物体细节的表现给予更多更自由的控制,无法达到更多更炫酷的效果。为了解决这个问题,可编程的渲染管线就诞生了,见下图:

具体的可编程的部分就是上图中两个橙色的节点:Vertex Shader(顶点着色)和 Fragment Shader(片元着色)。

了解了Shader在渲染管线中的工作位置,那么我们在看看Unity中如何进行Shader开发呢?

Unity3D Shader

Unity的Shader有四种:

  1. Fixed function shader 
    属于固定渲染管线Shader, 基本用于高级Shader在老显卡无法显示时的备用Shader。

  2. Vertex and Fragment Shader 
    最强大的Shader类型,属于可编程渲染管线。使用的是CG/HLSL语言。也就是我上面说过的两种。

  3. Surface Shader 
    Unity3d推荐的Shader类型。它是一个代码生成器,帮我们将重复的代码省去了,使得编写Shader更为容易。使用的也是CG/HLSL语言。

  4. Compute Shader 
    可直接将GPU作为并行处理器加以利用,GPU将不仅具有3D渲染能力,也具有其他的运算能力。


基本概念粗略的过完,下面我们来做一个比较常用的Shader特效:
游戏开发经常会用到一些颜色上的变化来标示当前的状态。例如:
中毒全是泛绿:
植物大战僵尸中冰冻缓速僵尸色彩变为 蓝色 的效果:

尤其是当年FC时代的游戏,因为内存的不足,用不同色调来标示不同的角色这方面用到了极致:
具体这些效果如何用Shader实现呢?
在撸代码之前先要介绍几个概念:

色彩是人的眼睛对于不同频率的光线的不同感受,色彩既是客观存在的(不同频率的光)又是主观感知的,有认识差异。计算机领域应用最多的是RGB,尤其是程序中处理但是美术处理和工业应用领域应用更多的是HSV或HSL(举个大家最熟悉的例子:打开电视里面的色彩设置就会看到色相,饱和度,明亮度的设置)


RGB(Red,Green,Blue) 

就是绿组成的三维坐标系:


HSV(Hue色相,Saturation饱和度,Value色调)

色相,饱和度,色调组成的倒立的圆锥体:

HSL(Hue色相,Saturation饱和度,Lightness亮度)

HSL 类似于 HSV。对于一些人,HSL 更好的反映了“饱和度”和“亮度”作为两个独立参数的直觉观念,但是对于另一些人,它的饱和度定义是错误的,因为非常柔和的几乎白色的颜色在 HSL 可以被定义为是完全饱和的。对于 HSV 还是 HSL 更适合于人类用户界面是有争议的(这个不在本文的讨论范围内,就不在深入了)


HSV和HSL的对比图:


色彩相关的概念介绍完毕,下面在Unity中新建一个Shader

Shader "Custom/HSLShader"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_DH("Hue",Range(0,360)) = 0
		_DS("Saturation",Range(-1,1)) = 0
		_DL("Lightness",Range(-1,1)) = 0
	}
	SubShader
	{
		// No culling or depth
		// Cull Off ZWrite Off ZTest Always
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};
			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};
			sampler2D _MainTex;
			float _DH;
			float _DS;
			float _DL;
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				float r = col.r;
				float g = col.g;
				float b = col.b;
				float a = col.a;
				float h;
				float s;
				float l;
				float maxv = max(max(r,g),b);
				float minv = min(min(r,g),b);
				if (maxv == minv){
					h = 0.0;
				} else if (maxv == r && g >= b){
					h = 60.0*(g-b)/(maxv-minv)+0.0;
				} else if (maxv == r && g < b ){
					h = 60.0*(g-b)/(maxv-minv)+360.0;
				} else if (maxv == g){
					h = 60.0*(b-r)/(maxv-minv)+120.0;
				} else if (maxv == b){
					h = 60.0*(r-g)/(maxv-minv)+240.0;
				}
				l = 0.5*(maxv+minv);
				if (l == 0.0 || maxv == minv){
					s = 0.0;
				} else if (0.0 <= l && l <= 0.5){
					s = (maxv-minv)/(2.0*l);
				} else if (l > 0.5){
					s = (maxv-minv)/(2.0-2.0*l);
				}
				h = h + _DH;
				s = min(1.0,max(0.0,s+_DS));
				l = l + _DL;
				// final color
				float q;
				if (l < 0.5){
					q = l*(1.0+s);
				}else if (l >= 0.5){
					q = l+s-l*s;
				}
				float p = 2.0*l-q;
				float hk = h/360.0;
				float t[3];
				t[0] = hk+1.0/3.0;
				t[1] = hk;
				t[2] = hk-1.0/3.0;
				for(int i=0;i<3;i++){
					if (t[i] < 0.0){
						t[i] += 1.0;
					}else if (t[i] > 1.0){
						t[i] -= 1.0;
					}
				}
				float c[3];
				for (int i=0;i<3;i++){
					if (t[i] < 1.0/6.0){
						c[i] = p+((q-p)*6.0*t[i]);
					}else if (1.0/6.0 <= t[i] && t[i] < 0.5){
						c[i] = q;
					}else if (0.5 <= t[i] && t[i] < 2.0/3.0){
						c[i] = p+((q-p)*6.0*(2.0/3.0-t[i]));
					}else{
						c[i] = p;
					}
				}
				fixed4 finalColor = fixed4(c[0],c[1],c[2],a);
				finalColor += fixed4(_DL,_DL,_DL,0.0);
				return finalColor;
			}
			ENDCG
		}
	}
}
然后新建一个Material:
将这个Material的Shader设置为刚新建的“HSLShader”,然后在Inspector中就会看到Hue,Saturation,Lightness三个属性:

新建一个Scene来测试我们的Shader,场景中放置个Sprite:
下面我们来调节材质Inspector中的Hue、Saturation、Lightness来看看对应的效果:

Hue 从0~360°的变化图:
Saturation 为-1(黑白)和1(艳丽)时的效果图:
Lightness 为0.2时的效果图:

好了,看到这相信大家对色彩变换应该有了直观的印象。对具体的实现感兴趣的同学可以点击链接下载

本文参与“Unity Shader”征文活动

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: