大家好,我是秦培,欢迎关注我的博客,我的博客地址blog.csdn.net/qinyuanpei。
在上一篇文章中,我们通过自己定义着色器实现了一个简单的在3D游戏中选取、显示物体轮廓的实例。
在文章最后,博主给大家留下了一个问题,就是我们的这样的方法存在一定的问题,无法运用到复杂的模型上。
原因是什么呢?这要从这样的方法的原理上来说。事实上这样的方法相似于摄像机的视角方向上对物体进行了一个投影。这样的话,假设模型被其他物体遮挡的话,就会出现渲染不全然的问题。如图所看到的,有一位朋友在评论中提出了这个问题。那么,怎么解决问题呢?对于遮挡的问题,我们一般採取的方法是拉近摄像机,因此,我们这里依旧採取这样的方法,即假设被渲染的物体前面有遮挡物体,则将摄像机拉近,这样就能够解决问题了。
那么攻克了上一篇文章中的问题后,我们就来開始学习今天的内容——《Unity3D游戏开发复杂模型的选取与轮廓高亮显示》。首先,我们今天的内容是基于边缘光(RimLight)的方法来实现的,在Unity3D的官方演示样例中。我们能够找到这个算法。其核心算法是:
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), IN.worldNormal)); 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:
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" }在这个着色器脚本中。最重要的一个属性是_RimColor,这个值将决定我们终于渲染的效果。
在上一篇文章中,我们是通过一个使用了自己定义着色器的材质来实现轮廓显示的。今天我们换一种方法,怎么做呢?我们这里通过Shader来实现,在Material中有一个Shader属性。一旦改变了该属性的值,那么全部材质都将依照新的渲染方式进行渲染。我们在上一篇文章中的脚本的基础上。扩展得到以下的脚本:
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<SkinnedMeshRenderer>(); //获取默认颜色 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"; } } } }在今天的脚本中,我们添加了一个RimLightShader和RimColor,它们的作用是指定着色器和边缘光的颜色,我们能够通过外部来引用我们刚才定义好的自己定义Shader,同一时候,为了保存模型的原始状态,我们定义了两个私有变量mShader和mColor,以便我们能够在适当的时候将模型的状态还原到原始状态。好了,我们来执行下今天的程序,效果例如以下:
能够注意到。当角色被选中时,角色以高亮显示的形式被渲染出来,博主这里使用的是金黄色的边缘光,所以得到了这样的结果。感觉效果还不错啊。博主在做今天的内容的时候,常常出现着色器无效的情况,后来发现是着色器定义的名称和文件名不符的缘故,所以大家在编写着色器的时候一定要注意啊。
好了。今天的内容就是这样了。希望大家喜欢啊。呵呵。
每日箴言:以前以为生命中最糟糕的事,就是孤独终老。事实上不是。
最糟糕的是与那些让你感到孤独的人一起终老。
喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei
转载请注明出处。本文作者:秦元培。本文出处:http://blog.csdn.net/qinyuanpei/article/details/26862743
.
版权声明:本文博客原创文章,博客,未经同意,不得转载。转载请注明出处和作者,谢谢!