本系列主要参考《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。

Shader "Custom/OptimizedShader001" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200 CGPROGRAM
#pragma surface surf SimpleLambert sampler2D _MainTex;
sampler2D _NormalMap; struct Input {
float2 uv_MainTex;
float2 uv_NormalMap;
}; inline float4 LightingSimpleLambert (SurfaceOutput s, float3 lightDir, float atten)
{
float diff = max (0, dot (s.Normal, lightDir)); float4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
c.a = s.Alpha;
return c;
} void surf (Input IN, inout SurfaceOutput o)
{
float4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb;
o.Alpha = c.a;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
}
ENDCG
}
FallBack "Diffuse"
}

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

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

实现

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

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

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

		CGPROGRAM
#pragma surface surf SimpleLambert noforwardadd

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

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

		struct Input {
half2 uv_MainTex;
};

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

		CGPROGRAM
#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. VC++ 6.0中添加库文件和头文件

    附加头文件包含 VC6.0中: VC6.0默认include包含路径:Tools>Options>Directories>Include files. 对于特定项目的头文件包含,在& ...

  2. 虚拟机搭建Zookeeper服务器集群完整笔记

    虚拟机搭建Zookeeper服务器集群完整笔记 本笔记主要记录自己搭建Zookeeper服务器的全过程,默认已经安装部署好Centos7. 一.虚拟机下Centos无法联网解决方案 1.首先调整虚拟机 ...

  3. 数据挖掘_requests模块的get方法

    关于requests模块 之前在跟大家讲通过字典列表批量获取数据的时候用过这个模块 安装过程就不再讲解了 requests模块是python的http库,可以完成绝大部分与http应用相关的工作,所以 ...

  4. ionic3-ng4学习见闻--(多环境方案)

    搜了很久,很难找到一个详细入微,开箱即用的方案. 于是我 百折不挠的,搞出来一个,也不知道是不是最完美的方案,有什么可以优化的地方可以指出,谢谢. 首先, 1.项目目录下(与src平级),新增conf ...

  5. 152. Maximum Product Subarray(中等, 神奇的 swap)

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  6. 阿里架构师带你深入浅出jvm

    本文跟大家聊聊JVM的内部结构,从组件中的多线程处理,JVM系统线程,局部变量数组等方面进行解析 JVM JVM = 类加载器(classloader) + 执行引擎(execution engine ...

  7. Ajax相关——get请求和post请求的区别

    一.完整的URL由以下几部分组成: scheme:通信协议,常用的有:http/ftp. host:主机,服务器(计算机)域名或IP地址 port:端口,整数,可选,省略时使用默认端口,http的默认 ...

  8. [转]关于OpenGL的绘制上下文

    [转]关于OpenGL的绘制上下文 本文转自(http://www.cnblogs.com/Liuwq/p/5444641.html) 什么是绘制上下文(Rendering Context) 初学Op ...

  9. Android查缺补漏(线程篇)-- IntentService的源码浅析

    本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8975114.html 在Android中有两个比较容易弄混的概念,Servic ...

  10. Java第1次实验提纲(基本概念与引入PTA+Git)

    0. 控制台下编译.运行 在Notepad++编写Java程序 学会使用控制台,javac.java 学会使用Notepad++ 参考资料: 控制台-cmd应用基础 扫盲教程 使用命令行编译并运行ja ...