前言
此文用作学习图形学过程中的一些不容易记住的点或者重要冷门的点而做的笔记。
不定期更新。
fmod in HLSL/CG 与mod in GLSL 的不同
在GLSL里 mod 是这样的(与C#的%相同):
float mod(float x, float y)
{
return x - y * floor(x/y)
}
在HLSL/CG里 fmod 是这样的:
float fmod(float x, float y)
{
return x - y * trunc(x/y)
}
两者差别在于对负数的计算,假如说 -2 % 5:
- GLSL会返回3
- C#会返回3
- HLSL/CG会返回2
UNORM 与 SRGB
SRGB是在Gamma空间下的,UNORM是在线性空间下的。
再提一句SNORM,也是线性的,负数的编码方式不是采用和编程语言中的补码,而是直接的线性变化,最小值-1有两个表示方法10000和10001(5位值)。
Alpha to coverage (A2C) (AlphaToMask in Unity)
Alpha to coverage是基于MSAA的技术,利用的是Coverage而不是Alpha混合。
主要思想是随机裁剪,再利用MSAA的模糊达到透明效果。
A2C与Alpha Test相似,都是属于非透明渲染(Opeque)。
AC2主要是为了解决Alpha混合带来的性能问题,但是效果赶不上Alpha混合。
MSAA
它包括4个步骤:
- 针对采样点进行覆盖检测
- 每个被覆盖的fragment执行一次fragment shader
- 针对采样点进行深度检测和模版检测
- 解析(resolve)
不能在延迟渲染里使用,因为G-Buffer只是一张图,无法记录一个像素被多个多边形覆盖的情况,所以不能实现resolve的过程。
正确的透明物体渲染方式
对于大部分透明物体渲染,就是把深度写入关了,这种方式可以处理大部分的透明渲染问题,但是这个方式存在2个问题,一个是如果对象是部分半透明,它的不透明部分会出现遮挡错误的问题。另一个是对于设置了不同混合因子的情况,得到的渲染结果可能不符合预期。正确的方式是使用和非透明物体一样的深度测试,然后保证先渲染非透明再渲染透明,之后把透明物体排序,先渲染远处再渲染近处。
Alpha Test(Discard)与 Early-Z
early-z是一种高效的深度测试方法,可以在fragment着色器之前进行深度测试从而节省性能开销。
正常情况下,进行early-z之后,正确的深度信息就会被记录下来,但是由于discard,会使得当前的像素点被抛弃,记录的深度信息也就失去了作用,所以遇到alpha-test时,会自动关掉early-z,这也是说法“使用Alpha Blend代替Alpha Test来做移动端优化”的原因。
UGUI (Unity)
UI元素在Hierarchy里的渲染顺序(即排列顺序)直接影响到合批处理,尽量把相同类型的放到同一顺序中,比如图片、文字,此外,用了Mask的元素也属于一种单独的类型,这样合批后能大大解决Drawcall太多的问题。此外,如果UI多为拼合且不需要过多改动的那种,可以直接渲染到一张RenderTexture保存,然后替换掉原元素,这样对Drawcall的优化最好(不适合有动态特效或者需要实时改动的)。
简单的顶点动画
函数:y=sin(x)*cos(2*x/3)
half dis = distance(v.vertex, _Pos);
half time = _Time.y * _TimeScale;
o.vertex.xyz += dis * (sin(time +v.vertex.y) * cos(time * 2 / 3 + v.vertex.y) + 1) * _Direction;
如果想试试乱动的话,也可以试试y=sin(x)*cos(2*x*x/3):
RayMarching 与 法线计算
vec3 GetNormal(vec3 p){
float d = GetDist(p);
vec2 e = vec2(.01, 0);
vec3 n = d - vec3(
GetDist(p - e.xyy),
GetDist(p - e.yxy),
GetDist(p - e.yyx)
);
return normalize(n);
}
RayMarching 中需要用一个法线公式同时计算平面和球体,这里使用的对球体的计算方式为一种经验公式,并没有直接算来得快。
高斯模糊与优化算法
参考:
原版高斯模糊时间复杂度:O(m2 * n2)。
采用两个pass从横向纵向分别模糊后的时间复杂度:O(m2 * 2n)。
利用双线性采样的GPU特性,采样中间一定距离的点相当于同时采样了两个点,时间复杂度:O(m2 * n)。
利用均值模糊(box blur)优化时间复杂度:由于均值模糊每个卷积核分量都是一样的,所以计算n+1时,从n减去最左边,加上n+1的最右边就可以得到结果,我们可以维护一个变量来记录这个结果,于是时间复杂度:O(m2)。
不过均值模糊有个缺点就是效果不太好,需要拟合高斯模糊的效果,就需要多次进行均值模糊:Fastest Gaussian Blur (in linear time)。
另外,对均值模糊的优化在GPU中并不能使用,因为GPU不能去做按照顺序的线性计算,所以有了以下的几种算法:
- Kawase 模糊(GDC 2013):利用GPU的双线性插值,在采样点的四个对角进行采样,大幅度降低采样数目,来达到优化的目的,缺点是为了拟合效果好,虽然一次采样只有四个点,会进行多次采样模糊,从而产生比较多的DrawCall。
- Dual 模糊(双滤波模糊)(Siggraph 2015):对Kawase算法进行改进,拥有独特的两个卷积核,利用上下采样来优化采样计算量,整体先变小再变大。
Dual 模糊这个算法也是Unity的后处理所使用的算法,带来的性能提升也使得以前不敢在手机上使用的特效(如Bloom)现在可以在手机上跑了。
这几个算法的性能比较图:
纹理打包浅谈
通常来说:
- RGB 用的是DXT1
- RGBA 用的是DXT5,内存比RGB大一倍
- 法线贴图是BC5,压缩RG通道并舍弃B通道
- R通道和B通道的质量会比G通道的高
- 内存便宜
贴图格式快查
默认的贴图比如Basecolor等就一般使用BC1或者BC3;
Normal Map使用BC5;
HDR、Cubemap、环境贴图使用BC6;
一些只需要灰度值的Alpha则使用BC4;
Shader 中四元数 VS 矩阵
参考:https://tech.metail.com/performance-quaternions-gpu/
虽然GPU对矩阵有优化,但是测试下来四元数的性能更好,但是如果四元数在Shader里构造的话,消耗还是比较大,如果是传入的话,那么会优于矩阵。
当只需要旋转时,优先考虑四元数。
PivotPainter
存储自定义子物体的中心和一些其他数据,来实现一些动画,如缩放、旋转、位移
vertexColor-RGB:物体空间的子物体X轴
vertexColor-A:自定义数据
UV2-XY:物体空间的子物体中心点XY
UV3-X:物体空间的子物体中心点Z
UV3-Y:随机数
PivotPainter改为贴图
法线转换空间,或者说法线贴平面,可以用四元数优化矩阵,方法是Reoriented Normal Mapping
在UE4里有个节点:BlendAngleCorrectedNormals