第八章:移动端着色器的优化-Mobile Shader Adjustment《Unity Shaders and Effets Cookbook》

发布于:2025-09-13 ⋅ 阅读:(24) ⋅ 点赞:(0)

 Unity Shaders and Effets Cookbook

《着色器和屏幕特效制作攻略》

这本书可以让你学习到如何使用着色器和屏幕特效让你的Unity工程拥有震撼的渲染画面。

                                                                                                       —— Kenny Lammers


第八章:移动端着色器的优化

介绍

第1节. 什么是轻量级的着色器?

1.1、准备工作

1.2、如何实现...

1.3、实现原理...

第2节. 对着色器性能分析

1.1、准备工作

1.2、如何实现...

第3节. 在移动平台修改你的着色器

1.1、准备工作

1.2、如何实现...

1.3、实现原理...


I like this book!


第八章:移动端着色器的优化

        在接下来的两章中,我们将了解如何使我们编写的着色器在性能上对不同平台高效的运行。我们不会专门讨论任何一个平台,但我们会进行分解我们可以调整的着色器元素,使得它们在移动设备上可以更优化,并使它们在任何平台上都更加高效。这些技术的范围从了解 Unity 为您提供的内置变量(可减少着色器内存开销)到了解我们如何提高自己的着色器代码的效率。本章将介绍以下部分:

    介绍

            在各种各样的游戏项目中都会出现优化着色器的任务。在制作中,总会有需要优化着色器的一个环节,或者它需要使用更少的纹理但产生相同的效果。作为技术美术师或着色器程序员,你必须了解这些核心基础知识来优化着色器,这样你才能提高游戏性能,同时仍能实现相同的视觉真实度。掌握这些知识还有助于从一开始就设置编写着色器的方式。例如,通过知道使用着色器构建的游戏将在移动设备上运行,我们可以自动将所有光照函数设置为使用 half 向量作为视方向,或者将所有浮点变量类型设置为 fixed 或 half。这些技术和许多其他技术都有助于着色器在目标硬件上高效运行。让我们开始我们的优化之旅,开始学习如何优化我们的着色器。

    第1节. 什么是轻量级的着色器?

            当第一次被问及什么是廉价着色器时,可能有点难以回答,因为制作更高效的着色器需要许多元素。它可能是变量占用的大量内存。这可能是着色器使用纹理的数量。也可能是我们的着色器工作正常,但我们实际上可以通过减少我们使用的代码量或我们正在创建的数据量来用一半的数据量产生相同的视觉效果。我们将在本节中探讨其中的一些技术,并展示如何将它们结合起来,使您的着色器快速高效,同时仍然产生当今每个人都期望从游戏中获得的高质量视觉效果,无论是移动端还是 PC端。

    1.1、准备工作

            为了开始这个案例,我们需要收集一些资源。因此,让我们执行以下任务:

    • 1. 创建一个新场景,里面放置一个简单的球体和一盏平行光。
    • 2. 创建一个新的着色器和材质球,并将着色器赋予给材质。
    • 3. 然后,我们需要将刚刚创建的材质球赋予给新场景中的球体。
    • 4. 最后,修改着色器,使其使用漫反射纹理、法线贴图,还有自定义的光照函数。

            下 图1.1 展示了我们修着色器改后的结果:

    图1.1

            现在,你应该有一个类似于下 图1.2 的设置。通过此设置,我们将了解在 Unity 中使用表面着色器优化 Shader 的一些基本概念:

    图1.2

    1.2、如何实现...

            为了解一些可以优化着色器的方法,我们将构建一个简单的漫反射着色器。

            首先,我们优化变量类型,以便它们在处理数据时使用更少的内存:

    • 步骤1. 让我们从着色器中的输入结构体开始。目前,我们的 UV 存储在 float2 类型的变量中。我们需要将其更改为使用 half2:
    struct Input 
    {
        float2 uv_MainTex;
        float2 uv_NormalMap;
    };
    • 步骤2. 然后,我们可以转到我们的光照函数,并通过将变量的类型更改为以下内容来减少变量的内存占用:
    inline half4 LightingSimpleLambert(SurfaceOutput s, half3 lightDir, half atten)
    {
        half diff = max(0, dot(s.Normal, lightDir));
    
        half4 c;
        c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
        c.a = s.Alpha;
        return c;
    }
    • 步骤3. 最后,我们可以通过更新 surf() 函数中的变量来完成此优化过程:
    void surf (Input IN, inout SurfaceOutput o) 
    {
        half4c = tex2D (_MainTex, IN.uv_MainTex);
    
        o.Albedo =  c.rgb;
        o.Alpha = c.a;
        o.Normal = UnpackNormal(tex2D (_NormalMap, IN.uv_NormalMap));
    }
    • 步骤4. 现在我们已经优化了变量,我们将利用内置的光照函数变量,这样我们就可以控制此着色器如何处理光源。通过这样做,我们可以大大减少着色器处理的光源数量。使用以下代码修改着色器中的 #pragma 语句:
    #pragma surface surf SimpleLambert noforwardadd 
    • 步骤5. 我们可以通过在法线贴图和漫反射纹理之间共享 UV 来进一步优化这一点。为此,我们只需更改 UnpackNormal() 函数中的 UV 查找,使用的时_MainTex UV 而不是 _NormalMap 的 UV:

    void surf (Input IN, inout SurfaceOutput o)

    {

        half4c = tex2D (_MainTex, IN.uv_MainTex);

        o.Albedo =  c.rgb;

        o.Alpha = c.a;

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

    }

    • 步骤6由于我们已经删除了法线贴图UV的需求,因此我们需要从输入结构体中也删除法线贴图UV代码:
    struct Input 
    {
        half2 uv_MainTex;
    };
    • 步骤7最后,我们可以通过告诉着色器它仅适用于某些渲染器来进一步优化此着色器:
    #pragma surface surf SimpleLambert exclude_path:prepass noforwardadd 
    • 完整代码:
    Shader "CookbookShaders/8-1.2 Cheap Shader"
    {
        Properties 
        {
            _MainTex("Base (RGB)", 2D) = "white"{}
            _NormalMap("Normal Map", 2D) = "bump"{}
        }
     SubShader 
        { 
            Tags { "RenderType"="Opaque" }
            LOD 200
            CGPROGRAM
            #pragma surface surf SimpleLambert exclude_path:prepass noforwardadd 
    
            // 将属性链接到CG程序
            sampler2D _MainTex;
            sampler2D _NormalMap;
    
            // 确保在struct中获得纹理的uv
            struct Input 
            {
                half2 uv_MainTex;
            };
    
            inline half4 LightingSimpleLambert(SurfaceOutput s, half3 lightDir, half atten)
            {
                half diff = max(0, dot(s.Normal, lightDir));
    
                half4 c;
                c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
                c.a = s.Alpha;
                return c;
            }
            
            void surf (Input IN, inout SurfaceOutput o) 
            {
                half4 c = tex2D (_MainTex, IN.uv_MainTex);
    
                o.Albedo =  c.rgb;
                o.Alpha = c.a;
                o.Normal = UnpackNormal(tex2D (_NormalMap, IN.uv_MainTex));
            }
            ENDCG
        }
     FallBack "Diffuse"
    }
    
    
    

            优化过程的结果表明,我们确实没有发现视觉质量的差异,但我们减少了将此着色器绘制到屏幕上所需的时间。我们将在下一节中学习如何了解着色器渲染需要多少时间,但这里要关注的点是,我们用更少的数据实现相同的结果。因此,在创建着色器时请记住这一点。下 图1.3 显示了着色器的最终结果:

    图1.3

    1.3、实现原理...

            现在我们已经了解了优化着色器的方法,让我们更深入地了解为什么所有这些技术都有效,并看看你自己可以尝试的其他一些技术。

            让我们首先将注意力集中在声明每个变量时存储的数据的大小。如果您熟悉编程,你就会清楚可以使用数据类型声明不同大小的值或变量。这意味着浮点实际上在内存中占据的容量是最大的。下面让我们更详细地看看这些变量类型:

    • float:这是一个完整的 32 位精度值,是我们在此处看到的三种不同类型中最慢的。它还具有相应的 float2、float3 和 float4 值。
    • half:这是一个简化的 16 位浮点值,适用于存储 UV 值、颜色值,并且比使用浮点值快得多。它有其相应的值,如浮点类型,即 half2、half3 和 half4。
    • fixed:该值是三种类型中体积最小的,但可用于光照计算、颜色,并具有 fixed2、fixed3 和 fixed4 的对应值。

            优化着色器的第二阶段是向我们的 #pragma 语句声明 noforwardadd 值。这基本上是一个开关,它会自动告诉 Unity 任何物体如果使用了此特性的 Shader 它就只会接受一盏方向光并进行逐像素光照。由此着色器计算的任何其他光源,将强制使用Unity内部生成的球面谐波值作为每个顶点的光源进行处理。当我们在场景中放置另一个光源来照亮球体对象时,这一点尤其明显,因为我们的着色器正在使用法线贴图执行逐像素操作。

            这很棒,但是如果你想在场景中放一堆方向光源,并且想要控制这些光源中的哪些灯光用于主光逐像素光源怎么办?好吧,如果你注意到的话,每个光源在属性面板中都有一个 渲染模式(Render Mode) 下拉菜单。如果你单击此下拉菜单,你会看到几个可以设置的标志。这些是 Auto, Important, 和 Not Importan。通过选择光源,你可以告诉 Unity 应将光源调整为逐像素光源,而不是逐顶点光源,方法是将其渲染模式设置为“Important”,反之亦然。如果将灯光设置为“Auto”,则是让 Unity 决定最佳作方案。

            在场景中放置另一个光源,并删除当前位于着色器主纹理中的贴图。你会注意到,第二个点光源不与法线贴图反应,只与我们第一个创建的方向光发生反应。这里的概念是,只需将所有额外光源计算为顶点光源,即可节省逐像素操作,只需将主方向光计算为逐像素光源即可节省性能。下  图1.4  直观地演示了这个概念,即点光源不与法线贴图做出反应:

    图1.4

            最后,我们做了一些清理,简单地告诉法线贴图纹理使用主纹理的UV值,我们去掉了专门为法线贴图拉取一组单独的UV值的代码行。这始终是简化代码和清理任何不需要的数据的好方法。

            我们还在 #pragma 语句中声明了 exclude_pass: prepass,以便此着色器不会接受来自延迟渲染器的任何自定义光照。这意味着我们只能在主摄像机设置中设置的前向渲染器中有效地使用此着色器。

            花一点时间,你会惊讶于着色器可以优化多少。你已经了解了我们如何将灰度纹理打包到单个RGBA纹理中,以及使用查找纹理来伪造光照。优化着色器的方法有很多种,这就是为什么它总是一个模棱两可的问题;但是,了解了这些不同的优化技术,你可以根据你的游戏项目和目标平台调整着色器,最终制作出非常简化的着色器和良好稳定的帧速率。

    第2节. 对着色器性能分析

            现在我们知道了如何减少着色器占用的开销,下面让我们看看如何在有很多着色器或大量对象、以及着色器和脚本同时运行的场景中找到有问题的着色器。在整个游戏中找到单个对象或单个着色器可能是非常麻烦的,但 Unity 为我们提供了内置的分析器。这使我们能够逐帧实际查看游戏中发生的情况,并让我们看到 GPU 和 CPU 正在使用的每个项目。

            使用分析器,我们只能隔离着色器、几何体和常规渲染等项目,方法是使用它们的接口创建分析作业块。我们可以过滤掉项目,直到我们只查看单个对象的性能。然后,这让我们可以看到对象在运行时执行其功能时对 CPU GPU 的影响。

            让我们来看看 Profiler 的不同部分,并了解如何调试我们的场景,最重要的是我们的着色器。

    1.1、准备工作

            准备好资产后,启动 Profiler 窗口来开始使用我们的性能分析:

    • 1. 让我们使用上一小节中的场景,然后选择 Window 菜单下的 Profiler,或者使用快捷键Ctrl+7
    • 2. 我们再复制几个球体,来看一看这如何影响我们渲染的。

            你应该会看到类似于以下屏幕截图的内容,如 图2.1

    图2.1

    1.2、如何实现...

            使用性能分析器之前,我们先来查看一下此窗口的一些UI元素。在点击 Play 按钮之前,让我们先看看如何从分析器中获取所需的信息。

    • 步骤1. 首先,单击 性能分析器(Profiler) 窗口中名为 GPU UsageCPU Usage Rendering 的较大块。您将在上层窗口的左侧找到这些块,如以下 图2.2  所示:

    图2.2  

            使用这些块,我们可以看到特定于游戏主要功能的不同数据。CPU Usage 块向我们展示了大多数脚本正在做什么,以及物理和整体渲染。GPU Usage 块为我们提供了有关于特定的光照、阴影和渲染队列的元素的详细信息。最后,  Rendering  块为我们提供了有关场景中的 draw calls 和几何体数量的信息。

            通过单击这些块中的每一项,我们可以隔离出我们在分析会话期间看到的数据类型。

    • 步骤2. 现在单击其中一个配置文件块内的微小彩色块,然后点击播放或使用快捷键 Ctrl + P 运行场景。

            这使我们能够更深入地研究所选中这段部分的性能分析,以便我们可以过滤掉为我们报告的内容。当场景运行时,在 GPU Usage 块中除 Opaque 之外,其它所有选框全部取消选中。请注意,我们现在只能看到 Opaque 渲染队列的对象使用了多少渲染时间。

    图2.3

    • 步骤3. Profiler 窗口的另一个很棒的功能是在图形视图中单击和拖动的操作。这会自动暂停你的游戏,方便你去进一步分析图表中的某个峰值,以准确找出导致性能问题的地方。在图表视图中单击并拖动以暂停游戏并查看使用此功能的效果,如 图2.4 所示:

    图2.4

    • 步骤4. 现在把注意力转向 性能分析器Profiler) 窗口的下半部分,你会注意到,当我们选择 GPU Usage 块时,有一个可用的下拉项目。我们可以扩展此内容,以获取有关当前活动分析会话的更多详细信息,在本例中,我们用它来获取有关摄像机当前渲染的内容以及它占用的时间,如 图2.5 

    图2.5

            这让我们全面了解了 Unity 在这个特定帧上处理内容的内部工作原理。在本例中,我们可以看到,使用优化着色器的三个球体大约需要 0.14 毫秒才能绘制到屏幕,它们用了 7 次 draw calls,而此过程每帧占用了 GPU 时间的 3.1%。我们可以通过此类信息来诊断和解决与着色器相关的性能问题。让我们进行一个测试,看看向着色器再添加一个纹理并使用插值函数将两个漫反射纹理混合在一起的效果。你会在 “Profiler” 窗口中非常清楚地看到效果。

    • 步骤5. 使用以下代码修改着色器的 Properties 块,来添加另一个纹理:
    Properties 
    {
        _MainTex("Base (RGB)", 2D) = "white"{}
        _BlendTex("Blend Texture", 2D) = "white"{}
        _NormalMap("Normal Map", 2D) = "bump"{}
    }
    • 步骤6. 然后将纹理输入到我们的 CGPROGRAM 语句中:
    // 将属性链接到CG程序
    sampler2D _MainTex;
    sampler2D _BlendTex;
    sampler2D _NormalMap;
    • 步骤7. 最后更新我们的 surf() 函数,以便我们将两个漫反射纹理混合在一起:
    void surf (Input IN, inout SurfaceOutput o) 
    {
        half4 c = tex2D (_MainTex, IN.uv_MainTex);
        half4 blendTex = tex2D (_BlendTex, IN.uv_MainTex); 
    
        c = lerp(c, blendTex, blendTex.r);
    
        o.Albedo =  c.rgb;
        o.Alpha = c.a;
        o.Normal = UnpackNormal(tex2D (_NormalMap, IN.uv_MainTex));
    }
    • 完整代码:
    Shader "CookbookShaders/8-2 Profiling Shaders"
    {
        Properties 
        {
            _MainTex("Base (RGB)", 2D) = "white"{}
            _BlendTex("Blend Texture", 2D) = "white"{}
            _NormalMap("Normal Map", 2D) = "bump"{}
        }
     SubShader 
        { 
            Tags { "RenderType"="Opaque" }
            LOD 200
            CGPROGRAM
            #pragma surface surf SimpleLambert exclude_path:prepass noforwardadd 
    
            // 将属性链接到CG程序
            sampler2D _MainTex;
            sampler2D _BlendTex;
            sampler2D _NormalMap;
    
            // 确保在struct中获得纹理的uv
            struct Input 
            {
                half2 uv_MainTex;
            };
    
            inline half4 LightingSimpleLambert(SurfaceOutput s, half3 lightDir, half atten)
            {
                half diff = max(0, dot(s.Normal, lightDir));
    
                half4 c;
                c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
                c.a = s.Alpha;
                return c;
            }
            
            void surf (Input IN, inout SurfaceOutput o) 
            {
                half4 c = tex2D (_MainTex, IN.uv_MainTex);
                half4 blendTex = tex2D (_BlendTex, IN.uv_MainTex); 
    
                c = lerp(c, blendTex, blendTex.r);
    
                o.Albedo =  c.rgb;
                o.Alpha = c.a;
                o.Normal = UnpackNormal(tex2D (_NormalMap, IN.uv_MainTex));
            }
            ENDCG
        }
     FallBack "Diffuse"
    }
    
    
    

            保存修改的着色器并返回 Unity 编辑器后,我们可以运行游戏并查看新着色器的毫秒增加。返回 Unity 后按播放,让我们在 Profiler 窗口中查看结果 如 图2.6 所示:

    图2.6

            你现在可以看到,在此场景中渲染 不透明着色器(Opaque) 所需的时间从 0.140 毫秒增加到 0.179 毫秒。通过添加了另一个纹理并使用 lerp() 函数,我们增加了球体的渲染时间。虽然这是一个很小的变化,但是可以想象一下如果有 20 个着色器都以不同的方式在不同的对象上工作,这个体量也是不小的。

            使用此处提供的信息,你可以更快地查明导致性能下降的区域,并使用上一小节中的技术解决这些问题。

    1.3、实现原理...

            虽然描述该工具在内部的实际工作原理已经完全超出了本书的范围,但我们可以推测 Unity 为我们提供了一种在游戏运行时查看计算机性能的方法。基本上,这个窗口与 CPU 和 GPU 非常紧密地联系在一起,方便为我们提供实时反馈,去了解每个脚本、对象和渲染队列所花费的时间。使用这些信息,我们可以跟踪着色器编写的效率,方便消除有问题的区域和代码。

    1.4、另请参阅

            也可以专门针对移动端进行具体分析。当在 Build Settings 中设置 Android 或 iOS 构建目标时,Unity 为我们提供了一些额外的功能。实际上我们可以在游戏运行时从移动端获取实时信息。这变得非常有用,因为你可以直接在设备本身上进行分析,而不是直接在编辑器中进行分析。要了解有关此过程的更多信息,请参阅以下链接中的 Unity 文档:

    docs.unity3d.comhttp://docs.unity3d.com/Documentation/Manual/MobileProfiling.html

    第3节. 在移动平台修改你的着色器

            现在我们看到了,制作真正优化的着色器具有着相当广泛技术,让我们来看看如何编写针对移动端看起来不错的、高质量的着色器。实际上,对我们编写的着色器进行一些调整是非常容易的,方便它们在移动设备上运行得更快。这包括使用 approxview 或 halfasview 照明函数变量等元素。我们还可以减少所需的纹理数量,甚至为我们正在使用的纹理应用更好的压缩。在本节结束时,我们将拥有一个经过优化的法线贴图镜面反射着色器,用于我们移动端的游戏。

    1.1、准备工作

            在开始之前,让我们制作一个全新的场景,并在场景中创建一些物体,用来展示我们的移动着色器:

    • 1. 创建一个新场景,并新建一个球体和一盏平行光。
    • 2. 新建一个材质球和着色器,并将着色器赋予给材质球。
    • 3. 最后将材质球赋予给场景中的球体对象。

            完成后,您应该会有一个类似于下 图3.1 中的场景:

    图3.1

    1.2、如何实现...

            对于这个案例,我们将从头开始编写一个适合移动端的着色器,并讨论使其更适合移动设备的元素:

    • 步骤1. 首先让我们用所需的纹理填充我们的 Properties 块。在本例中,我们将使用单张漫反射纹理,其 alpha 通道中有光泽贴图,加上法线贴图和 高光强度(Specular intensity) 滑块:
    Properties 
    {
        _Diffuse("Base (RGB) Specular Amount (A)", 2D) = "white"{}
        _SpecIntensity("Specular Width", Range(0.01, 1)) = 0.5
        _NormalMap("Normal Map", 2D) = "bump"{}
    }
    • 步骤2. 下一个任务是设置我们的 #pragma 声明。这只会打开和关闭表面着色器的某些功能,最终使着色器更轻量或更昂贵:
    #pragma surface surf MobileBlinnPhong exclude_path:prepass nolightmap noforwardadd halfasview
    • 步骤3. 然后,我们需要在 Properties 块和 CGPROGRAM 语句之间建立连接。不过,这次我们将对 高光强度(Specular-intensity) 滑块使用固定变量类型来减少其内存使用:
    // 将属性链接到CG程序
    sampler2D _Diffuse;
    sampler2D _NormalMap;
    float _SpecIntensity;
    • 步骤4. 为了将纹理映射到对象表面,我们需要获得一些 UV。在这种情况下,我们只会获得一组UV,以将着色器中的数据量保持在最低限度:
    // 确保在struct中获得纹理的uv
    struct Input 
    {
        half2 uv_Diffuse;
    };
    • 步骤5. 下一步是在光照函数中写入一些新的变量,获得这些变量可以通过使用新 #pragma 来声明:
    inline half4 LightingMobileBlinnPhong(SurfaceOutput s, half3 lightDir, half3 halfDir, half atten)
    {
        half diff = max(0, dot(s.Normal, lightDir));
        half nh = max(0, dot(s.Normal, halfDir));
        half spec = pow(nh, s.Specular * 128) * s.Gloss;
    
        half4 c;
        c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
        c.a = s.Alpha;
        return c;
    }
    • 步骤6. 最后,我们通过创建 surf() 函数,处理表面的最终颜色来完成着色器:
    void surf (Input IN, inout SurfaceOutput o) 
    {
        half4 diffuseTex = tex2D (_Diffuse, IN.uv_Diffuse);
        o.Albedo =  diffuseTex.rgb;
        o.Gloss = diffuseTex.a;
        o.Alpha = 0.0;
        o.Specular = _SpecIntensity;
        o.Normal = UnpackNormal(tex2D (_NormalMap, IN.uv_Diffuse));
    }
    • 完整代码:
    Shader "CookbookShaders/8-3 Modifying Shaders for mobile"
    {
        Properties 
        {
            _Diffuse("Base (RGB) Specular Amount (A)", 2D) = "white"{}
            _SpecIntensity("Specular Width", Range(0.01, 1)) = 0.5
            _NormalMap("Normal Map", 2D) = "bump"{}
        }
     SubShader 
        { 
            Tags { "RenderType"="Opaque" }
            LOD 200
            CGPROGRAM
            #pragma surface surf MobileBlinnPhong exclude_path:prepass nolightmap noforwardadd halfasview
    
            // 将属性链接到CG程序
            sampler2D _Diffuse;
            sampler2D _NormalMap;
            float _SpecIntensity;
    
            // 确保在struct中获得纹理的uv
            struct Input 
            {
                half2 uv_Diffuse;
            };
    
            inline half4 LightingMobileBlinnPhong(SurfaceOutput s, half3 lightDir, half3 halfDir, half atten)
            {
                half diff = max(0, dot(s.Normal, lightDir));
                half nh = max(0, dot(s.Normal, halfDir));
                half spec = pow(nh, s.Specular * 128) * s.Gloss;
    
                half4 c;
                c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
                c.a = s.Alpha;
                return c;
            }
            
            void surf (Input IN, inout SurfaceOutput o) 
            {
                half4 diffuseTex = tex2D (_Diffuse, IN.uv_Diffuse);
                o.Albedo =  diffuseTex.rgb;
                o.Gloss = diffuseTex.a;
                o.Alpha = 0.0;
                o.Specular = _SpecIntensity;
                o.Normal = UnpackNormal(tex2D (_NormalMap, IN.uv_Diffuse));
            }
            ENDCG
        }
     FallBack "Diffuse"
    }
    
    
    

            完成此案例的代码部分以后,保存着色器并返回到 Unity 编辑器以让着色器编译。如果未发生错误,你会看到类似于下 图3.2 的结果:

    图3.2

    1.3、实现原理...

            因此,让我们通过解释它做什么和不做什么来开始这个着色器的描述。首先,它不包括延迟照明通道。这意味着,如果你创建了一个连接到延迟渲染器预处理的光照函数,它将不会使用该特定的光照函数,它会查找默认的光照函数。例如,我们本书迄今为止一直在创建的光照函数。

            这个特定的着色器不支持 Unity 内部光照映射系统的光照映射。这只是防止着色器去为着色器附加到的对象,去查找光照贴图。从而使着色器的性能更加友好,因为它不必执行光照贴图检查。

            我们包含了 noforwardadd 声明,以便我们只处理具有单个方向光源的每像素纹理。在 surf() 函数中,所有其他光源都强制成为逐顶点的光源,并且不会包含任何逐像素的操作。

            最后,我们使用 halfasview 声明告诉 Unity 我们不会使用普通光照函数中的主 viewDir 参数。相反,我们将使用半角向量作为视方向,并用它处理我们的镜面反射。由于着色器将基于每个顶点完成,所以它的处理速度会快得多。在现实世界中模拟镜面反射时,它并不完全准确。但在移动设备上,视觉上看起来还不错,着色器也得到了更多的优化。

            诸如此类的技术使着色器在代码方面更加高效和简洁。始终确保你只使用所需要的数据,同时将其与目标硬件和游戏所需的视觉质量进行权衡。最后,它成为这些技术的混合物,最终构成了游戏的着色器。


    ​这本书可以让你学习到如何使用着色器和屏幕特效让你的Unity工程拥有震撼的渲染画面。

    作者:Kenny Lammers


    网站公告

    今日签到

    点亮在社区的每一天
    去签到