Unity3D引擎之高级渲染技术

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社 和《Unity3D实战核心技术详解》电子工业出版社等书籍

在游戏开发中尤其对于角色的材质渲染一直是被游戏开发者所看重,也成为衡量游戏品质的一个指标,关于渲染就需要考虑到Shader编程,Shader主要是对模型的顶点和模型的材质图片做处理,下面告诉读者如何去编写Shader。

在公司,策划需求提出用Unity3D引擎实现角色材质高光、法线效果。策划需求提出后,美术首先用PS和MAX工具将模型制作出来,MAX中有自身带的Shader,它可以完整的实现高光、法线效果,这也是程序编写和调试Shader时的参照依据,程序是参照MAX中的效果去调试,程序实现的效果只能无限接近MAX工具制作的效果。Unity5.4以上版本也有自己的高光法线Shader,但是在使用时效果并不理想,不如自己去实现一下。在这里首先需要提供三张贴图:Diffuse(原图),Specular(高光),Normal(法线)。









接下来就需要对其进行Shader编程实现,为了让美术调试方便,在这里我们使用了控制面板。首先实现一个继承于Materal类的编辑器脚本,将其放到Editor文件夹下面,内容如下所示:


[cpp]  view plain  copy
  1. using System.Collections.Generic;  
  2. using UnityEngine;  
  3. using UnityEditor;  
  4. using System.Linq;  
  5. using System.Text.RegularExpressions;  
  6.   
  7. public abstract class CustomMaterialEditor : MaterialEditor  
  8. {  
  9.     public class FeatureEditor  
  10.     {  
  11.         // The name the toggle will have in the inspector.  
  12.         public string InspectorName;  
  13.         // We will look for properties that contain this word, and hide them if we're not enabled.  
  14.         public string InspectorPropertyHideTag;  
  15.         // The keyword that the shader uses when this feature is enabled or disabled.  
  16.         public string ShaderKeywordEnabled;  
  17.         public string ShaderKeywordDisabled;  
  18.         // The current state of this feature.  
  19.         public bool Enabled;  
  20.   
  21.         public FeatureEditor(string InspectorName, string InspectorPropertyHideTag, string ShaderKeywordEnabled, string ShaderKeywordDisabled)  
  22.         {  
  23.             this.InspectorName = InspectorName;  
  24.             this.InspectorPropertyHideTag = InspectorPropertyHideTag;  
  25.             this.ShaderKeywordEnabled = ShaderKeywordEnabled;  
  26.             this.ShaderKeywordDisabled = ShaderKeywordDisabled;  
  27.             this.Enabled = false;  
  28.         }  
  29.     }  
  30.   
  31.     // A list of all the toggles that we have in this material editor.  
  32.     protected List<FeatureEditor> Toggles = new List<FeatureEditor>();  
  33.     // This function will be implemented in derived classes, and used to populate the list of toggles.  
  34.     protected abstract void CreateToggleList();   
  35.   
  36.     public override void OnInspectorGUI ()  
  37.     {  
  38.         // if we are not visible... return  
  39.         if (!isVisible)  
  40.             return;  
  41.   
  42.         // Get the current keywords from the material  
  43.         Material targetMat = target as Material;  
  44.         string[] oldKeyWords = targetMat.shaderKeywords;  
  45.   
  46.         // Populate our list of toggles  
  47.         //Toggles.Clear();  
  48.         Toggles = new List<FeatureEditor>();  
  49.         CreateToggleList();  
  50.   
  51.         // Update each toggle to enabled if it's enabled keyword is present. If it's enabled keyword is missing, we assume it's disabled.  
  52.         for(int i = 0; i < Toggles.Count; i++)  
  53.         {  
  54.             Toggles[i].Enabled = oldKeyWords.Contains (Toggles[i].ShaderKeywordEnabled);  
  55.         }  
  56.   
  57.         // Begin listening for changes in GUI, so we don't waste time re-applying settings that haven't changed.  
  58.         EditorGUI.BeginChangeCheck();  
  59.   
  60.         serializedObject.Update ();  
  61.         var theShader = serializedObject.FindProperty ("m_Shader");  
  62.         if (isVisible && !theShader.hasMultipleDifferentValues && theShader.objectReferenceValue != null)  
  63.         {  
  64.             float controlSize = 64;  
  65.             EditorGUIUtility.labelWidth = Screen.width - controlSize - 20;  
  66.             EditorGUIUtility.fieldWidth = controlSize;  
  67.   
  68.             Shader shader = theShader.objectReferenceValue as Shader;  
  69.   
  70.             EditorGUI.BeginChangeCheck();  
  71.   
  72.             // Draw Non-toggleable values  
  73.             for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++)  
  74.             {  
  75.                 ShaderPropertyImpl(shader, i, null);  
  76.             }  
  77.             // Draw toggles, then their values.  
  78.             for (int s = 0; s < Toggles.Count; s++)  
  79.             {  
  80.                 EditorGUILayout.Separator();  
  81.                 Toggles[s].Enabled = EditorGUILayout.BeginToggleGroup(Toggles[s].InspectorName, Toggles[s].Enabled);  
  82.   
  83.                 if (Toggles[s].Enabled)  
  84.                 {  
  85.                     for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++)  
  86.                     {  
  87.                         ShaderPropertyImpl(shader, i, Toggles[s]);  
  88.                     }  
  89.                 }  
  90.                 EditorGUILayout.EndToggleGroup();  
  91.             }  
  92.   
  93.             if (EditorGUI.EndChangeCheck())  
  94.                 PropertiesChanged ();  
  95.         }  
  96.   
  97.         // If changes have been made, then apply them.  
  98.         if (EditorGUI.EndChangeCheck())  
  99.         {  
  100.             // New list of key words.  
  101.             List<string> newKeyWords = new List<string>();  
  102.   
  103.             // If true, add the enabled keyword (ending with _ON), if false, add the disabled keyword(ending with _OFF).  
  104.             for(int i = 0; i < Toggles.Count; i++)  
  105.             {  
  106.                 newKeyWords.Add(Toggles[i].Enabled ? Toggles[i].ShaderKeywordEnabled : Toggles[i].ShaderKeywordDisabled);  
  107.             }  
  108.   
  109.             // Send the new list of keywords to the material, this will define what version of the shader to use.  
  110.             targetMat.shaderKeywords = newKeyWords.ToArray ();  
  111.             EditorUtility.SetDirty (targetMat);  
  112.         }  
  113.     }  
  114.   
  115.     // This runs once for every property in our shader.  
  116.     private void ShaderPropertyImpl(Shader shader, int propertyIndex, FeatureEditor currentToggle)  
  117.     {  
  118.         string propertyDescription = ShaderUtil.GetPropertyDescription(shader, propertyIndex);  
  119.   
  120.         // If current toggle is null, we only want to show properties that aren't already "owned" by a toggle,  
  121.         // so if it is owned by another toggle, then return.  
  122.         if (currentToggle == null)  
  123.         {  
  124.             for (int i = 0; i < Toggles.Count; i++)  
  125.             {  
  126.                 if (Regex.IsMatch(propertyDescription, Toggles[i].InspectorPropertyHideTag , RegexOptions.IgnoreCase))  
  127.                 {  
  128.                     return;  
  129.                 }  
  130.             }  
  131.         }  
  132.         // Only draw if we the current property is owned by the current toggle.  
  133.         else if (!Regex.IsMatch(propertyDescription, currentToggle.InspectorPropertyHideTag , RegexOptions.IgnoreCase))  
  134.         {  
  135.             return;  
  136.         }  
  137.         // If we've gotten to this point, draw the shader property regulairly.  
  138.         ShaderProperty(shader,propertyIndex);  
  139.     }  
  140. }  


上面是我们自己实现的可以作为父类使用,其实就是做了一个接口封装,下面代码是自己定义的为我们自己的Shader脚本操作写的几个开关。如下所示:


[cpp]  view plain  copy
  1. using System.Collections.Generic;  
  2. using UnityEngine;  
  3. using UnityEditor;   
  4.   
  5. public class EditorInspector : CustomMaterialEditor  
  6. {  
  7.     protected override void CreateToggleList()  
  8.     {  
  9.         Toggles.Add(new FeatureEditor("Normal Enabled","normal","NORMALMAP_ON","NORMALMAP_OFF"));  
  10.         Toggles.Add(new FeatureEditor("Specular Enabled","specular","SPECULAR_ON","SPECULAR_OFF"));  
  11.         Toggles.Add(new FeatureEditor("Fresnel Enabled","fresnel","FRESNEL_ON","FRESNEL_OFF"));  
  12.         Toggles.Add(new FeatureEditor("Rim Light Enabled","rim","RIMLIGHT_ON","RIMLIGHT_OFF"));  
  13.     }  
  14. }   

相对来说比较简单,它只是做了几个Toggle作为Shader脚本中的控制。将上述两个文件拖放到Editor文件夹下面。

  下面才开始真正的Shader编写工作,在这里我们使用了SurfaceOutput作为我们的输出结构体,SurfaceOutput简单地描述了surface的属性(  properties of the surface ),如反射率颜色(albedo color)、法线(normal)、散射(emission)、镜面反射(specularity )等。首先定义一个输入结构体如下所示:

[cpp]  view plain  copy
  1. struct Input  
  2.         {  
  3.             float2 uv_DiffuseMap;  
  4.             #if SPECULAR_ON  
  5.             float2 uv_SpecMap;  
  6.             #endif  
  7.             #if NORMALMAP_ON  
  8.             float2 uv_NormalMap;  
  9.             #endif  
  10.             #if FRESNEL_ON || RIMLIGHT_ON  
  11.             float3 viewDir;  
  12.             #endif  
  13.         };   

结构体中包含 Diffuse贴图的uv坐标uv_DiffuseMap,高光的uv坐标uv_SpecMap,法线的uv坐标uv_NormalMap,另外还定义了一个方向值viewDir。接下来就是实现主函数surf了,对于高光法线的渲染就在这里面了,它调用了大量的CG库函数,如下所示:

[cpp]  view plain  copy
  1.  void surf (Input IN, inout SurfaceOutput o)  
  2.         {    
  3.             float3 TexData = tex2D(_DiffuseMap, IN.uv_DiffuseMap);  
  4.             float3 _BlendColor =  _TintColor.rgb * _TintColorMultiply;  
  5.                
  6.             o.Albedo.rgb = _Brightness * lerp(TexData, _TintColor.rgb, _TintColorMultiply) ;  
  7.                
  8.             #if SPECULAR_ON  
  9.             o.Specular = _Gloss;  
  10.             o.Gloss = max(_SpecAdd + _SpecularMultiply, 1.0) * tex2D (_SpecMap, IN.uv_SpecMap);  
  11.             //o.Emission = _Gloss * tex2D (_SpecMap, IN.uv_SpecMap);  
  12.             #endif  
  13.             #if NORMALMAP_ON  
  14.             o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));  
  15.             #endif  
  16.            
  17.             #if FRESNEL_ON && SPECULAR_ON || RIMLIGHT_ON  
  18.             float facing = saturate(1.0 - max(dot(normalize(IN.viewDir), normalize(o.Normal)), 0.0));  
  19.    
  20.                 #if FRESNEL_ON && SPECULAR_ON  
  21.                 float fresnel = max(_FresnelBias + (1.0-_FresnelBias) * pow(facing, _FresnelPower), 0);  
  22.                 fresnel = fresnel * o.Specular * _FresnelMultiply;  
  23.                 o.Gloss *= 1+fresnel;  
  24.                 #endif          
  25.                 #if RIMLIGHT_ON  
  26.                 float rim = max(_RimBias + (1.0-_RimBias) * pow(facing, _RimPower), 0);  
  27.                 rim = rim * o.Specular * _RimMultiply;  
  28.                 o.Albedo *= 1+rim;  
  29.                 #endif  
  30.             #endif  
  31.            
  32.         }   

下面将Shader的完整代码展示如下:


[cpp]  view plain  copy
  1. Shader "Custom_Shaders/DNSRender"  
  2. {  
  3.     Properties  
  4.     {  
  5.         _TintColor ("Color Tint",color) = (1.0,1.0,1.0,1.0)  
  6.         //Diffuse Sliders  
  7.         _TintColorMultiply("Color Tint Multiply", Range(0.0, 1.0)) = 0.0  
  8.         _Brightness ("Diffuse Brightness", Range(0.0, 2.0)) = 1.0  
  9.         _DiffuseMap ("Diffuse (RGB)", 2D) = "white" {}  
  10.         _NormalMap ("Normal Map(RGB)", 2D) = "bump" {}  
  11.         _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)  
  12.         _SpecularMultiply ("Specular Brightness",float) = 1.0  
  13.         _SpecAdd ("Specular Boost"float) = 0  
  14.         _SpecMap ("Specular Map (RGB)", 2D) = "grey" {}  
  15.         _Gloss ("Specular Glossiness"float) = 0.5  
  16.         _FresnelPower ("Fresnel Power",float) = 1.0  
  17.         _FresnelMultiply ("Fresnel Multiply"float) = 0.2  
  18.         _FresnelBias ("Fresnel Bias"float) = -0.1  
  19.         _RimPower ("RimLight Power",float) = 1.0  
  20.         _RimMultiply ("RimLight Multiply"float) = 0.2  
  21.         _RimBias ("RimLight Bias"float) = 0  
  22.         _EmissionColor("Emission Color", color) = (1.0,1.0,1.0,1.0)  
  23.     }  
  24.     SubShader  
  25.     {  
  26.         Tags { "RenderType"="Opaque" }  
  27.         LOD 300  
  28.         CGPROGRAM  
  29.         #pragma surface surf BlinnPhong  
  30.         #pragma target 3.0  
  31.         #pragma shader_feature NORMALMAP_ON NORMALMAP_OFF  
  32.         #pragma shader_feature SPECULAR_ON SPECULAR_OFF  
  33.         #pragma shader_feature FRESNEL_ON FRESNEL_OFF  
  34.         #pragma shader_feature RIMLIGHT_ON RIMLIGHT_OFF  
  35.         float3 _TintColor;  
  36.         float _TintColorMultiply;  
  37.         float _Brightness;  
  38.   
  39.         sampler2D _DiffuseMap;  
  40.         sampler2D _NormalMap;      
  41.         sampler2D _SpecMap;  
  42.   
  43.         float _SpecularMultiply;  
  44.         float _SpecAdd;  
  45.         float _Gloss;  
  46.         float _FresnelPower;  
  47.         float _FresnelMultiply;  
  48.         float _FresnelBias;  
  49.         float _RimPower;  
  50.         float _RimMultiply;  
  51.         float _RimBias;  
  52.         float3 _EmissionColor;  
  53.   
  54.         struct Input  
  55.         {  
  56.             float2 uv_DiffuseMap;  
  57.             #if SPECULAR_ON  
  58.             float2 uv_SpecMap;  
  59.             #endif  
  60.             #if NORMALMAP_ON  
  61.             float2 uv_NormalMap;  
  62.             #endif  
  63.             #if FRESNEL_ON || RIMLIGHT_ON  
  64.             float3 viewDir;  
  65.             #endif  
  66.         };  
  67.    
  68.         void surf (Input IN, inout SurfaceOutput o)  
  69.         {    
  70.             float3 TexData = tex2D(_DiffuseMap, IN.uv_DiffuseMap);  
  71.             float3 _BlendColor =  _TintColor.rgb * _TintColorMultiply;  
  72.               
  73.             o.Albedo.rgb = _Brightness * lerp(TexData, _TintColor.rgb, _TintColorMultiply) ;  
  74.               
  75.             #if SPECULAR_ON  
  76.             o.Specular = _Gloss;  
  77.             o.Gloss = max(_SpecAdd + _SpecularMultiply, 1.0) * tex2D (_SpecMap, IN.uv_SpecMap);  
  78.             //o.Emission = _Gloss * tex2D (_SpecMap, IN.uv_SpecMap);  
  79.             #endif  
  80.             #if NORMALMAP_ON  
  81.             o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));  
  82.             #endif  
  83.            
  84.             #if FRESNEL_ON && SPECULAR_ON || RIMLIGHT_ON  
  85.             float facing = saturate(1.0 - max(dot(normalize(IN.viewDir), normalize(o.Normal)), 0.0));  
  86.    
  87.                 #if FRESNEL_ON && SPECULAR_ON  
  88.                 float fresnel = max(_FresnelBias + (1.0-_FresnelBias) * pow(facing, _FresnelPower), 0);  
  89.                 fresnel = fresnel * o.Specular * _FresnelMultiply;  
  90.                 o.Gloss *= 1+fresnel;  
  91.                 #endif          
  92.                 #if RIMLIGHT_ON  
  93.                 float rim = max(_RimBias + (1.0-_RimBias) * pow(facing, _RimPower), 0);  
  94.                 rim = rim * o.Specular * _RimMultiply;  
  95.                 o.Albedo *= 1+rim;  
  96.                 #endif  
  97.             #endif  
  98.            
  99.         }  
  100.         ENDCG  
  101.     }  
  102.   
  103.     CustomEditor "EditorInspector"  
  104. }  
  105.    

将Shader挂接到材质球上效果如下:




最终展示效果如下所示:



版权声明:本文为博主原创文章,未经博主允许不得转载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值