好久沒堅持寫blog了,是時候開始擼一波新博文了!學習Unity有一段時間了,關於Shader的書也看了幾本《Unity Shader入門精要》,《Unity 3D ShaderLab 開發實戰詳解》,開一個系列記錄一下學習的心得筆記。原理就不多講了,一篇一個實際Shader樣例就好了。
貌似一開始關於shader的講解都是diffuse,不過,我趕腳后處理貌似更簡單,所以第一篇來一發簡單后處理,屏幕的簡單顏色校正--調整亮度,飽和度,對比度。
一.概念介紹
我們在做游戲的時候,雖然現在有了Unity等引擎,不用我們自己處理一些繁瑣的東西,但是不管怎么樣,最后顯示在屏幕上的還是一些RGB的像素信息,了解這些基本的概念,肯定對我們做游戲有更大的幫助。
1.顏色模型的概念
既然是校正屏幕的顏色,我們有必要了解一下我們要校正的這幾個屬性的概念。這里就不得不提到我們常用的顏色定義方式,RGB顏色模型和HSV顏色模型。
1.1RGB顏色模型
RGB顏色模型也就是我們最常用的三原色,紅綠藍。RGB顏色模型在混色時屬於加法混色,RGB中每種顏色數值越高,色彩越明亮。RBG為(0,0,0)時為黑色,RGB為(255,255,255)時為白色。計算機在處理顏色信息時一般都采用RGB顏色模型,可以很精確地表示某種顏色。
1.2HSV顏色模型
RGB顏色模型對於計算機來說很容易計算,但是並不適合人類理解,於是就有了HSV顏色模型,所謂HSV代表的是Hue(色相),Saturation(飽和度),Value(色調)也有一種說法是HSB模型,B代表的是Brightness(明度)。當然,還有一些說法,比如HSL模型,L代表的是Lightness(亮度)。HSV模型使用一個圓錐形坐標系,頂面對應的V(Value色調)為1,表示顏色較亮,底面的Value為0,表示顏色較暗;而H(Hue色相)是由繞着V軸的旋轉角度給定,從紅色開始逆時針方向計算,紅色對應0度,綠色對應120度,藍色對應240度;S(Saturation飽和度)由模型的半徑來代表,由內向外Saturation逐漸增大,圓心處為0,邊緣處為1。下圖是HSV顏色模型的圖示:

1.3RGB顏色模型和HSV顏色模型的轉化
既然兩種顏色模型都可以表示顏色,那么兩者之間一定有某種轉化關系;
RGB轉化到HSV模型:
假設RGB分別用(r,g,b)代表,其中r,g,b分別為0-1之間的實數;max為r,g,b中最大值,min為最小值;HSV分別用(h,s,v)表示,h為0-360之間實數,而s和v分別為0-1之間的實數,轉化關系如下:



HSV模型轉化到RGB模型:


2.亮度,飽和度,對比度,灰度的概念
2.1亮度
圖像中RGB值的大小,RGB各個值越大,那么亮度越亮,越小,亮度越暗。比如我們要增加亮度,那么直接增加RGB值即可。
2.2飽和度
指的是顏色的純度。一般用彩度除以明度,表征彩色偏離同亮度灰色的程度。簡單來說,當顏色越偏向某個值,即越偏離灰度,飽和度越大;當顏色越偏向灰度,飽和度越小。
下面是百度百科關於飽和度的一段定義:
飽和度是指色彩的鮮艷程度,也稱色彩的純度。飽和度取決於該色中含色成分和消色成分(灰色)的比例。含色成分越大,飽和度越大;消色成分越大,飽和度越小。純的顏色都是高度飽和的,如鮮紅,鮮綠。混雜上白色,灰色或其他色調的顏色,是不飽和的顏色,如絳紫,粉紅,黃褐等。完全不飽和的顏色根本沒有色調,如黑白之間的各種灰色
2.3對比度
指的是一幅圖像中明暗區域最亮的白和最暗的黑之間不同亮度層級的測量,差異范圍越大代表對比越大,差異范圍越小代表對比越小。一般來說對比度越大,圖像越清晰醒目,色彩也越鮮明艷麗;而對比度小,則會讓整個畫面都灰蒙蒙的。
2.4灰度
灰度使用黑色調表示物體,即用黑色為基准色,不同的飽和度的黑色來顯示圖像。 每個灰度對象都具有從 0%(白色)到100%(黑色)的亮度值。
了解了一些基本的色彩概念,我們就可以開始進行處理了。首先我們看一下Unity后處理效果的原理。
二.Unity屏幕后處理原理
所謂屏幕后處理,簡單來說就是渲染流水線的最后階段,對由整個場景生成的一張圖片進行處理,比如HDR,運動模糊等等效果,通過屏幕空間的后處理,可以整體改變整個游戲的風格或者效果。所以,要制作屏幕后處理,我們需要兩樣東西,一個是用於渲染后處理效果的shader,而另一個是我們需要調用這個渲染的腳本,好在Unity為我們提供了相關的功能。
1.OnRenderImage函數
該函數在MonoBehaviour中提供,該函數在所有渲染完成后才進行調用,也就是我們上文所說的生成了一張場景圖片,函數的原型如下:
- void OnRenderImage(RenderTexture sourceTexture,RenderTexture destTexture);
2.Graphics.Blit函數
該函數是Graphics的函數,用於將源紋理拷貝到目標紋理,函數原型如下:
- public static void Blit(Texture source,RenderTexture dest);
- public static void Blit(Texture source,RenderTexture dest, Material mat, int pass = -1);
- public static void Blit(Texture source,Material mat, int pass = -1);
在了解了Untiy的后處理流程后,我們就可以着手寫我們的亮度對比度飽和度調整后處理了。
三.后處理效果代碼
后處理效果需要兩部分,分別是腳本部分和shader部分,我們分別來看一下。
1.腳本部分
后處理腳本主要做的是兩件事,第一件是獲取需要的shader,生成材質,第二件是通過OnRenderImage使用材質處理屏幕效果。第一步具有一些普遍性,不管是什么后處理效果,都要有這一步相同的操作,所以我們將該步驟抽離出來,創建一個后處理效果的基類PostEffectBase,代碼如下:
- using UnityEngine;
- using System.Collections;
- //非運行時也觸發效果
- [ExecuteInEditMode]
- //屏幕后處理特效一般都需要綁定在攝像機上
- [RequireComponent(typeof(Camera))]
- //提供一個后處理的基類,主要功能在於直接通過Inspector面板拖入shader,生成shader對應的材質
- public class PostEffectBase : MonoBehaviour {
- //Inspector面板上直接拖入
- public Shader shader = null;
- private Material _material = null;
- public Material _Material
- {
- get
- {
- if (_material == null)
- _material = GenerateMaterial(shader);
- return _material;
- }
- }
- //根據shader創建用於屏幕特效的材質
- protected Material GenerateMaterial(Shader shader)
- {
- if (shader == null)
- return null;
- //需要判斷shader是否支持
- if (shader.isSupported == false)
- return null;
- Material material = new Material(shader);
- material.hideFlags = HideFlags.DontSave;
- if (material)
- return material;
- return null;
- }
- }
然后,我們以后所有的后處理效果腳本都可以繼承該類PostEffectBase,就都自動具有了通過shader生成后處理材質的功能。
接下來就是我們這一篇的亮度,飽和度,對比度調整的腳本,腳本很簡單,主要的功能就在於設置幾個參數,覆寫OnRenderImage函數后將參數實時傳入shader,然后通過Blit函數完成后處理效果,代碼如下:
- using UnityEngine;
- using System.Collections;
- //繼承自PostEffectBase
- public class ColorAdjustEffect : PostEffectBase {
- //通過Range控制可以輸入的參數的范圍
- [Range(0.0f, 3.0f)]
- public float brightness = 1.0f;//亮度
- [Range(0.0f, 3.0f)]
- public float contrast = 1.0f; //對比度
- [Range(0.0f, 3.0f)]
- public float saturation = 1.0f;//飽和度
- //覆寫OnRenderImage函數
- void OnRenderImage(RenderTexture src, RenderTexture dest)
- {
- //僅僅當有材質的時候才進行后處理,如果_Material為空,不進行后處理
- if (_Material)
- {
- //通過Material.SetXXX("name",value)可以設置shader中的參數值
- _Material.SetFloat("_Brightness", brightness);
- _Material.SetFloat("_Saturation", saturation);
- _Material.SetFloat("_Contrast", contrast);
- //使用Material處理Texture,dest不一定是屏幕,后處理效果可以疊加的!
- Graphics.Blit(src, dest, _Material);
- }
- else
- {
- //直接繪制
- Graphics.Blit(src, dest);
- }
- }
- }
1.可以通過[Range(min,max)]來控制Inspector面板中的值,限定其范圍,並提供滑動條控制。
2.OnRenderImage函數每幀渲染完全部內容后執行,我們在每一幀設置Material的各項參數,通過Material.SetXXX("name",value)可以向shader中傳遞各種參數。
3.各種后處理效果可以疊加,這里的dest並不一定就是屏幕。不過后處理是很耗費性能的,一方面是pixel shader全屏幕overdraw,另一方面,一個rendertexture的內存占用很大,尤其是大分辨率手機上,多個后處理效果可能造成內存耗盡,程序崩潰。
2.shader部分
終於步入正題了,下面看一下后處理效果的shader。由於后處理效果是對於一個場景的渲染圖進行處理,所以vertex shader基本沒有什么好說的,大部分后處理都是基於pixel shader。先整理一下思路:
最簡單的是亮度,我們可以直接在采樣texture后乘以一個系數,達到放大或者縮小rgb值的目的,這樣就可以調整亮度了。
其次是飽和度,飽和度是離灰度偏離越大,飽和度越大,我們首先可以計算一下同等亮度條件下飽和度最低的值,根據公式:gray = 0.2125 * r + 0.7154 * g + 0.0721 * b即可求出該值(公式應該是一個經驗公式),然后我們使用該值和原始圖像之間用一個系數進行差值,即可達到調整飽和度的目的。
最后是對比度,對比度表示顏色差異越大對比度越強,當顏色為純灰色,也就是(0.5,0.5,0.5)時,對比度最小,我們通過在對比度最小的圖像和原始圖像通過系數差值,達到調整對比度的目的。代碼如下:
- //shader的目錄
- Shader "Custom/ColorAdjustEffect"
- {
- //屬性塊,shader用到的屬性,可以直接在Inspector面板調整
- Properties
- {
- _MainTex ("Albedo (RGB)", 2D) = "white" {}
- _Brightness("Brightness", Float) = 1
- _Saturation("Saturation", Float) = 1
- _Contrast("Contrast", Float) = 1
- }
- //每個shader都有Subshaer,各個subshaer之間是平行關系,只可能運行一個subshader,主要針對不同硬件
- SubShader
- {
- //真正干活的就是Pass了,一個shader中可能有不同的pass,可以執行多個pass
- Pass
- {
- //設置一些渲染狀態,此處先不詳細解釋
- ZTest Always Cull Off ZWrite Off
- CGPROGRAM
- //在Properties中的內容只是給Inspector面板使用,真正聲明在此處,注意與上面一致性
- sampler2D _MainTex;
- half _Brightness;
- half _Saturation;
- half _Contrast;
- //vert和frag函數
- #pragma vertex vert
- #pragma fragment frag
- #include "Lighting.cginc"
- //從vertex shader傳入pixel shader的參數
- struct v2f
- {
- float4 pos : SV_POSITION; //頂點位置
- half2 uv : TEXCOORD0; //UV坐標
- };
- //vertex shader
- //appdata_img:帶有位置和一個紋理坐標的頂點着色器輸入
- v2f vert(appdata_img v)
- {
- v2f o;
- //從自身空間轉向投影空間
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- //uv坐標賦值給output
- o.uv = v.texcoord;
- return o;
- }
- //fragment shader
- fixed4 frag(v2f i) : SV_Target
- {
- //從_MainTex中根據uv坐標進行采樣
- fixed4 renderTex = tex2D(_MainTex, i.uv);
- //brigtness亮度直接乘以一個系數,也就是RGB整體縮放,調整亮度
- fixed3 finalColor = renderTex * _Brightness;
- //saturation飽和度:首先根據公式計算同等亮度情況下飽和度最低的值:
- fixed gray = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
- fixed3 grayColor = fixed3(gray, gray, gray);
- //根據Saturation在飽和度最低的圖像和原圖之間差值
- finalColor = lerp(grayColor, finalColor, _Saturation);
- //contrast對比度:首先計算對比度最低的值
- fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
- //根據Contrast在對比度最低的圖像和原圖之間差值
- finalColor = lerp(avgColor, finalColor, _Contrast);
- //返回結果,alpha通道不變
- return fixed4(finalColor, renderTex.a);
- }
- ENDCG
- }
- }
- //防止shader失效的保障措施
- FallBack Off
- }
四.效果展示
完成shader和后處理腳本后,我們可以創建一個場景,在場景的MainCamera下掛在上該腳本,然后把ColorAdjustEffect的shader賦給腳本的shader槽,如下圖所示:

首先將亮度,對比度,飽和度都置為1,場景如下所示:

調整亮度的圖像效果如下:

調整對比度效果如下:

調整飽和度的情況如下圖:

版權聲明:歡迎轉載,共同進步。請注明出處:http://blog.csdn.net/puppet_master https://blog.csdn.net/puppet_master/article/details/52423905