渲染技术

 渲染技术这个东西,在项目前期我并没有投入太多精力去思考,在当时的环境下我更看中手游的性能,我做端游,可以无限地挖掘电脑的性能,比如多线程去处理角色的软件蒙皮之类,但是做手游,则要保护手机的硬件资源,防止手机耗电快发热.比如限帧30fps.直到两年前经历了一次美术和技术的撕逼以后,我才重新思考了一下这个东西.整个过程也很有趣,老板觉得游戏画面不行,其实是美术做得不好,但是美术领导用了一堆次世代的技术名词来忽悠老板说我们技术不行,啥都不支持...一个页游公司的美术整天吹嘘次世代我也是醉了,真正做过次世代的我反而成了外行...次世代本身就是个大坑,手游上投入的性价比更低,很多大点的公司都深陷次世代的泥潭,我要对游戏的技术负责和把关,撕逼是难免的.但是撕逼归撕逼,想要画面好是大家的共同心愿,能不能在保证效率和投入性价比的情况下,提升一些画面呢?

       我的想法是,一些牛逼技术是一定是要有的,如果性能有问题,那么尽量优化,甚至限制性的使用,有总比没有好,不管是为了应付美术,还是老板,当然这不是我的初衷.我作为一个热爱引擎的程序员,画面好本身就是我们追求的东西.做技术的一定要有追求.

       然后对整个渲染技术架构思考了几天,做出了一个大胆的想法,就是升级Unity4到Unity5,然后放弃Unity自带的surface shader和实时光照,利用Unity5新增的shader_feature功能自己写一套比较万能通用的standard shader,而避免整个项目一堆乱七八糟混乱的shader.至于为什么要这么干,主要原因有几个.

1.角色的动态光照,用全局shader参数来模拟灯光,比在Unity场景里面摆几个光源的效率更高一些.

2.surface shader的一些默认的定制会占据一些texture interpolator,对于我实现扩展一些效果有限制.而且不够清爽.

3.一些特殊效果需要场景所有对象shader的支持,比如天气系统的渐变过渡.如果所有shader都自己写的,那会特别方便.

4.Unity4的时候,光照图和Unity的动态光源是不能叠加的,如果自己实现的动态光源,就可以叠加的,当然Unity5后来是可以叠加的.

        我这套shader基本涵盖了场景中的所有对象:

1.物件用SceneStandard.shader(有PBR版本)

2.角色用CharacterStandard.shader(有PBR版本)

3.粒子用ParticleStandard.shader

4.天空用SkyBox.shader

5.地形用TerrainT4M.shader

6.水面用Water.shader

7.小草用Grass.shader

       整个场景的光照方案,场景用光照图 + 全局shader光源,角色用全局shader光源 + projector shadowmap.对于角色和场景,都有两套shader,一套传统光照模型,一套PBR光照模型.

       以一个传统光照模型的SceneStandard.shader来说.

