译自aras的博客,总共3篇文章,讲述unity5优化自己渲染器的过程
吸取大神调试与优化经验,了解unity5内部渲染器的优化方法
上篇文章写了关于清理和优化。从那时起,我已经转变到做一些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