UNITY SHADER入门精要 看了将近三个月,虽然去年因为有点事情暂停了看书计划,今年在业余时间一直看,终于看完了。大致了解了shader的渲染,但是写shader还是。。。。。。需要多看大佬的shader代码,学习更多的书籍。
ch2_0.渲染流水线:
应用阶段->几何阶段->光栅化阶段
应用阶段:输出渲染图元
几何阶段:处理所有和我们要绘制的几何相关的事情, 将顶点坐标变换到屏幕坐标
光栅化阶段:决定每个渲染图元中的那些像素要绘制在屏幕上
ch2_1.注意OpenGL和DirectX的坐标轴:
ch3_0.部分专业名词介绍
#pragma surface surfaceFunction lightModel[optionalparams]
声称着色器 表面着色器 着色器代码名称 光照模型
It uses #pragma surface ... directive to indicate it’s a surface shader.
eg:#pragma surface surf Standard fullforwardshadows
介绍lightModel
1)Standard:
This lighting model uses SurfaceOutputStandard output struct,
and matches the Standard (metallic workflow) shader in Unity.
2)Standard:
This Specular lighting model uses SurfaceOutputStandardSpecular output struct,
and matches the Standard (specular setup) shader in Unity.
3).Lambert and BlinnPhong:郎伯模型
This lighting models are not physically based (coming from Unity 4.x),
but the shaders using them can be faster to render on low-end hardware.
3.Unity shader类型
1)Standard Surface Shader:标准光照模型
2)Standard Surface Shader(instanced)
3)Unlit Shader:不包含光照,包含雾效
4)Image Effect Shader:实现屏幕后处理效果
5)Compute Shader:产生一个特殊的shader文件,目的是利用cpu的并行性进行一些与常规渲染流水线无关·
ch3_1.Unity Shader语法
查API:https://docs.unity3d.com/Manual/SL-CullAndDepth.html
SubShader{
Tags {}
Log 200
Pass{
.........
Cull off
.......
}
}
UsePass :Pass的名字时候一定要大写,
ch3_1.自定义材质编辑界面:https://docs.unity3d.com/Manual/SL-CustomShaderGUI.html
ch4_0.unity内置的变化矩阵:
举例:把顶点从观察空间变换到模型空间
ch4_1.摄像机和屏幕参数
ch4_2.CG是行优先,一行一行的填充矩阵。Unity Shader使用的是行类型,但是有一种矩阵类型Matrix4x4是列优先。
ch4_4.渲染流水线顶点的空间变化过程:
ch5_0.ShaderLab属性和CG变量匹配的关系:
ch5_1:常用语义:常用,经常看
ch5_2:关于shader调试
1)用unity自带的FrameDebug:windows->Frame Debug
2)用vs的Graphics Debugger: http://blog.sina.com.cn/s/blog_12d58e52d0102x4qk.html
ch5_3:注意平台差异
当开启抗锯齿后,需要处理DirectX下的图片翻转。
ch6_0:常用的函数
ch6_1:标准光照模型
1)环境光:C(ambient) = g(ambient)
2)自发光:C(emissive) = M(emissive)
3)漫反射:
a.兰伯特定律 :C(diffuse) = (C(light) * M(diffuse)) * max(0, n* I );
n:表面法线 I:指向光源的单位矢量 m(diffuse):材质的漫反射颜色 C(light):光源颜色
b.半兰伯特模型:C(diffuse) = (C(light) * M(diffuse)) * (α(n * I) + β)
大多数情况下:α β 都是0.5
3)高光:
a.Phong模型: C(specular) = (C(light)*M(specular)) * max(0, v * r) ^ m(gloss)
C(light):光源颜色和强度 m(gloss):光泽度 m(specular):材质的高光反射颜色
V:视角方向 r:反射方向
b.Blinn-Phong模型:
n:法线方向
h矢量:定义为
ch7_1:unity中使用纹理名_ST 获取某个纹理的属性,ST(Scale和translation的缩写)
纹理名_ST.xy:获取缩放值
纹理名_ST.zw:获取偏移量
法线纹理和切线方向映射:
ch8:透明效果
1)大部分游戏引擎用的渲染顺序:
缺点:无法解决多个物体重叠
优点:容易实现足够有效
避免缺点:模型尽量凸面体,复杂的模型拆分独立排序的子物体
2)Queue标签:
3)Blend命令:
混合要求:使用blend命令 并且设置混合因子DstFactor
混合后的颜色:
SrcFactor(设为SrcAlpha),DstFactor(设为OneMinusSrcAlpha)
3)混合透明:
Albedo:我们通常理解的对光源的反射率。它是通过在Fragment Shader中计算颜色叠加时,和一些变量(如vertex lights)相乘后,叠加到最后的颜色上的。
4)渲染命令:ColorMask RGB | A | 0 (0-不写入任何颜色通道,即不会输出任何颜色)
5)混合命令:
Src:SourceColor 源颜色
Dst:Destination Color 目标颜色
O:输出颜色
eg: Blend SrcAlpha OneMinusSrcAlpha, One Zero
输出颜色的透明度值就是源颜色的透明度
仔细看之前的公式:
O (a) = SrcAlpha * One + OneMinusSrcAlpha * Zero
ch9 更复杂的光照
1)LightMode标签:
(1_1)前向渲染:
ForwardBase:
Tags{"LightMode"="ForwardBase"}
#pragam multi_compile_fwdbase (Bass Pass)
ForwardAdd:
Tags{"LightMode"="ForwardAdd"}
#pragam multi_compile_fwdadd (Additional Pass)
前向渲染可以使用的内置光照变量:
前向渲染可以使用的内置光照函数:
(1_2)顶点照明渲染路径:只是用逐顶点方法计算光照,属于前向渲染的子集。运行快,效果差,基本不用。
(1_3)延迟渲染路径:解决前向渲染的瓶颈,例如同一区域多个光源影响区域覆盖的问题
2)光照衰减:unity中使用一张纹理作为查找表来在片元着色器中逐像素光照衰减。
(用于计算点光源和聚光灯,平行光无衰减)
_LightTexture0: 没使用cookie
_LightTextureB0:使用了cookie
eg:_LightTexture0(x,y)表明了光源空间中不同位置的点的衰减值
(0,0)与光源位置重合点的衰减值
(1,1) 光源空间中最远的点的衰减
_LightMatrix0:顶点从世界空间变换到光源空间
dot(lightCoord, lightCoord) 算出来的是平方值,.rr是取r的属性
UNITY_ATTEN_CHANNEL 得到衰减纹理中衰减值所在的分量
eg:光源的线性衰减
关于聚光灯和点光源计算公式推导:
https://github.com/candycat1992/Unity_Shaders_Book/issues/47
3)阴影:注意光源要开启shadow,物体的castShadows属性也要开启
SHADOW_COORDS:声明了_ShadowCoord的阴影纹理坐标变量
TRANSFER_SHADOW(v2f :在顶点着色器中计算声明的阴影纹理坐标
SHADOW_ATTENUATION:计算阴影值
使用时必须注意:
a2f结构体顶点坐标变量名必须为vertex
顶点着色器的输出结构体v2f必须命名为v
v2f顶点坐标变量必须命名为pos
ch10:高级纹理
1)立方体纹理:
unity官方推荐使用第一种:第一种可以压缩纹理数据,支持边缘修正,光滑反射,HDR等功能
2)反射,折射,菲涅尔反射:
折射公式:
3)渲染纹理(Render Texture):
4)程序纹理:
Texture2D.SetPixel() 写好要显示的像素
Texture2D.Apply()
ch11:让画面动起来
1)帧动画:
uv是0-1之间,所以 time感觉是距离,_Time.y是一个增量,time与之不断增加,time / _HorizontalAmount 获取当前是第几行, row*_VerticalAmount是这几行走过的距离,time-该距离,就等于水平方向的长度
2)动画阴影:配置这几处,顶点跟着计算就可以了
ch12 屏幕后处理效果
1)抓取屏幕函数:
2)关于亮度,饱和度,对比度计算?
摘抄:https://www.cnblogs.com/lancidie/p/8780514.html
参考网址https://wenku.baidu.com/view/2413b07131126edb6f1a105b.html
最简单的是亮度,我们可以直接在采样texture后乘以一个系数,达到放大或者缩小rgb值的目的,这样就可以调整亮度了。
其次是饱和度,饱和度是离灰度偏离越大,饱和度越大,我们首先可以计算一下同等亮度条件下饱和度最低的值,根据公式:gray = 0.2125 * r + 0.7154 * g + 0.0721 * b即可求出该值(公式应该是一个经验公式),然后我们使用该值和原始图像之间用一个系数进行差值,即可达到调整饱和度的目的。
最后是对比度,对比度表示颜色差异越大对比度越强,当颜色为纯灰色,也就是(0.5,0.5,0.5)时,对比度最小,我们通过在对比度最小的图像和原始图像通过系数差值,达到调整对比度的目的。
3)边缘检测:
xxx_TexelSize 是untiy提供的访问xxx纹理对应的每个纹素的大小
Sobel:书上有错
Gx Gy
-1 0 1 -1 -2 -1
-2 0 2 0 0 0
-1 0 1 1 2 1
4)高斯方程:
ch13 使用深度和法线文理
1)获取深度和法线纹理:
camera.depthTextureMode = DepthTextureMode.Depth
camera.depthTextureMode |= DepthTextureMode.DepthNormal
对深度纹理采样:(不用tex2D是因为平台差异)
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
NDC下的z分量:
根据公式: d = 0.5*Z(ndc) + 0.5
在unity视角空间,z值均为负值,所以要取反, Z'(visw) = 1 / ( (Near-Far)*d/(Near*Far) + 1 / Near )
d的范围要在[0,1]之间,所以除以Far, Z(01) = 1/ ( (Near-Far)* d / Near + Far/Near)
Unity提供的内置函数:
LinearEyeDepth:负责把深度纹理的采样结果转换到世界空间下的深度值,即Z'zisw
Linear01Depth:返回一个范围[0,1]的线性深度值,即 Z(01)
_ZBufferParams:得到远近裁剪平面的距离
2)雾化:
快速从深度纹理重建世界坐标:
世界坐标:
_WorldSpaceCameraPos:是摄像机在世界空间下的位置
linearDepth * interpolatedRay:得到该像素相对摄像机的偏移量
interpolatedRay:由顶点着色器输出并插值后得到的射线
此图理解:dist =( |TL| / |Near| ) * depth
雾的计算: 雾效系数f
关于计算方式:
ch14 非真实渲染
1)处理高光平滑:
ch15 使用噪音
1)消融原理:噪音纹理+透明度测试
2)水波效果:噪音纹理作为一个高度图,不断修改水面的法线方向。为了模拟水不断流动的效果,我们会使用和时间相关的变量来对噪音纹理进行采样,当得到法线信息后,再进行正常的反射+折射计算,得到水面波动效果。
ch16 Unity中的渲染技术优化
1)优化技术:
CPU优化:批处理技术减少drawcall
GPU优化:
减少需要处理的顶点数量-优化几何体,使用模型的LOD技术,使用遮挡剔除技术
减少需要处理的片元数量-控制绘制顺序,警惕透明物体,减少实时光照
减少计算复杂度-使用shader的LOD技术,代码优化
节省内存带宽-减少纹理大小,使用分辨率缩放
影响游戏性能的瓶颈:
CPU:过多的drawCall,复杂的脚本或者物理模拟
GPU:顶点处理-过多的顶点,过多的逐顶点计算;片元处理-过多的片元,过多的逐片元计算
带宽:使用了尺寸大未压缩的纹理;分辨率过高的帧缓存
2)渲染分析工具:
unity的:渲染统计窗口,性能分析器
Android:Adreno GPU Profiler分析工具,NVPerfHUD
IOS:PowerVRAM的PVRUniSCo shader分析器,XCode的OpenGL ES Driver Instruments
https://docs.unity3d.com/Manual/MobileProfiling.html
3)批处理(batching):
静态批处理:自由度很高,限制少,但是会占用更多的内存,静态批处理后的物体不能移动
动态批处理:unity自动完成,物体可以移动,但是限制多,容易导致unity无法动态批处理一些使用了相同材质单物体。
4)批处理的注意事项:
a。尽可能用静态批处理,但注意内存消耗以及物体不可移动
b。用动态批处理,注意条件限制,物体尽量少顶点属性和数目
c。小道具,例如拾取的金币可以用动态批处理
d。包含动画的物体,无法全部静态批处理,但是可以把不动的部分标识城static。
e。批处理需要多个模型变换到世界空间下合并他们,因此,shader存在模型空间下的坐标运算,可能会出错。一个解决方法:DisbleBatching。使用半透明的物体需要严格的从后往前渲染,对于这些物体,unity会保证绘制顺序,然后在批处理。即:当绘制顺序无法满足是,批处理无法被执行。
5)减少顶点数目:移除不必要的硬边和纹理衔接(避免边界平滑和纹理分离),LOD技术,遮挡剔除技术。
减少片元数量:减少overdraw(同一个像素被绘制了多次):控制绘制顺序,警惕透明物体,减少实时光照和阴影
6)节省贷款:减少纹理大小-长宽值最好2的整数幂,分辨率缩放
7)减少计算复杂度:LOD技术-控制使用的shader等级,代码方面-用低精度的浮点数、数据类型少转换、不要分支和循环语句、避免使用sin\tan\pow\log等复杂的数学运算(用查找表代替),不要使用discard。
float、highp :存储例如顶点坐标等变量
half、mediump:标量、纹理坐标等变量
fixed、lowp:绝大多数颜色变量和归一化方向矢量,它的计算速度大约是float的4倍
ch17:Unity的表面着色器探索
1)表面着色器:定义了模型表面的反射率、法线、高光。
光照模型:选择使用兰伯特还是Blinn-Phong模型。
光照着色器:计算光照衰减、阴影等。
2)表面函数:
https://docs.unity3d.com/Manual/SL-SurfaceShaders.html
void surfI(Input IN, inout SurfaceOutput o)
void surfI(Input IN, inout SurfaceOutputStandard o)
void surfI(Input IN, inout SurfaceOutputStandardSpecular o)
struct SurfaceOutput{ fixed3 Albedo; //对光源的反射率,通常由纹理采样和颜色属性的乘积计算而得 fixed3 Normal; // 表面法线方向
fixed3 Emission;//自发光 eg:c.rgb += o.Emission;(unity通常会在片元着色器最后输出前计算) half Specular; // specular power in 0..1 range 高光反射中的指数部分的系数 fixed Gloss; // specular intensity 高光反射中的强度系数 fixed Alpha; // alpha for transparencies 透明通道,开启了透明度的话,会使用该值进行颜色混合};
struct SurfaceOutputStandard{ fixed3 Albedo; // base (diffuse or specular) color fixed3 Normal; // tangent space normal, if written half3 Emission; half Metallic; // 0=non-metal, 1=metal half Smoothness; // 0=rough, 1=smooth half Occlusion; // occlusion (default 1) fixed Alpha; // alpha for transparencies};struct SurfaceOutputStandardSpecular{ fixed3 Albedo; // diffuse color fixed3 Specular; // specular color fixed3 Normal; // tangent space normal, if written half3 Emission; half Smoothness; // 0=rough, 1=smooth half Occlusion; // occlusion (default 1) fixed Alpha; // alpha for transparencies};
Input结构体属性:
注意:主纹理和法线纹理的采样坐标uv_MainTex和uv_BumpMap,必须以“uv”为前缀。只要在input里面声明就可以使用,不需要计算。例外:自定义了顶点修改函数,需要传递数据。
光照函数:
#pragma surface surf Lambert
#pragma surface surf BlinnPhong
3)编译指令:自己看官网吧
https://docs.unity3d.com/Manual/SL-Properties.html
ch18_基于物理的渲染(PBS)
1)辐射率:单位面积、单位方向上光源的辐射通量,通常用L来表示,被认为是对单一光线的亮度和颜色评估。
BRDF(双向反射分布函数):f(l,v),l为入射方向,v为观察方向
反射等式:给定观察视角V,该方向上的出辐射率LO(V)等于所有入射方向的辐射率积分乘以BRDF值f(I,V),乘以余弦值(N * I)
精准光源:大小为无限小,方向确定的光源,如点光源,聚光灯
精准光源的出射辐射率: Ic:方向 C(light):颜色
2)高光反射:
微面元理论:物体表面实际上有很多人眼看不到的微面元组成的。
菲涅尔效应:https://baike.baidu.com/item/%E8%8F%B2%E6%B6%85%E5%B0%94%E6%8A%98%E5%B0%84%E7%8E%87/2712906?fr=aladdin
例如:我们站在湖边的时候,低头看脚下的水,水是透明的,反射不是特别强烈;远处的湖面,你会发现水并不是透明的,并且反射非常强烈。这就是“菲涅尔效应”。
简单的讲,就是视线垂直于表面时,反射较弱,而当视线非垂直表面时,夹角越小,反射越明显。如果你看向一个圆球,那圆球中心的反射较弱,靠近边缘较强。不过这种过度关系被折射率影响。
D(h):法线分布函数,用于计算有多少比例的微面元的法线满足m==h,只有这部分微面元才会把光线从I方向反射到v上。
G(I,V,h):阴影-遮掩函数:用于计算满足满足m==h的微面元中有多少由于被遮挡而不会被人眼看到,给出了活跃的微面元所占的浓度。
F(I,h):菲涅尔反射函数,每个活跃的微面元会把多少入射光线反射到观察方向上,即反射光线占入射光线的比率。
4(n*I)(n*v):用于校正从微面元的局部空间到整体宏观表面数量差异的校正因子。
3)unity5实现的2中PBS模型:基于GGX模型,基于归一化的Blinn-Phong模型
GGX模型:http://www.taikr.com/article/2796 GGX具有更亮的高光部分并在其周围蔓延着光晕,给人以更加真实的视觉效果。
unity使用的BRDF的漫反射项:
D(h)采用了GGX模型的一种实现: a =roughness 的平方
G(I,V,h)使用了GGX衍生出的Smith-Schlick模型: k = roughness的平方 / 2
F(I,h):使用了Schlick菲涅尔近似等式, F0表示高光反射系数,在unity往往指的是高光反射颜色。
这些公式看的我很晕,只要记得这个是由前人根据效果,总结出来的就行了。
4)Unity支持两种基于物理的工作流程:金属工作流和高光反射工作流 。
金属工作流是默认的工作流程,这个名字来源与它定义了材质表面的金属值,并非它只能模拟金属类型的材质。
下图介绍了:builtin_shaders-5.x\CGIncludes里面定义的一些头文件
金属材质:几乎没有漫反射,强烈的高光反射,高光反射带颜色
非金属材质:大多数角度高光反射的强度比较弱,高光反射的颜色比较单一,漫反射的颜色多种多样
5)使用基于物理的渲染方法:线性空间 和伽马校正
https://blog.csdn.net/bill2ccssddnn/article/details/53423410
伽马:来源于伽马权限,最早用于处理拍摄的图像。原因:在正常的光照条件下,人眼对于较暗区域的变化更加敏感,人眼对于亮度上的变化感知是非线性的。
通常在显示设备使用的颜色缓冲中每个通道的精度为8位,为了用更多的空间存储暗部区域,这样存储空间可以充分利用起来了。
一个巧合:CRT显示的伽马值大约就是编码伽马的倒数。
绝大数的摄像机、PC和打印机都是用sRGB标准。
(编码伽马和显示伽马)
Unity的线性空间并不支持所有平台。例如:移动平台无法使用,必须自己在shader中进行伽马校正。
float3 difuseCol = pow(tex2D(diffTex, texCoord), 2.2); //非线性输入纹理的矫正代码
fragColor.rgb = pow(fragColor.rgb, 1.0/2.2); //最后输出前,对输出像素值的矫正代码处理
6)预计算实时全局光照:动态为场景实时更新复杂的光照结果。一旦物体和光源的位置被固定了,这些物体对光线的反弹路径以及漫反射光照也是固定的,与摄像机位置无关。在实时运行时,只要光源的位置不变,即使改变了光源颜色和强度、物体材质属性(漫反射和自发光相关的属性),这些信息一直是有效的,不需要实时更新。在预计算阶段,Enlighten会吧所有静态物体组成的场景上,进行简单的“光线追踪”过程。它会把场景分割成很多个子系统,目的是为了得到物体之间的关系。实时阶段,unity会根据预计算得到的信息来计算光照信息,并把它们存储在额外的光照纹、光照探针或Cubemap中,再和物体材质进行必要的光照计算,得到最后的渲染效果。
7)HDR:高动态范围。使用这个,可以获取到更多的高亮度区域。HDR使用远远高于8位的精度记录亮度信息。
动态范围:最高的和最低的亮度值之间的比值。
8)PBS的优点:只需要一个万能的shader就可以渲染相当一部分类型的材质,而不是使用传统的做法为每种材质编写一个特定的shader。
结束语:
不知不觉这本书已经看完了,大概明白了shader的语法。但是很多关键字不明白,很多陌生的API,看来渲染学习之路很长,还需要多看几遍。。。。。。。