致敬学姐,向学姐学习
读书笔记,稍有补充
一、渲染流水管线:
CPU[应用阶段(输出渲染图元)]->GPU[几何阶段(顶点坐标变换到屏幕空间,包含顶点着色器)->光栅化阶段(产生像素渲染图像,包含片元着色器,逐片元操作)]
应用阶段:数据加载到显存中 设置渲染状态 Draw Call
几何阶段:顶点着色器->曲面细分着色器->几何着色器->裁剪->屏幕映射
光栅化阶段:三角形设置(点到边计算三角形网格)->三角形遍历(像素若被三角网格覆盖则生成一个片元)->片元着色器->逐片元操作
顶点着色器:坐标变换和逐顶点光照
必须完成顶点坐标从模型空间到齐次裁剪空间的转换(屏幕映射)
光栅化(三角形设置-三角形遍历->输出一个片元序列)
片元着色器:纹理采样(对由顶点着色器输出的纹理坐标插值)
逐片元操作:决定片元可见性(模板测试->深度测试->混合)
Unity中的深度测试是在片元着色器之前(Early-Z),提升GPU性能
二、
Standard Surface Shader包含了标准光照模型的表面着色器模板
Unlit Shader不含光照但含雾效的顶点/片元着色器
Image Effect Shader屏幕后处理效果的基本模板
Compute Shader利用GPU并行性进行一些与常规渲染流水线无关的计算
可将表面着色器看成是在顶点、片元着色器上层的一层封装,本质也是顶点、片元着色器,UnityShader只有两种形式:顶点/片元着色器和固定函数着色器
Properties:Int Float Range Color Vector 2D Cube 3D
Unity并没有提供矩阵类型的属性,但我们依然可以在CG代码块中定义矩阵并从脚本中设置
Subshader:
Subshader{
//标签
[Tags]
//状态
[RenderSetup]
Pass{}
}
RenderSetup:
Cull剔除模式:Cull Back|Front|Off 剔除正、背面,关闭剔除
ZTest设置深度测试使用的函数:ZTest Less Greater|LEqual|GEqual|Equal|NotEqual|Always
ZWrite深度写入:ZWrite On|Off
Blend开启并设置混合模式:Blend SrcFactor DstFactor
Tags(键值都为字符串的键值对):
Queue:控制渲染顺序,指定物体的渲染队列,可以保证所有透明物体在所有不透明物体之后渲染,也可自定义使用的渲染队列来控制物体的渲染顺序
Background 队列索引号1000 第一个被渲染,常使用该队列来渲染那些需要绘制在背景上的物体
Geometry 2000 默认渲染队列,大部分物体用本队列,不透明物体使用该队列
AlphaTest 2450 需要透明度测试的物体用该队列,在所有不透明物体渲染后再渲染它们会更高效
Transparent 3000 在所有Geometry、AlphaTest物体渲染后再按从后往前的顺序渲染,任何使用了透明度混合(例如关闭了深度写入的Shader)的物体都应该使用该队列
Overlay 4000 该队列用于实现一些叠加效果,最后渲染
自定义中间队列:如Tags{"Queue"="Geometry+1"} 能很方便地满足需求
RenderType:对着色器分类,如不透明着色器、透明着色器,可被用于着色器替换功能
Opaque 用于大多数着色器(法线着色器、自发光着色器、反射着色器以及地形的着色器)
Transparent 用于半透明着色器(透明着色器、粒子着色器、字体着色器、地形额外通道的着色器)
TransparentCutout 蒙皮透明着色器(Transparent Cutout,两个通道的植被着色器)
Background Skybox shaders. 天空盒着色器
Overlay GUITexture, Halo, Flare shaders. 光晕着色器、闪光着色器
TreeOpaque terrain engine tree bark. 地形引擎中的树皮
TreeTransparentCutout terrain engine tree leaves. 地形引擎中的树叶
TreeBillboard terrain engine billboarded trees. 地形引擎中的广告牌树
Grass terrain engine grass. 地形引擎中的草
GrassBillboard terrain engine billboarded grass. 地形引擎中的广告牌草
DisableBatching:一些Subshader在Unity用批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画,可用该标签指明是否对该Subshader进行批处理
因为批处理会合并所有相关的模型,模型各自的模型空间就会丢失,取消批处理增加DrawCall可能会造成一定的性能下降
ForceNoShadowCasting:控制使用该Shader的物体是否会投射阴影
IgnoreProjector:若该标签为true,那么使用该Subshader的物体将不会受Projector(投影器)影响,通常用于半透明物体
CanUseSpriteAtlas:当该Subshader是用于精灵时,将其设为false
PreviewType:指明材质面板将如何预览该材质。默认情况下,材质显示为球形,可通过把标签值设置为"Plane""SkyBox"来改变预览模型
Pass:
Pass{
[Name]
[Tags]
[RenderType]
//code
}
设置名称可在别的shader中用UsePass调用这个Pass,使用UsePass时必须用大写形式的名字
Tags:
LightMode:定义该Pass在Unity的渲染流水线中的角色
Always 不管使用哪种渲染路径,该Pass总会被渲染,但不会计算任何光照
ForwardBase 用于前向渲染。该Pass会计算环境光、最重要的平行光、逐顶点/SH光源和Lightmaps
ForwardAdd 用于前向渲染。该Pass会计算额外的逐像素光源,每个Pass对应一个光源
Deferred 用于延迟渲染。该Pass会计算G缓冲(G-buffer)
ShadowCaster 把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中
PrepassBase 用于遗留的延迟渲染。该Pass会渲染法线和高光反射的指数部分
PrepassFinal 用于遗留的延迟渲染。该Pass通过合并纹理、光照和自发光来渲染得到最后的颜色
Vertex、VertexLMRGBM、VertexLM 用于遗留的顶点照明渲染
RequireOptions:用于指定当满足某些条件时才渲染该Pass,它的值是一个由用空格分隔的字符串
目前Unity支持的选项有SoftVegetation:如果在QualitySettings中开启渲染软植被(Edit->Project Settings->Quality),则该pass可以渲染
特殊Pass:
GrabPass 负责抓取屏幕并将结果储存在一张纹理中,用于后续Pass处理
FallBack:
FallBack也会影响阴影的投射,渲染阴影纹理时Unity会在每个UnityShader中寻找一个阴影投射的Shader
三、涉及的一些数学计算:
求空间变换的矩阵,如P->C,求得P->C后C->P也可通过求逆矩阵求得
P->C的变换矩阵可以通过坐标空间P在坐标空间C中的原点和坐标轴的矢量表示出来
略。
四、https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html
UnityShader的内置变量:
空间变换(全为4X4的矩阵):
左手坐标系:模型空间、世界空间、剪裁空间、屏幕空间
右手坐标系:观察空间
顶点坐标到屏幕坐标的变换过程:
模型空间->世界空间->观察空间-(投影矩阵|裁剪矩阵)->齐次剪裁空间-(齐次除法|映射输出)->屏幕空间
观察空间:摄像机本身的模型空间,是三维空间
齐次剪裁空间->屏幕空间:齐次除法使透视投影的剪裁空间变换到一个立方体,然后根据其x,y坐标映射输出屏幕对应像素坐标
变换矩阵:
UNITY_MATRIX_MVP 当前模型的模型*视图*投影矩阵,用于将顶点/方向矢量从模型空间变换到剪裁空间
UNITY_MATRIX_MV 当前模型的模型*视图矩阵,用于将顶点/方向矢量从模型空间变换到观察空间
UNITY_MATRIX_V 当前模型的视图矩阵,用于将顶点/方向矢量从世界空间变换到观察空间
UNITY_MATRIX_P 当前模型的投影矩阵,用于将顶点/方向矢量从观察空间变换到剪裁空间
UNITY_MATRIX_VP 当前模型的视图*投影矩阵,用于将顶点/方向矢量从世界空间变换到剪裁空间
UNITY_MATRIX_T_MV UNITY_MATRIX_MV 的转置矩阵
UNITY_MATRIX_IT_MV UNITY_MATRIX_MV 的逆转置矩阵,用于将法线从模型空间变换到观察空间,也可用于得到UNITY_MATRIX_MV的逆矩阵
_Object2World 当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间
_World2Object _Object2World的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间
摄像机和屏幕参数(访问当前正在渲染的摄像机的参数信息,参数对应了摄像机上Camera组件中的属性值):
_WorldSpaceCameraPos (float3) 当前摄像机在世界空间中的位置
_ProjectionParams (float4) x是1.0 (或–1.0如果正在使用一个翻转的投影矩阵进行渲染),y=camera's near plane, z=camera's far plane,w=camera's 1/FarPlane
_ScreenParams(float4) x=width,y=height,z=1.0 + 1.0/width,w=1.0 + 1.0/height,其中width和height是该摄像机的渲染目标(render target)的像素宽度和高度
_ZBufferParams(float4) x=(1-far/near),y=(far/near),z=(x/far),w=(y/far),用于线性化Z缓存中的深度值
unity_OrthoParams(float4) x=width,y=height,z无定义,w=1.0(该摄像机是正交相机), w=0.0(该相机是透视相机),width和height是正交投影相机的宽度和高度
unity_CameraProjection(float4x4) 该相机的投影矩阵
unity_CameraInvProjection(float4x4) 该相机投影矩阵的逆矩阵
unity_CameraWorldClipPlanes[6](float4)该相机的6个剪裁平面在世界空间下的等式,顺序:左、右、下、上、近、远裁剪平面
时间参数:
_Time (float4) 场景加载后的时间(t/20, t, t*2, t*3),用于着色器内部动画
_SinTime (float4) Sin(t/8, t/4, t/2, t)
_CosTime (float4) Cos(t/8, t/4, t/2, t)
unity_DeltaTime(float4) dt是时间的增量 Delta time(dt, 1/dt, smoothDt, 1/smoothDt)
光照参数(光照参数取决于不同的渲染路径和不同的光照模式的标签):
1.前向渲染(ForwardBase和ForwardAdd路径)
_LightColor0(Lighting.cginc中声明) (fixed4) 该Pass处理的逐像素光源的颜色
_WorldSpaceLightPos0 (float4) 平行光:(逐像素光源的位置,0) 其他光:(逐像素光源的位置,1),_WorldSpaceLightPos为该Pass处理的逐像素光源的位置
_LightMatrix0(AutoLight.cginc中声明)(float4x4)从世界空间到光源空间的变换矩阵,可用于采样cookie和光强衰减纹理
unity_4LightPosX0, unity_4LightPosY0,
unity_4LightPosZ0 (float4) (仅用于Base Pass) 前四个非重要的点光源在世界空间中的位置
unity_4LightAtten0 (float4) (仅用于Base Pass) 存储了前四个非重要的点光源的衰减因子
unity_LightColor (half4[4])(仅用于Base Pass) 存储了前四个非重要的点光源的颜色
2.延迟渲染(UnityDeferredLibrary.cginc中声明)
_LightColor (float4) 光源颜色
_LightMatrix0 (float4x4) 从世界空间到光源空间的变换矩阵,可用于采样cookie和光强衰减纹理
3.顶点照明渲染(顶点照明渲染是前向渲染的一个子集,效果最差,以后可能会被移除)
(一个顶点照明Pass中最多可访问到8个逐顶点光源,若影响该物体的光源数目小于8,数组中剩下的光源颜色设置为黑色)
unity_LightColor (half4[8]) 光源颜色
unity_LightPosition (float4[8]) xyz分量是视角空间中的光源位置,平行光(-direction,0),其他光(position,1)
unity_LightAtten (half4[8]) 光照衰减因子,如果光源是聚光灯,x=cos(spotAngle/2),y=1/cos(spotAngle/4),如果是其他类型光源,x=-1,y=1,z=衰减的平方,w=光源范围开根号
unity_SpotDirection (float4[8]) 如果光源是聚光灯,值为视角空间的聚光灯的位置,如果是其他类型的光源,值(0,0,1,0)
雾和环境:
unity_AmbientSky (fixed4) Sky ambient lighting color in gradient ambient lighting case.
unity_AmbientEquator (fixed4) Equator ambient lighting color in gradient ambient lighting case.
unity_AmbientGround (fixed4) Ground ambient lighting color in gradient ambient lighting case.
UNITY_LIGHTMODEL_AMBIENT (fixed4) Ambient lighting color (sky color in gradient ambient case). Legacy variable.
unity_FogColor (fixed4) Fog color.
unity_FogParams (float4) Parameters for fog calculation: (density / sqrt(ln(2)), density / ln(2), –1/(end-start), end/(end-start)). x is useful for Exp2 fog mode, y for Exp mode, z and w for Linear mode.
Various:
unity_LODFade float4 Level-of-detail fade when using LODGroup. x is fade (0..1), y is fade quantized to 16 levels, z and w unused.
五、https://docs.unity3d.com/Manual/SL-BuiltinIncludes.html
https://docs.unity3d.com/Manual/SL-BuiltinFunctions.html
Unity内置文件和变量:
内置包含文件(xxx.cginc):
UnityCG.cginc 包含了最常使用的帮助函数、宏和结构体等
UnityShaderVariables.sginc 编译UnityShader时自动包含进来,包含许多内置全局变量,如UNITY_MATRIX_MVP等
Lighting.cginc 包含各种内置光照模型,如果编写SurfaceShader会自动包含进来
HLSLSupport.cginc 编译UnityShader时自动包含进来,声明了很多用于跨平台编译的宏和定义
AutoLight.cginc 包含实现光照、阴影功能的函数,表面着色器内部也使用了这个文件
TerrainEngine.cginc 包含了地面、植被的一些着色帮助函数
UnityCG.cginc内置结构体(作为顶点着色器的输入和输出方便编写):
appdata_base 可用于顶点着色器的输入 顶点位置、顶点法线、第一组纹理坐标
appdata_tan 可用于顶点着色器的输入 顶点位置、顶点切线、顶点法线、第一组纹理坐标
appdata_full 可用于顶点着色器的输入 顶点位置、顶点切线、顶点法线、四组(或更多)纹理坐标
appdata_img 可用于顶点着色器的输入 顶点位置、第一组纹理坐标
v2f_img 可用于顶点着色器的输出 裁剪空间中的位置、纹理坐标
UnityCG.cginc内置帮助函数:
1.顶点变换函数:
float4 UnityObjectToClipPos(float3 pos) 模型空间到齐次剪裁空间,和mul(UNITY_MATRIX_MVP, float4(pos, 1.0))等效
float3 UnityObjectToViewPos(float3 pos) 模型空间到观察空间,和mul(UNITY_MATRIX_MV, float4(pos, 1.0)).xyz等效
2.通用函数:
float3 UnityObjectToWorldNormal(float3 norm) 把法线方向从模型空间转换到世界空间中
float3 UnityObjectToWorldDir(float3 dir) 把方向矢量从模型空间转换到世界空间中
float3 UnityWorldToObjectDir(float3 dir) 把方向矢量从世界空间转换到模型空间中
float3 WorldSpaceViewDir (float4 v) 输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。内部实现使用了UnityWorldSpaceViewDir函数
float3 UnityWorldSpaceViewDir(float4 v) 输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向
float3 ObjSpaceViewDir (float4 v) 输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向
float2 ParallaxOffset (half h, half height, half3 viewDir) 为视差法线贴图计算UV偏移
fixed Luminance (fixed3 c) 将颜色转化为亮度(灰度)
fixed3 DecodeLightmap (fixed4 color) 解码Unity光照贴图的颜色(RGBM或者dLDR视平台而定)
float4 EncodeFloatRGBA (float v) 将[0..1)的浮点数编码为RGBA颜色,为了低精度目标的存储
float DecodeFloatRGBA (float4 enc) 将RGBA颜色解码为浮点数
float2 EncodeFloatRG (float v) 将[0..1)的float编码为float2
float DecodeFloatRG (float2 enc) 解码先前编码的RG浮点数(Decodes a previously-encoded RG float)
float2 EncodeViewNormalStereo (float3 n) 将观察空间的法线编码为0-1间的2个数字(Encodes view space normal into two numbers in 0..1 range)
float3 DecodeViewNormalStereo (float4 enc4) 将enc4的xy分量解码为观察空间的法线(Decodes view space normal from enc4.xy)
3.前向渲染帮助函数(ForwardBase或ForwardAdd路径):
float3 WorldSpaceLightDir (float4 v) 输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向,内部实现使用了UnityWorldSpaceLightDir函数,没有被归一化
float3 UnityWorldSpaceLightDir (float4 v)输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向,没有被归一化
float3 ObjSpaceLightDir (float4 v) 输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向,没有被归一化
float3 Shade4PointLights (...) 从四个点光源计算光照,将光压缩成矢量,正向渲染使用这个来计算每个顶点的光照
4.屏幕空间帮助函数(帮助计算用于屏幕采样空间纹理的坐标):
float4 ComputeScreenPos (float4 clipPos) 为空间映射纹理的采样计算纹理坐标,输入是裁剪空间的位置
float4 ComputeGrabScreenPos (float4 clipPos) 为GrabPass纹理的采样计算纹理坐标,输入是裁剪空间的位置
5.顶点照明渲染帮助函数:
float3 ShadeVertexLights (float4 vertex, float3 normal) 计算4个点光源和环境光的光照,参数为模型空间下的位置和法线
六、纹理:
通常用纹理来代替物体的漫反射,常用渐变纹理控制漫反射光照的效果
TRANSFORM_TEX(tex,name) 接受参数顶点纹理坐标和纹理名,在它的实现中将利用纹理名_ST的方式来计算变换后(平移和缩放)的纹理坐标
tex2D(_MainTex,i.uv) 第一个参数是需要被采样的纹理,第二个是float2类型的纹理坐标,它将返回纹素值
tex2Dproj(_Tex,i.pos) 返回值会除以i.pos的最后一个分量,其余和tex2D相同
法线纹理:
发线方向分量[-1,1],像素分量[0,1] 因此需做一次映射 pixel = (normal + 1)/2,故在shader中对法线纹理采样后需要对结果进行一次反映射的过程以得到原来的法线方向
常用模型顶点的切线空间存储法线,这种纹理被称为切线空间的法线纹理,这种法线纹理其实就是存储了每个点在各自的切线空间中的法线扰动方向
通常把法线纹理的类型设置为NormalMap,纹理采样后用内置函数UnpackNormal()处理映射后的法线得到正确的法线方向
把纹理设置成NormalMap可以让Unity根据不同平台对纹理进行压缩以减少法线纹理占用的空间,UnpackNormal针对不同的压缩格式对法线纹理进行正确的采样
七、透明(透明度测试、透明度混合,渲染顺序极度重要):
前面提到过,再次强调
Background 1000 第一个被渲染,常使用该队列来渲染那些需要绘制在背景上的物体
Geometry 2000 默认渲染队列,大部分物体用本队列,不透明物体使用该队列
AlphaTest 2450 需要透明度测试的物体用该队列,在所有不透明物体渲染后再渲染它们会更高效
Transparent 3000 在所有Geometry、AlphaTest物体渲染后再按从后往前的顺序渲染,任何使用了透明度混合(例如关闭了深度写入的Shader)的物体都应该使用该队列
Overlay 4000 该队列用于实现一些叠加效果,最后渲染
剔除:Cull Front|Back|Off
深度测试:ZTest (Less |Greater | LEqual | GEqual | Equal | NotEqual | Always)默认LEqual:
Greater 只渲染大于AlphaValue值的像素
GEqual 只渲染大于等于AlphaValue值的像素
Less 只渲染小于AlphaValue值的像素
LEqual 只渲染小于等于AlphaValue值的像素
Equal 只渲染等于AlphaValue值的像素
NotEqual只渲染不等于AlphaValue值的像素
Always 渲染所有像素,等于关闭透明度测试。等于用AlphaTest Off
Never 不渲染任何像素
深度偏移(可以使位于同一位置上的两个集合体中的一个几何体绘制在另一个的上层):
Offset Factor ,Units
Factor参数表示Z缩放的最大斜率的值
Units参数表示可分辨的最小深度缓冲区的值
透明度测试:只要片元的透明度不满足条件就舍弃,要么不透明要么完全透明
常用clip(float4 x|float3 x|float2 x|float1 x|float x); 参数为裁剪时使用的标量或矢量条件,参数的任意一个分量若为负数就舍弃
SubShader Tags{"Queue"="AlphaTest""IgnoreProjector"="True""RenderType"="TransparentCutout"}(标配~)
透明度混合:关闭深度写入(ZWrite Off),没关闭深度测试。对透明度混合来说,深度缓冲是只读的。透明度混合同样不能保证物体都正确渲染,因为深度缓冲中的值都是像素级别的。
Pass中使用Blend指定混合时使用的函数:
Blend Off 关闭混合
Blend SrcFator DstFactor 开启混合,并设置混合因子。源颜色(该片源产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,两者相加后存入颜色缓存中,此命令在设置混合因子的同时也开启了混合模式
Blend SrcFactor DstFactor,SrcFactorA DstFactorA 和上面几乎一样,只是使用不同的因子来混合透明通道
BlendOp BlendOperation 并非是把源颜色和目标颜色简单相加后混合,而是使用BlendOperation对它们进行其他操作
SubShader Tags{"Queue"="Transparent""IgnoreProjector"="True""RenderType"="Transparent"}(标配~)
一种解决透明度混合中排序错误的方法:
使用两个Pass,第一个开启深度写入但不输出颜色,第二个正常的透明度混合,2个Pass会影响性能但模型内部之间不会有任何真正的半透明效果
双面渲染的透明效果:
默认情况下渲染引擎剔除了物体背面(相对于摄像机方向)的渲染图元,为得到双面渲染效果可用Cull指令控制需要剔除哪个面的渲染图元
Cull Back|Front|Off
使用两个Pass,第一个只渲染背面,第二个只渲染正面,分别在两个Pass中用Cull剔除图元
八、混合:
参与混合的颜色都有RGBA通道,Unity中使用Blend时除了设置混合状态外也开启了混合
混合因子:
One 因子为1
Zero 因子为0
SrcColor 因子为源颜色值。当用于混合RGB的混合等式时使用SrcColor的RGB分量作为混合因子,当用于混合A的混合等式时,使用SrcColor的A分量作为混合因子
SrcAlpha 因子为源颜色的透明度值(A通道)
DstColor 因子为目标颜色值,当用于混合RGB通道的混合等式时使用DstColor的RGB分量作为混合因子,当用于混合A的混合等式时,使用DstColor的A分量作为混合因子
DstAlpha 因子为目标颜色的透明度值
OneMinusSrcColor 因子为(1-源颜色)。当用于混合RGB的混合等式时使用结果的RGB分量作为混合因子,当用于混合A的混合等式时,使用结果的的A分量作为混合因子
OneMinusSrcAlpha 因子为(1-源颜色透明度值)
OneMinusDstColor 因子为(1-目标颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子,当用于混合A的混合等式时,使用结果的的A分量作为混合因子
OneMinusDstAlpha 因子为(1-目标颜色的透明度值)
混合时需要两个混合等式,一个混合RGB通道,一个混合A通道,混合等式默认加操作
混合因子命令:
Blend SrcFator DstFactor 开启混合,并设置混合因子。源颜色(该片源产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,两者相加后存入颜色缓存中,此命令在设置混合因子的同时也开启了混合模式
Blend SrcFactor DstFactor,SrcFactorA DstFactorA 和上面几乎一样,只是使用不同的因子来混合透明通道
Orgb = SrcFactor x Srgb + DstFactor x Drgb;
Oa = SrcFactorA x Sa + DstFactorA x Da;
以上混合等式结果均为相加得到,可用BlendOp BlendOperation混合操作命令进行混合操作
混合操作:
Add 混合后的源颜色和目标颜色相加,默认
Sub 混合后的源颜色减去混合后的目标颜色
RevSub 混合后的目标颜色减去混合后的源颜色
Min 使用源颜色和目标颜色中较小的值,逐分量比较,RGBA份量均为最小值
Max 使用源颜色和目标颜色中较大的值,逐分量比较
Min和Max混合操作会忽略混合因子
常见混合类型:
Blend SrcAlpha OneMinusSrcAlpha(正常透明度混合)
Blend OneMinusDstColor One(柔和相加)
Blend DstColor Zero(正片叠底,即相乘)
Blend DstColor SrcColor(两倍相乘)
BlendOp Min
Blend One One(变暗)
BlendOp Max
Blend One One(变亮)
Blend OneMinusDstColor One = Blend One OneMinusSrcColor(滤色)
Blend One One(线性减淡)
九、光照(渲染路径决定):
前面提到过,再次强调
LightMode标签支持的渲染路径:
Always 不管使用哪种渲染路径,该Pass总会被渲染,但不会计算任何光照
ForwardBase 用于前向渲染。该Pass会计算环境光、最重要的平行光、逐顶点/SH光源和Lightmaps
ForwardAdd 用于前向渲染。该Pass会计算额外的逐像素光源,每个Pass对应一个光源
Deferred 用于延迟渲染。该Pass会计算G缓冲(G-buffer)
ShadowCaster 把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中
PrepassBase 用于遗留的延迟渲染。该Pass会渲染法线和高光反射的指数部分
PrepassFinal 用于遗留的延迟渲染。该Pass通过合并纹理、光照和自发光来渲染得到最后的颜色
Vertex、VertexLMRGBM、VertexLM 用于遗留的顶点照明渲染
前向渲染路径(Forward Rendering Path,一次前向渲染需渲染该对象的渲染图元,计算颜色缓冲区和深度缓冲区的信息):
Unity中有三种处理光照的方式:逐顶点处理、逐像素处理、球谐函数(SH)处理 ,取决于光的类型和渲染模式(是否Important)
前向渲染的两种Pass:
BasePass(可实现的光照效果:光照纹理、环境光、自发光、平行光的阴影,处理的光源类型只能是平行光):
Tags{"LightMode"="ForwardBase"}
#pragma muti_compile_fwdbase
AdditionalPass(默认不支持阴影,可通过用#pragma muti_compile_fwdadd_fullshadows代替#pragma muti_compile_fwdbase开启阴影):
Tags{"LightMode"="ForwardAdd"}
Blend One One(开启和设置混合模式防止覆盖)
#pragma muti_compile_fwdbase
前向渲染路径内置的光照变量和光照函数参考之前文段
https://docs.unity3d.com/Manual/RenderTech-DeferredShading.html
延迟渲染路径(Deferred Rendering Path):
除颜色缓冲和深度缓冲外,还会利用G缓冲(G-buffer),G缓冲区存储了表面的其他信息如法线、位置、用于光照计算的材质属性等
缺点:内存开销,对透明物体渲染存在问题需结合正向渲染,对多重采样抗锯齿(MSAA)的支持不友好需开启MRT(多渲染目标技术)
Unity中的延迟渲染需两个Pass:
第一个Pass用于渲染G缓冲,把物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度信息渲染到屏幕空间的G缓存中,对每个物体来说只执行一次。
第二个Pass用于计算真正的光照模型。该Pass用G-buffer中的数据计算光照,存储到帧缓冲中。
默认的G缓冲区(不同版本渲染纹理存储内容可能有所不同)包含以下几个渲染纹理(RT):
RT0:格式ARGB32,RGB通道用于存储漫反射颜色,A通道没有被使用
RT1:格式ARGB32,RGB通道用于存储高光反射颜色,A通道用于存储高光反射的指数部分
RT2:格式ARGB2101010,RGB通道用于存储法线,A通道没有被使用
RT3:格式ARGB32(非HDR)或ARGBHalf(HDR),用于存储自发光+lightmap+反射探针(reflection probes)
深度缓冲和模板缓冲
当在第二个Pass中计算光照时,默认仅用内置的Standard光照模型,如果想要使用其他光照模型,就要替换原有的Internal-DefferedShading.shader文件
光照衰减:
平行光衰减值为1,其他光源Unity使用一张_LightTexture0纹理作为查找表(Lookup Table,LUT)来在片元着色器中计算逐像素光照的衰减。
_LightTexture0对角线上的纹理颜色值表明光源空间中不同位置点的衰减值
https://github.com/candycat1992/Unity_Shaders_Book/issues/47 给出了点光源和聚光灯下光照衰减的采样方式
阴影:
ShadowMap技术:把摄像机的位置放在与光源重合的位置,将从该光源的位置出发,能看到的场景中距离它最近的表面位置(深度信息)记录到阴影映射纹理中,shadowmap本质上也是一张深度图
Unity使用一个额外的Pass来专门更新光源的阴影映射纹理:
LightMode标签被设置为ShadowCaster的Pass,该Pass的渲染目标不是帧缓存而是阴影映射纹理(深度纹理),#pragma multi_compile_shadowcaster
Unity使用屏幕空间的阴影映射技术对阴影采样:
LightMode为ShadowCaster的Pass产生阴影映射纹理和摄像机深度纹理,以此得到屏幕空间的阴影图,想要物体接收来自其他物体的阴影就对阴影图采样,把采样结果和最后的光照结果相乘来产生阴影效果
若想要物体向其他物体投射阴影,就必须把该物体加入光源的阴影映射纹理的计算
接收阴影:
在AutoLight.icginc中声明的
SHADOW_COORDS(声明一个名为_ShadowCoord的阴影纹理坐标变量,参数是下一个可用的差值寄存器的索引值)
TRANSFER_SHADOW(在顶点着色器中计算上一步的阴影纹理坐标,若当前平台支持屏幕空间的阴影映射技术,调用内置ComputeScreenPos函数计算_ShadowCoord,若不支持则用传统方式,把顶点坐标从模型空间变换到光源空间后存储到_ShadowCoord中)
SHADOW_ATTENUATION(在片元着色器中用_ShadowCoord对相关纹理采样计算阴影值)
以上宏需保证自定义变量名和宏中使用的变量名相同:a2v的顶点坐标变量名为vertex,顶点着色器输出结构体v2f名为v,v2f中的顶点位置变量名为pos
UNITY_LIGHT_ATTENUATION(计算光照衰减和阴影的宏,将光照衰减和阴影值相乘后的结果存储到第一个参数atten中):
含三个参数,第一个atten自动声明,第二个是v2f结构体传递给SHADOW_ATTENUATION计算阴影,第三个是世界空间的坐标用于计算光源空间坐标,再对光照衰减纹理采样得到光照衰减
对透明物体的阴影:
若使用的透明度测试,则需要一个也计算了透明度测试的ShadowCaster Pass,可将FallBack设为内置的Transparent/Cutout/VertexLit,应提供名为_Cutoff的属性
若使用的透明度混合,比较复杂,需要在每个光源空间下严格按照从后往前的顺序渲染,会让阴影处理变得很复杂。Unity中内置的半透明shader都不会产生任何阴影
十、高级纹理
GrabPass可用于实现许多透明材质的模拟,与简单透明混合不同,可对物体后面的图像进行复杂的处理,用GrabPass需小心物体的渲染队列设置,("Queue"="Transparent")
天空盒子是在所有不透明物体之后渲染的
可用命令缓冲实现类似抓屏效果 https://docs.unity3d.com/Manual/GraphicsCommandBuffers.html
立方体纹理(Cubemap):
https://docs.unity3d.com/Manual/class-Cubemap.html
创建用于环境映射的立方体纹理的方法:
1.由特殊布局的纹理创建,在基于物理的渲染中通常用一张HDR图像来生成高质量的Cubemap
2.创建Cubemap,赋值6张图
3.脚本生成:Camera.RenderToCubemap(cubemap) 此函数可以把从任意位置观察到的场景图像存储到6张图像中
对立方体纹理的采样需使用CG的texCUBE函数
反射思路:通过入射光线的方向和表面法线方向来计算反射方向,再利用反射方向对立方体纹理采样,用到CG的reflect(入射方向,法线方向)函数计算反射方向
折射思路:实时渲染中通常仅模拟第一次折射,得到折射方向后用它对立方体纹理采样,用到CG的refract(归一化的入射方向,归一化的表面法线方向,入射光线所在的介质的折射率和折射光线所在的介质的折射率之间的比值)函数
*菲涅尔反射:根据视角方向控制反射程度,实时渲染中常用近似公式如Schlick和Empricial近似式
渲染纹理(Render Texture):
把场景渲染到中间缓冲,即渲染目标纹理(RTT),还有多重渲染目标(MRT)
创建RT的方法:
1.创建一个渲染纹理,设置为某个摄像机的渲染目标,就可实时更新该纹理
2.用GrabPass或者OnRenderImage时
用渲染纹理实现简单镜子效果思路:用一个渲染纹理作为输入,把该纹理在水平方向向上翻转后显示
玻璃效果:用一张法线纹理修改模型法线信息,用Cubemap模拟反射,用GrabPass获取玻璃后的屏幕图像,用切线空间下的法线对屏幕纹理坐标偏移(因为该空间下的法线可以反映顶点局部空间下的法线方向)后再对屏幕图像采样来模拟近似的折射效果
十一、广告牌
本质是构建旋转矩阵(表面法线n、向上方向u、向右方向r,锚点(旋转中不变以确定空间位置))
先根据表面法线方向和指向上的方向计算出指向右的方向(叉积),对其归一化后再由法线方向和指向右的方向计算出正交的指向上的方向
Screen-Aligned Billboard:
对齐于屏幕的公告板,n是镜头平视面法线的逆方向,u是镜头的up
World-Oriented Billboard:
面向世界的公告板,r=u*n(u是其在世界上的up,n是镜头视线方向的逆方向),最后再算一次u'=r*n,u'为最后的u,即不是物体本身相对世界的up也不是镜头的up
Axial Billboard:
轴向公告板,u是受限制的轴,r=u*n(n是镜头视平面法线的逆方向),最后再算一次n'=r*u,n'为最后的n
billboard不同种类的原因:
View Oriented/View plane oriented的不同
Sphere/Axial的不同
Cameraup/World up的不同
十二、屏幕后处理
OnRenderImage(RenderTexture src,RenderTexture dest)
抓取屏幕,处理后得到目标渲染纹理,默认在所有透明和不透明Pass后执行,若不想对透明物体产生影响,可在函数前加[ImageEffectOpaque]属性
Graphics.Blit():
void Blit(Texture src,RenderTexture dest);
void Blit(Texture src,RenderTexture dest,Material mat,int pass = -1);
void Blit(Texture src,Material mat,int pass = -1);
mat材质使用的shader会进行后处理操作,src纹理将传递给shader中名为_MainTex的纹理属性,pass默认-1表示依次调用Shader内所有Pass否则调用给定索引的Pass
卷积操作,对图像中某个像素进行卷积时把卷积核的中心放置于该像素上,翻转核(水平、垂直翻转)之后依次计算核中每个元素和其覆盖的图像像素值的乘积并求和
边缘检测:
相邻像素之间的插值用梯度表示,边缘处的梯度绝对值较大
边缘检测算子:
Roberts、Prewitt、Sobel
利用深度和法线纹理的边缘检测:
用Roberts算子,设置法线和深度的检测灵敏度,当邻域的深度值或法线值相差一定值时被认为存在一条边界,得到边缘信息后利用该值进行颜色混合
模糊:
均值模糊(卷积核各个元素值相等,相加为1)
中值模糊(选择邻域内对所有像素排序后的中值替换原色)
高斯模糊(用高斯核卷积计算,可用两个一维高斯核先后对图像滤波,等同于二维且能减少采样次数):
用两个Pass分别对竖直、水平方向的一维高斯核对图像进行滤波,利用缩放提高性能,重复迭代次数控制模糊程度,用RenderTexture.GetTemporary分配一块与屏幕图像大小相同的缓存区存储第一个Pass结果,
并将临时渲染纹理的滤波模式设为双线性,最后用RenderTexture.ReleaseTemporary来释放缓存
径向模糊:
设置中心坐标,在片元着色器中将像素颜色按照一条直线(uv * scale + center)进行迭代采样累加,坐标缩放比例依据循环参数的改变而变化,最终将采样的颜色的总和除以采样次数
运动模糊:
累积缓存(混合多张连续图像,取平均值作为最后的运动模糊图像,性能消耗较大)
速度缓存(存储各个像素当前的运动速度,然后用该值来确定模糊的方向和大小)
一种使用速度映射图的运动模糊的方法:
用深度纹理在片元着色器中计算像素在世界空间中的位置,得到顶点坐标后用前一帧的视角*投影矩阵对其变换得到前一帧中的NDC坐标,然后计算两帧的位置差得到速度
Bloom:
根据一个阈值提取出图像中的较亮区域,存储在一张渲染纹理中,再用高斯模糊对其进行模糊处理,模拟光线扩散效果,最后再将其和原图像混合得到结果
基于屏幕后处理和DepthTexture的全局垂直雾效:
对深度纹理采样后由深度纹理获取像素在世界空间下的位置,用随高度变化的指数|线性|指数的平方等函数得到相应高度雾的浓度,将雾色和原色混合
上述为均匀雾效,可用噪声纹理实现不均匀雾效:
利用和时间相关的参数计算当前噪声纹理的偏移量,据此对噪声纹理采样以及处理后得到最终噪声值
十三、深度和法线纹理
深度纹理中深度值范围是[0,1],且通常是非线性分布。
透视、正交投影,经过齐次除法后裁剪空间变换到一个z[-1,1]的立方体,深度值即NDC中z分量的值,映射后存储(NDC中的xyz分量范围均[-1,1])
延迟渲染中法线信息很容易得到,Unity只需合并深度和法线缓存即可,前向渲染中默认不会创建法线缓存
深度和法线纹理可通过单独Pass渲染得到,Unity会使用着色器替换(Shader Replacement)技术选择Subshader的RenderType为Opaque的物体,判断他们的使用的渲染队列是否小于2500,满足就渲染到深度和法线纹理中
可让相机生成一张深度纹理,Unity获取深度缓存或者用着色器替换技术,选取需要的不透明物体选择其LightMode为ShadowCaster的Pass得到深度纹理,没有这个Pass物体就不会出现在深度纹理中,深度纹理的精度通常是24位或16位
获取深度纹理: camera.depthTextureMode = DepthTextureMode.Depth
之后可在shader中声明_CameraDepthTexture变量来访问
可让相机生成一张深度+法线纹理,Unity会创建一张和屏幕分辨率相同、精度为32位(每个通道8位)的纹理,观察空间下的法线信息编码进纹理的R和G通道,深度信息编码进B和A通道
获取深度+法线纹理: camera.depthTextureMode = DepthTextureMode.DepthNormals
之后可在shader中声明_CameraDepthNormalsTexture变量来访问
可让相机生成一张深度和深度+法线纹理:
camera.depthTextureMode |= DepthTextureMode.Depth
camera.depthTextureMode |= DepthTextureMode.DepthNormals
在shader中访问到深度纹理_CameraDepthTexture后可用SAMPLE_DEPTH_TEXTURE|SAMPLE_DEPTH_TEXTURE_PROJ|SAMPLE_DEPTH_TEXTURE_LOD宏用当前像素的纹理坐标对其采样
通过纹理采样后得到的深度值往往是非线性的,由深度纹理中的深度信息计算得到视角空间下的深度值:
内置 LinearEyeDepth函数把深度纹理的采样结果转换到视角空间下的深度值,Linear01Depth函数返回一个[0,1]的线性深度值,这两个函数内部使用了_ZBufferParams变量来得到远近裁平面的距离
在shader中访问到深度纹理_CameraDepthNormalsTexture后可用tex2D采样:
内置 DecodeDepthNormal|DecodeFloatRG|DecodeViewNormalStereo可对采样结果解码一获得深度值和法线方向,如用DecodeDepthNormal解码得到[0,1]的线性深度值和视角空间下的法线方向
由深度纹理获取像素在世界空间下的位置:
1.在片元着色器中利用深度纹理和当前帧的视角*投影矩阵的逆矩阵,对深度纹理采样,把求得的深度值和像素的纹理坐标映射回NDC,得到NDC中下的坐标后用当前帧的视角*投影矩阵的逆矩阵对其变换,
把结果除以w分量来得到世界空间下的坐标(在片元着色器中进行矩阵乘法,影响性能)
2.原理:处在同一条摄像机射出的射线上的点,最终会被绘制在同一个位置上(深度测试的意义之一——只让最近的点绘制出来),若知道射线上某个点到摄像机的距离,那么这个点就是唯一确定的。
实现:对图像空间下的视锥体射线进行插值,该射线存储了该像素在世界空间下到摄像机的方向信息,然后把他和线性化后的视角空间下的深度值相乘,再加上世界空间下的摄像机的位置就得到像素在世界空间下的位置。
重点:interpolatedRay的求法,由摄像机近裁平面距离、FOV、横纵比得到近裁平面四个角对应的向量与屏幕的四个点分别对应,在顶点着色器中决定点对应的4个角中的哪个角
自带雾效:
#pragma multi_complie_fog
宏:
UNITY_FOG_COORDS 创建一个结构成员来保存雾的坐标,参数表示用第几组纹理坐标
UNITY_TRANSFERFOG 计算雾的坐标并将其存储到v2f结构体中
UNITY_APPLY_FOG 从顶点着色器中输出雾效数据,将第二个参数中的颜色值作为雾效的颜色值
十四、NPR
锐利的阴影 少或没有高亮的点 对物体轮廓进行描边
基于色调的着色技术:用一张渐变纹理模拟漫反射的渐变,同理计算完高光因子后判断范围,超过1不超0,为抗锯齿可用smoothstep函数平滑处理
绘制轮廓线的方法:
1.基于观察角度和表面法线的描边
视点方向和表面法线之间的点乘结果得到轮廓线信息。如果此点乘结果接近于零,那么可以断定这个表面极大概率是侧向的视线方向,而我们就将其视做轮廓边缘,进行描边
2.基于过程几何方法的描边
使用两个Pass,第一个渲染背面的面片,并用偏置等其他技术使其轮廓可见,第二个Pass再正常渲染正面面片
3.基于图像处理的描边
通过在各种缓冲区上执行图像处理技术,可以将其理解为一种后处理操作,但一些深度和法线变化很小的轮廓无法被检测出来
4.基于轮廓边缘检测的描边
(n0·v>0) ≠ (n1·v) 来检测一条边是否是轮廓边,n0.n1表示相邻两三角面的法向,v是从视角到该边上任意顶点的方向,由于逐帧单独提取轮廓,会有动画连续性问题
5.混合轮廓描边
结合了图像处理方法和几何要素方法,首先找到一系列轮廓边缘的列表,然后把所有三角形和轮廓渲染到纹理中,再用图像处理识别出可见的轮廓边,再将这些线段检测连接成平滑路径,最后再对路径风格化渲染
简易素描风格:
使用不同的素描纹理模拟不同光照下的漫反射效果,这些纹理还有笔触间隔相同的多级渐远纹理,在顶点着色器阶段计算光照的漫反射系数来决定素描纹理的混合权重,并传递给片元着色器,在片元着色器中根据权重混合素描纹理的采样结果
https://zhuanlan.zhihu.com/p/25339585
一种简易水墨风格:
边缘检测+降低色阶(简化颜色)+和一张有质感的纹理颜色叠加+内部颜色处理
http://blog.csdn.net/candycat1992/article/details/47777937
水彩风格
十五、噪声
基于晶格的方法:
1.梯度噪声(Perlin、Simplex、Wavelet等)
2.Value噪声
基于点的方法:
Worley噪声
简单的溶解效果:
噪声纹理+透明度测试,使用对噪声纹理采样的结果和控制消融程度的阈值比较,若小于阈值就用clip裁减掉对应像素
需定义单独用于投射阴影的Pass:
V2F_SHADOW_CASTER 在v2f结构体中定义阴影投射需要的变量
TRANSFER_SHADOW_CASTER_NORMALOFFSET 在顶点着色器中填充V2F_SHADOW_CASTER在背后声明的变量
SHADOW_CASTER_FRAGMENT 片元着色器中完成阴影投射,把结果输出到深度图和阴影映射纹理中
需保证为上述内置宏提供相应变量(如TRANSFER_SHADOW_CASTER_NORMALOFFSET用名为v作为输入结构体,v中需包含v.vertex和v.normal的信息)
简单的水波效果:
水波法线纹理由噪声纹理生成,噪声纹理作为高度图不断修改水面法线方向,用和时间相关的变量对噪声纹理采样,得到法线信息后进行正常的反射+折射计算
和之前玻璃效果类似,Cubemap模拟反射,Grabpass获取渲染纹理,用切线空间下的法线方向对像素屏幕坐标偏移,再使用该坐标对渲染纹理屏幕采样模拟折射
用菲涅尔系数决定最终反射和折射颜色的混合 fresnel = pow(1-max(0,v·n),4)