Unity3D遊戲開發之在3D場景中選擇物體並顯示輪廓效果強化版

    在上一篇文章中,我們通過自定義着色器實現了一個簡單的在3D遊戲中選取、顯示物體輪廓的實例。在文章最後,給大家留下了一個問題,就是我們的這種方法存在一定的問題,無法運用到複雜的模型上。原因是什麼呢?這要從這種方法的原理上來說,其實這種方法類似於攝像機的視角方向上對物體進行了一個投影。這樣的話,如果模型被其它物體遮擋的話,就會出現渲染不完全的問題,如圖所示,有一位朋友在評論中提出了這個問題。那麼,怎麼解決這個問題呢?對於遮擋的問題,我們一般採取的方法是拉近攝像機,因此,我們這裏依然採取這種方法,即如果被渲染的物體前面有遮擋物體,則將攝像機拉近,這樣就可以解決這個問題了。

       那麼解決了上一篇文章中的問題後,我們就來開始學習今天的內容——《Unity3D遊戲開發複雜模型的選取與輪廓高亮顯示》。首先,我們今天的內容是基於邊緣光(RimLight)的方法來實現的,在Unity3D的官方示例中,我們可以找到這個算法,其核心算法是:

[csharp] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  1. half rim = 1.0 - saturate(dot (normalize(IN.viewDir), IN.worldNormal));  

  2. o.Emission = _RimColor.rgb * pow (rim, _RimPower);  

其中,IN.viewDir是當前視角向量,IN.worldNormal是物體的法線。dot是計算視角和法線的點積,等於視角和法線夾角的cos值,如下圖:

       由於Cos的值域是1到0,所以1-cos就成了0到1,在夾角90度時達到最大值,正好用來模擬側光的強度(與視角成90度的部分光線最強,就是邊緣光了),把這個值的變化率用一個pow函數(rim的_rimPower次方)進行放大,就能強化邊緣發亮的效果。但是當物體表面比較平直時(例如立方體),由於各個面上的法向量都是一個方向上的,因此無法體現出變化和輪廓。此外,這種方法在描繪凹的幾何體時,凹的部分(包括法線貼圖造成的凹凸)的邊緣也都會被畫出來,並不是真正意義上的邊緣輪廓,就是一種側光效果。如果大家想了解更多的內容,可以參考這裏:http://game.ceeger.com/forum/read.php?tid=3592。好了,下面我們開始具體地來講通過這種方法來實現物體輪廓高光顯示。首先編寫Shader:

[csharp] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  1. Shader "Custom/Outline" {  

  2. Properties {  

  3.     _Color ("Main Color", Color) = (1,1,1,1)  

  4.     _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)  

  5.     _Shininess ("Shininess", Range (0.03, 1)) = 1  

  6.     _MainTex ("Base (RGB) Gloss (A)", 2D) = "black" {}  

  7.     _BumpMap ("Normalmap", 2D) = "bump" {}  

  8.     //邊緣光顏色  

  9.     _RimColor ("Rim Color", Color) = (0,0,0,0.0)  

  10.     //放大倍數  

  11.     _RimPower ("Rim Power", Range(0.5,8.0)) = 2.0  

  12. }  

  13. SubShader {   

  14.     Tags { "RenderType"="Opaque" }  

  15.     LOD 400  

  16.   

  17. CGPROGRAM  

  18. #pragma surface surf BlinnPhong  

  19. #pragma target 3.0  

  20.   

  21. sampler2D _MainTex;  

  22. sampler2D _BumpMap;  

  23. fixed4 _Color;  

  24. half _Shininess;  

  25. float4 _RimColor;  

  26. float _RimPower;  

  27.   

  28. struct Input {  

  29.     float2 uv_MainTex;  

  30.     float2 uv_BumpMap;  

  31.     float3 viewDir;  

  32. };  

  33.   

  34. void surf (Input IN, inout SurfaceOutput o) {  

  35.       

  36.     fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);  

  37.     o.Albedo = tex.rgb * _Color.rgb;  

  38.     o.Gloss = tex.a;  

  39.     o.Alpha = tex.a * _Color.a;  

  40.     o.Specular = _Shininess;  

  41.     o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));  

  42.     //核心算法  

  43.     half rim = 1 - saturate(dot (normalize(IN.viewDir), o.Normal));  

  44.     o.Emission = _RimColor.rgb * pow (rim, _RimPower);  

  45. }  

  46. ENDCG  

  47.       

  48. }  

  49.   

  50. FallBack "Specular"  

  51. }  

      在這個着色器腳本中,最重要的一個屬性是_RimColor,這個值將決定我們最終渲染的效果。在上一篇文章中,我們是通過一個使用了自定義着色器的材質來實現輪廓顯示的,今天我們換一種方法,怎麼做呢?我們這裏通過Shader來實現,在Material中有一個Shader屬性,一旦改變了該屬性的值,那麼所有材質都將按照新的渲染方式進行渲染。我們在上一篇文章中的腳本的基礎上,擴展得到下面的腳本:

[csharp] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  1. using UnityEngine;  

  2. using System.Collections;  

  3.   

  4. public class ShowBoundry : MonoBehaviour {  

  5.   

  6.     //使用顯示輪廓的簡單材質  

  7.     public Material mSimpleMat;  

  8.     //默認材質  

  9.     public Material mDefaultMat;  

  10.   

  11.     //我們今天使用Shader來直接改變模型的渲染效果,這樣可以避免使用一個材質  

  12.     public Shader RimLightShader;  

  13.     public Color RimColor = new Color(0.2F,0.8F,10.6F,1);  

  14.     //定義私有變量以存儲模型的原始信息  

  15.     private SkinnedMeshRenderer mSkin;  

  16.     private Color mColor;  

  17.     private Shader mShader;  

  18.   

  19.     void Start ()   

  20.     {  

  21.         //獲取模型的SkinnedMeshRenderer  

  22.         mSkin=GameObject.Find("Person").  

  23.             GetComponentInChildren<SkinnedMeshRenderer>();  

  24.         //獲取默認顏色  

  25.         mColor=mSkin.material.color;  

  26.         //獲取默認Shader  

  27.         mShader=mSkin.material.shader;  

  28.   

  29.     }  

  30.   

  31.     void Update ()   

  32.     {  

  33.        //獲取鼠標位置  

  34.        Vector3 mPos=Input.mousePosition;  

  35.        //向物體發射射線  

  36.        Ray mRay=Camera.main.ScreenPointToRay(Input.mousePosition);  

  37.        RaycastHit mHit;  

  38.        //射線檢驗  

  39.        if(Physics.Raycast(mRay,out mHit))  

  40.        {  

  41.           //Cube  

  42.           if(mHit.collider.gameObject.tag=="Cube")  

  43.           {  

  44.              //將當前選中的對象材質換成帶輪廓線的材質  

  45.              mHit.collider.gameObject.renderer.material=mSimpleMat;  

  46.              //將未選中的對象材質換成默認材質  

  47.              GameObject.Find("Sphere").renderer.material=mDefaultMat;  

  48.              //將模型恢復到初始狀態  

  49.              mSkin.material.shader=mShader;  

  50.              mSkin.material.SetColor("_RimColor",mColor);  

  51.              //設置提示信息  

  52.              GameObject.Find("GUIText").guiText.text="當前選擇的對象是:Cube";  

  53.           }  

  54.           //Sphere  

  55.           if(mHit.collider.gameObject.tag=="Sphere")  

  56.           {  

  57.              //將當前選中的對象材質換成帶輪廓線的材質  

  58.              mHit.collider.gameObject.renderer.material=mSimpleMat;  

  59.              //將未選中的對象材質換成默認材質  

  60.              GameObject.Find("Cube").renderer.material=mDefaultMat;  

  61.              //將模型恢復到初始狀態  

  62.              mSkin.material.shader=mShader;  

  63.              mSkin.material.SetColor("_RimColor",mColor);  

  64.              //設置提示信息  

  65.              GameObject.Find("GUIText").guiText.text="當前選擇的對象是:Sphere";  

  66.           }  

  67.           //Person  

  68.           if(mHit.collider.gameObject.tag=="Person")  

  69.           {  

  70.              //更換Shader  

  71.              mSkin.material.shader=RimLightShader;  

  72.              mSkin.material.SetColor("_RimColor",RimColor);  

  73.              //將未選中的對象材質換成默認材質  

  74.              GameObject.Find("Cube").renderer.material=mDefaultMat;  

  75.              GameObject.Find("Sphere").renderer.material=mDefaultMat;  

  76.              //設置提示信息  

  77.              GameObject.Find("GUIText").guiText.text="當前選擇的對象是:Person";  

  78.           }  

  79.        }  

  80.   

  81.     }  

  82. }  

       在今天的腳本中,我們增加了一個RimLightShader和RimColor,它們的作用是指定着色器和邊緣光的顏色,我們可以通過外部來引用我們剛纔定義好的自定義Shader,同時,爲了保存模型的原始狀態,我們定義了兩個私有變量mShader和mColor,以便我們可以在適當的時候將模型的狀態還原到原始狀態。好了,我們來運行下今天的程序,效果如下:

       可以注意到,當角色被選中時,角色以高亮顯示的形式被渲染出來,這裏使用的是金***的邊緣光,所以得到了這樣的結果。感覺效果還不錯啊。在做今天的內容的時候,經常出現着色器無效的情況,後來發現是着色器定義的名稱和文件名稱不符的緣故,所以大家在編寫着色器的時候一定要注意啊。

更多精彩請到http://www.gopedu.com/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章