在上一篇文章中,我們通過自定義着色器實現了一個簡單的在3D遊戲中選取、顯示物體輪廓的實例。更多精彩請關注【狗刨學習網】在文章最後,給大家留下了一個問題,就是我們的這種方法存在一定的問題,無法運用到複雜的模型上。原因是什麼呢?這要從這種方法的原理上來說,其實這種方法類似於攝像機的視角方向上對物體進行了一個投影。這樣的話,如果模型被其它物體遮擋的話,就會出現渲染不完全的問題,如圖所示,有一位朋友在評論中提出了這個問題。那麼,怎麼解決這個問題呢?對於遮擋的問題,我們一般採取的方法是拉近攝像機,因此,我們這裏依然採取這種方法,即如果被渲染的物體前面有遮擋物體,則將攝像機拉近,這樣就可以解決這個問題了。
那麼解決了上一篇文章中的問題後,我們就來開始學習今天的內容——《Unity3D遊戲開發複雜模型的選取與輪廓高亮顯示》。首先,我們今天的內容是基於邊緣光(RimLight)的方法來實現的,在Unity3D的官方示例中,我們可以找到這個算法,其核心算法是:
-
<font face="新宋體" size="2">half rim = 1.0 - saturate(dot (normalize(IN.viewDir), IN.worldNormal));
-
o.Emission = _RimColor.rgb * pow (rim, _RimPower); </font>
複製代碼
其中,IN.viewDir是當前視角向量,IN.worldNormal是物體的法線。dot是計算視角和法線的點積,等於視角和法線夾角的cos值,如下圖:
由於Cos的值域是1到0,所以1-cos就成了0到1,在夾角90度時達到最大值,正好用來模擬側光的強度(與視角成90度的部分光線最強,就是邊緣光了),把這個值的變化率用一個pow函數(rim的_rimPower次方)進行放大,就能強化邊緣發亮的效果。但是當物體表面比較平直時(例如立方體),由於各個面上的法向量都是一個方向上的,因此無法體現出變化和輪廓。此外,這種方法在描繪凹的幾何體時,凹的部分(包括法線貼圖造成的凹凸)的邊緣也都會被畫出來,並不是真正意義上的邊緣輪廓,就是一種側光效果。好了,下面我們開始具體地來講通過這種方法來實現物體輪廓高光顯示。首先編寫Shader:
-
<font face="新宋體" size="2">Shader "Custom/Outline" {
-
Properties {
-
_Color ("Main Color", Color) = (1,1,1,1)
-
_SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)
-
_Shininess ("Shininess", Range (0.03, 1)) = 1
-
_MainTex ("Base (RGB) Gloss (A)", 2D) = "black" {}
-
_BumpMap ("Normalmap", 2D) = "bump" {}
-
//邊緣光顏色
-
_RimColor ("Rim Color", Color) = (0,0,0,0.0)
-
//放大倍數
-
_RimPower ("Rim Power", Range(0.5,8.0)) = 2.0
-
}
-
SubShader {
-
Tags { "RenderType"="Opaque" }
-
LOD 400
-
-
CGPROGRAM
-
#pragma surface surf BlinnPhong
-
#pragma target 3.0
-
-
sampler2D _MainTex;
-
sampler2D _BumpMap;
-
fixed4 _Color;
-
half _Shininess;
-
float4 _RimColor;
-
float _RimPower;
-
-
struct Input {
-
float2 uv_MainTex;
-
float2 uv_BumpMap;
-
float3 viewDir;
-
};
-
-
void surf (Input IN, inout SurfaceOutput o) {
-
-
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
-
o.Albedo = tex.rgb * _Color.rgb;
-
o.Gloss = tex.a;
-
o.Alpha = tex.a * _Color.a;
-
o.Specular = _Shininess;
-
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
-
//核心算法
-
half rim = 1 - saturate(dot (normalize(IN.viewDir), o.Normal));
-
o.Emission = _RimColor.rgb * pow (rim, _RimPower);
-
}
-
ENDCG
-
-
}
-
-
FallBack "Specular"
-
}</font>
複製代碼
在這個着色器腳本中,最重要的一個屬性是_RimColor,這個值將決定我們最終渲染的效果。在上一篇文章中,我們是通過一個使用了自定義着色器的材質來實現輪廓顯示的,今天我們換一種方法,怎麼做呢?我們這裏通過Shader來實現,在Material中有一個Shader屬性,一旦改變了該屬性的值,那麼所有材質都將按照新的渲染方式進行渲染。我們在上一篇文章中的腳本的基礎上,擴展得到下面的腳本:
-
<font face="新宋體" size="2">using UnityEngine;
-
using System.Collections;
-
public class ShowBoundry : MonoBehaviour {
-
//使用顯示輪廓的簡單材質
-
public Material mSimpleMat;
-
//默認材質
-
public Material mDefaultMat;
-
//我們今天使用Shader來直接改變模型的渲染效果,這樣可以避免使用一個材質
-
public Shader RimLightShader;
-
public Color RimColor = new Color(0.2F,0.8F,10.6F,1);
-
//定義私有變量以存儲模型的原始信息
-
private SkinnedMeshRenderer mSkin;
-
private Color mColor;
-
private Shader mShader;
-
void Start ()
-
{
-
//獲取模型的SkinnedMeshRenderer
-
mSkin=GameObject.Find("Person").
-
GetComponentInChildren();
-
//獲取默認顏色
-
mColor=mSkin.material.color;
-
//獲取默認Shader
-
mShader=mSkin.material.shader;
-
}
-
void Update ()
-
{
-
//獲取鼠標位置
-
Vector3 mPos=Input.mousePosition;
-
//向物體發射射線
-
Ray mRay=Camera.main.ScreenPointToRay(Input.mousePosition);
-
RaycastHit mHit;
-
//射線檢驗
-
if(Physics.Raycast(mRay,out mHit))
-
{
-
//Cube
-
if(mHit.collider.gameObject.tag=="Cube")
-
{
-
//將當前選中的對象材質換成帶輪廓線的材質
-
mHit.collider.gameObject.renderer.material=mSimpleMat;
-
//將未選中的對象材質換成默認材質
-
GameObject.Find("Sphere").renderer.material=mDefaultMat;
-
//將模型恢復到初始狀態
-
mSkin.material.shader=mShader;
-
mSkin.material.SetColor("_RimColor",mColor);
-
//設置提示信息
-
GameObject.Find("GUIText").guiText.text="當前選擇的對象是:Cube";
-
}
-
//Sphere
-
if(mHit.collider.gameObject.tag=="Sphere")
-
{
-
//將當前選中的對象材質換成帶輪廓線的材質
-
mHit.collider.gameObject.renderer.material=mSimpleMat;
-
//將未選中的對象材質換成默認材質
-
GameObject.Find("Cube").renderer.material=mDefaultMat;
-
//將模型恢復到初始狀態
-
mSkin.material.shader=mShader;
-
mSkin.material.SetColor("_RimColor",mColor);
-
//設置提示信息
-
GameObject.Find("GUIText").guiText.text="當前選擇的對象是:Sphere";
-
}
-
//Person
-
if(mHit.collider.gameObject.tag=="Person")
-
{
-
//更換Shader
-
mSkin.material.shader=RimLightShader;
-
mSkin.material.SetColor("_RimColor",RimColor);
-
//將未選中的對象材質換成默認材質
-
GameObject.Find("Cube").renderer.material=mDefaultMat;
-
GameObject.Find("Sphere").renderer.material=mDefaultMat;
-
//設置提示信息
-
GameObject.Find("GUIText").guiText.text="當前選擇的對象是:Person";
-
}
-
}
-
}
-
}</font>
複製代碼
在今天的腳本中,我們增加了一個RimLightShader和RimColor,它們的作用是指定着色器和邊緣光的顏色,我們可以通過外部來引用我們剛纔定義好的自定義Shader,同時,爲了保存模型的原始狀態,我們定義了兩個私有變量mShader和mColor,以便我們可以在適當的時候將模型的狀態還原到原始狀態。好了,我們來運行下今天的程序,效果如下:
可以注意到,當角色被選中時,角色以高亮顯示的形式被渲染出來,這裏使用的是金黃色的邊緣光,所以得到了這樣的結果。感覺效果還不錯啊。在做今天的內容的時候,經常出現着色器無效的情況,後來發現是着色器定義的名稱和文件名稱不符的緣故,所以大家在編寫着色器的時候一定要注意啊。