找回密码
 注册帐号

扫一扫,访问微社区

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

2015-1-8 17:45| 发布者: 杨炎| 查看: 3640| 评论: 0|原作者: 蛮牛|来自: unity3d脚本manual

摘要: 移动优化实用指南 — 渲染优化本节将介绍渲染优化的技术细节。它展示了如何烘焙光照结果以达到更佳性能,以及 shadowgun 的开发人员如何平衡高对比度纹理以及光照烘焙,让游戏更加精美。如果您想了解有关经过优化的 ...

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

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

秀出艺术才华!

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

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

如何进行深入了解?

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

内置着色器

查看内置着色器的源代码。通常,如果需要制作一款与众不同的新着色器,可以借鉴两个已经存在的着色器,并将其合并在一起。

表面着色器调试(#pragma 调试)

cg 着色器由每个表面着色器生成,然后在那里彻底编译。如果将#pragma debug添加至表面着色器顶部,在通过检视器打开编译的着色器时,将看到 cg 中间代码。这有助于检视着色器的特定部分在实际上如何计算,并且它也有助于从表面着色器中提取您想要的某些方面,并将其应用到 cg 着色器。

着色器包含文件

大部分着色器辅助代码都包含在每个着色器中,并且通常不会使用,但这也正是您有时候可以看到着色器调用 worldreflectionvector 之类的函数,但这些函数似乎没有在任何地方定义的原因。unity 拥有若干内置着色器包含文件,这些文件就包括这些辅助定义。如需查找特定参数,必须在所有不同的包含文件中搜索。

这些文件是 unity 用来简化着色器编写的内部结构的主要组成部分;它们可以提供实时阴影、不同的光照类型、光照贴图以及多平台支持等功能。

硬件文档

请花时间学习 apple硬件和编写着色器最佳实践文档。

请注意,对于浮点精度提示,建议您更加大胆尝试。

shadowgun深入分析

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

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

+ 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 资源包,以确保顶点光照代替缺失的低频光照细节。

相关阅读

文章点评
相关文章
Unity大学【第二期】!