[cpp]  view plain  copy
  1. Shader "Luoyinan/Scene/SceneStandard"   
  2. {  
  3.     Properties  
  4.     {  
  5.         _Color ("Main Color", Color) = (1, 1, 1, 1)  
  6.         _MainTex("Main Texture RGB(Albedo) A(Gloss & Alpha)", 2D) = "white" {}  
  7.         _NormalTex("Normal Texture", 2D) = "bump" {}  
  8.         _GlossTex ("Gloss Texture", 2D) = "white" {}  
  9.   
  10.         _HalfLambert("Half Lambert", Range (0.5, 1)) = 0.75  
  11.   
  12.         _SpecularIntensity("Specular Intensity", Range (0, 2)) = 0  
  13.         _SpecularSharp("Specular Sharp",Float) = 32  
  14.         _SpecularLuminanceMask("Specular Luminance Mask", Range (0, 2)) = 0  
  15.     }  
  16.   
  17.     SubShader  
  18.     {  
  19.         Tags  
  20.         {  
  21.             "Queue" = "Background"        
  22.             "RenderType" = "Opaque" // 支持渲染到_CameraDepthNormalsTexture  
  23.         }  
  24.   
  25.         Pass  
  26.         {  
  27.             Lighting Off  
  28.             CGPROGRAM  
  29.   
  30.             #pragma vertex vert  
  31.             #pragma fragment frag  
  32.             #pragma multi_compile_fog  
  33.             #pragma fragmentoption ARB_precision_hint_fastest  
  34.             #include "UnityCG.cginc"  
  35.   
  36.             #pragma shader_feature _NORMAL_MAP  
  37.             #pragma shader_feature _LUMINANCE_MASK_ON  
  38.   
  39.             #pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON  
  40.             #pragma multi_compile __ _FADING_ON  
  41.             #pragma multi_compile __ _POINT_LIGHT  
  42.             #pragma multi_compile __ _FANCY_STUFF  
  43.           
  44.             struct appdata_lightmap   
  45.             {  
  46.                 float4 vertex : POSITION;  
  47.                 half2 texcoord : TEXCOORD0;  
  48.                 half2 texcoord1 : TEXCOORD1;  
  49. #if _FANCY_STUFF  
  50.                 half3 normal : NORMAL;  
  51.     #if _NORMAL_MAP  
  52.                 half4 tangent : TANGENT;  
  53.     #endif  
  54. #endif   
  55.             };  
  56.   
  57.             // SM2.0的texture interpolator只有8个,要合理规划.  
  58.             struct v2f   
  59.             {  
  60.                 float4 pos : SV_POSITION;  
  61.                 half2 uv0 : TEXCOORD0;  
  62. #ifndef LIGHTMAP_OFF   
  63.                 half2 uv1 : TEXCOORD1;  
  64. #endif  
  65.                 UNITY_FOG_COORDS(2)  
  66.                 float3 posWorld : TEXCOORD3;  
  67. #if _FANCY_STUFF  
  68.                 half3 normalWorld : TEXCOORD4;  
  69.     #if _NORMAL_MAP  
  70.                 half3 tangentWorld : TEXCOORD5;  
  71.                 half3 binormalWorld : TEXCOORD6;  
  72.     #endif  
  73. #endif   
  74.             };  
  75.           
  76.             fixed4 _Color;  
  77.             sampler2D _MainTex;  
  78.             half4 _MainTex_ST;  
  79.   
  80. #if _POINT_LIGHT  
  81.             float4 _GlobalPointLightPos;  
  82.             fixed4 _GlobalPointLightColor;  
  83.             fixed _GlobalPointLightRange;  
  84. #endif   
  85.   
  86. #ifndef LIGHTMAP_OFF  
  87.     #if _FADING_ON  
  88.             sampler2D _GlobalLightMap;  
  89.             fixed _GlobalFadingFactor;  
  90.     #endif  
  91. #endif  
  92.   
  93. #if _FANCY_STUFF  
  94.             sampler2D _GlossTex;  
  95.             fixed _HalfLambert;  
  96.   
  97.             fixed _SpecularIntensity;  
  98.             fixed _SpecularSharp;  
  99.             half4 _GlobalMainLightDir;  
  100.             fixed4 _GlobalMainLightColor;  
  101.             half4 _GlobalBackLightDir;  
  102.             fixed4 _GlobalBackLightColor;  
  103.   
  104.     #if _LUMINANCE_MASK_ON  
  105.             fixed _SpecularLuminanceMask;  
  106.     #endif  
  107.   
  108.     #if _NORMAL_MAP  
  109.             uniform sampler2D _NormalTex;  
  110.             half4 _NormalTex_ST;  
  111.     #endif  
  112. #endif   
  113.   
  114.             v2f vert(appdata_lightmap i)  
  115.             {  
  116.                 v2f o;  
  117.                 o.pos = mul(UNITY_MATRIX_MVP, i.vertex);  
  118.                 o.uv0 = TRANSFORM_TEX(i.texcoord, _MainTex);  
  119. #ifndef LIGHTMAP_OFF   
  120.                 o.uv1 = i.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;  
  121. #endif  
  122.                 o.posWorld = mul(unity_ObjectToWorld, i.vertex).xyz;  
  123. #if _FANCY_STUFF  
  124.                 o.normalWorld = UnityObjectToWorldNormal(i.normal);  
  125.     #if _NORMAL_MAP  
  126.                 o.tangentWorld = UnityObjectToWorldDir(i.tangent);  
  127.                 o.binormalWorld = cross(o.normalWorld, o.tangentWorld) * i.tangent.w;  
  128.     #endif  
  129. #endif   
  130.                 UNITY_TRANSFER_FOG(o, o.pos);  
  131.                 return o;  
  132.             }  
  133.           
  134.             fixed4 frag(v2f i) : COLOR  
  135.             {             
  136.                 fixed4 mainColor = tex2D(_MainTex, i.uv0);  
  137.                 fixed alpha = mainColor.a;   
  138.                 fixed4 finalColor = mainColor * _Color;  
  139.   
  140.                 // lightmap  
  141. #ifndef LIGHTMAP_OFF   
  142.                 fixed3 lm = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv1));  
  143.     #if _FADING_ON  
  144.                 fixed3 lm_fading = DecodeLightmap(UNITY_SAMPLE_TEX2D(_GlobalLightMap, i.uv1));  
  145.                 lm = lerp(lm, lm_fading, _GlobalFadingFactor);            
  146.     #endif  
  147.     #if _FANCY_STUFF && _LUMINANCE_MASK_ON  
  148.                 half lumin = saturate(Luminance(lm) * _SpecularLuminanceMask);  
  149.     #endif  
  150.                 finalColor.rgb *= lm;     
  151. #endif                
  152.   
  153. #if _FANCY_STUFF              
  154.                 // gloss  
  155.                 alpha *= tex2D(_GlossTex, i.uv0).r;  
  156.   
  157.                 // normalmap  
  158.     #if _NORMAL_MAP  
  159.                 fixed3x3 tangentToWorld = fixed3x3(i.tangentWorld, i.binormalWorld, i.normalWorld);  
  160.                 half3 normalMap = UnpackNormal(tex2D(_NormalTex, i.uv0));  
  161.                 half3 fixedNormal = normalize(mul(normalMap, tangentToWorld));  
  162.     #else  
  163.                 half3 fixedNormal = normalize(i.normalWorld);  
  164.     #endif  
  165.   
  166.                 // main light diffuse  
  167.     #if _NORMAL_MAP || LIGHTMAP_OFF   
  168.                 half nl = dot(fixedNormal, normalize(_GlobalMainLightDir.xyz));  
  169.                 half diff = saturate(nl) * (1 - _HalfLambert) + _HalfLambert;   
  170.                 finalColor *= diff;  
  171.     #endif    
  172.           
  173.     #if _NORMAL_MAP   
  174.                 // main light specular  
  175.                 half3 viewDir = normalize(_WorldSpaceCameraPos - i.posWorld);  
  176.                 half3 h = normalize(normalize(_GlobalMainLightDir.xyz) + viewDir);  
  177.                 half nh = saturate(dot(fixedNormal, h));  
  178.                 nh = pow(nh, _SpecularSharp) * _SpecularIntensity;  
  179.         #if _LUMINANCE_MASK_ON && LIGHTMAP_ON  
  180.                 finalColor.rgb += _GlobalMainLightColor.rgb * nh * alpha * _GlobalMainLightColor.a * lumin;  
  181.         #else  
  182.                 finalColor.rgb += _GlobalMainLightColor.rgb * nh * alpha * _GlobalMainLightColor.a;  
  183.         #endif  
  184.           
  185.                 // back light specular  
  186.                 h = normalize(normalize(_GlobalBackLightDir.xyz) + viewDir);  
  187.                 nh = saturate(dot(fixedNormal, h));  
  188.                 nh = pow(nh, _SpecularSharp) * _SpecularIntensity;  
  189.         #if _LUMINANCE_MASK_ON && LIGHTMAP_ON  
  190.                 finalColor.rgb += _GlobalBackLightColor.rgb * nh * alpha * _GlobalBackLightColor.a * lumin;  
  191.         #else  
  192.                 finalColor.rgb += _GlobalBackLightColor.rgb * nh * alpha * _GlobalBackLightColor.a;  
  193.         #endif  
  194.   
  195.     #endif  
  196.   
  197.     #if _POINT_LIGHT              
  198.                 half3 toLight = _GlobalPointLightPos.xyz - i.posWorld ;  
  199.                 half ratio = saturate(length(toLight) / _GlobalPointLightRange);  
  200.                 //half attenuation = 1 - ratio; // linear attenuation  
  201.                 ratio *= ratio;  
  202.                 half attenuation = 1.0 / (1.0 + 0.01 * ratio) * (1 - ratio); // quadratic attenuation  
  203.                 if (attenuation > 0) // performance  
  204.                 {  
  205.                     // point light diffuse  
  206.                     toLight = normalize(toLight);  
  207.                     half intensity = 8;  
  208.                     half nl2 = max(0, dot(fixedNormal, toLight));  
  209.                     finalColor.rgb += mainColor.rgb * _GlobalPointLightColor.rgb * nl2 * attenuation * intensity;  
  210.   
  211.                     // point light specular  
  212.         #if _NORMAL_MAP   
  213.                     h = normalize(toLight + viewDir);  
  214.                     nh = saturate(dot(fixedNormal, h));  
  215.                     nh = pow(nh, _SpecularSharp) * _SpecularIntensity;  
  216.                     intensity *= _GlobalPointLightColor.a;  
  217.                     finalColor.rgb += _GlobalPointLightColor.rgb * nh * alpha * attenuation * intensity;  
  218.         #endif  
  219.                 }     
  220.     #endif  
  221.   
  222. #endif   
  223.                 UNITY_APPLY_FOG(i.fogCoord, finalColor);  
  224.   
  225.                 // 没有高光贴图,alpha默认为0,便于处理Bloom的Alpha Gloss  
  226. #if _NORMAL_MAP  
  227.                 finalColor.a = alpha;  
  228. #else  
  229.                 finalColor.a = 0;  
  230. #endif  
  231.                 return  finalColor;  
  232.             }  
  233.             ENDCG  
  234.         }  
  235.   
  236.         // 没用Unity自带的阴影,只是用来来渲染_CameraDepthsTexture.  
  237.         Pass  
  238.         {  
  239.             Tags { "LightMode" = "ShadowCaster" }  
  240.   
  241.             Fog { Mode Off }  
  242.             ZWrite On   
  243.             Offset 1, 1  
  244.   
  245.             CGPROGRAM  
  246.   
  247.             #pragma vertex vert  
  248.             #pragma fragment frag  
  249.             #pragma multi_compile_shadowcaster  
  250.             #pragma fragmentoption ARB_precision_hint_fastest  
  251.             #include "UnityCG.cginc"  
  252.   
  253.             struct v2f  
  254.             {  
  255.                 V2F_SHADOW_CASTER;  
  256.             };  
  257.   
  258.             v2f vert(appdata_base v)  
  259.             {  
  260.                 v2f o;  
  261.                 TRANSFER_SHADOW_CASTER(o)  
  262.                 return o;  
  263.             }  
  264.   
  265.             fixed4 frag(v2f i) : COLOR  
  266.             {  
  267.                 SHADOW_CASTER_FRAGMENT(i)  
  268.             }  
  269.   
  270.             ENDCG  
  271.         }  
  272.     }  
  273.   
  274.     Fallback off  
  275.     CustomEditor "SceneStandard_ShaderGUI"  
  276. }  

      这shader比较简单,主要用于场景中的物件,实现了几个功能

1.光照图和光照图的融合过渡.

      光照图过渡用于天气系统的渐变效果处理,比如白天的光照图和夜晚的光照图的渐变处理.

2.法线贴图和高光贴图

      法线贴图可用高模去生成,投入有限的公司建议直接工具生成,效果差点但是性价比高,高光贴图的分辨率可减半,效果差不多.高光贴图的通道渲染进屏幕的alpha通道,对于后期生成基于gloss的bloom效果很有用.

3.光照图阴影区域的高光处理

       理论上来说,静态阴影区域不应该有高光效果的,但是Unity的光照图,没用alpha通道来存储阴影区域的掩码,导致我们无法知道阴影区域,这也是我一直吐槽Unity的地方,较新版本的Unity已经支持光照图阴影掩码...但是我们不可能升级到最新的Unity版本...所以只好选用了一个有些开销的方案来处理,就是用亮度来判断这是否是阴影区域.然后淡化高光.

4.点光源

       我原来只打算用全局shader参数模拟两盏方向光,对于有些地宫的暗黑场景,确实需要点光源的支持,这样角色走到哪里,就能照亮到哪里,最早考虑过一个面片叠加的假的光源绑定在角色身上,但是效果一般,最后还是模拟了一个实时点光源.逐像素的.对于点光源的衰减处理,Unity是用一张图来处理,我试了几种曲线函数,最终选用

