您的位置:首页 > 移动开发 > Unity3D

Unity5内部渲染的优化3:移除固定功能

2015-09-11 07:02 375 查看
译自aras的博客,总共3篇文章,讲述unity5优化自己渲染器的过程

吸取大神调试与优化经验,了解unity5内部渲染器的优化方法

第一篇:Unity5内部渲染的优化1:介绍

第二篇:Unity5内部渲染的优化2:清理

上篇文章写了关于清理和优化。从那时起,我已经转变到做一些unity5.1的工作了,移除了固定功能着色器Fixed Function Shaders和一些别的事。

固定功能是什么

以前,GPU还没有“可编程着色器programmable shaders”;通过启用和禁用某些功能,配置或多或少(大多很少)变得灵活。例如,让他们去计算每个顶点的光照;或者在每个像素上把两个贴图颜色相加。

Unity在很久之前就有了,所以很自然地支持固定功能着色器。它们的语法很简单,如果只是写一些简单的着色器,运行速度要比顶点/像素着色器要快。

例如:一个shader pass设置为alpha混合,输出纹理与颜色的乘积:

ass

{

Blend SrcAlpha OneMinusSrcAlpha

SetTexture [_MainTex] { constantColor[_Color] combine texture * contant }

}

与顶点+像素做完全相同的事情,产生的结果也相同

Pass

{

Blend SrcAlpha OneMinusSrcAlpha

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#include "UnityCG.cginc"

struct v2f

{

float2 uv : TEXCOORD0;

float4 pos : SV_POSITION;

};

float4 _MainTex_ST;

v2f vert (float4 pos : POSITION, float2 uv : TEXCOORD0)

{

v2f o;

o.pos = mul(UNITY_MATRIX_MVP, pos);

o.uv = TRANSFORM_TEX(uv, _MainTex);

return o;

}

sampler2D _MainTex;

fixed4 _Color;

fixed4 frag (v2f i) : SV_Target

{

return tex2D(_MainTex, i.uv) * _Color;

}

ENDCG

}

现在我们已经移除了对fixed function 固定功能GPU的支持,和在unity4.3(这2013年的时候)上的一些平台(OpenGL ES 1.1 on mobile and Direct3D 7 GPUs on Windows)。现在对写固定功能着色器没有什么技术需求了、没有必要了。除非:1.在现有项目中有很多这些着色器。2.只是想少打字。

事实上,固定功能着色器也有很多缺点:

1. 他们在主机上不工作(PS4,Xbox one,vita),在这些平台上实时生成着色器是非常困难的。

2. 它们不能和MaterialPropertyBlocks一起工作,不能用在unity 的Sprite的渲染上,也不能用在动画材质上。

3. 它们只适合做非常简单的事情,你做一个简单的固定功能着色器,之后却发现需要添加更多的功能,也没办法再做更多。

固定功能着色器在unity上是如何执行的?为什么?

大多数平台我们不支持 “固定功能渲染管线” ,它们内部转化为“actual shaders”才能用于渲染。只有一处固定功能着色器可以存在的例外是legacy desktop OpenGL (GL 1.x-2.x) 和 Direct3D 9.

到更多的平台出现,在OpenGL ES 2.0上我们实现了一个和D3D9差不多的东西,替代D3D9shader的二进制连接装配为连接GLSL片段。然后又更多的平台出现(D3D11, Flash, Metal);每个平台执行“固定功能”代码。代码不是特别复杂,问题很好理解并且我们有做足够的图形测试验证工作。

在这个过程中的每一步,没有人质疑为什么我们要继续做“为什么要实时生成?而不是离线的,在固定功能着色器导入的时候就把它转换好了呢”?(如果有人问这个问题,答案就是“这样做会很有意义,只是需要有人去做”一段时间。。。)”

在很久之前,离线转换固定功能着色器并不是很实用,因为有大量的可能的变体需要被支持。最棘手的部分是对纹理坐标的支持(发送uv到纹理阶段,可选的纹理变换矩阵,可选的纹理投影,和可选的texture coordinate generation纹理坐标生成)。但是嘿,我们在unity5移除了很多东西。它变得更简单了吗?是的。

在导入时将固定功能着色器转化为普通着色器



上图翻译:

在unity中的shader可以写“固定功能”形式的,例如:你可以只写“Light On“来得到逐顶点的光,在超级采样shader(键入内容少)中也很有用,及大量的shader是这样写出的。

现在(4.,5.0/5.1)这些固定功能着色器是在运行时处理的:

他们载入&解析到内部shaderlab表现。

无论何时需要新的“固定功能状态“,产生并使用一个新的“actual shader”来计算

完全依靠平台:D3D9,D3D11,OpenGL,Metal,GLES,PSM

在主机/DX12不能完全执行

在shader导入时“生成actual shader”与之相比要好很多,然后移除所有运行时代码。

好处:移除了很多代码!

好处:移除了在渲染循环的无用的一小部分,它只是由固定功能产生的。

好处:可以在主机,DX12,Vulkan等上面工作了

好处:跨平台行为更加一致(现在还是有细微的不同,如:在移动端的高光就是不同的;雾的工作也有些不同)

缺点:从一个脚本通过“new Material(string)”产生一个固定功能shader会停止工作

“new Material(string)”被5.1标记为过时的。

意味着向后兼容.破坏改变。意味着5.2应该把5.0/5.1的webplayer分开为一个分离的通道。

所以我打算这样做,移除了“固定功能着色器”所有运行时的代码;替换为只在Unity editor中导入shader是把它们转化为“普通着色器”。在wiki上创建一个数据&计划工作的概述,然后开始编程。我以为最终的结果会是“我新写了1000行代码移除了4000行”但是我错了!

一次我在做导入shader方面的基础的工作(结果,大约1000行代码),我开始移除整个固定功能部分。那是快乐的一天(如下图:)




大约一万两千行代码,消失了,真是惊人!



我完全不记得固定功能有那么多代码,你为了一个平台写它,然后它基本上工作了;然后一些新的平台出现又要写关于它的新的代码,然后它基本上工作了。之后又出现N各平台,所有代码加在一起的量是巨大的,因为它不是一下子就那么多代码,所以没人发现这个问题。

拿走:偶尔,看看整个子系统。你也许会震惊,在多年后它扩张了那么多。也许其中的一些东西因为某些原因已经不适用了。


旁注:在一个vertex shader的逐顶点光照

如果有一件事能在固定功能管线上变得简单,那就是很容易组合很多功能。你能使用很多个光(最多8个)他们是direction light,point light 或者spot light 。只是一个flag标志控制高光开闭,fog雾也一样。

感觉像是“特征的简单构成”当我们把所有都放到shader里时,我们失去一件重要的事情。我们知道的shader(vertex/fragment/… stages)一个都不能组合!想要添加一些可选的特征->这几乎意味着“加两倍shader”,或者分支shader,或者实时生成shader,这些方法都有利弊。

例如,你怎样写一个可以接近8个光源的顶点shader?有很多种方法,我现在正在做的是:

分离顶点着色器为“有spot light?”“有 point light?”“只有directional light”这些情况。我猜spot light 很少用在逐顶点固定功能光照上;它们看起来效果特别不好。所以在很多种情况,不会有“计算spot light”的消耗。

光源的数量作为整数被传入到shader中,并且shader在它们中循环。复杂:OpenGL ES 2.0 / WebGL,循环是你只能由常数次的循环次数:实践中发现,OpenGL ES 2.0有很多次都没有这个限制,然而webGL是肯定存在这个限制的。在此时我没有好的答案。在ES2/WebGL我只是循环遍历8个可能的光源(不使用的光源设置为黑色)。一个真实的解决方法,一个常规的循环是这样的:

uniform int lightCount;

// ...

for (int i = 0; i < lightCount; ++i)

{

// compute light #i

}

当在ES2.0/WebGL下编译时,我要这样编辑shader:(博主注:根据上文说的8个都循环一遍)

uniform int lightCount;

// ...

for (int i = 0; i < 8; ++i)

{

if (i == lightCount)

break;

// compute light #i

}

很讨厌处理像这样看似任意的限制(我听说webGL2 没有这样的限制,这真是太好了)

我们现在有什么

所以现在的情况是这样的,移除了大量的代码,我得到了下面的好处:

1. “固定功能风格”shader可以在所有平台下运行了(主机!DX12!)

2. 他们跨平台工作更加稳定(例如:之前在PC&手机上高光和衰减有着细微的区别)

3. “固定功能风格”shader可以使用MaterialPropertyBlocks工作了,意味着它可以渲染sprite,等等。

4. 固定功能着色器在windows phone 上不会再有光栅中怪异的半像素偏移了

5. 固定功能shader转换到actual shader变得更加简单;我在 shader inspector中加了一个按钮,可以显示所有生成的代码;你可以复制走并扩展它。

6. 代码变少了,转换成的可执行文件大小也变小了。例如:Windows 64 bit小了 300 kilobytes.

7. 渲染稍微变得快了些(即使没有使用固定功能着色器)

最后的一点并不是主要目的,但是是个不错的福利。没有特别大的影响,但是相当多的分支和数据从平台图形抽象中移除(只有在支持运行时固定功能的地方)。我根据项目试过了,在渲染线程中节省了5%的时间(如:10.2ms->9.6ms),效果非常好。

有什么缺点吗?是的,有几个:

1. 你不能再在运行时创建固定功能着色器了。之前你可以这样做var mat = new Material("<fixed function shader string>")除了在主机上都可以运行。由于这个原因,我做了Material(string)在unity5.1会标记为过时,而且发出警告,但是实际上它会停止工作。

2. 对web player的向后兼容产生破坏性的变化,如果代码在unity 5.2开发了,这意味着不能再在unity5.0/5.1上运行

3. 在几种特殊情况下可能不工作,例如:固定功能着色器使用一个全局设置纹理而不是一个2D纹理。在shader资源本身,任何关于那个纹理都不会被指定;所以在我在固定功能shader导入时生成actual shader,我不知道它是一个2D还是Cubemap 纹理。所以对于全局纹理,我只是假设他们都是2D纹理。

4. 大概就是这样!

移除实时固定功能的支持还有很多潜在的好处。在内部对于所有纹理的改变我们通过像“纹理类型(2D,cubemap等)”这样的东西-但是貌似只有固定功能管线会用到它。同样的,对于每个draw call我们通过一个vertex-declaration-like结构;但是现在我认为再也不需要它们了。

三篇结束。。。

------译自: wolf96 http://blog.csdn.net/wolf96
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: