本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。

这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。

========================================== 分割线 ==========================================

写在前面

之前学习的各种Shader时,我们从没有考虑在所有平台下的可用性。Unity是一个强大的跨平台游戏引擎,但这也决定了在编写代码时我们需要考虑更多的平台因素。对于Shader而言,如果没有进行相应的优化,很有可能无法运行在移动平台等对性能限制较高的平台上。我们需要理解一些关键的因素来优化我们的Shader,以提高游戏性能而又能尽可能保持取得同样的视觉效果。

尤其是如果你的目标平台包括Android系统,那么就一定要小心中国各种山寨机的大浪一下把你拍在沙滩上的后果。。。所以,如果你从来没有为你的Shader考虑过这些情况,那么,且用且小心吧。。。

这一章中,我们会学习三节内容:什么是一个高效的Shader,怎样对Shader进行性能分析,为移动平台优化我们的Shader。

那么,什么是一个高效的Shader呢?这是个有点复杂的问题,它涉及到了很多因素。例如,和你使用的变量个数及其所占内存,Shader使用的纹理个数有关等等。还有可能,你的Shade虽然工作良好,但我们实际商可以使用一半数目的变量就可以取得相同的效果。我们将在本节中发掘这样的一些技巧,并向你说明它们是如何组合起来让我们的Shader更快更高效的,而又可以各种平台上取得同样高质量的视觉效果。

准备工作

我们将首先使用一个最常见的Shader之一:Bumped Diffuse Shader。也就是应用了法线贴图的Shader。

  1. 创建一个新的场景和一个球体,添加一个平行光。
  2. 创建一个新的Shader和Material,可以命名为OptimizedShader001。
  3. 把Shader赋给Material,把Material赋给球体。
  4. 最后,使用下列代码修改Shader。

  1. Shader "Custom/OptimizedShader001" {
  2. Properties {
  3. _MainTex ("Base (RGB)", 2D) = "white" {}
  4. _NormalMap ("Normal Map", 2D) = "bump" {}
  5. }
  6. SubShader {
  7. Tags { "RenderType"="Opaque" }
  8. LOD 200
  9.  
  10. CGPROGRAM
  11. #pragma surface surf SimpleLambert
  12.  
  13. sampler2D _MainTex;
  14. sampler2D _NormalMap;
  15.  
  16. struct Input {
  17. float2 uv_MainTex;
  18. float2 uv_NormalMap;
  19. };
  20.  
  21. inline float4 LightingSimpleLambert (SurfaceOutput s, float3 lightDir, float atten)
  22. {
  23. float diff = max (0, dot (s.Normal, lightDir));
  24.  
  25. float4 c;
  26. c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
  27. c.a = s.Alpha;
  28. return c;
  29. }
  30.  
  31. void surf (Input IN, inout SurfaceOutput o)
  32. {
  33. float4 c = tex2D (_MainTex, IN.uv_MainTex);
  34.  
  35. o.Albedo = c.rgb;
  36. o.Alpha = c.a;
  37. o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
  38. }
  39. ENDCG
  40. }
  41. FallBack "Diffuse"
  42. }

简单的光照函数里面进行了简单的漫反射处理,surf函数里则改变了模型的法线。

最后,你得到的效果大概是这样的:

实现

下面,我们来一步步优化这个Shader。

首先,我们需要优化变量类型,以便它们尽可能少地占用内存:

  1. 修改Input结构。之前,我们的UV坐标都是存储在了float2类型的变量中,现在我们将它们改为half2
    1. struct Input {
    2. half2 uv_MainTex;
    3. half2 uv_NormalMap;
    4. };
  2. 接下来是光照函数。同样,将其中float家族的变量改成对应的fixed类型变量:
    1. inline fixed4 LightingSimpleLambert (SurfaceOutput s, fixed3 lightDir, fixed atten)
    2. {
    3. fixed diff = max (0, dot (s.Normal, lightDir));
    4.  
    5. fixed4 c;
    6. c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
    7. c.a = s.Alpha;
    8. return c;
    9. }
  3. 最后,修改surf函数中的变量类型。同样使用fixed类型变量:
    1. void surf (Input IN, inout SurfaceOutput o)
    2. {
    3. fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    4.  
    5. o.Albedo = c.rgb;
    6. o.Alpha = c.a;
    7. o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
    8. }
在修改了变量类型后,我们现在来利用Unity内置的光照函数变量,以便我们可以控制Shader是如何处理光源的。为此,我们可以很大程度上减少Shader处理的光源个数。修改#pragma声明:

  1. CGPROGRAM
  2. #pragma surface surf SimpleLambert noforwardadd

