[Shader] Shader Cookbook 移动设备着色器优化调整[7]

56 篇文章 3 订阅

  在接下来的两章中,我们将看看如何为不同的平台编写性能友好的着色器。我们不会特别讨论一个平台,但我们将分解着色器的元素,我们可以调整,使它们更适合移动和高效的任何平台。这些技术包括从理解Unity提供的内置变量来减少着色器内存的开销,到学习如何使我们的着色器代码更高效。

  1066/5000
学习优化着色器的艺术将出现在你所从事的任何游戏项目中。在任何生产环境中,总会有一个点需要优化着色器,或者可能它需要使用更少的纹理,但产生相同的效果。作为一名技术美工或着色器程序员,你必须理解这些核心基础来优化你的着色器,这样你就可以提高游戏的性能,同时仍然获得相同的视觉保真度。拥有这些知识也可以帮助你从一开始就如何编写你的着色器。例如,通过知道使用着色器构建的游戏将在移动设备上运行,我们可以自动设置所有照明函数使用 Half 向量作为视图方向,或者我们甚至可以将所有 float 变量类型设置为fixed或 half,以减少所使用的内存量。这些,以及许多其他技术,都有助于你的着色器在你的目标硬件上高效地运行。让我们开始我们的旅程,开始学习如何优化我们的着色器。

本章将介绍以下食谱:

  • 使着色器更有效的技术
  • 分析你的着色器
  • 修改我们的移动着色器

使着色器更有效的技术

  什么是廉价的着色器?当你第一次被问到这个问题时,可能会有点难回答,因为很多元素都是用来制作一个更高效的着色器的。它可以是变量占用的内存量。它可以是着色器正在使用的纹理的数量。也可能是我们的着色器工作得很好,但我们可以通过减少我们正在使用的代码或我们正在创建的数据的数量,用一半的数据量产生相同的视觉效果。我们将在这道食谱中探索其中的一些技巧。我们将展示如何将它们结合起来,使您的着色器快速和高效,同时仍然产生高质量的视觉效果,每个人都希望从今天的游戏,无论是在手机或PC上。

准备

在开始之前,我们需要收集一些资源。那么,让我们执行以下任务:

  1. 创建一个新的场景,用一个简单的球体对象和一个方向光

  2. 创建一个新的着色器(OptimizedShader01)和材质(OptimizedShader01Mat),并将着色器分配给材质。

  3. 现在,我们需要将刚刚创建的材质分配给新场景中的球体对象
    在这里插入图片描述

  4. 最后,修改着色器,使它使用漫反射纹理和法线贴图,并包括您的自定义照明功能

    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)
    	{
    		fixed4 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"
    
  5. 最后,为你的材质分配一个基础和法线贴图(我使用了MudRocky纹理,它包含在第一章的资产中,Post Processing Stack,在 Assets\Chapter 01\Standard Assets\Environment\TerrainAssets\SurfaceTextures文件夹中)。你现在应该有一个类似于以下截图的设置:

在这里插入图片描述
这个设置将允许我们看看在Unity中使用Surface shaders优化着色器的一些基本概念。

怎么做……

我们将构建一个简单的DiffuseShader来看看你可以优化你的着色器的几种方法。

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

  1. 让我们从着色器中的结构输入开始。目前,我们的uv被存储在float2类型的变量中。
  2. 记住,浮点数在满32位的内存中提供了最高形式的精度。对于复杂的三角函数或指数函数,它是必需的,但如果你可以处理较低的精度,最好使用half或fixed来代替。half类型使用一半大小或16位内存提供最多3位的精度。这意味着我们可以让half2拥有与单个浮点数相同的内存。我们需要将其改为使用half2:
    struct Input
    {
    	half2 uv_MainTex;
    	half2 uv_NormalMap;
    };
    
  3. 然后我们可以转移到Lighting函数,通过将变量的类型更改为以下类型来减少变量的内存占用:
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;
}
  1. 在本例中,我们使用精度最低的fixed类型,它只有11位,而float类型有32位。这对于简单的计算很有用,比如颜色或纹理数据,这非常适合这个特定的情况。

TIP
如果你想复习一下Fixed类型,以及我们正在使用的所有其他类型,请查看第二章,创建你的第一个着色器,或查看: https://docs.unity3d.com/Manual/SL-DataTypesAndPrecision.html

  1. 最后,我们可以通过更新surf()函数中的变量来完成这个优化过程。因为我们使用的是纹理数据,所以我们可以在这里使用fixed4来代替:
    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) ) ;
    }
    
  2. 现在我们已经优化了我们的变量,我们将利用一个内置的照明函数变量,这样我们就可以控制灯光如何被这个着色器处理。通过这样做,我们可以大大减少着色器处理的光的数量。用下面的代码在你的着色器中修改#pragma语句:
    CGPROGRAM
    #pragma surface surf SimpleLambert noforwardadd
    
  3. 我们可以通过在法线贴图和漫射纹理之间共享uv来进一步优化。为此,我们可以简单地更改UnpackNormal()函数中的UV查找,以便它使用_MainTex UV而不是_NormalMap的UV:
    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_MainTex) ) ;
    }
    
  4. 现在我们已经删除了对法线映射UV的需求,我们需要确保我们已经从结构输入中删除了法线映射UV代码:
    struct Input
    {
    	half2 uv_MainTex;
    };
    
  5. 最后,我们可以进一步优化这个着色器,告诉着色器它只与特定的渲染器工作:
    CGPROGRAM
    #pragma surface surf SimpleLambert exclude_path: prepass noforwardadd
    

  优化通过的结果显示,我们没有注意到视觉质量的差异,但我们减少了将这个着色器绘制到屏幕上所需的时间。在下一个食谱中,你会发现渲染一个着色器需要多少时间,但这里的重点是我们可以用更少的数据实现相同的结果。所以,当你创建着色器时要记住这一点。

NOTE
同样重要的是,当在PC上使用fixed/half时,你不会注意到任何不同,因为它仍然使用全精度。

下面的截图显示了我们着色器的最终结果:
在这里插入图片描述

它是如何工作的……

  现在我们已经了解了如何优化着色器,让我们更深入一点,了解所有这些技术是如何工作的,为什么我们应该使用它们,并看看一些其他的技术,你可以在你的着色器中自己尝试。

  首先,让我们把注意力集中在声明每个变量时所存储的数据大小上。如果您熟悉编程,那么您将理解可以声明具有不同大小类型的值或变量。这意味着float在内存中有一个最大的大小。下面的列表更详细地描述了这些变量类型:

  • Float: Float是一个全32位精度值,是我们将在这里看到的三种不同类型中最慢的。它还具有相应的float2、float3和float4值,这允许我们在一个变量中存储多个浮点数。
  • Half: Half变量类型是一个简化的16位浮点值,适用于存储UV值和颜色值。它比使用浮点值要快得多。与foat类型一样,它有相应的值,分别是half2、half3和half4。
  • Fixed:Fixed是三种类型中最小的,但它可以用于照明计算和颜色,有对应的值fixed2、fixed3和fixed4。

NOTE
有关使用着色器的数组类型的更多信息,请查看第3章“使用表面着色器”中的访问和修改打包数组配方。

  优化简单着色器的第二阶段是在#pragma语句中声明noforwardadd值。这是一个开关,自动告诉Unity,任何具有这个特定着色器的对象只从单个方向光接收每像素光。由这个着色器计算的任何其他光都将被强制处理为使用Unity内部产生的球形调和值的逐顶点光。当我们在场景中放置另一盏灯来照亮我们的球体对象时,这一点尤其明显。这是因为我们的着色器正在使用法线贴图进行逐像素操作。

  这很好,但如果你想在场景中有一堆方向光,并控制这些光中的哪个用于主逐像素光呢?嗯,你可能已经注意到,每个光都有一个渲染模式下拉菜单。如果单击这个下拉菜单,您将看到几个可以设置的标记。它们分别是自动、重要和不重要。通过选择一个光,你可以告诉Unity一个光应该被认为是一个逐像素光而不是逐顶点光,通过设置它的渲染模式为重要,反之亦然。如果你将灯光设置为Auto,那么你将让Unity决定最佳的行动方案:
在这里插入图片描述
  在你的场景中放置另一个光,并删除当前在我们着色器的主纹理中的纹理。你会注意到第二个点光没有与法线贴图发生反应,只有我们第一个创建的方向光。这里的概念是通过将所有额外的光计算为顶点光来节省逐像素操作,并通过将主方向光计算为逐像素光来节省性能。下图演示了这个概念,因为点光对法线贴图没有反应:

在这里插入图片描述
  最后,我们做了一点清理,简单地告诉法线贴图纹理使用主纹理的UV值,我们去掉了为法线贴图拉入一组单独UV值的代码行。这总是一种很好的方法来简化您的代码和清除任何不需要的数据。

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

  花一点时间在这上面,你会惊讶于一个着色器可以优化多少。到目前为止,您已经了解了如何将灰度纹理打包到单个RGBA纹理中,以及如何使用查找纹理来伪造照明(lut短语指的是使用查找纹理)。

NOTE
你可以在这里看到一个关于如何创建和使用带有颜色渐变属性的查找纹理的例子,来自Post Processing Stack,见第一章: https://www.youtube.com/watch?v=hYhL0qK_NC0

  有很多方法可以优化着色器,这就是为什么它总是一个模糊的问题首先提出,但通过了解这些不同的优化技术,你可以使你的着色器适合你的游戏和目标平台,最终产生非常流线型的着色器和一个漂亮的,稳定的帧率。

  优化着色器的另一种方法是研究数学。有时,它可以用代数简化或重写,这样更有效。有时,复杂的方程可以用多项式近似,例如麦克劳林级数(Maclaurin series).


分析你的着色器

  现在我们知道了如何减少着色器的开销,让我们看看如何在一个场景中找到有问题的着色器,在这个场景中,你可能有很多着色器或大量的对象、着色器和脚本同时运行。在整个游戏中找到一个单一的对象或着色器是相当困难的,但Unity为我们提供了内置的Profiler。这让我们能够看到,在一帧一帧的基础上,游戏中发生了什么,以及GPU和CPU所使用的每个项目。

  使用Profiler,我们可以使用它的界面来隔离项目,如着色器、几何图形和一般渲染项目,以创建分析作业块。我们可以在只观察单个对象的性能之前,筛选项目。这让我们看到对象在运行时执行其功能时对CPU和GPU的影响。

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

准备

让我们通过准备好一些资产并启动Profiler窗口来使用我们的Profiler:

  1. 让我们使用前面菜谱中的场景,从Window | Analysis | Profiler或使用Ctrl + 7启动Unity Profiler。请随意拖放或移动它,以便您可以清楚地看到它。我把它放在Inspector标签的地方。

  2. 在Scene视图中,重复我们的球体几次,看看这是如何影响我们的渲染的。

  3. 在Profiler选项卡中,单击Deep Profile选项,以获取有关项目的其他信息。然后,play你的游戏!

    您应该看到与下面类似的内容:
    在这里插入图片描述

怎么做呢?

  要使用Profler,我们将看一下这个窗口的一些UI元素。在点击Play之前,让我们看看如何从Profiler获取我们需要的信息:

  1. 首先,单击Profiler窗口中称为CPU Usage、Rendering和Memory的较大块。你会在上面窗户的左手边看到这些块:
    在这里插入图片描述
    使用这些模块,我们可以看到针对游戏主要功能的不同数据。CPU使用率向我们展示了我们的大多数脚本在做什么,以及物理和整体渲染。渲染块给我们关于绘制调用的信息,以及我们在场景中任何一帧的几何体数量。内存块显示正在使用的总内存、分配发生的时间以及垃圾收集器被利用的时间。这个面板用来给我们提供关于特定于我们的照明、阴影和渲染队列的元素的详细信息。

  2. 转到窗口的左上角,选择Profiler Modules下拉菜单。从那里,检查GPU Usage选项,如果它还没有:
    在这里插入图片描述
      GPU Usage块为我们提供了关于特定于我们的照明、阴影和渲染队列的元素的详细信息。默认禁用,因为收集GPU Profler数据可能会产生开销。然而,由于这是与着色器直接相关的信息,我们将希望让它运行。

NOTE
如果您的显卡驱动程序不是最新的,则GPU Usage菜单可能不会显示。

  通过单击这些块中的每一个,我们可以隔离我们在profiling会话中看到的数据类型。

  1. 现在,单击这些Profile块中的一个小的彩色块,并点击Play,或Ctrl + P,以运行场景。

  2. 这让我们可以更深入地进行分析,以便我们能够找出反馈给我们的信息。当场景正在运行时,取消勾选所有的框,除了GPU Usage块中的Opaque。我们现在可以看到渲染设置为render Queue of Opaque的对象花费了多少时间:

在这里插入图片描述

  1. Profiler窗口的另一个重要功能是在图形视图中单击和拖动。
  2. 这将自动暂停你的游戏,以便你能够分析图表中的某个峰值。通过这样做,您可以确切地找出是哪个项目导致了性能问题。在图形视图中点击并拖动来暂停游戏并查看使用该功能的效果:
    在这里插入图片描述
  3. 让我们把注意力转向Profiler窗口的下半部分。您会注意到有一个名为Timeline的下拉项可用,它允许我们以百分比查看程序的每个部分所花费的时间。对于更详细的视图,我们可以通过单击Timeline下拉菜单并选择Hierarchy来更改模式。从那里,选择GPU块。此时,你应该能够看到一个详细描述GPU如何在特定帧上使用的列表。我们可以展开它来获得关于当前活动的profiling session的更详细的信息,在这种情况下,更多关于摄像机当前渲染的内容以及它所占用的时间的信息:
    在这里插入图片描述
      也可以使用列的顶部对选项进行排序。例如,我们可以使用GPU ms来衡量执行各种操作所花费的时间:
    在这里插入图片描述

NOTE
如果单击视图右侧的No Details下拉框,并将选项更改为Related Data,就可以看到调用的函数中使用了什么对象。

  1. 这让我们能够完整地看到Unity在这个特定框架中处理的内部工作。在这种情况下,我们可以看到我们优化的着色器的三个球体在屏幕上绘制大约需要0.101毫秒,它们需要15次绘制调用,这一过程在每帧中花费了25.9%的GPU时间(数字可能会有所不同,这取决于你的计算机的硬件)。我们可以使用这些信息来诊断和解决有关着色器的性能问题。让我们进行一个测试,看看在我们的着色器中添加一个纹理并使用lerp函数混合两个漫反射纹理的效果。你将在Profiler中非常清楚地看到这些效果。

  2. 用下面的代码修改着色器的属性块,这样我们就可以使用另一个纹理:

    Properties
    {
    	_MainTex("Base (RGB) ", 2D) = "white" {}
    	_NormalMap("Normal Map", 2D) = "bump" {}
    	_BlendTex("Blend Texture", 2D) = "white" {}
    }
    
  3. 现在,让我们把我们的纹理提供给CGPROGRAM:

    	sampler2D _MainTex;
    	sampler2D _NormalMap;
    	sampler2D _BlendTex;
    
  4. 现在,是时候相应地更新我们的surf()函数,以便我们可以混合我们的漫反射纹理:

    void surf(Input IN, inout SurfaceOutput o)
    {
    	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) ;
    	fixed4 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) ) ;
    }
    

  一旦你在着色器中保存了修改并返回到Unity编辑器,你就可以运行游戏并看到新的着色器在毫秒内增加。

  1. 添加一个新的纹理到混合纹理:
    在这里插入图片描述
  2. 按播放开始游戏再次与Profiler打开。返回Unity后按Play键。现在,让我们看看Profiler的结果:
    在这里插入图片描述
      在这里,你可以看到渲染不透明着色器的时间从0.101毫秒增加到0.116毫秒。通过添加另一个纹理并使用lerp()函数,我们增加了球体的渲染时间。虽然这是一个小的变化,想象一下有20个着色器都在不同的对象上以不同的方式工作。

  使用这里提供的信息,您可以更快地查明导致性能下降的区域,并使用前一个方法中所示的技术解决这些问题。

它是如何工作的……

  虽然这完全超出了本书的范围来描述这个工具是如何内部工作的,但我们可以推测,Unity为我们提供了一种在游戏运行时查看计算机性能的方法。这个窗口与CPU和GPU紧密相连,为我们提供关于每个脚本、对象和渲染队列占用了多少时间的实时反馈。使用这些信息,我们可以跟踪着色器编写的效率,以消除问题区域和代码。

  注意,在Profiler打开的情况下运行的游戏,以及在编辑器中运行的游戏,会使游戏比在正常情况下编译和运行时稍微慢一些。您甚至可以在CPU费用的Profilers列表中看到Editor。

有更多的…

  还可以专门为移动平台配置文件。当在build Settings中设置Android或iOS的构建目标时,Unity为我们提供了一些额外的功能。我们可以在游戏运行时从移动设备获取实时信息。这非常有用,因为您可以直接在设备本身上展开,而不是直接在编辑器中展开。要了解更多关于这个过程的信息,请参考Unity的文档,链接如下:
http://docs.unity3d.com/Documentation/Manual/MobileProfiling.html

  在移动设备和PC上进行分析可能会得到完全不同的结果,如果你制作的是一款只面向移动设备的游戏,就不值得花时间在PC上的编辑器中进行分析。例如,可能存在这样一种情况,动态批处理在PC上工作得很好,但在移动设备上却不能正常工作。如果可能的话,我建议总是在目标设备上使用独立构建进行分析。


修改我们的移动着色器

  现在我们已经了解了制作优化着色器的广泛技术,让我们看看如何针对移动设备编写一个漂亮的、高质量的着色器。对我们编写的着色器做一些调整是很容易的,这样它们在移动设备上运行得更快。它包括使用approxview或halfasview照明函数变量等元素。我们还可以减少我们需要的纹理数量,甚至对我们正在使用的纹理应用更好的压缩。在这个配方的最后,我们将有一个很好的优化,正常映射的镜面着色器,我们可以在我们的手机游戏中使用。

准备

在我们开始之前,让我们创建一个新的场景,并使用一些对象,我们将应用到我们的MobileShader:

  1. 创建一个新的场景,用默认的球体和单方向光填充它。

  2. 创建一个新的材质(MobileMat)和着色器(MobileShader),并将着色器分配给材质。

  3. 最后,将材质分配给场景中的球体对象。

    一旦你这样做了,你应该有一个类似于如下截图所示的场景:
    在这里插入图片描述

怎么做……

  对于这个配方,我们将从头开始编写一个移动友好的着色器,并讨论使其更移动友好的元素:

  1. 首先,让我们用所需的纹理填充我们的Properties块。在这个例子中,我们将使用一个单独的_Diffuse纹理,它的alpha通道中有光泽贴图,法线贴图,以及一个用于反射强度的滑块:
Properties
{
	_Diffuse ("Base (RGB) Specular Amount (A) ", 2D) = "white" {}
	_SpecIntensity ("Specular Width", Range(0. 01, 1) ) = 0. 5
	_NormalMap ("Normal Map", 2D) = "bump"{}
}
  1. 接下来,我们必须设置#pragma声明。这将简单地打开某些表面着色器的功能,最终使着色器更便宜或更昂贵:
CGPROGRAM
#pragma surface surf MobileBlinnPhong exclude_ path: prepass nolightmap noforwardadd halfasview
  1. 接下来,删除#pragma目标3.0行,因为我们没有使用它的任何功能。
  2. 现在,我们需要在Properties块和CGPROGRAM之间建立连接。这一次,我们将使用固定变量类型的镜面强度滑块,以减少其内存使用量:
sampler2D _Diffuse;
sampler2D _NormalMap;
fixed _SpecIntensity;
  1. 为了将纹理映射到物体表面,我们需要一些uv。在这种情况下,我们将获得一组uv来保持我们着色器中的数据量到最小:
struct Input
{
	half2 uv_Diffuse;
};
  1. 下一步是使用一些新的输入变量(在使用新的#pragma声明时可用)填充Lighting函数:
inline fixed4 LightingMobileBlinnPhong(SurfaceOutput s, fixed3 lightDir, fixed3 halfDir, fixed atten)
{
	fixed diff = max(0, dot(s.Normal, lightDir) ) ;
	fixed nh = max(0, dot(s.Normal, halfDir) ) ;
	fixed spec = pow(nh, s.Specular * 128) * s.Gloss;
	fixed4 c;
	c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0. rgb * spec) * (atten * 2) ;
	c.a = 0.0;
	return c;
}
  1. 最后,我们将通过创建surf()函数和处理表面的最终颜色来完成着色器:
void surf (Input IN, inout SurfaceOutput o)
{
	fixed4 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) ) ;
}
  1. 现在,保存你的着色器,并返回到Unity编辑器,让着色器编译。如果没有发生错误,将一些属性分配给Base和法线映射属性:
    在这里插入图片描述
  2. 添加一些点灯和新对象的一些副本。此时,你应该会看到类似如下的结果:
    在这里插入图片描述
它是如何工作的……

  让我们来解释这个着色器做什么和不做什么。首先,它排除了延迟照明通道。这意味着如果你创建了一个连接到延迟渲染器的预封装的照明函数,它不会使用那个特定的照明函数,而是会寻找默认的照明函数,类似于我们在本书中迄今为止创建的那些。

  这个特殊的着色器不支持Unity内部的 lightmapping 系统的光映射。这使得着色器不必尝试为着色器附加的对象寻找光映射,这使得着色器的性能更加友好,因为它不需要执行 lightmapping 检查。

  我们包含了noforwardadd声明,以便我们只处理单方向光的逐像素纹理。所有其他的光都被迫变成逐顶点光,并且不会包含在你可能在surf()函数中执行的任何逐像素操作中。

  最后,我们使用halfasview声明告诉Unity,我们不会使用在普通照明函数中可以找到的主viewDir参数。相反,我们将使用 half vector 作为视图方向,并处理我们的镜面与此。这将使着色器处理的速度更快,因为它将在每个顶点的基础上完成。当它在现实世界中模拟镜面时,它并不完全准确,但在视觉上,在移动设备上,它看起来很好,着色器更优化。

  正是这些技术使得着色器更加高效,代码更加清晰。确保你只使用你需要的数据,同时权衡你的目标硬件和游戏所需的视觉质量。最后,这些技术的组合将最终构成你游戏的着色器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值