【Unity Shaders】遊戲性和畫面特效——建立一個老電影式的畫面特效,unityshaders
本系列主要參考《Unity Shaders and Effects Cookbook》一書(感謝原書作者),同時會加上一點個人理解或拓展。
這裡是本書所有的插圖。這裡是本書所需的代碼和資源(當然你也可以從官網下載)。
========================================== 分割線 ==========================================
寫在前面
終於到了本書的最後一章了,好激動有木有!作為壓軸章,雖然只有兩篇,但每篇的內容是比之前的任何一篇都要複雜。寫完這章要去總結一下啦~那麼,開始學習吧!
學習這本書的人絕大部分在業餘時間玩過一兩個遊戲。那麼,你肯定有感觸,即時遊戲的一個很重要的特性就是要讓玩家有種身臨其境的感覺。越是現代的遊戲,越是使用了更多的畫面特效來達到這種沉浸感。
使用畫面特效,只需通過改變遊戲畫面,我們就可以把某個環境的氛圍烘托成冷靜到恐怖各個層次。想象我們在某個關卡中走入一個房間,然後遊戲接管,開始播放過場動畫。許多現代遊戲都是用了不同的畫面特效來改變當前時刻的氣氛。而理解怎樣建立這些在遊戲性中被觸發的畫面特效將是我們下面要完成的工作。
在本章中,我們將會學習一些常見的遊戲畫面特效。這些包括,如何把一個正常的畫面改變成一個老電影式的畫面效果,在許多第一人稱射擊遊戲中如何在螢幕上應用夜視效果(night vision effects)。
首先,我們來學習如何建立一個老電影式的畫面特效。
遊戲往往會建立在不同的背景時間上。一些發生在想象的世界中,一些發生在未來世界中,還有一些甚至發生在古老的西方,而那時候電影攝像機才剛剛發展起來,人們看到的電影都是黑白的,有時還會呈現出復古色調(a sepia effect,Unity Pro中有內建的指令碼和Shader)的著色效果。這種效果看起來非常獨特,我們將在Unity中使用畫面特效來重現這種效果。
實現這個效果需要一些步驟。我們先來分析一下下面的映像,然後分解製作這樣老電影視覺的步驟:
上面的映像實際是有一系列從網上找到的圖片組合起來實現的。我們可以利用Photoshop來建立這樣風格的圖片,來協助你完成畫面特效的草圖。進行這樣的過程(在Photoshop裡製作原型)不僅可以告訴我們需要哪些元素,還可以快速讓我們知道應該使用哪些混合模式,以及如何構建螢幕特效的圖層(layers)。不過作者說的Photoshop源檔案我沒有找到。。。
本文最後實現的效果大概就是下面這樣啦:
而原始的畫面是:
準備工作
現在讓我們來看一下每一個圖層是如何被組合在一起從而建立出最後的效果的,以便我們為Shader和指令碼準備下所需的資源。
- 復古色調(Sepia Tone):這種效果是比較容易是建立的,我們只需要從原始的render texture中把所有像素色彩轉換到一個單一的色彩範圍即可。這可以通過使用原始映像的光度(luminance)加上一個常量顏色值來實現。我們第一個圖層看起來像下面這樣:
- 暈影效果(Vignette effect):我們總是可以看到,當使用老的電影投影機把老電影投影到螢幕上時,總有些模糊的邊框。這是因為,老式投影儀使用的燈泡在中央的亮度高於四周的亮度。這種效果通常被稱為暈影效果(Vignette Effect),而這正是我們螢幕特效的第二個圖層。我們可以使用一張疊加的紋理覆蓋在整個螢幕上來達到這種效果。下面的映像展示了這個圖層單獨看起來的樣子:
- 灰塵(Dust)和劃痕(Scratches):最後一層圖層就是灰塵(Dust)和劃痕(Scratches)了。這個圖層利用了兩張不同的平鋪(tiled)紋理,一個用於灰塵,一個用於劃痕。使用它們的原因是因為我們想要使用不同的平鋪速率,按時間來移動這兩張紋理。由於老電影的每一幀通常都會出現一些小的劃痕和灰塵,這使得整個畫面看起來像電影正在放映。下面的圖片展示了這個圖層單獨看起來的效果:
上面是分析了Photoshop裡面各圖層的樣子和實現。現在,我們來使用上述紋理在Unity裡正式實現我們的畫面特效!
實現
我們的老電影式的畫面特效中的每一個獨立圖層實際都很簡單,但是,當我們把它們整合在一起我們就可以得到非常震撼的效果。現在你的畫面特效指令碼系統應該已經建立好了,現在我們來實現具體的指令碼和Shader。
首先,我們來填寫指令碼的主要代碼。
接下來,我們來實現關鍵的Shader部分。
完整的指令碼和Shader如下:
OldFilmEffect指令碼:
using UnityEngine;using System.Collections;[ExecuteInEditMode]public class OldFilmEffect : MonoBehaviour {#region Variablespublic Shader oldFilmShader;public float oldFilmEffectAmount = 1.0f;public Color sepiaColor = Color.white;public Texture2D vignetteTexture;public float vignetteAmount = 1.0f;public Texture2D scratchesTexture;public float scratchesXSpeed;public float scratchesYSpeed;public Texture2D dustTexture;public float dustXSpeed;public float dustYSpeed;private Material curMaterial;private float randomValue;#endregion#region Propertiespublic Material material {get {if (curMaterial == null) {curMaterial = new Material(oldFilmShader);curMaterial.hideFlags = HideFlags.HideAndDontSave;}return curMaterial;}}#endregion// Use this for initializationvoid Start () {if (SystemInfo.supportsImageEffects == false) {enabled = false;return;}if (oldFilmShader != null && oldFilmShader.isSupported == false) {enabled = false;}}void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){if (oldFilmShader != null) {material.SetColor("_SepiaColor", sepiaColor);material.SetFloat("_VignetteAmount", vignetteAmount);material.SetFloat("_EffectAmount", oldFilmEffectAmount);if (vignetteTexture) {material.SetTexture("_VignetteTex", vignetteTexture);}if (scratchesTexture) {material.SetTexture("_ScratchesTex", scratchesTexture);material.SetFloat("_ScratchesXSpeed", scratchesXSpeed);material.SetFloat("_ScratchesYSpeed", scratchesYSpeed);}if (dustTexture) {material.SetTexture("_DustTex", dustTexture);material.SetFloat("_DustXSpeed", dustXSpeed);material.SetFloat("_DustYSpeed", dustYSpeed);material.SetFloat("_RandomValue", randomValue);}Graphics.Blit(sourceTexture, destTexture, material);} else {Graphics.Blit(sourceTexture, destTexture);}}// Update is called once per framevoid Update () {vignetteAmount = Mathf.Clamp(vignetteAmount, 0.0f, 1.0f);oldFilmEffectAmount = Mathf.Clamp(oldFilmEffectAmount, 0.0f, 1.0f);randomValue = Random.Range(-1.0f, 1.0f);}void OnDisable () {if (curMaterial != null) {DestroyImmediate(curMaterial);}}}
OldFilmEffectShader如下:
Shader "Custom/OldFilmEffectShader" {Properties {_MainTex ("Base (RGB)", 2D) = "white" {}_VignetteTex ("Vignette Texture", 2D) = "white" {}_VignetteAmount ("Vignette Opacity", Range(0, 1)) = 1_ScratchesTex ("Scraches Texture", 2D) = "white" {}_ScratchesXSpeed ("Scraches X Speed", Float) = 10.0_ScratchesYSpeed ("Scraches Y Speed", Float) = 10.0_DustTex ("Dust Texture", 2D) = "white" {}_DustXSpeed ("Dust X Speed", Float) = 10.0_DustYSpeed ("Dust Y Speed", Float) = 10.0_SepiaColor ("Sepia Color", Color) = (1, 1, 1, 1)_EffectAmount ("Old Film Effect Amount", Range(0, 1)) = 1_RandomValue ("Random Value", Float) = 1.0}SubShader {Pass {CGPROGRAM#pragma vertex vert_img#pragma fragment frag#include "UnityCG.cginc"uniform sampler2D _MainTex;uniform sampler2D _VignetteTex;uniform sampler2D _ScratchesTex;uniform sampler2D _DustTex;fixed4 _SepiaColor;fixed _VignetteAmount;fixed _ScratchesXSpeed;fixed _ScratchesYSpeed;fixed _DustXSpeed;fixed _DustYSpeed;fixed _EffectAmount;fixed _RandomValue;fixed4 frag (v2f_img i) : COLOR {half2 renderTexUV = half2(i.uv.x, i.uv.y + (_RandomValue * _SinTime.z * 0.005));fixed4 renderTex = tex2D(_MainTex, renderTexUV);// Get teh pixed from the Vignette Texturefixed4 vignetteTex = tex2D(_VignetteTex, i.uv);// Process the Scratches UV and pixelshalf2 scratchesUV = half2(i.uv.x + (_RandomValue * _SinTime.z * _ScratchesXSpeed), i.uv.y + (_Time.x * _ScratchesYSpeed));fixed4 scratchesTex = tex2D(_ScratchesTex, scratchesUV);// Process the Dust UV and pixelshalf2 dustUV = half2(i.uv.x + (_RandomValue * _SinTime.z * _DustXSpeed), i.uv.y + (_Time.x * _DustYSpeed));fixed4 dustTex = tex2D(_DustTex, dustUV);// Get the luminosity values from the render texture using the YIQ valuesfixed lum = dot(fixed3(0.299, 0.587, 0.114), renderTex.rgb);// Add the constant calor to the lum valuesfixed4 finalColor = lum + lerp(_SepiaColor, _SepiaColor + fixed4(0.1f, 0.1f, 0.1f, 0.1f), _RandomValue);// Create a constant white color we can use to adjust opacity of effectsfixed3 constantWhite = fixed3(1, 1, 1);// Composite together the different layers to create final Screen EffectfinalColor = lerp(finalColor, finalColor * vignetteTex, _VignetteAmount);finalColor.rgb *= lerp(scratchesTex, constantWhite, _RandomValue);finalColor.rgb *= lerp(dustTex, constantWhite, (_RandomValue * _SinTime.z));finalColor = lerp(renderTex, finalColor, _EffectAmount);return finalColor;}ENDCG}} FallBack "Diffuse"}
儲存後返回Unity。我們需要在面板中設定對應的圖片和屬性,像下面這樣:
擴充連結
關於YIQ:
- http://en.wikipedia.org/wiki/YIQ
- http://www.blackice.com/colorspaceYIQ.htm
- http://dcssrv1.oit.uci.edu/~wiedeman/cspace/me/infoyiq.html
想學習unity3d,誰有中文手冊PDF格式??可以下載分享 一下?
很好很強大,如果你想學習unity3d這是最佳搭檔!
歡迎使用 Unity................................................................................................................................1
一、使用者指南..................................................................................................................................2
1.1. Unity 基礎(Unity Basics) .........................................................................................2
1.1.1. 學習介面...........................................................................................................2
1.1.2. 資源工作流程(Asset Workflow) ........................................................................17
1.1.3. 建立情境(Creating Scenes) ............................................................................18
1.1.4. 發布(Publishing Builds)..................................................................................19
1.1.5. 教程(Tutorials)................................................................................................22
1.2. 構建情境(Building Scenes) ....................................................................................23
1.2.1. 遊戲物體(GameObject)..................................................................................23
1.2.2. 使用組件(Using Components) .......................................................................25
1.2.3. 預設(Prefab)....................................................................................................30
1.2.4. 光照(Lights)......................................................餘下全文>>