[cpp]  view plain  copy
  1. half attenuation = 1.0 / (1.0 + 0.01 * ratio) * (1 - ratio); // quadratic attenuation  

       这个shader的效果图如下:

漫反射贴图:

    

    漫反射贴图 + 光照图:


漫反射贴图 + 光照图 + 高光:


漫反射贴图 + 光照图 + 高光 + 法线贴图 + 高光贴图:


对于SceneStardard来说,有个功能,就是植被的随风摆动问题,原来集成在一起的,后来独立开来了一个shader.

[cpp]  view plain  copy
  1. Shader "Luoyinan/Scene/SceneStandard_Cutout"   
  2. {  
  3.     Properties  
  4.     {  
  5.         _Color ("Main Color", Color) = (1, 1, 1, 1)  
  6.         _MainTex("Main Texture RGB(Albedo) A(Gloss & Alpha)", 2D) = "white" {}  
  7.         _NormalTex("Normal Texture", 2D) = "bump" {}  
  8.         _GlossTex ("Gloss Texture", 2D) = "white" {}  
  9.   
  10.         _HalfLambert("Half Lambert", Range (0.5, 1)) = 0.75  
  11.   
  12.         _SpecularIntensity("Specular Intensity", Range (0, 2)) = 0  
  13.         _SpecularSharp("Specular Sharp",Float) = 32  
  14.   
  15.         _MainBendingFactor ("Wind Main Bending Factor (Blue)"float) = 0.25  
  16.         _MainBendingFactor2 ("Wind Main Bending Factor 2 (Blue)"float) = 1.0  
  17.         _BranchBendingFactor ("Wind Branch Bending Factor (Red)"float) = 2.5  
  18.         _EdgeBendingFactor ("Wind Edge Bending Factor (Green)"float) = 1.0  
  19.         _EdgeFrequencyFactor ("Wind Edge Frequency Factor"float) = 1.0          
  20.   
  21.         [HideInInspector] _DoubleSided("", Float) = 2.0  
  22.         [HideInInspector] _WindOn("", Float) = 1.0  
  23.         [HideInInspector] _Cutoff("", Float) = 0.2  
  24.     }  

自定义了shader以后,那么shader编辑器,也需要自定义.

[csharp]  view plain  copy
  1. // 2016.5.14 luoyinan 自定义shader的编辑器  
  2. using UnityEditor;  
  3. using UnityEngine;  
  4. using UnityEngine.Rendering;  
  5.   
  6. public class SceneStandard_Cutout_ShaderGUI : ShaderGUI  
  7. {  
  8.     MaterialProperty cutOff = null;  
  9.     MaterialProperty wind = null;  
  10.   
  11.     public void FindProperties(MaterialProperty[] props)  
  12.     {  
  13.         wind = FindProperty("_WindOn", props);  
  14.         cutOff = FindProperty("_Cutoff", props);  
  15.     }  
  16.   
  17.     public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)  
  18.     {  
  19.         FindProperties(props);  
  20.   
  21.         // Check change  
  22.         EditorGUI.BeginChangeCheck();  
  23.         {  
  24.             float co = cutOff.floatValue;  
  25.             cutOff.floatValue = EditorGUILayout.Slider("Cut Off", co, 0f, 1f);  
  26.   
  27.             bool w = wind.floatValue == 0;  
  28.             wind.floatValue = EditorGUILayout.Toggle("Wind", w) ? 0 : 1;  
  29.   
  30.             // Render the default gui  
  31.             base.OnGUI(materialEditor, props);  
  32.         }  
  33.   
  34.         if (EditorGUI.EndChangeCheck())  
  35.         {  
  36.             foreach (var obj in materialEditor.targets)  
  37.             {  
  38.                 MaterialChanged((Material)obj);  
  39.             }  
  40.         }  
  41.     }  
  42.   
  43.     public static void MaterialChanged(Material material)  
  44.     {  
  45.         float value = material.GetFloat("_Cutoff");  
  46.         material.SetFloat("_Cutoff", value);  
  47.         if (value > 0)  
  48.         {  
  49.             material.EnableKeyword("_ALPHA_TEST_ON");  
  50.             material.SetFloat("_DoubleSided", (float)CullMode.Off);  
  51.         }  
  52.         else  
  53.         {  
  54.             material.DisableKeyword("_ALPHA_TEST_ON");  
  55.             material.SetFloat("_DoubleSided", (float)CullMode.Back);  
  56.         }  
  57.   
  58.         value = material.GetFloat("_WindOn");  
  59.         material.SetFloat("_WindOn", value);  
  60.         SetKeyword(material, "_WIND_ON", value == 0);  
  61.   
  62.         // 有纹理就自动开启  
  63.         SetKeyword(material, "_NORMAL_MAP", material.GetTexture("_NormalTex"));  
  64.     }  
  65.   
  66.     private static void SetKeyword(Material mat, string keyword, bool enable)  
  67.     {  
  68.         if (enable)  
  69.             mat.EnableKeyword(keyword);  
  70.         else  
  71.             mat.DisableKeyword(keyword);  
  72.     }  
  73. }  

传统光照模型的效果,除了反射,基本都实现了,那么反射呢,我是去掉了,移到了PBR里面,因为传统光照模型的环境反射效果其实并不好,要用反射,可用PBR的shader代替.

[cpp]  view plain  copy
  1. Shader "Luoyinan/Scene/SceneStandard_PBR"   
  2. {  
  3.     Properties  
  4.     {  
  5.         _Color ("Main Color", Color) = (1, 1, 1, 1)  
  6.         _MainTex("Main Texture RGB(Albedo) A(Gloss & Alpha)", 2D) = "white" {}  
  7.         _NormalTex("Normal Texture", 2D) = "bump" {}  
  8.         _GlossTex ("Gloss Texture", 2D) = "white" {}  
  9.         _SpecularColor ("Specular Color", Color) = (1, 1, 1, 0.5)     
  10.         _Roughness ("Roughness", Range (0, 1)) = 0  
  11.         _RefectionTex("Refection Texture (Cubemap)", Cube) = "" {}  
  12.         _RefectionColor ("Refection Color", Color) = (1, 1, 1, 1)  
  13.   
  14.         [HideInInspector] _UseRoughness("", Float) = 0  
  15.     }  

       如果要集成很多效果在一个shader里面,就需要利用multi_compile和shader_feature设置好各种效果的功能开关,高中低的画质的切换也特别方便.

猜你喜欢

转载自blog.csdn.net/qq_14914623/article/details/80723426