笔者介绍:姜雪伟,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... returnif (!isVisible)return;// Get the current keywords from the materialMaterial 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 valuesfor (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_ONfloat2 uv_SpecMap;#endif#if NORMALMAP_ONfloat2 uv_NormalMap;#endif#if FRESNEL_ON || RIMLIGHT_ONfloat3 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_ONo.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_ONo.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));#endif#if FRESNEL_ON && SPECULAR_ON || RIMLIGHT_ONfloat facing = saturate(1.0 - max(dot(normalize(IN.viewDir), normalize(o.Normal)), 0.0));#if FRESNEL_ON && SPECULAR_ONfloat fresnel = max(_FresnelBias + (1.0-_FresnelBias) * pow(facing, _FresnelPower), 0);fresnel = fresnel * o.Specular * _FresnelMultiply;o.Gloss *= 1+fresnel;#endif        #if RIMLIGHT_ONfloat 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 300CGPROGRAM#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_OFFfloat3 _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_ONfloat2 uv_SpecMap;#endif#if NORMALMAP_ONfloat2 uv_NormalMap;#endif#if FRESNEL_ON || RIMLIGHT_ONfloat3 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_ONo.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_ONo.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));#endif#if FRESNEL_ON && SPECULAR_ON || RIMLIGHT_ONfloat facing = saturate(1.0 - max(dot(normalize(IN.viewDir), normalize(o.Normal)), 0.0));#if FRESNEL_ON && SPECULAR_ONfloat fresnel = max(_FresnelBias + (1.0-_FresnelBias) * pow(facing, _FresnelPower), 0);fresnel = fresnel * o.Specular * _FresnelMultiply;o.Gloss *= 1+fresnel;#endif        #if RIMLIGHT_ONfloat 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挂接到材质球上效果如下:

最终展示效果如下所示:

Unity3D引擎之高级渲染技术相关推荐

  1. 【Unity3D】Unity3D引擎技术点图谱

    Unity3D引擎技术点图谱 这图是对Unity3D引擎(版本5)的技术点罗列,未涉及到具体点的相关只是,可以将其当做自身知识体系的梳理对照图来看. 参考 [1] Unity用户手册 [2] 本人gi ...

  2. Nicholas谈UE4高级渲染:动态光照迭代快

    GameLook报道/6月5日,2015年第三期GameLook开放日‧虚幻引擎专场活动在上海正式举行,此次活动由Epic Games与GameLook联合主办. 如何用好UE4引擎,发挥其强大的画面 ...

  3. Unity3D引擎之渲染技术系列一

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

  4. Unity3D引擎之渲染技术系列四

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

  5. 【我的渲染技术进阶之旅】基于Filament渲染引擎绘制一个不停旋转的彩色矩形

    一.绘制三角形回顾 在上一篇博客 [我的渲染技术进阶之旅]Google开源的基于物理的实时渲染引擎Filament源码分析:Android版本的Filament第一个示例:sample-hello-t ...

  6. Unity3D 游戏引擎之Unity3D回馈IOS高级界面消息 (十)

    Unity3D 游戏引擎之Unity3D回馈IOS高级界面消息 雨松MOMO原创文章如转载,请注明:转载至我的独立域名博客雨松MOMO程序研究院,原文地址:http://www.xuanyusong. ...

  7. Unity3D手游项目的总结和思考(1) - 渲染技术

    有朋友私信我问我为啥很久不更新博客,是不是转行了...我当然不可能承认自己懒啊,只能回复说太忙了.不过项目开发中,确实很难有时间和心力去总结和思考一些东西,不过现在忙完一些项目以后,我又回来了. 渲染 ...

  8. Unity3D Linear和Gamma渲染

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

  9. 基于物理的渲染技术(PBR)系列一

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

最新文章

  1. 认知科学顶刊:挑战过去50年神经科学观点,人类智力的优势或来自于记忆储存方式...
  2. C语言结构联合位字段知识体系总结大学霸IT达人
  3. 研究人员使用宽场脑成像技术研究意图控制,探索更好的脑机接口使用方式
  4. linux内核之内存管理.doc,linux内核之内存管理.doc
  5. 【PyCharm疑问】在pycharm中带有中文时,有时会导致程序判断错误,是何原因?...
  6. android TextView 的setTextSize方法的使用
  7. Scala 内部类及外部类
  8. Android SDK安装与环境配置
  9. Atom飞行手册翻译: 4.2 深入键表(keymap)
  10. 【Python笔记】集合
  11. 2020-2022年最新的智能群优化算法
  12. 数据分析-Excel简介
  13. 判断点集与多边形的位置关系
  14. 字节还能如何“跳动”
  15. 公众号运营该如何快速找到内容方向定位?
  16. 【量化投资】策略一(聚宽)
  17. 【创业分享】2022年,仅赚几万,但却很踏实?
  18. 办公网络终端都应该做哪些安全防御措施
  19. 主播都在播的王牌战争:代号英雄是款什么样的游戏?王牌战争模拟器电脑版教程
  20. 生物医学工程方向——SCI投稿经验分享 (Ultrasound in Medicine Biology)

热门文章

  1. NALU的解析(包括SEI的知识)decodeSODBSeiData
  2. 小米测试总监,10年软件测试工程师感悟,写给还在迷茫中的朋友!
  3. SCADE Suite 状态机之变量隐式赋值
  4. 史上最萌挑战现身海天蚝油《挑战不可能之加油中国》
  5. 拓扑排序--猫猫向前冲
  6. 【Zuul2】Zuul2网关介绍及案例(非spring集成)
  7. 《R语言游戏数据分析与挖掘》一2.2 数据对象
  8. JavaSE-02 Operater运算符
  9. 天创速盈:关于拼多多店铺层级,你了解多少?
  10. 计算机组成原理学习笔记:海明校验码