Unity Shader-後處理:簡單的顏色調整(亮度,飽和度,對比度)
版權宣告:歡迎轉載,共同進步。請註明出處:http://blog.csdn.net/puppet_master https://blog.csdn.net/puppet_master/article/details/52423905
好久沒堅持寫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);
RenderTexture表示的是渲染紋理,我們渲染物體並不是僅僅渲染在螢幕空間,也可以將物體渲染到特定紋理上,也就是RenderTexture。sourceTexture就是我們渲染的場景圖片,而destTexture是目標渲染紋理。我們可以在這個函式中進行相關的後處理效果,使用帶有後處理效果shader的材質將場景內容重新渲染。
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);
source是源紋理,dest是目標紋理,當dest為null時,直接將源紋理拷貝到螢幕;mat是拷貝時使用的材質,也就是我們後處理時使用的材質,Unity會使用該材質將源紋理進行處理拷貝給目標紋理,pass是使用的材質shader所使用的pass,我們知道,一個shader中可能有多個pass,使用哪個pass進行處理就可以從該引數傳入,當然,預設為-1時表示所有的pass都會執行。
在瞭解了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,場景如下所示:
調整亮度的影象效果如下:
調整對比度效果如下:
調整飽和度的情況如下圖: