zoukankan      html  css  js  c++  java
  • 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/

  • 相关阅读:
    Java中容器的两种初始化方式比较
    java8之stream
    java8之lambda表达式入门
    java8之lambda表达式(1)-基本语法
    java中Comparable和Comparator两种比较器的区别
    将博客搬至CSDN
    vsphere6.5 创建数据中心、集群和添加主机
    VMware-VCSA-6.5安装过程
    docker--数据卷与数据卷容器
    docker私有库搭建过程(Registry)
  • 原文地址:https://www.cnblogs.com/Unity3Dqishituan/p/3966132.html
Copyright © 2011-2022 走看看