现在,我们可以使用共享UV坐标来继续优化Shader。为此,我们使用_MainTex的UV坐标代替_NormalMap的UV在UnpackNormal()中的查找作用,并移除Input结构中的uv_NormalMap:

  1. o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));

  1. struct Input {
  2. half2 uv_MainTex;
  3. };

最后,我们告诉Unity,这个Shader只工作在特定的渲染器上

  1. CGPROGRAM
  2. #pragma surface surf SimpleLambert exclude_path:prepass noforwardadd

最后优化前后效果如下(左前右后):

    

可以看出,我们肉眼几乎看不出任何差别,但是我们已经减少了这个Shader被绘制到屏幕上所花费的时间。我们将在下一节中利用Unity的可视化工具来分析这种减少程度的大小。但在这里,我们关注的是,使用了更少的数据来得到相同的渲染效果。在创建我们自己的Shader的时候,也要一直记住这个思想!

解释

上面一共提到了4种优化方式:优化变量类型,共享UV坐标,减少处理的光源个数,让Shader只工作在特定的渲染器上。下面,我们来更深入地理解这些技术是如何工作的,最后再学习其他一些技巧。

优化变量类型

首先,我们来看一下在我们声明变量时每个变量存储的数据大小。由于在声明变量时,我们往往有多个选择(float,half,fixed),我们需要来看一下这些类型的特点:

  • float:高精度浮点值,通常是32位,也是三者中最慢的一个。它对应的还有float2,float3和float4。
  • half:中精度浮点值。通常是16位,范围是-60000至+60000,它适合存储UV坐标,颜色值等,比float类型快很多。它对应的还有half2,half3,和half4。
  • fixed:低精度浮点值。通常是11位,范围是-2.0至+2.0,精度为1/256。这是三者中最小的一个,可以用于光照计算颜色等。它对应的值有fixed2,fixed3和fixed4。
官网对类型的选择,给出了下面的建议:
  • 尽可能使用低精度变量。
  • 对于颜色值和单位长度的向量,使用fixed。
  • 对于其他类型,如果范围和精度合适的话,使用half;其他情况使用float。

减少处理的光源个数

从上可以看出,这一步优化是通过在#pragma语句中声明noforwardadd值来实现的。这主要是告诉Unity,使用这种Shader的对象,只接受一个单一的平行光光源作为逐像素光源,其他的光源都使用内置的球谐函数处理后作为逐顶点的光源。当我们在场景中放置了另一个光源时,这种策略会很明显,因为我们的Shader使用一个法线贴图进行逐像素的操作。

这样做当然很好,但是如果我们需要不止一个平行光,而且想要控制哪一个是用于该逐像素计算的主光源,又该怎么办呢?这就需要Unity面板中的一个设置啦!如果你仔细观察,就会法线每一个光源都有一个Render Mode下拉菜单。当你点击它时,会出现Auto, Important, 和Not Important三种选项。通过选择Important,你可以告诉Unity这个光源更需要被当成一个逐像素光源,而非一个逐顶点光源。如果设置为Auto,那么就由Unity自己做决定啦!

懵了是不是。。。为了说明上述意思,我们来做个试验!在场景里放置另一个点光源,然后移除Shader中的Main Texture。第一次,打开平行光,关闭点光源(左图);第二次关闭平行光,打开点光源(右图)。你可以发现第二个点光源并不会影响我们的法线贴图(只是照亮了模型,也就是它只是逐顶点处理),只有第一个平行光才会影响。

   

这里的优化,是由于我们把其他所有光源当成了顶点光源,而在计算像素颜色时只计算一个主平行光作为像素光源。

共享UV坐标

这步优化很简单,仅仅使用了Main Texture的UV坐标来代替法线贴图的UV坐标,这样实际上减少了内部提取法线贴图UV坐标的代码。这种方法可以很好地简化我们的代码。

只工作在特定渲染器上

最后,我们在语句中声明了,以便告诉Unity,这个Shader不会再接受来自延迟渲染中的其他任何自定义的光照。这意味着,我们仅可以在正向渲染(forward render)中有效地使用这个Shader,这是在主摄像机的设置中设置的。

帮助链接:正向渲染延迟渲染

写在最后

其他的优化策略还有很多。我们之前学过如何把多个灰度图打包到一个RGBA贴图中,以及如何使用一张贴图来模拟光照效果。由于这些众多的技术,因此问如何优化Shader是一个很模糊的问题。但是,了解这些技术使得我们可以根据不同的Shader和平台采用合适的技术,来得到一个具有稳定帧率的Shader。

【Unity Shaders】Mobile Shader Adjustment—— 什么是高效的Shader的更多相关文章

  1. 【Unity Shaders】Mobile Shader Adjustment —— 为手机定制Shader

    本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图.这里是本书所需的代码和 ...

  2. Unity Shaders Vertex & Fragment Shader入门

    http://blog.csdn.net/candycat1992/article/details/40212735 三个月以前,在一篇讲卡通风格的Shader的最后,我们说到在Surface Sha ...

  3. 【Unity Shaders】使用CgInclude让你的Shader模块化——创建CgInclude文件存储光照模型

    本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图. 这里是本书所需的代码 ...

  4. 【Unity Shaders】使用CgInclude让你的Shader模块化——Unity内置的CgInclude文件

    本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图. 这里是本书所需的代码 ...

  5. 【Unity Shaders】Vertex & Fragment Shader入门

    写在前面 三个月以前,在一篇讲卡通风格的Shader的最后,我们说到在Surface Shader中实现描边效果的弊端,也就是只对表面平缓的模型有效.这是因为我们是依赖法线和视角的点乘结果来进行描边判 ...

  6. 【Unity Shaders】使用CgInclude让你的Shader模块化——使用#define指令创建Shader

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  7. 【Unity Shaders】Shader学习资源和Surface Shader概述

    写在前面 写这篇文章的时候,我断断续续学习Unity Shader半年了,其实还是个门外汉.我也能体会很多童鞋那种想要学好Shader却无从下手的感觉.在这个期间,我找到一些学习Shader的教程以及 ...

  8. 【Unity Shaders】Transparency —— 透明的cutoff shader

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  9. 【Unity Shaders】Diffuse Shading——在Surface Shader中使用properties

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

随机推荐

  1. python (3.5)字符串 持续更新中………………

    # 字符串与变量连接输出 name = input("请输入姓名")age = input("请输入年龄")job = input("请输入工作&qu ...

  2. salesforce lightning零基础学习(二) lightning 知识简单介绍----lightning事件驱动模型

    看此篇博客前或者后,看一下trailhead可以加深印象以及理解的更好:https://trailhead.salesforce.com/modules/lex_dev_lc_basics 做过cla ...

  3. 4月18开始看《C++Primer Plus》

    好久没有写博客了,之前也说过水平有限,也写不出什么技术博客,只能写些感悟. 过年之后,陆续做了2.3个项目,刚开始可能不太熟悉流程,怎么和页面传数据?最近一个项目1/2天就写完代码了,真的很简单,主要 ...

  4. 初识Redis系列之四:.net使用Redis存储数据

    首先Redis在Windows上的安装前面的文章已经介绍过,这里不介绍了,直奔主题, 直接来看看.net怎么使用Redis 首先需要引用redis相关的dll,两个途径,意识网上下载编译好的dll : ...

  5. 一个蒟蒻对FFT的理解(蒟蒻也能看懂的FFT)

    建议同学们先自学一下"复数(虚数)"的性质.运算等知识,不然看这篇文章有很大概率看不懂. 前言 作为一个典型的蒟蒻,别人的博客都看不懂,只好自己写一篇了. 膜拜机房大佬 HY 一. ...

  6. 如何改变hr颜色

    html中用css改变颜色,<hr style="border:0;background-color:#ff0000;height:1px;">如果不加border:0 ...

  7. C语言关闭日志文件时忘了将日志文件全局变量指针置为NULL

    C语言写了一个write_log函数以写日志,写了一个close_log_file函数以关闭日志,声明了一个日志文件全局变量文件指针plogFile. write_log中首先判断plogFile是否 ...

  8. synchronized与条件同步

    在并发编程中,有这样的需求:当满足某个条件时线程执行同步块中的代码,条件不满足时,让线程在此等待,直至条件满足再执行同步代码块. java的Object类即提供了一类这样的方法wait(),notif ...

  9. Android开发过程中在sh,py,mk文件中添加log信息的方法

    Android开发过程中在sh,py,mk文件中添加log信息的方法 在sh文件中: echo "this is a log info" + $info 在py文件中: print ...

  10. 永远不要在循环之外调用wait方法

    1. 前言 随着摩尔定律的失效,Amdahl定律成为了多核计算机性能发展的指导.对于现在的java程序员们来说,并发编程越来越重要和习以为常.很惭愧和恐慌的是我对java的并发编程一直是只知道概念,入 ...