返回介绍

移动优化实用指南 — 渲染优化

发布于 2021-06-19 18:03:23 字数 19344 浏览 985 评论 0 收藏 0

本节将介绍渲染优化的技术细节。它展示了如何烘焙光照结果以达到更佳性能,以及 Shadowgun 的开发人员如何平衡高对比度纹理以及光照烘焙,让游戏更加精美。如果您想了解有关经过优化的移动游戏的一般信息,请参阅图形方法页面

秀出艺术才华!

有时候,优化游戏的渲染是一种粗活累活。Unity 提供 的所有结构都可以轻松加快游戏的运行速度,但如果要求在有限的设备上实现出类拔萃的保真度,那么,独立完成并且回避这些结构才是正确的选择,只要可以引进让游戏运行更快速的关键结构变化。可以选择的工具包括编辑器脚本、简单着色器,以及良好的旧式艺术作品。

Unity Indie 用户请注意:此处所指的编辑器脚本使用渲染纹理 (RenderTextures) 让产品更加光滑,因此,它们不会马上有所帮助,但其背后的原则对屏幕截图同样有效,因此,您可以任意使用这些技巧烘焙属于自己的纹理。

如何进行深入了解?

首先,请参阅着色器编写简介

  • 内置着色器
    • 查看内置着色器的源代码。通常,如果需要制作一款与众不同的新着色器,可以借鉴两个已经存在的着色器,并将其合并在一起。
  • 表面着色器调试(#pragma 调试)
    • CG 着色器由每个表面着色器生成,然后在那里彻底编译。如果将 #pragma debug 添加至表面着色器顶部,在通过检视器打开编译的着色器时,将看到 CG 中间代码。这有助于检视着色器的特定部分在实际上如何计算,并且它也有助于从表面着色器中提取您想要的某些方面,并将其应用到 CG 着色器。
  • 着色器包含文件
    • 大部分着色器辅助代码都包含在每个着色器中,并且通常不会使用,但这也正是您有时候可以看到着色器调用 WorldReflectionVector 之类的函数,但这些函数似乎没有在任何地方定义的原因。Unity 拥有若干内置着色器包含文件,这些文件就包括这些辅助定义。如需查找特定参数,必须在所有不同的包含文件中搜索。
    • 这些文件是 Unity 用来简化着色器编写的内部结构的主要组成部分;它们可以提供 实时阴影、不同的光照类型、光照贴图以及多平台支持等功能。
  • 硬件文档
 请注意,对于浮点精度提示,建议您更加大胆尝试。

Shadowgun 深入分析

考虑到运行的硬件,Shadowgun 无愧为一项突出的图形成就。其艺术品质是解答这一秘密的关键,当然还有一些其他技巧,让程序员可以实现这一品质,并将设计师的潜力发挥到极限。

图形方法页面,我们将 Shadowgun 中的黄金雕像作为其非凡优化的一个示例,他们没有使用正常的贴图来获得可靠的清晰度,相反,他们只是将光照细节烘焙成纹理。在这里,我们将向您展示在您自己的游戏中使用类似技术的方式和原因。

function showhide(link) { var theDiv = document.getElementById(link.id+"_div"); if (theDiv) { var curState = theDiv.style.display != 'none'; theDiv.style.display = (curState ? 'none' : 'block'); var str = (curState ? "+ Show [" : "- Hide [") + link.title + (curState ? "] +" : "] -"); link.innerHTML = str; } } + Show [Shader code for Real-Time vs Baked Golden Statue] +
 // This is the pixel shader code for drawing normal-mapped
 // specular highlights on static lightmapped geometry
 
 // 5 texture reads, lots of instructions
 
 SurfaceOutput o;
 
 fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
 fixed4 c = tex * _Color;
 o.Albedo = c.rgb;
 
 o.Gloss = tex.a;
 o.Specular = _Shininess;
 
 o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
 
 float3 worldRefl = WorldReflectionVector (IN, o.Normal);
 fixed4 reflcol = texCUBE (_Cube, worldRefl);
 reflcol *= tex.a;
 o.Emission = reflcol.rgb * _ReflectColor.rgb;
 o.Alpha = reflcol.a * _ReflectColor.a;
 
 fixed atten = LIGHT_ATTENUATION(IN);
 fixed4 c = 0;
 
 half3 specColor;
 fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
 fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);
 
 const float3x3 unity_DirBasis = float3x3( 
 float3( 0.81649658,  0.0, 0.57735028),
 float3(-0.40824830,  0.70710679, 0.57735027),
 float3(-0.40824829, -0.70710678, 0.57735026) );
 
 half3 lm = DecodeLightmap (lmtex);
 
 half3 scalePerBasisVector = DecodeLightmap (lmIndTex);
 
 half3 normalInRnmBasis = saturate (mul (unity_DirBasis, o.Normal));
 lm *= dot (normalInRnmBasis, scalePerBasisVector);
 
 return half4(lm, 1);
 
 // This is the pixel shader code for lighting which is
 // baked into the texture
 
 // 2 texture reads, very few instructions
 
 fixed4 c = tex2D (_MainTex, i.uv.xy);	
 
 c.xyz += texCUBE(_EnvTex,i.refl) * _ReflectionColor * c.a;
 
 return c;
 
 // This is the pixel shader code for drawing normal-mapped
 // specular highlights on static lightmapped geometry
 
 // 5 texture reads, lots of instructions
 
 SurfaceOutput o;
 
 fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
 fixed4 c = tex * _Color;
 o.Albedo = c.rgb;
 
 o.Gloss = tex.a;
 o.Specular = _Shininess;
 
 o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
 
 float3 worldRefl = WorldReflectionVector (IN, o.Normal);
 fixed4 reflcol = texCUBE (_Cube, worldRefl);
 reflcol *= tex.a;
 o.Emission = reflcol.rgb * _ReflectColor.rgb;
 o.Alpha = reflcol.a * _ReflectColor.a;
 
 fixed atten = LIGHT_ATTENUATION(IN);
 fixed4 c = 0;
 
 half3 specColor;
 fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
 fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);
 
 const float3x3 unity_DirBasis = float3x3( 
 float3( 0.81649658,  0.0, 0.57735028),
 float3(-0.40824830,  0.70710679, 0.57735027),
 float3(-0.40824829, -0.70710678, 0.57735026) );
 
 half3 lm = DecodeLightmap (lmtex);
 
 half3 scalePerBasisVector = DecodeLightmap (lmIndTex);
 
 half3 normalInRnmBasis = saturate (mul (unity_DirBasis, o.Normal));
 lm *= dot (normalInRnmBasis, scalePerBasisVector);
 
 return half4(lm, 1);
 
 // This is the pixel shader code for lighting which is
 // baked into the texture
 
 // 2 texture reads, very few instructions
 
 fixed4 c = tex2D (_MainTex, i.uv.xy);	
 
 c.xyz += texCUBE(_EnvTex,i.refl) * _ReflectionColor * c.a;
 
 return c;
 


反射凸点高光

使用反射烘焙光照

Render to Texel

实时光照无疑具有更高品质,但烘焙版本可以获取更大的性能。那么,怎样进行这一操作呢? 一种名为 Render to Texel 的编辑器工具就专为这一目的而创造。请注意:若要使用这一工具,必须运行 Unity 专业版。它通过以下过程将光照烘焙成纹理:

  • 通过脚本将切线空间法线贴图转换为自然空间。
  • 通过脚本创建自然空间位置贴图。
  • 使用之前两种贴图,纹理渲染 (Render to Texture) 整个纹理的全屏途径,每种光照使用另外一种途径。
  • 多个不同有利位置的平均结果可以产生一些从各个角度,或者至少从游戏的正常视角来说似乎合理的物体。

这就是最佳图形优化的工作原理。它们通过在编辑器中或在游戏运行前执行,回避了大量计算。一般来说,这就是您想要做的:

%

  • 创建精美游戏,而无需担心性能。
  • 使用 Unity 的光照贴图 (lightmapper) 等工具和 Render to Texel 以及 子画面包装机 (Sprite Packer) 之类的编辑器扩展就可以将其烘焙成极容易渲染的对象。
    • 制作自己的工具是完成这一操作的最佳方式,您可以为游戏出现的任何问题创建完美的工具。
  • 创建着色器和脚本,以调整烘焙输出并为其赋予少许“光泽”;创建动态光照的错觉将是一项夺人眼球的效果。

光频率的理念

正如音轨的低音 (Bass) 和高音 (Treble),图像也有高频和低频部分,在渲染时,最好用不同的方式处理,就像立体声使用低音炮和高音扬声器来创造环绕声效果。将图像的不同频率形象化的一种方式是使用 Photoshop 中的“高反差保留 (High Pass)” 滤镜。滤镜 (Filters)->其他 (Other)->高反差保留 (High Pass)。如果您之前曾接触过音频处理,您可能会熟悉高反差保留 (High Pass) 这一名称。本质上说,它的原理是切断所有低于 X 的频率,也就是您传递到滤镜的参数。对于图像,高斯模糊 (Gaussian Blur) 与高反差保留 (High Pass) 具有同样的效果。

这一原理应用到了实时图形中,因为频率非常有助于分离并决定处理方式。例如,在基本的光照贴图环境下,最终图像通过光照贴图的合成获得,这属于低频,但是纹理则属于高频。在 Shadowgun 中,低频光照通过光照探测器快速应用于角色,同时通过使用简单凸点贴图着色器以任意光照方向捏造高频光照。

一般而言,通过使用不同的方法渲染不同的光照频率(例如,烘焙与动态,逐对象与逐级别,逐像素与逐顶点等),可以在有限的硬件上创建内容充实的图像。这是一种风格上的选择,此外,这也是尝试在高频和低频同时拥有更强的变化色彩或值的不错选择。

实践中的频率:Shadowgun 分解

  • 顶行
    • 超低频高光顶点光照(动态) (Ultra-Low-Frequency Specular Vertex Light (Dynamic)) | 高频 Alpha 通道 (High Frequency Alpha Channel) | 低频光照贴图 (Low Frequency Lightmap) | 高频发射率 (High Frequency Albedo)
  • 中间行
    • 高光定点光照 (Specular Vertex Light) * Alpha | 高频添加细节 (High Frequency Additive Details ) | 光照贴图 (Lightmap) * 色彩通道 (Color Channel)
  • 底行
    • 最终效果

请注意: 通常,这些分解指的是延迟渲染中的步骤,但此处不适用。所有这些效果都是一次处理。以下是这个合成场景依靠的两种相关着色器:

+ Show [Lightmapped with Virtual Gloss Per-Vertex Additive] +
 Shader "MADFINGER/Environment/Virtual Gloss Per-Vertex Additive (Supports Lightmap)" {
 Properties {
 	_MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
 	//_MainTexMipBias ("Base Sharpness", Range (-10, 10)) = 0.0
 	_SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0)
 	_SpecRange ("Specular Range", Float) = 20
 	_SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)
 	_Shininess ("Shininess", Range (0.01, 1)) = 0.078125
 	_ScrollingSpeed("Scrolling speed", Vector) = (0,0,0,0)
 }
 
 SubShader {
 	Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
 	LOD 100
 
 
 
 	CGINCLUDE
 	#include "UnityCG.cginc"
 	sampler2D _MainTex;
 	float4 _MainTex_ST;
 	samplerCUBE _ReflTex;
 
 	#ifndef LIGHTMAP_OFF
 	float4 unity_LightmapST;
 	sampler2D unity_Lightmap;
 	#endif
 
 	//float _MainTexMipBias;
 	float3 _SpecOffset;
 	float _SpecRange;
 	float3 _SpecColor;
 	float _Shininess;
 	float4 _ScrollingSpeed;
 
 	struct v2f {
 		float4 pos : SV_POSITION;
 		float2 uv : TEXCOORD0;
 		#ifndef LIGHTMAP_OFF
 		float2 lmap : TEXCOORD1;
 		#endif
 		fixed3 spec : TEXCOORD2;
 	};
 
 
 	v2f vert (appdata_full v)
 	{
 		v2f o;
 		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
 
 		o.uv = v.texcoord + frac(_ScrollingSpeed * _Time.y);
 
 		float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal);
 		float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex);
 		float3 viewDir = float3(0,0,1);
 		float3 viewLightPos = _SpecOffset * float3(1,1,-1);
 
 		float3 dirToLight = viewPos.xyz - viewLightPos;
 
 		float3 h = (viewDir + normalize(-dirToLight)) * 0.5;
 		float atten = 1.0 - saturate(length(dirToLight) / _SpecRange);
 
 		o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten;
 
 		#ifndef LIGHTMAP_OFF
 		o.lmap = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
 		#endif
 		return o;
 	}
 	ENDCG
 
 
 	Pass {
 		CGPROGRAM
 		#pragma vertex vert
 		#pragma fragment frag
 		#pragma fragmentoption ARB_precision_hint_fastest		
 		fixed4 frag (v2f i) : COLOR
 		{
 			fixed4 c = tex2D (_MainTex, i.uv);
 
 			fixed3 spec = i.spec.rgb * c.a;
 
 			#if 1
 			c.rgb += spec;
 			#else			
 			c.rgb = c.rgb + spec - c.rgb * spec;
 			#endif
 
 			#ifndef LIGHTMAP_OFF
 			fixed3 lm = DecodeLightmap (tex2D(unity_Lightmap, i.lmap));
 			c.rgb *= lm;
 			#endif
 
 			return c;
 		}
 		ENDCG 
 	}	
 }
 }
 
 Shader "MADFINGER/Environment/Virtual Gloss Per-Vertex Additive (Supports Lightmap)" {
 Properties {
 	_MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
 	//_MainTexMipBias ("Base Sharpness", Range (-10, 10)) = 0.0
 	_SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0)
 	_SpecRange ("Specular Range", Float) = 20
 	_SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)
 	_Shininess ("Shininess", Range (0.01, 1)) = 0.078125
 	_ScrollingSpeed("Scrolling speed", Vector) = (0,0,0,0)
 }
 
 SubShader {
 	Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
 	LOD 100
 
 
 
 	CGINCLUDE
 	#include "UnityCG.cginc"
 	sampler2D _MainTex;
 	float4 _MainTex_ST;
 	samplerCUBE _ReflTex;
 
 	#ifndef LIGHTMAP_OFF
 	float4 unity_LightmapST;
 	sampler2D unity_Lightmap;
 	#endif
 
 	//float _MainTexMipBias;
 	float3 _SpecOffset;
 	float _SpecRange;
 	float3 _SpecColor;
 	float _Shininess;
 	float4 _ScrollingSpeed;
 
 	struct v2f {
 		float4 pos : SV_POSITION;
 		float2 uv : TEXCOORD0;
 		#ifndef LIGHTMAP_OFF
 		float2 lmap : TEXCOORD1;
 		#endif
 		fixed3 spec : TEXCOORD2;
 	};
 
 
 	v2f vert (appdata_full v)
 	{
 		v2f o;
 		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
 
 		o.uv = v.texcoord + frac(_ScrollingSpeed * _Time.y);
 
 		float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal);
 		float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex);
 		float3 viewDir = float3(0,0,1);
 		float3 viewLightPos = _SpecOffset * float3(1,1,-1);
 
 		float3 dirToLight = viewPos.xyz - viewLightPos;
 
 		float3 h = (viewDir + normalize(-dirToLight)) * 0.5;
 		float atten = 1.0 - saturate(length(dirToLight) / _SpecRange);
 
 		o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten;
 
 		#ifndef LIGHTMAP_OFF
 		o.lmap = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
 		#endif
 		return o;
 	}
 	ENDCG
 
 
 	Pass {
 		CGPROGRAM
 		#pragma vertex vert
 		#pragma fragment frag
 		#pragma fragmentoption ARB_precision_hint_fastest		
 		fixed4 frag (v2f i) : COLOR
 		{
 			fixed4 c = tex2D (_MainTex, i.uv);
 
 			fixed3 spec = i.spec.rgb * c.a;
 
 			#if 1
 			c.rgb += spec;
 			#else			
 			c.rgb = c.rgb + spec - c.rgb * spec;
 			#endif
 
 			#ifndef LIGHTMAP_OFF
 			fixed3 lm = DecodeLightmap (tex2D(unity_Lightmap, i.lmap));
 			c.rgb *= lm;
 			#endif
 
 			return c;
 		}
 		ENDCG 
 	}	
 }
 }
 

+ Show [Lightprobes with Virtual Gloss Per-Vertex Additive] +
 Shader "MADFINGER/Environment/Lightprobes with VirtualGloss Per-Vertex Additive" {
 Properties {
 	_MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
 	_SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0)
 	_SpecRange ("Specular Range", Float) = 20
 	_SpecColor ("Specular Color", Color) = (1, 1, 1, 1)
 	_Shininess ("Shininess", Range (0.01, 1)) = 0.078125	
 	_SHLightingScale("LightProbe influence scale",float) = 1
 }
 
 SubShader {
 	Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
 	LOD 100
 
 
 
 	CGINCLUDE
 	#pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON
 	#include "UnityCG.cginc"
 	sampler2D _MainTex;
 	float4 _MainTex_ST;
 
 
 	float3 _SpecOffset;
 	float _SpecRange;
 	float3 _SpecColor;
 	float _Shininess;
 	float _SHLightingScale;
 
 	struct v2f {
 		float4 pos : SV_POSITION;
 		float2 uv : TEXCOORD0;
 		float3 refl : TEXCOORD1;
 		fixed3 spec : TEXCOORD3;
 		fixed3 SHLighting: TEXCOORD4;
 	};
 
 
 	v2f vert (appdata_full v)
 	{
 		v2f o;
 		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
 		o.uv = v.texcoord;
 
 		float3 worldNormal = mul((float3x3)_Object2World, v.normal);		
 		float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal);
 		float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex);
 		float3 viewDir = float3(0,0,1);
 		float3 viewLightPos = _SpecOffset * float3(1,1,-1);
 
 		float3 dirToLight = viewPos.xyz - viewLightPos;
 
 		float3 h = (viewDir + normalize(-dirToLight)) * 0.5;
 		float atten = 1.0 - saturate(length(dirToLight) / _SpecRange);
 
 		o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten;
 
 		o.SHLighting	= ShadeSH9(float4(worldNormal,1)) * _SHLightingScale;
 
 		return o;
 	}
 	ENDCG
 
 
 	Pass {
 		CGPROGRAM
 		#pragma vertex vert
 		#pragma fragment frag
 		#pragma fragmentoption ARB_precision_hint_fastest		
 		fixed4 frag (v2f i) : COLOR
 		{
 			fixed4 c	= tex2D (_MainTex, i.uv);
 
 			c.rgb *= i.SHLighting;
 			c.rgb += i.spec.rgb * c.a;
 
 			return c;
 		}
 		ENDCG 
 	}	
 }
 }
 
 Shader "MADFINGER/Environment/Lightprobes with VirtualGloss Per-Vertex Additive" {
 Properties {
 	_MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
 	_SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0)
 	_SpecRange ("Specular Range", Float) = 20
 	_SpecColor ("Specular Color", Color) = (1, 1, 1, 1)
 	_Shininess ("Shininess", Range (0.01, 1)) = 0.078125	
 	_SHLightingScale("LightProbe influence scale",float) = 1
 }
 
 SubShader {
 	Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
 	LOD 100
 
 
 
 	CGINCLUDE
 	#pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON
 	#include "UnityCG.cginc"
 	sampler2D _MainTex;
 	float4 _MainTex_ST;
 
 
 	float3 _SpecOffset;
 	float _SpecRange;
 	float3 _SpecColor;
 	float _Shininess;
 	float _SHLightingScale;
 
 	struct v2f {
 		float4 pos : SV_POSITION;
 		float2 uv : TEXCOORD0;
 		float3 refl : TEXCOORD1;
 		fixed3 spec : TEXCOORD3;
 		fixed3 SHLighting: TEXCOORD4;
 	};
 
 
 	v2f vert (appdata_full v)
 	{
 		v2f o;
 		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
 		o.uv = v.texcoord;
 
 		float3 worldNormal = mul((float3x3)_Object2World, v.normal);		
 		float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal);
 		float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex);
 		float3 viewDir = float3(0,0,1);
 		float3 viewLightPos = _SpecOffset * float3(1,1,-1);
 
 		float3 dirToLight = viewPos.xyz - viewLightPos;
 
 		float3 h = (viewDir + normalize(-dirToLight)) * 0.5;
 		float atten = 1.0 - saturate(length(dirToLight) / _SpecRange);
 
 		o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten;
 
 		o.SHLighting	= ShadeSH9(float4(worldNormal,1)) * _SHLightingScale;
 
 		return o;
 	}
 	ENDCG
 
 
 	Pass {
 		CGPROGRAM
 		#pragma vertex vert
 		#pragma fragment frag
 		#pragma fragmentoption ARB_precision_hint_fastest		
 		fixed4 frag (v2f i) : COLOR
 		{
 			fixed4 c	= tex2D (_MainTex, i.uv);
 
 			c.rgb *= i.SHLighting;
 			c.rgb += i.spec.rgb * c.a;
 
 			return c;
 		}
 		ENDCG 
 	}	
 }
 }
 

最佳实践

GPU 优化:Alpha–测试

某些 GPU,尤其是移动设备上的 GPU,都会引发 alpha–测试高性能开销(或在像素着色器中使用丢弃 (discard) 和 裁剪 (clip) 操作)。如果可能,应使用 alpha-混合着色器代替 alpha–测试着色器。如果 alpha–测试不可避免,那么应该将可见的经 alpha–测试的像素值保证在最小范围。

iOS 纹理压缩

某些图像(尤其是如果使用 iOS/Android PVR 纹理压缩)倾向于 alpha 通道中的人为视觉效果。在这种情况下,可能需要在图像软件上直接调整 PVRT 压缩参数。可以安装 PVR 导出插件或使用来自 PVRTC 格式创造者 Imagination Tech 的 PVRTexTool。压缩之后的图像文件扩展名为 .pvr,将由 Unity 编辑器直接导入,并保留指定的压缩参数。如果经 PVRT 压缩的纹理不能提供 较好的视觉质量,或者您尤其需要简洁的成像(GUI 纹理可能需要),那么应该考虑使用 16 位纹理而不是 32 位。通过这些操作,将减少一半的内存带宽和存储要求。

Android 纹理压缩

所有支持 OpenGL ES 2.0 的 Android 设备也支持 ETC1 压缩格式;因此,可能的话请尽量使用 ETC1 作为首选纹理格式。

如果针对特定的图形结构,如 Nvidia Tegra 或 Qualcomm Snapdragon,那么应该考虑使用这些结构专用的压缩格式。Android Market 也支持对支持的纹理压缩格式进行过滤,这意味着可以阻止不支持拥有 DXT 压缩纹理等分配存档 (.apk) 的设备下载该程序。

练习(仅限 Unity 专业版)

  • 下载 Render to Texel
  • 在模型上烘焙光照。
  • 使用 Photoshop 在烘焙之后的图片上运行“高反差保留 (High Pass)” 滤镜。
  • 编辑 "Mobile/Cubemapped" 着色器,包括 Render to Texel 资源包,以确保顶点光照代替缺失的低频光照细节。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文