Unity3D引擎之高階渲染技術
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
筆者介紹:姜雪偉,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資料夾下面,內容如下所示:
using System.Collections.Generic;using UnityEngine;using UnityEditor;using System.Linq;using System.Text.RegularExpressions;public abstract class CustomMaterialEditor : MaterialEditor{ public class FeatureEditor { // The name the toggle will have in the inspector. public string InspectorName; // We will look for properties that contain this word, and hide them if we're not enabled. public string InspectorPropertyHideTag; // The keyword that the shader uses when this feature is enabled or disabled. public string ShaderKeywordEnabled; public string ShaderKeywordDisabled; // The current state of this feature. public bool Enabled; public FeatureEditor(string InspectorName, string InspectorPropertyHideTag, string ShaderKeywordEnabled, string ShaderKeywordDisabled) { this.InspectorName = InspectorName; this.InspectorPropertyHideTag = InspectorPropertyHideTag; this.ShaderKeywordEnabled = ShaderKeywordEnabled; this.ShaderKeywordDisabled = ShaderKeywordDisabled; this.Enabled = false; } } // A list of all the toggles that we have in this material editor. protected List<FeatureEditor> Toggles = new List<FeatureEditor>(); // This function will be implemented in derived classes, and used to populate the list of toggles. protected abstract void CreateToggleList(); public override void OnInspectorGUI () { // if we are not visible... return if (!isVisible) return; // Get the current keywords from the material Material targetMat = target as Material; string[] oldKeyWords = targetMat.shaderKeywords; // Populate our list of toggles //Toggles.Clear(); Toggles = new List<FeatureEditor>(); CreateToggleList(); // Update each toggle to enabled if it's enabled keyword is present. If it's enabled keyword is missing, we assume it's disabled. for(int i = 0; i < Toggles.Count; i++) { Toggles[i].Enabled = oldKeyWords.Contains (Toggles[i].ShaderKeywordEnabled); } // Begin listening for changes in GUI, so we don't waste time re-applying settings that haven't changed. EditorGUI.BeginChangeCheck(); serializedObject.Update (); var theShader = serializedObject.FindProperty ("m_Shader"); if (isVisible && !theShader.hasMultipleDifferentValues && theShader.objectReferenceValue != null) { float controlSize = 64; EditorGUIUtility.labelWidth = Screen.width - controlSize - 20; EditorGUIUtility.fieldWidth = controlSize; Shader shader = theShader.objectReferenceValue as Shader; EditorGUI.BeginChangeCheck(); // Draw Non-toggleable values for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++) { ShaderPropertyImpl(shader, i, null); } // Draw toggles, then their values. for (int s = 0; s < Toggles.Count; s++) { EditorGUILayout.Separator(); Toggles[s].Enabled = EditorGUILayout.BeginToggleGroup(Toggles[s].InspectorName, Toggles[s].Enabled); if (Toggles[s].Enabled) { for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++) { ShaderPropertyImpl(shader, i, Toggles[s]); } } EditorGUILayout.EndToggleGroup(); } if (EditorGUI.EndChangeCheck()) PropertiesChanged (); } // If changes have been made, then apply them. if (EditorGUI.EndChangeCheck()) { // New list of key words. List<string> newKeyWords = new List<string>(); // If true, add the enabled keyword (ending with _ON), if false, add the disabled keyword(ending with _OFF). for(int i = 0; i < Toggles.Count; i++) { newKeyWords.Add(Toggles[i].Enabled ? Toggles[i].ShaderKeywordEnabled : Toggles[i].ShaderKeywordDisabled); } // Send the new list of keywords to the material, this will define what version of the shader to use. targetMat.shaderKeywords = newKeyWords.ToArray (); EditorUtility.SetDirty (targetMat); } } // This runs once for every property in our shader. private void ShaderPropertyImpl(Shader shader, int propertyIndex, FeatureEditor currentToggle) { string propertyDescription = ShaderUtil.GetPropertyDescription(shader, propertyIndex); // If current toggle is null, we only want to show properties that aren't already "owned" by a toggle, // so if it is owned by another toggle, then return. if (currentToggle == null) { for (int i = 0; i < Toggles.Count; i++) { if (Regex.IsMatch(propertyDescription, Toggles[i].InspectorPropertyHideTag , RegexOptions.IgnoreCase)) { return; } } } // Only draw if we the current property is owned by the current toggle. else if (!Regex.IsMatch(propertyDescription, currentToggle.InspectorPropertyHideTag , RegexOptions.IgnoreCase)) { return; } // If we've gotten to this point, draw the shader property regulairly. ShaderProperty(shader,propertyIndex); }}
上面是我們自己實現的可以作為父類使用,其實就是做了一個介面封裝,下面程式碼是自己定義的為我們自己的Shader指令碼操作寫的幾個開關。如下所示:
using System.Collections.Generic;using UnityEngine;using UnityEditor; public class EditorInspector : CustomMaterialEditor{ protected override void CreateToggleList() { Toggles.Add(new FeatureEditor("Normal Enabled","normal","NORMALMAP_ON","NORMALMAP_OFF")); Toggles.Add(new FeatureEditor("Specular Enabled","specular","SPECULAR_ON","SPECULAR_OFF")); Toggles.Add(new FeatureEditor("Fresnel Enabled","fresnel","FRESNEL_ON","FRESNEL_OFF")); Toggles.Add(new FeatureEditor("Rim Light Enabled","rim","RIMLIGHT_ON","RIMLIGHT_OFF")); }}
相對來說比較簡單,它只是做了幾個Toggle作為Shader指令碼中的控制。將上述兩個檔案拖放到Editor資料夾下面。
下面才開始真正的Shader編寫工作,在這裡我們使用了SurfaceOutput作為我們的輸出結構體,SurfaceOutput簡單地描述了surface的屬性( properties of the surface ),如反射率顏色(albedo color)、法線(normal)、散射(emission)、鏡面反射(specularity )等。首先定義一個輸入結構體如下所示:
struct Input { float2 uv_DiffuseMap; #if SPECULAR_ON float2 uv_SpecMap; #endif #if NORMALMAP_ON float2 uv_NormalMap; #endif #if FRESNEL_ON || RIMLIGHT_ON float3 viewDir; #endif };
結構體中包含 Diffuse貼圖的uv座標uv_DiffuseMap,高光的uv座標uv_SpecMap,法線的uv座標uv_NormalMap,另外還定義了一個方向值viewDir。接下來就是實現主函式surf了,對於高光法線的渲染就在這裡面了,它呼叫了大量的CG庫函式,如下所示:
void surf (Input IN, inout SurfaceOutput o) { float3 TexData = tex2D(_DiffuseMap, IN.uv_DiffuseMap); float3 _BlendColor = _TintColor.rgb * _TintColorMultiply; o.Albedo.rgb = _Brightness * lerp(TexData, _TintColor.rgb, _TintColorMultiply) ; #if SPECULAR_ON o.Specular = _Gloss; o.Gloss = max(_SpecAdd + _SpecularMultiply, 1.0) * tex2D (_SpecMap, IN.uv_SpecMap); //o.Emission = _Gloss * tex2D (_SpecMap, IN.uv_SpecMap); #endif #if NORMALMAP_ON o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)); #endif #if FRESNEL_ON && SPECULAR_ON || RIMLIGHT_ON float facing = saturate(1.0 - max(dot(normalize(IN.viewDir), normalize(o.Normal)), 0.0)); #if FRESNEL_ON && SPECULAR_ON float fresnel = max(_FresnelBias + (1.0-_FresnelBias) * pow(facing, _FresnelPower), 0); fresnel = fresnel * o.Specular * _FresnelMultiply; o.Gloss *= 1+fresnel; #endif #if RIMLIGHT_ON float rim = max(_RimBias + (1.0-_RimBias) * pow(facing, _RimPower), 0); rim = rim * o.Specular * _RimMultiply; o.Albedo *= 1+rim; #endif #endif }
下面將Shader的完整程式碼展示如下:
Shader "Custom_Shaders/DNSRender"{ Properties { _TintColor ("Color Tint",color) = (1.0,1.0,1.0,1.0) //Diffuse Sliders _TintColorMultiply("Color Tint Multiply", Range(0.0, 1.0)) = 0.0 _Brightness ("Diffuse Brightness", Range(0.0, 2.0)) = 1.0 _DiffuseMap ("Diffuse (RGB)", 2D) = "white" {} _NormalMap ("Normal Map(RGB)", 2D) = "bump" {} _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1) _SpecularMultiply ("Specular Brightness",float) = 1.0 _SpecAdd ("Specular Boost", float) = 0 _SpecMap ("Specular Map (RGB)", 2D) = "grey" {} _Gloss ("Specular Glossiness", float) = 0.5 _FresnelPower ("Fresnel Power",float) = 1.0 _FresnelMultiply ("Fresnel Multiply", float) = 0.2 _FresnelBias ("Fresnel Bias", float) = -0.1 _RimPower ("RimLight Power",float) = 1.0 _RimMultiply ("RimLight Multiply", float) = 0.2 _RimBias ("RimLight Bias", float) = 0 _EmissionColor("Emission Color", color) = (1.0,1.0,1.0,1.0) } SubShader { Tags { "RenderType"="Opaque" } LOD 300 CGPROGRAM #pragma surface surf BlinnPhong #pragma target 3.0 #pragma shader_feature NORMALMAP_ON NORMALMAP_OFF #pragma shader_feature SPECULAR_ON SPECULAR_OFF #pragma shader_feature FRESNEL_ON FRESNEL_OFF #pragma shader_feature RIMLIGHT_ON RIMLIGHT_OFF float3 _TintColor; float _TintColorMultiply; float _Brightness; sampler2D _DiffuseMap; sampler2D _NormalMap; sampler2D _SpecMap; float _SpecularMultiply; float _SpecAdd; float _Gloss; float _FresnelPower; float _FresnelMultiply; float _FresnelBias; float _RimPower; float _RimMultiply; float _RimBias; float3 _EmissionColor; struct Input { float2 uv_DiffuseMap; #if SPECULAR_ON float2 uv_SpecMap; #endif #if NORMALMAP_ON float2 uv_NormalMap; #endif #if FRESNEL_ON || RIMLIGHT_ON float3 viewDir; #endif }; void surf (Input IN, inout SurfaceOutput o) { float3 TexData = tex2D(_DiffuseMap, IN.uv_DiffuseMap); float3 _BlendColor = _TintColor.rgb * _TintColorMultiply; o.Albedo.rgb = _Brightness * lerp(TexData, _TintColor.rgb, _TintColorMultiply) ; #if SPECULAR_ON o.Specular = _Gloss; o.Gloss = max(_SpecAdd + _SpecularMultiply, 1.0) * tex2D (_SpecMap, IN.uv_SpecMap); //o.Emission = _Gloss * tex2D (_SpecMap, IN.uv_SpecMap); #endif #if NORMALMAP_ON o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)); #endif #if FRESNEL_ON && SPECULAR_ON || RIMLIGHT_ON float facing = saturate(1.0 - max(dot(normalize(IN.viewDir), normalize(o.Normal)), 0.0)); #if FRESNEL_ON && SPECULAR_ON float fresnel = max(_FresnelBias + (1.0-_FresnelBias) * pow(facing, _FresnelPower), 0); fresnel = fresnel * o.Specular * _FresnelMultiply; o.Gloss *= 1+fresnel; #endif #if RIMLIGHT_ON float rim = max(_RimBias + (1.0-_RimBias) * pow(facing, _RimPower), 0); rim = rim * o.Specular * _RimMultiply; o.Albedo *= 1+rim; #endif #endif } ENDCG } CustomEditor "EditorInspector"}
將Shader掛接到材質球上效果如下:
最終展示效果如下所示: