游戏图像学习入门到精通-Shader总结篇

来源:互联网 发布:阿里云大数据平台 sql 编辑:程序博客网 时间:2024/05/29 15:12
流水线
1.应用阶段:(CPU)输出渲染图元,粗粒度剔除等 比如完全不在相机范围内的需要剔除,文件系统的粒子系统实现就用到粗粒度剔除。
2.几何阶段:(GPU)把顶点坐标转换到屏幕空间,包含了模型空间 到世界空间 到观察空间(相机视角view) 到齐次裁剪空间(投影project空间,四维矩阵,通过-w<x<w判断是否在裁剪空间,xyz值都在-w,w之间) 
到归一化设备坐标NDC(四维矩阵通过齐次除法,齐次坐标的xyz除以w实现归一化(x/w,y/w,z/w,w/w),xy坐标到在-1,1上,z坐标一般是0,1)  到屏幕空间(通过屏幕宽高和归一化坐标计算)。
a.顶点着色器:坐标变换和逐顶点光照,将顶点空间转换到齐次裁剪空间。
b.曲面细分着色器:可选
c.几何着色器:可选
d.裁剪:通过齐次裁剪坐标的-w<x<w判断不在视野范围内的部分或者全部裁剪,归一化。
e.屏幕映射:把NDC坐标转换为屏幕坐标
3.光栅化阶段:(GPU)把几何阶段传来的数据来产生屏幕上的像素,计算每个图元覆盖了哪些像素,计算他们的颜色、
a.三角形设置:计算网格的三角形表达式
b.三角形遍历:检查每个像素是否被网格覆盖,被覆盖就生成一个片元。
c.片元着色器:对片元进行渲染操作
d.逐片元操作:模板测试,深度测试 混合等
e.屏幕图像
-------------------------------------------------------
矩阵:
M*A=A*M的转置(M是矩阵,A是向量,该公式不适合矩阵与矩阵)


坐标转换:
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);顶点位置模型空间到齐次空间
o.worldNormal = mul((float3x3)_Object2World,v.normal);//游戏中正常的法向量转换,转换后法向量可能不与原切线垂直,但是不影响游戏显示,而且大部分显示也是差不多的。一般用这个就行了。
o.worldNormal = mul(v.normal, (float3x3)_World2Object);顶点法向量从模型空间转换到世界空间的精确算法,公式是用_Object2World该矩阵的逆转置矩阵去转换法线。然后通过换算得到该行。


-------------------------------------------------------
API:
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的逆转置矩阵,用于将法线从模型空间转换到观察空间
_Object2World 将顶点方向矢量从模型空间变换到世界空间,矩阵。
_World2Object 将顶点方向矢量从世界空间变换到模型空间,矩阵。
模型空间到世界空间的矩阵简称M矩阵,世界空间到View空间的矩阵简称V矩阵,View到Project空间的矩阵简称P矩阵。
这些矩阵需要先声明了才能用
---------------------------------------------
_WorldSpaceCameraPos 该摄像机在世界空间中的坐标
_ProjectionParams   x = 1,如果投影翻转则x = -1   y是camera近裁剪平面   z是camera远裁剪平面    w是1/远裁剪平面
_ScreenParams   x = 屏幕宽度,y = 屏幕高度,z =  1 + 1.0/屏幕宽度, w = 1 + 1.0/height屏幕高度(指像素数)
_ZBufferParams
unity_OrthoParams
unity_Cameraprojection
unity_CameraInvProjection
unity_CameraWorldClipPlanes[6] 摄像机在世界坐标下的6个裁剪面,分别是左右上下近远、








----------------------------
1.表面着色器
void surf (Input IN, inout SurfaceOutput o) {}表面着色器,unity特殊封装的着色器
Input IN:可以引用外部定义输入参数
inout SurfaceOutput o:输出参数
struct SurfaceOutput//普通光照
{
 half3 Albedo;//纹理,反射率,是漫反射的颜色值
 half3 Normal;//法线坐标
 half3 Emission;//自发光颜色
 half  Specular;//高光,镜面反射系数
 half  Gloss;//光泽度
 half  Alpha;//alpha通道
}
基于物理的光照模型:金属工作流SurfaceOutputStandard 高光工作流SurfaceOutputStandardSpecular 
half3,half4代表rgba或者xyz,可以分开用 Albedo.xy=1.或Albedo.ga=1


---------------------------------
#pragma surface surfname lightModel op - 指出函数surfname 表面着色器。lightModel的光照模型和可选op操作,还可以添加顶点修改函数vertex和颜色修改函数finalcolor。
#pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
#pragma vertex name - 指出函数name 是顶点着色器。
#pragma fragment name - 指出函数name 是片段着色器。
#pragma fragmentoption option - 添加option 到编辑的OpenGL片段程序。参看ARB fragment program说明书了解被允许的选项列表。这个指示在顶点程序或者编辑到非OpenGL targets的程序没有影响。 
#pragma multi_compile_builtin - 为了pixel-lit shaders;;这个将告知Unity去编辑大量的这个着色器程序数列以支持所有的照明种类,和所有的阴影选项。 
#pragma multi_compile_builtin_noshadows - 对于pixel-lit 着色器,不接受阴影。这将告知Unity去编辑几个该着色器程序的数列来支持所有的照明种类。这比multi_compile_builtin pragma可以更快的编辑,而且结果着色器也更小。
#pragma target name - 那个着色器target 去编辑。细节参看shader targets。 
#pragma only_renderers space separated names - 只为给定的渲染器编辑着色器。默认情况下,着色器为所有的渲染器被编辑。细节参看 renderers。
#pragma exclude_renderers space separated names - 不为给定的渲染器编辑着色器。默认情况下,着色器为所有的渲染器被编辑。细节参看 renderers。
---------------------------------
2.顶点着色器
struct appdata_full {//vertex输入
float4 vertex : POSITION;//must
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;//该顶点的纹理坐标,第一组纹理坐标uv 也就是第一张贴图的坐标、为了实现多重纹理贴图,比如子弹打在墙上的弹痕等
float4 texcoord1 : TEXCOORD1;//n的数量和shadermodel版本有关
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
fixed4 color : COLOR;//顶点颜色
};@上次买的




3.片段着色器
struct v2f{//vertec的输出和frag的输入
float4 vertex :SV_POSITION;//must
float3 color0:COLOR0;
float3 color1:COLOR1;
float4:texcoord:TEXCOORD0;//TEXCOORD0-TEXCOORD7自定义纹理坐标
}
SV_Tatget //frag的输出,输出float4的颜色 也可以是COLOR0各个平台会自动转换标志


-----------------------------------
光照:
1.逐顶点光照:在顶点着色器阶段计算光照,效率高但是效果不好,在边缘像素映射的时候插值可能会产生锯齿。
2.逐像素光照:在片元着色器阶段计算光照,计算量大,但是边缘表现效果好。
3.半兰伯特模型:处理无光照的地方,也让其有光,不然可能是全黑。经验模型。
4.Blinn-Phong模型:高光反射模型的经验模型,高光部分看起来会更大更亮写。更符合实际些。


#include "Lighting.cginc"
Tags { "LightMode"="ForwardBase" }


WorldSpaceViewDir(float4 v)  输入模型空间中的顶点坐标,返回世界空间中从该点到摄像机的观察方向
UnityWorldSpaceViewDir(float4 v)  输入世界空间中的顶点坐标,返回世界空间中从该点到摄像机的观察方向
ObjSpaceViewDir(float4 v) 输入模型空间中的顶点坐标,返回模型空间中从该点到摄像机的观察方向
WorldSpaceLightDir() 仅用于前向渲染,输入模型空间中的顶点坐标,返回世界空间中从该点到光源光照方向,没有归一化。
UnityWorldSpaceLightDir() 仅用于前向渲染,输入世界空间中的顶点坐标,返回世界空间中从该点到光源光照方向,没有归一化。
ObjSpaceLightDir() 仅用于前向渲染,输入模型空间中的顶点坐标,返回模型空间中从该点到光源光照方向,没有归一化。
UnityObjectToWorldNormal(float3 v)把法线从模型空间转换到世界空间
UnityObjectToWorldDir(float3 v)把方向矢量从模型空间转换到世界空间
UnityWorldToObjectDir(float3 v)把方向矢量从世界空间转换到模型空间
_WorldSpaceLightPos0.xyz 获取平行光光源方向,或者点光源的光源位置
_LightColor0.rgb 获取当前pass的光源颜色和强度
UNITY_LIGHTMODEL_AMBIENT.xyz; 环境光
normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); 视觉方向 UnityWorldSpaceViewDir


a.漫反射公式:diff=C*max(0,dot<L,N>);//C是颜色和强度_LightColor0.rgb
代码:  diff=max(0,dot(i.normal,i.lightDir))//i的单位向量and单位法向量
c=tex2D(tex,i.uv)*_LightColor0*diff//_LightColor0表示的是场景中平行光的颜色和强度






b.高光反射公式:Spec=pow(max(0,dot(R,V),gloss))//R 单位反射向量reflect(ray,normal)函数获取,V视线单位方向向量 ,gloss光色度
代码: Spec=pow(max(0,dot(reflect(-i.lightDir,i.normal),viewdir),32))
c=c**_LightColor0*(diff+Spec)

颜色相加和相乘的物理意义,一般的各种光源都是颜色叠加,相乘会变淡。


在PS中 tex2D 自动计算应该使用的纹理层。
tex2Dbias需要在t.w中指定一个偏移量来把自动计算出的纹理层全部偏移指定的值。
tex2Dgrad需要提供屏幕坐标x和y方向上的梯度来确定应该使用的纹理层。
tex2Dlod需要在t.w中明确指定要使用的纹理层。
-----------------------------------------------
纹理 uv坐标是顶点存储了图片上的坐标
_MainTex ("Main Tex", 2D) = "white" {}
sampler2D _MainTex;
float4 _MainTex_ST;//Unity中 纹理_ST来默认声明该纹理的属性_MainTex_ST.xy表示Scale, Till缩放,_MainTex_ST.zw表示Transform 偏移
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);//vs输入纹理坐标和纹理值输出UV,ps对uv进行纹理采样和计算。UV通常在0-1范围,等于o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;//反射率


法线贴图:xyz映射存成rgb值。一般存在切线空间,z轴法线方向,x轴切线方向,y轴副(法)切线方向,我们ds的法线贴图xy存法线z存粗糙度。
TANGENT_SPACE_ROTATION;//Unity来获取rotation矩阵,从模型空间到切线空间变换的矩阵。仅存在旋转和平移时,一个矩阵的转置矩阵等于他的逆矩阵。
自己实现:
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; //切线空间的w分量用来存储负法线向内还是向外
float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);//float3x3是按行存储


float3 tangentNormal = UnpackNormal(packedNormal);Unity将法线贴图纹理坐标0,1映射到正常法线坐标-1,1,返回切线空间下的法线方向。法线贴图要设置成Normal格式。该设置unity有优化 rgb值不再是法线xyz的映射了,如果不设置的话要自己算 该公式不能用。
自己实现:
tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;//坐标反映射,自己计算的方法 因为packedNormal.z分量一般被压缩去掉,比如我们ds的z分量存储的是粗糙度,xy存法线。
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));//通过xy计算z  法线贴图一般会采用压缩的方式存储 那么只有xy分量是正确的,z分量需要经过xy计算出来。
如果法线贴图是蓝色:代表存在切线空间,而且xyz可能没有被压缩。如果显示了其他颜色比如ds的绿色,就代表z通道被用做其他用途了。
虽然在切线空间下计算光照更省,但为同一建议一般在世界空间下计算。


遮罩纹理


----------------------------------------
1.透明度测试AlphaTest:只要有一个片元的透明度不、、满足条件就被裁剪,用来优化显示。
AlphaTest Greater AlphaValue//仅渲染 alpha 值大于 AlphaValue 的像素。AlphaValue :0-1
AlphaTest GEqual AlphaValue //仅渲染 alpha 值大于或等于 AlphaValue 的像素。
AlphaTest Less AlphaValue//仅渲染 alpha 值小于 AlphaValue 的像素。
AlphaTest LEqual AlphaValue //仅渲染 alpha 值小于或等于 AlphaValue 的像素。
AlphaTest Equal AlphaValue //仅渲染 alpha 值等于 AlphaValue 的像素。
AlphaTest NotEqual AlphaValue //仅渲染 alpha 值不等于 AlphaValue 的像素。
AlphaTest Always //渲染所有像素。这在功能上相当于 Alpha 测试关 (AlphaTest Off)。
AlphaTest Never //不渲染任何像素。
第一步透明度测试不通过的放弃该片元
2.模板测试:Stencil如果开启了模板测试,GPU会首先会读取模板缓冲区的值,然后把读取的参考值ref和该缓存值进行比较,比较方式由Comp指定,比如大于Greater就表示通过模板测试,
然后由Pass Fail ZFail去指定通过和不通过模板和深度测试后对缓冲区的值进行的Operation处理。
Stencil 
{ Ref 2   //设置模板参考值为2
Comp equal //比较方式,有8种比较方式。  Greater指ref值大于缓存值。
Pass  Operation //这个是当stencil测试和深度测试都通过的时候,进行的stencilOperation操作方法
Fail Operation //这个是在stencil测试通过的时候执行的stencilOperation方法
ZFail Operation//这个是在stencil测试通过,但是深度测试没有通过的时候执行的stencilOperation方法。
ReadMask readMask//readMask默认是255,ds按位进行对比的时候常用,设置隐码后 读取ref和buff值都需要与该码进行与操作后再比较Comp。(0-255)
WriteMask writeMask//写操作进行与操作  ,设置该掩码后先进行按位与操作后再写入
}
Comp 的参数包括Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never
Operation的参数包括:
Keep 保持
Zero 归零
Replace 拿比较的参考值替代原来buffer的值
IncrSat 值增加1,但是不溢出,如果是255,就不再加
DecrSat 值减少1,不溢出,到0就不再减
Invert 翻转所有的位,所以1会变成254
IncrWrap 值增加1,会溢出,所以255会变成0
DecrWrap 值减少1,会溢出,所以0会变成255


第二步模板测试不通过的放弃该片元


clip(x) //x的任何分量小于0 被裁剪掉
discard//舍弃当前片元
ZWrite Off//关闭深入写入  默认开启
ColorMask RGB|A|0 //设置颜色通道的写掩码,为0表示该pass不进行颜色输出。
3.深度测试ZTEST:一个片元离摄像机的远近,渲染后会进行深度写入,通常会判断缓存深度和当前片元深度 可知前后关系。
ZTest Always //指的是直接将当前像素颜色(不是深度)写进颜色缓冲区中 相当于ZTest Off
ZTest Never //而Never指的是不要将当前像素颜色写进颜色缓冲区中,相当于消失。
ZTest Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never/Off,默认值为LEqual 即当物体深度小于或等于缓存深度值时(越远深度越大),该物体渲染,就是默认的先后顺序渲染。
第三步深度测试不通过的放弃该片元


透明度混合AlphaBlending:该片元需要关闭深度写入,不关闭深度测试。会导致片元之间深度穿插。可以采用2个pass,第一个pass只用来做深度写入ZWrite On,第二个pass只用来输出颜色ZWrite Off,这样深度和颜色效果才会正确
Blend Off//关闭混合,只有blend打开后ps输出a通道才有意义
Blend SrcFactor DstFactor//用同样的因子对rgba进行混合(默认都开启混合)第一个参数对源颜色(当前片元颜色,ps输出的颜色)*SrcFactor混合,
第二个参数对目标颜色(当前读到的缓冲区颜色)*DstFactor混合,混合后默认相加后会重新写入缓冲区(相加后超过1的自动截断到1)。混合包括RABG值。结果都是源颜色和目标颜色与各自因子相乘后再加起来作为输出颜色。
shader里边的向量相乘不同于点乘叉乘,相当于各项分别相乘。


Blend SrcFactor DstFactor,SrcFactorA DstFactorA//把rgb和a的混合因子分开。
混合因子
One //因子是1
Zero    //因子是0
SrcColor //因子为源颜色值,当前片元颜色,对应rgba分量分别与SrcColor分量相乘
SrcCAlpha //因子为源颜色透明值值,对应rgba分别与SrcCAlpha相乘。
DstColor //因子为目标颜色值,当前读到的缓冲区颜色
DstAlpha //因子为目标颜色透明值值
OneMinusSrcColor//因子为1-源颜色
OneMinusSrcAlpha//因子为1-源alpha
OneMinusDstColor//因子为1-目标颜色
OneMinusDstAlpha//因子为1-目标alpha
例子:
Blend SrcAlpha OneMinusSrcAlpha// Alpha混合,正常的透明度混合
Blend OneMinusDstColor One //柔和相加Soft Additive
Blend One One                       // Additive相加 线性减淡
Blend One OneMinusDstColor          // Soft Additive比较柔和的相加
Blend DstColor Zero                 // Multiplicative乘法
Blend DstColor SrcColor             // 2x Multiplicative2倍乘法


BlendOp OP//对源和目标颜色进行其他操作,而不是默认的相加,op操作包括:
Add //相加
Sub //源颜色减目标颜色
RevSub//目标颜色减源颜色
Min //使用2者较小的值
Min //使用2者较大的值
chen
BlendOp Min
Blend One One //组合变暗


双面渲染:一般采用多个pass分别渲染正面和背面
Cull Back|Front|Off   
Cull Back默认的背对相机图元不渲染
Cull Front朝向相机图元不渲染,只显示背面
Cull Off关闭剔除功能 全部渲染 性能低,但是可以实现比如看见物体内部结构。
不透明物体有深度测试,先渲前后没有关系,但是先渲染近的效率会更高,因为远的会被深度测试自动剔除不用渲染。
透明物体一般要先渲远的,再渲近的才能保证视觉顺序正确。
------------------------------------------
SubShader的Tag{}标签类型:
Queue:渲染顺序,保证渲染顺序小的先渲  大的后渲,在quene一样大的时候无法保证渲染顺序。
RenderType:Unity可以运行时替换符合特定RenderType的所有Shader,着色器分类 也可以自定义。
ForceNoShadowCasting:值为”true”时,表示不接受阴影。 
IgnoreProjector:值为”true”时,表示不接受Projector组件的投影。常用语半透明物体
DisableBatching:是否对subshader进行批处理,当shader中需要对顶点进行偏移的时候,该项设置为true
CanUseSpriteAtlas:当该subshader用于sprite时 该标签设为false
PreviewType:指明材质面包怎么预览材质 比如 "PreviewType"="Plane"


LightMode : 渲染路径 ,pass的标签类型
-------
渲染队列:"Queue"="Transparent"
Background:1000  //该声明的物体最先被渲染
Geometry:2000 //默认的不透明物体使用的渲染队列
AlphaTest:2450 //透明度测试,默认不透明物体渲染完后就渲染该物体
Transparent:3000//透明物体,在Geometry和AlphaTest后渲染,保证不透明物体渲染完了再渲染透明的。
Overlay:4000 //该队列用来实现叠加效果,该物体会在最后被渲染。
------
RenderType:
Opaque:绝大部分不透明的物体都使用这个; 
Transparent:绝大部分透明的物体、包括粒子特效都使用这个; 
Background:天空盒都使用这个; 
Overlay:GUI、镜头光晕都使用这个
-----------------------------------------------------------------------------------------
渲染路径
Tag{   "LightMode" = "ForwardBase"}//为每个pass指定渲染路径
LightMode包括:
Always:所有渲染路径该pass都会渲染,但不计算光照
ForwardBase:前向渲染,该pass会计算环境光,最重要的平行光,逐顶点光和 Lightmaps
ForwardAdd:前向渲染,该pass会计算额外的逐像素光源,每个pass对应一个光源。光源多该pass会被多次调用 效率变低。
Deferred:延时渲染,该Pass会渲染G-buffer
ShadowCaster:把物体的深度信息渲染到阴影映射纹理或深度纹理中
PrepassBase:遗留的延迟渲染,该pass会渲染法线和高光反射的指数部分、
PrepassFinal:遗留的延迟渲染,该pass通过合并纹理 光照 自发光来渲染得到最后的颜色
Vertex:遗留的顶点照明渲染


1.前向渲染:包括ForwardBase类型渲染常用光照和ForwardAdd额外光照
#pragma multicompile_fwdbase //ForwardBase中用来保证光照衰减等参数正确赋值。
#pragma multicompile_fwdadd //ForwardAdd中用来保证可以访问到正确的光照变量.
#pragma multicompile_fwdadd_fullshadows //ForwardAdd中用来计算阴影效果
USING_DIRECTIONAL_LIGHT//平行光的宏定义
_LightColor0 //该pass的桌像素光照颜色
_WorldSpaceLightPos0 //获取平行光光源方向,或者点光源的光源位置
_LightMatrix0 //世界空间到光源空间(光源位置为坐标原点的坐标系)的变换矩阵
_LightTexture0 //光照衰减纹理
...........
tips:光源的RendeMode参数设置为Important unity会自动采用像素光源,如果不重要就是顶点光源。还有qualitysetting里边的PixelLIghtCount,超过这个数也会用顶点光照
光照衰减
float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;//UNITY_ATTEN_CHANNEL获得衰减值所在的分量
float shadow=SHADOW_ATTENUATION(i);//负值使用SHADOW_COORDS对相关纹理进行采样,返回值为阴影。关闭阴影的状态是等于1
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);//atten为衰减,shadow为阴影
------计算别人投到自己身上的阴影和衰减
SHADOW_COORDS(n)//声明一个_ShadowCoord的阴影纹理坐标 ps输入坐标,n的值是声明TEXCOORD0-7坐标使用的个数
TRANSFER_SHADOW(o);//用于在顶点着色器中计算上一步声明中的阴影纹理坐标 并传向ps阶段。
float shadow=SHADOW_ATTENUATION(i);//负值使用SHADOW_COORDS对相关纹理进行采样,返回值为阴影。关闭阴影的状态是等于1
UNITY_LIGHT_ATTENUATION(atten,v2f i, i.worldPos);//计算别人投影到身上的阴影#include "AutoLight.cginc" Unity会将光照衰减和阴影相乘后存在第一个参数中,并自动声明atten变量。第二个参数结构体包含SHADOW_COORDS,第三个参数世界空间坐标
return fixed4((diffuse + specular) * atten, 1.0);//UNITY_LIGHT_ATTENUATION出的atten为衰减和阴影
-----计算阴影投影到别人身上,自己的阴影
V2F_SHADOW_CASTER//unity里边定义阴影投射需要定义的变量
TRANSFER_SHADOW_CASTER_NORMALOFFSET(0)//unity对顶点进行自动处理
SHADOW_CASTER_FRAGMENT(i)//unity自动完成阴影投射部分,把结果输出到深度图和阴影映射纹理中
--ds2的阴影采用的是屏幕后处理的方式去计算阴影,延迟渲染
2.顶点照明渲染:过时的渲染方式。效果差。


3.延迟渲染:通常2个pass,第一个pass计算哪些片元可见,第二个pass计算真实光照。


---------------------------------------------------------------------------
1.反射方向:float3 reflect = reflect(o.lightDir, o.worldNormal);//入射光线,表面法线
2.折射: float3 refract  = refract(normalize(o.lightDir), normalize(o.worldNormal), _RefractRatio);//入射光线单位向量,表面法线单位向量,介质比


3.镜子效果:使用相机的RenderTexture来设置渲染纹理。o.uv.x = 1 - o.uv.x;坐标需要翻转一下。
4.玻璃效果:反射和折射使用cubemap进行采样 是天空盒的cubemap,然后反射需要采样的是周围环境的光照和纹理。
GrabPass { "_RefractionTex" }//会把屏幕输出到_RefractionTex的texture中, _RefractionTex_TexelSize ,要先声明该变量 就可以可以得到该纹理纹素大小,例如255*255的纹素是(1/255,1/255) 值是:  Vector4(1 / width, 1 / height, width, height);
GrabPass{} //然后用_GrabTexture直接访问屏幕图像,但是这样效率比较低,推荐要上面需要声明的方法。
o.scrPos = ComputeGrabScreenPos(o.pos);//得到对应被抓取的屏幕图像的采样坐标
反射和折射需要显示环境的效果,所以需要对环境的cubemap进行采样。先用反射和折射的公式计算出光线,然后对环境贴图进行采样texCUBE(_Cubemap, i.worldRefl).rgb就可以得到具体效果了。
反射skybox 3d采样,折射屏幕抓取图像2d采样。
---------------------------------------------------------------------------
时间变量
_Time:float4 //t是自该场景加载开始所经过的时间,4个分量是(t/20,t,2t,3t)
_SinTime:float4 //t是时间的正玄弦值,四个分量的值分别是(t/8,t/4,t/2,t)
_CosTime:float4 //t是时间的余玄弦值,四个分量的值分别是(t/8,t/4,t/2,t)
unity_DeltaTime:float4// dt是时间增量,4个分量分别是(dt,1/dt,smoothDt,1/smoothDt)
序列帧动画:时间去控制uv坐标映射转换。uv坐标的xy是顶点坐标,映射到小格子里边,和UItexture的xy和宽高不一样。
背景偏移动画:时间控制uv坐标偏移。
水流动画:通过时间和正弦函数去控制顶点偏移,通过时间控制uv移动。设置DisableBatching=true
广告牌BillBoarding:根据视觉方向来旋转被纹理着色的多边形。顶点动画


-------------------------------------------------------------------------
屏幕后处理
void OnRenderImage(RenderTexture src, RenderTexture dest){}//全部渲染完后将调用,屏幕纹理存在src上,用来计算后通过dest返回,添加[ImageEffectOpaque]属性,可使不透明物体(AlphaTest)被渲染完后立即调用.
Graphics.Blit(src, dest);//直接copy纹理。src是屏幕当前或上一步渲染的纹理,dest是目标纹理
Graphics.Blit(src, dest, material,pass=-1);//将把src传到shader的material的_MainTex纹理。经过material(shader)的处理后输出到dest渲染到屏幕.pass默认是-1会调用所有pass,否则只调用给定顺序的pass。指定pass渲染很重要。


基于颜色变化的边缘检测:Sobel卷积算法,对边缘点进行采样计算 和特定矩阵卷积相乘。
高斯模糊:多次采样纹理混合 消耗较大
Bloom效果:把较亮区域提取出来进行高斯模糊 模拟扩散效果,然后再与原纹理混合。
运动模糊:将上一帧的屏幕图像存到renderTexture中,然后执行Graphics.Blit(src, renderTexture, material),shader将开启混合Blend SrcAlpha OneMinusSrcAlpha把src纹理和目标缓冲纹理renderTexture进行混合,然后再Blit输出到dst进行渲染。就得到了运动模糊效果。




----------------------------------------------------------------------------
深度和法线纹理
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
_CameraDepthNormalsTexture //unity中调用camera.depthTextureMode=DepthTextureMode.Depth/DepthNormal;这句话后可以通过该变量访问深度纹理或者深度和法线纹理,project空间
float depth=SAMPLE_DEPTH_TEXTURE(tex,uv)//对深度纹理进行采样,返回project空间下非线性深度值。和tex2D类似 只是封装了平台。自动将NDC坐标下的深度映射(0,1)需要转换到(-1,1)veiw空间去计算
LinearEyeDepth(depth)负责把深度纹理的采样结果转换到视角view空间下的线性深度值
Linear01Depth(depth)则会返回一个范围在0,1的线性深度值


half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv);
half2 centerNormal = DecodeViewNormalStereo(sample1);//center.xy存的法线的映射值
float centerDepth = DecodeFloatRG(center.zw);//zw深度
DecodeDepthNormal(sample1,out centerNormal,out centerDepth)//获取采样的法线和深度


Camera.worldToCameraMatrix //世界转相机矩阵 world2view
Camera.cameraToWorldMatrix //相机转世界矩阵
Camera.projectionMatrix //get投影矩阵viewToproject 视角空间到2维投影空间矩阵,set自定义的投影矩阵。如果你改变这个矩阵,相机的渲染不再基于它的fieldOfView更新,直到调用ResetProjectionMatrix
默认把view2project矩阵叫成project矩阵,默认把World2view矩阵叫做view矩阵。比如ViewProject就是world 2 project矩阵
全局雾效
深度雾效:通过每个顶点的深度值计算出该点到摄像机的距离d,然后把距离d进行参与公式计算得到雾效图(远的雾浓 rgb值大,近的雾淡 rgb值小),再把原图和雾效图进行混合。一般用线性,指数,指数平方公式,ds采用指数平方。
地面雾效:通过深度值和摄像机的方向向量计算该点到摄像机的偏移量,再加上摄像机的位置得到该顶点在世界空间中的坐标,然后把该坐标的y值参与雾效计算。如果用坐标z参与计算和深度雾类似。
#pragma multi_compile_fog


基于法线的边缘检测:防止阴影等信息干扰检测,判断临近的4个点的法线和深度值是否是近似,如果差距过大则是边缘roberts算法。(屏幕后处理)
---
渲染轮廓线:基于法线方向扩散的边缘检测,第一个pass对顶点进行法线方向扩散渲染,第二个pass用真实渲染实际光照,覆盖第一次,对扩散的顶点未被覆盖的像素就产生了轮廓效果。(模型轮廓)
---------------------------------------------------------------------------------
噪声
消融效果:怪物消失渐散的效果,把某个像素值小于阈值的裁剪掉,阈值附近的值用burncolor进行混合。阴影的pass里边算阴影时也把该项给clip掉,这样阴影就动态变化了//clip(burn.r - _BurnAmount);
水面扰动效果:用时间去控制偏移距离,然后对该顶点的uv偏移两点的法线平均值来代替该点的法线值。水面=反射+折射+绕动,波浪效果要用正弦函数偏移顶点、
ds水内的扰动UV=uv+0.03*offset, offset通过时间和噪声图计算,这样会产生顶点和旁边点的扰动0.03倍距离左右。
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
/ Get the normal in tangent space
fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
fixed3 bump = normalize(bump1 + bump2);
全局(动态)雾效:通过时间控制噪声纹理的偏移距离,然后根据噪声颜色值来参与计算雾效浓度,然后计算雾效,就有了流动和淡浓的效果。






线性空间:颜色的正常编码比如0,0.5,1编码到0,0.5,1.
伽马空间:颜色编码存储在伽马空间,对颜色值一般进行Power(col,0.45)计算,比如0,0.22,1编码到0,0.5,1.把更多的颜色值用来存储眼睛容易辨别的较暗局域。该转换会使颜色看起来变亮了、我们的大多数图片颜色值存储在该空间。
伽马矫正:指将线性空间衍射到伽马空间。2.2*0.45=1
CRT显示伽马:由于历史和巧合,屏幕对颜色进行输出的时候会进行显示伽马操作,计算Power(col,2.2),所以输出会比原图变暗。power有很多好处,比如可以使线性硬边缘软化,看起来更柔和。
Unity ColorSpace Setting:不支持移动平台,所以我们一般都选择Gamma Space
Gamma Space:实际上就是“放任模式”,不会对shader的输入进行任何处理,即使输入可能是非线性的;也不会对输出像素进行任何处理,这意味着输出的像素会经过显示器的display gamma转换后得到非预期的亮度,通常表现为整个场景会比较昏暗。
Linear Space:Unity会背地里把输入纹理设置为sRGB模式,这种模式下硬件在对纹理进行采样时会自动将其转换到线性空间中.也会设置一个sRGB格式的buffer,此时GPU会在shader写入color buffer前自动进行伽马校正再次转换到伽马空间。
流程Gamma Space:原光照-伽马编码(图片存储的格式编码,变亮)-Unity不处理-CRT显示伽马(变暗)-输出原光照颜色(相比编码后的原图会变暗)
流程Linear Space:Unity光照-Unity自动转换线性空间计算 然后再伽马矫正转换到伽马空间存储在缓冲中-CRT显示伽马-输出颜色正确,如果用gamma的话 计算完光照不会进行伽马矫正 所以经过CRT会变暗。linear的线性转换指图片采样 ,光照计算两者一样。
ds2:图片格式在伽马空间,选择ColorSpace为Gamma,进行颜色采样时先进行float3 diffuseCol = pow(tex2D( diffTex, texCoord ), 2.2 ); 伽马矫正,在线性空间计算光照屏幕后处理,最后在matcopy输出的时候再return fragColor.rgb = pow(fragColor.rgb, 1.0/2.2);还原回去。最后经过CRT的伽马矫正输出。




HDR高动态范围渲染:正常光照图像范围是0-1,但是实际光照可能超过1 ,HDR使用16位图进行渲染 使颜色值包括在更大的范围内计算 最后再通过Tonemapping衍射到0-1范围 ,使颜色更加真实。
当camera上的hdr被激活时,OnRenderImage的source图会存储成16位的ARGBHalf格式,然后计算Bloom和Tonemapping生成的采样图也可以定义成16位格式,那么效果会更好。HDR对美术和硬件性能要求高,所以手游一般很少用,难达到那么好的效果。
Tonemapping 色调映射:HDR使效果看起来更柔和,防止过亮和过暗, tonemapping根据当前的场景推算出场景的平均亮度,再根据这个平均亮度选取一个合适的亮度域,再将整个场景映射到这个亮度域得到正确的结果。用该公式把采用的hdr颜色转换到ldr范围值内。
如果没有HDR也能简单的柔和。只有在相机启用HDR时才更有意义。这个特效需要显卡拥有像素着色器(2.0)或者OpenGL ES 2.0,但是我们采用的是简单的伽马参数控制的色调映射。同时在该算法里边计算亮度,饱和度和锐化修改。简单的tonemapping算法有很多种实现方式。
算法千差万别,但是目的是一样的,就是通过公式,把输入值HDR颜色或正常颜色映射到0-1范围内去显示,可以画出公式曲线理解都在0-1范围内,并且根据曝光系数调整曲线 使大部分颜色输出到看起来更加接近该亮度的区域。
Bloom :可通过HDR实现效果更佳,bloom本身可通过模糊实现,开启HDR后高斯采样图我们可以使用RenderTextureFormat.ARGBHalf格式的纹理 采用了更高精度的纹理后,再通过算法效果会更好。
数据点云模型的作用:有时候求比如高斯模糊的时候,可以用工具按规则生成周围随机的一些偏移点,数据点云的模型效果有时候比直接计算效果好,如果提前知道模型和计算方式的话。


延迟渲染:所谓延迟渲染只是预先把所有灯光的颜色算好后,叠加到主光上参与光照计算,比如ds会把预先把点光源光照计算出来,通过在光源处Camera.RenderToCubemap(rt)渲染周围全景图,用该全景图去计算光照,全部点光源计算完了 再去叠加计算主光的光照。而不用像延迟渲染那样每个灯光要渲染计算所有场景光照。


全局光照GI:计算的是光的二次反射,有两种算法,漫反射为主:一般从该点法线方向按一定规则随机散射几条射线出去一定距离,只要计算到该射线点和该采样点相交的(该种射线方向可只采样一次对比),就把交点算入该点的颜色影响范围内,把几条射线的交点颜色按照距离衰减叠加到源颜色 就可以了。
另一种是高光为主(ds)方法,从视线的反射方向方向射出几条射线,射出去计算交点和SSR一样,分8段计算深度对比,每段都有一定随机偏移,计算交点后出uv采样叠加到源颜色上。如果所有射线采样点深度都比实际采样点深度大,说明该方向没有对该点颜色有贡献。


环境遮蔽AO:统的AO实现方法通常是使用光线跟踪的来完成。通过从每个可视点发出若干条光线 ,并与全局的场景进行相交测试来得到相应点的AO值。用于计算某点除了光源以为被其他的物体遮挡导致的软阴影。所以开启会变暗。
屏幕空间环境光遮蔽SSAO:屏幕后处理实现,根据点云模型,取周围规则随机点进行深度采样,然后将该点深度和周围采样点的深度进行对比,正常模型是对周围法线求半球积分 半球积分需要更多的采样次数效果才会比球积分好,为效率我们求得是对视角方向整球积分,半球积分需要对cos积分后进行二次积分,不然会导致半球面的同一个角度的圆圈上的点算出来的值是一样的,结果不正确。
我们简化为深度差与viewdir的点乘(cos是sin的积分),当该点为凸点 周围的采样深度大于该点,当该点为凹点时周围深度小于该点,点乘后大于0为凹,小于0为凸,根据深度差与ViewDir的点乘就知道周围点对该点的AO贡献值。由于我们blend one one,而且是整球积分点乘后的AO值-1,1,大于0表示凹处被遮挡,小于0表示凸出不遮挡,转换到0,1后,减去0.5可只显示遮挡处,但是会导致夹角处半圆覆盖面积不够采样失真,所以我们用原值,整体场景会变亮很多,再通过其他方式把暗度调下


高斯模糊Blur:高斯公式计算了周围所有点对该点的叠加的影响值,越远影响越小,采样范围越大效果越正确。我们一般取周围2圈的附近代表点进行模拟,因为实际方式去采样一圈8个点效率太低、
取该点周围1个像素和2个像素距离的四个点,再乘以一个倍数,然后再将多张图按高斯规律叠加,也就是根据高斯算法离中心点越近的所占该点像素值比率越大,因为高斯公式是2维的,所以正常采用2个pass分别对水平竖直方向一维采样模拟降维,但是效果要求不高的时候我们也可以用一个pass取模拟近似效果,9个点:float weight[3] = {0.4026, 0.2442/2, 0.0545/2}保证所有采样点叠加在一起的权重和为1。如果不按高斯权重进行叠加颜色会得到其他类似模糊效果 
另外cpu阶段传过来采样的图最好先进行降分辨率(参数控制分辨率降低倍数)处理再采样提高效率。然后CPU还要多次采样混合效果才好,但效率低。


Bloom:把较亮区域提取出来进行高斯模糊 模拟扩散效果,然后再与原纹理混合。bloom的高斯模糊需要用精确的二维采样效果才能好。
运动模糊Blur:指的的是物体运动的拖影模糊,但赛车拖影模糊一般用环境径向模糊,物体运动模糊可计算上一帧和当前帧进行混合采样 和 相机运动模糊可求取相机运动方向在uv里边进行偏移后混合采样。


Dof景深DepthOfFeild:指目标渲染附近的一定深度范围内显示清晰图片,其他范围呈现模糊效果。游戏中一般用一个Dis,深度大于Dis的点经模糊处理 越远越模糊 形成景深效果。ds的方式是用DrawMesh 第一个pass把深度大于Dis的点模糊渲染到tex中,第二个pass去把上帧绘制的tex直接加到currentColor中。其实我们也可以采用一个pass 把深度大于DIs的直接模糊返回,小于的返回原值就好了。
自发光:一般是用做边缘发光,正常就是在shader内部自定义光照参数,比如UI上显示的模型不经过光照可以自定义光照。
抗锯齿AA:FXAA快速近似抗锯齿是屏幕后处理常用方法,取uv当前顶点和左上,左下,右上,右下的半个uv像素偏移点,求取5个点的亮度值(dot(col,float3(0.22,0.707,0.071))或其他公式Luminance()),然后分别求出5个点的最小和最大亮度值,如果最大亮度与最小亮度值的差小于某个阀值(比如0.5或者计算)判定为非边缘点直接返回当前颜色,
然后对uv加减半个像素偏移(*Dir)采样出col0,col1,再对uv加减2个像素偏移(*Dir)采样出col2,col3(其中像素偏移值要和两对角线亮度之差的差and和组成的向量通过公式计算单位向量Dir后相乘取近似),然后把前2个颜色合并(col0/2+col1/2)得ColA如果该值亮度不在之前计算的最小值与最大值之间返回该值,否则把四个颜色进行合并来返回、
求像素偏移倍数Dir是FXAA关键,该向量相乘后得到的uv点可以更加趋向边缘,比如四个采样点,左边和右边色差大,乘以Dir后取样的左上右下点会趋向上下方向,使得边缘更加真实。抗锯齿采样其实就是轻微的模糊处理,只是采样范围小点,亮度变化不大的不模糊就好了。
草随风动:
水的泡沫:
边缘光RimLight:有两种方法实现,传统的算法是求视角方向和顶点法向量,如果两者垂直说明该顶点边缘光,加上边缘光颜色就好,该种效果所有轮廓线边缘都会渲出。ds的办法是屏幕后处理用法线贴图的xy(节省效率可不用decodenormal)对一张masktexture进行采样,采样后的颜色乘以边缘光颜色返回即可。
  原理在于mask贴图左下角为原点,向上y变大,向右x变大(0-1),对于模型顶点的法向量也是向上y为正 向下y为负,向右x变大(-1,1),其未经decode的法线贴图xy值(0-1)方向刚好对应了mask采样方向,那么对应maxk的上半部分赋一个凹圆颜色值,其他地方为0值,那么采样的叠加的结果就是所有模型的上边缘会有边缘光因为normal.y>0采样、
  ds里边还采用了stencil开关开材质是否开启边缘光。然后相机离主角位置远近来动态设置边缘光颜色倍数,越远设置越大看起来边缘光更多。


橡胶皮肤材质SSS次表面散射:光线进入材质后进入材质内部经反射折射影响周围点的亮度呈现橡胶透明效果。ds的ssss优化过用模拟点来找到周围的点的采样的亮度值再来计算这些点对该点的亮度影响值,然后叠加,影响值一般与距离平方成反比,越近影响越大。


体积光:有三种实现方式,第一种是对顶点光源反方向进行拉丝偏移,第二种是对亮度较大的顶点uv进行光源反方向模糊偏移径向模糊处理。第三种是光线追踪、
径向模糊:可实现赛车的运动周围环境模糊和太阳光光线射出的效果,太阳光将亮度高的图分离出来降分辨率进行模糊然后和原图叠加。从屏幕中心点或者太阳位置向四周各自方向进行uv偏移实现太阳光的体积光。先求中心点到当前uv点的方向dir(包含距离和方向信息),最后对该点进行多次偏移采样叠加,每次偏移uvoffset=dir*i*_Dis就可以实现该效果。为了优化效率可以把采样模糊的图分辨率降低,模糊后和原图混合。


水下:绘制Quad,通过blend 混合,判断世界空间该点的高度是否小于水面高度,小于水面高度部分进行扰动效果加雾效处理,当摄像机高于水面不渲染,低于渲染,处于水面之间的和水进行lerp操作。
下雨:用数张雨滴的法线贴图,轮流采样作为纹理的法线方向就有了下雨效果,法线贴图内含有数个雨滴各种效果。通过drawmesh绘制quad。雨周围的反射可以用cubemap。一般小场景的反射用cubemap采样,大场景只能再计算一次反射效果了。
 float2 rainuv=frac(worldpos.xz*0.25);//用世界坐标的每隔四米采样一次rain图片。frac取小数部分。然后blend one one
屏幕反射:绘制Quad,通过blend one one,从视角方向计算顶点的反射光线,首先对当前点顶点坐标通过噪声图随机偏移一点使之产生模糊效果(因为该算法得不到精确的镜面反射效果),然后从改点的反射光线方向射出一条射线比如for循环10次 每次在该反射方向加上一个偏移值进行对比计算,
 如果加偏移后的点的深度大于该点对应屏幕(实际背景)的深度值,说明射线已经射到模型内部了,那么取出该uv给原顶点用,同事对skytexture进行反射采样和射线反射进行叠加。其中射线的每次偏移量根据摄像机角度计算,远处偏移大,垂直角度偏移小。由于射线采样深度次数多,所以效率不高。
垂直同步:显示器上的所有图像都是一线一线的扫描上去的,垂直同步就是等上一帧的线扫完了再画下一帧 ·防止画面撕裂,,高配置硬件开启垂直同步能使得游戏进程和显示器刷新率同步,使得画面平滑,使得画面稳定,低配置关闭垂直同步可以一定程度上减小显卡的负担,让画面更加流畅一些。


噪音的作用:
-------------------------------------
OnPreCull () // 把这个赋给相机.每帧在相机裁剪场景前调用,裁剪决定哪些物体可见 会被渲染。
OnPreRender ()// 把这个赋给相机.每帧在相机渲染场景之前被调用
OnPostRender () // 把这个赋给相机.每帧在相机完成场景渲染之后被调用。
void OnRenderImage(RenderTexture src, RenderTexture dest){}//屏幕后处理函数
Graphics.Blit (src, dest,mat,pass);//屏幕后处理 src当前屏幕纹理
Graphics.DrawMesh(aMesh, Vector3.zero, Quaternion.identity, aMaterial, 0);//给定材质绘制网格模型 只能在Update里边调用生效。


mat.SetPass(0);//给当前渲染帧设置shader渲染,为渲染激活给定的pass 设置后如果blit没有使用材质也会使用该pass,Blit and DrawMeshNow.
Graphics.SetRenderTarget(rtex)//设置当前的渲染纹理,如果设置该项后,DrawMeshNow再绘制的图像将保存在该纹理中,然后blit到dst就可以单独渲染显示了。这是渲染总的图形目标不同于相机视角的渲染目标。
Graphics.SetRenderTarget(colorbuff,depthbuff)//把渲染好的纹理和深度设置好 再绘制DrawMeshNow 才会有叠加效果。一般情况下DrawMeshNow的rendertarget设置成相同的一个。
Graphics.DrawMeshNow(aMesh, Vector3.zero, Quaternion.identity);//只可渲染静态物体,在相机上,用上一次(最近一次)处理的材质进行绘制,一般前边加mat.SetPass(1);只用该pass进行渲染。matrix 可以对mesh进行矩阵变换。一定要SetRenderTarget才会绘制在对应的buffer上
Graphics 是unity的底层API,Camera是Unity的上层API
手动绘制动态物体可用:CommandBuffer渲染,buff.DrawRender(render,mat);Graphics.ExcuteCommandBuffer(buff);该API会消耗2个drawcall,所以静态物体尽量用DrawMeshNow.


Rendertexture rt=new Rendertexture(...)//w.h.depthbuffer,对于要渲染深度的rt,depthbuffer要设置至少16位。
camera.targetTexture=rt //设置相机的rt 相机会把渲染内容设置到rt上,然后该相机game窗口不可见
camera.clearFlags=CameraFlags.SolidColor;//记得设置的默认颜色
camera.SetTargetBuffers(rt.colorBuffer,rt.depthBuffer)//设置当前相机的渲染目标RenderTarget设置后要么EncodeDepth
camera.RenderWithShader(shader,"RenderType")//找物体shader有"RenderType"字段 和shader有"RenderType"字段相等的shader的subshader进行替换渲染。每帧调用才会生效。也可以自定义如"Tag"="MainChar"
绘制到rt后再和其他相机rt混合然后blit输出,在该功能下的相机深度和颜色都是新的只满足条件的物体的。如果在场景中,多个camera需要把深度和颜色存起来,然后设进去。 
用camera渲染和其他camera混合渲染的时候camera.clearFlags=CameraFlags.Nothing不然如果设置SolidColor会把以前的color 给Clear掉


ds渲染colortexture和depthtexture,colortexture的colorbuffer用来存颜色和粗糙度,depthbuffer用来存系统深度,depthtexture的colorbuffer用来存深度和法线。
RenderTexture的colorbuffer和depthbuffer的理解:colorbuffer用来存camera.SetTargetBuffers后的颜色值,其中的值可以自己定义 可以是颜色也可以是深度和法向量 然后自己用来计算 对应RenderTexture的rgba值,depthbuffer是系统用来写depth用的,还包含了stencil等信息,只决定前后关系,不会给我们参与计算。
所以在SetTargetBuffers为多个RT时,shader里边的frag输出也是输出多个color值。
Camera.SetReplacementShader(shader,tag)//调用这个函数之后,相机将使用替换的shader来渲染它的视图,一直替换渲染。当replacementTag为空时会替换视口中所有物体的shader。
Camera.ResetReplacementShader()//从相机上移除shader替换。


要分清楚camera的SetTargetBuffers和Graphics的SetRenderTarget。如果是相机渲染到某个纹理用camera的,但是如果要设置图形输出的纹理设置Graphics,不要搞混。


ds渲染流程:shader写好后在colorbuff.cg里边运算和输出,屏幕后处理会根据平台和设置使用替换的shader如MRT.shader去渲染,然后MRT.cg去输出颜色。
模型材质上使用的shader并不是最终渲染使用的shader,根据设置在precull阶段 会使用替换shader去渲染,比如流程用前向渲染和支持MRT的Shader渲染和低版本的DiffuseRoughness渲染。然后屏幕后处理阶段用替换shader去实现一些特殊效果。
同一个shader的pass越多那么提交GPU绘制的批次就越多 也就是drawcall越多。所以主角轮廓线渲染怪物深度时要当心。 
屏幕水波纹扰动效果:1.把上一帧的屏幕图像传给水波纹和maintex参与计算扰动,然后缩放水波纹大小就形成了水波纹大小。技能特效的扰动也用该方法。
--------
阴影:1.先用阴影相机变换到光源的位置渲染深度,将深度存储在自定义深度纹理中。由于深度z范围从nearclip到farclip,将z除以远裁剪值,以把深度转换到0-1空间进行存储。
 2.用正常相机渲染顶点 将顶点转换到阴影相机空间 求得深度值z,再除以farclip转换到0-1范围内 再和上面采样的depth做比较 判断该点是否是阴影。阴影相机的targetTexture=rendertexture一定要设置,保证获取阴影相机proj矩阵是按照阴影相机宽高渲染,不然project矩阵宽高会不对。
阴影锯齿解决:  
 1.由于场景阴影分辨率不足,阴影计算锯齿会比较严重,为了使阴影效果更逼真 没有锯齿,可以采用周围四次采样深度值进行插值计算。普通场景可采用该方式提高效果。
 5.对于主角或一些较高要求的物件,可以采用另外的阴影相机去渲染一张高分辨率的深度图计算简单计算阴影,这样就不会有锯齿了,最多可能会显得比较自然硬。
 6.可以在normal方向或者光照方向偏移一点点的bias来减少锯齿,不能直接对z进行偏移。另外在画阴影的时候参与光照计算效果会更好,如果直接画阴影可能会
 4.渲染RT的new Rendertexture(...)的时候 一定要设置深度至少16位才有效。第三位参数,不然深度可能没写进去。
 5.一般采用计算光照渲染的时候乘以阴影系数的效果会比直接画阴影效果好
 6.在设置相机的buffers和RenderWithShader后必须调用Graphics.SetRenderTarget,否则GPU上后续pass会失去原rt的信息。因为camera.SetTargetBuffers会自动设置gpu的SetRenderTarget,但是调用完后会清除,所以必须重设。
 7.normal等信息最好来自depthTex采样,而不是vertex传过来,因为depthTex已经处理过了是否有bump等情况,会方便点。
 8.RenderWithShader计算的参数来自于原材质的shader,需要声明原变量就可以用,但是在计算主角的时候 我们直接采样RT 就是用pro_uv去采样参与计算、DiffuseRT经ColorBuffer.cg计算里边已经包含了粗糙度等基本信息 方便后续计算。
 9.关于阴影系数,正常就是0和1,对于多次采样有过度的可能有插值,对于单次采样的0-1表现可能会生硬,一般采用该系数乘以一个值转换到0-0.n或者0.n到1
场景普通阴影(低分辨率)等级分级:
0级:没有阴影处理
1-级:采样1次深度进行简单的深度对比。(当相机分辨率足够的时候 用这个最精确,主角阴影绘制)
1级:y方向采样2次深度进行对比插值,会有一点点边缘软阴影。
2级:2个方向进行4次采样,再进行2次lerp,软阴影效果会更好一点。
3级:周围9个点进行采样和多次插值,软阴影效果可以很棒了。
4级:周围12个点按一定规则去采样,再和rot随机图进行矩阵乘法组合随机转向,保证相连点不采样到同一点,最后叠加的效果是最棒的。rot采样图很重要。
--------
常用矩阵变换
Camera.worldToCameraMatrix //世界转相机矩阵 world2view
Camera.cameraToWorldMatrix //相机转世界矩阵
Camera.projectionMatrix //get投影矩阵viewToproject 视角空间到2维投影空间矩阵
transform.TransformDirection (Vector3.forward);//从自身坐标到世界坐标变换方向。返回的向量与direction有同样的长度。
transform.InverseTransformDirection(0, 0, 1);//变换方向从世界坐标到自身坐标
transform.TransformPoint(2, 0, 0);//顶点变换位置从自身坐标到世界坐标
transform.InverseTransformPoint(0, 0, 0);//顶点变换位置从世界坐标到自身坐标,相机的世界空间转view空间用这个。
GL.GetGPUProjectionMatrix(camera.projectionMatrix,true)//从Camera的投影矩阵得到GPU(图形处理器)的投影矩阵(该函数在跨平台是有用)。view2project
vec3=Matrix4x4.MultiplyPoint(vec3) //通过该矩阵把vec3点变换位置,从某一空间投影到另一个空间。
vec3=Matrix4x4.MultiplyVector(vec3) //通过该矩阵把vec3向量变换方向
Camera.RenderToCubemap(rt)//用相机渲染360全景图,其中rt要设置isCubeMap=true,isPowerOfTwo=true; 这时候修改camera的旋转是没用的,一般用RenderTexture去渲染比CubeMap消耗少、
Camera.WorldToScreenPoint(p)//wolrd to 屏幕空间 左下角0,0 右上角width,height。 z就是深度了
Camera.WorldToViewportPoint(p)//world to view空间 左下角0,0 右上角1,1
--------------------------------
平台和宏定义
Direct3D里面是左上角为原点;
OpenGL和OpenGL ES是左下角为原点。
三维坐标转换为齐次坐标 顶点把w分量设为1,向量把w分量设为0
SHADER_API_OPENGL - desktop OpenGL
SHADER_API_D3D9 - Direct3D 9
SHADER_API_D3D11 - desktop Direct3D 11
SHADER_API_GLES - OpenGL ES 2.0 (desktop or mobile), use presence of SHADER_API_MOBILE to determine.
SHADER_API_GLES3


顶点转换:
#if UNITY_UV_STARTS_AT_TOP//dx   这个不管用了 无法识别Shadow_API_Metal 苹果的新设备支持的平台
        proj.y*=-1;
#endif
不完全等价:
#if SHADER_API_GLES||SHADER_API_GLES3
//大部分安卓手机和部分ios手机,一般用该宏定义
#else
proj.y*=-1;    //一般用这个 
#endif
uv转换:
#if SHADER_API_MOBILE
#if UNITY_UV_STARTS_AT_TOP
#else
        uv.y = 1-uv.y;
#endif
#else
#if UNITY_UV_STARTS_AT_TOP
        uv.y = 1-uv.y;
#endif
#endif
---------------
矩阵乘法和投影矩阵:
M*N*A:代表A先经过N矩阵变换再经过M矩阵变换。从右往左看。
矩阵变换的顺序一般是:先缩放,再旋转再平移,Mt*Mr*Ms*P
缩放矩阵:左对角线值
旋转矩阵:左3x3矩阵
平移矩阵:第四列
Matrix4x4 cameraviewToshadowview=shadowcamera.worldToCameraMatrix*camera.cameraToWorldMatrix//从一个相机的view空间转换到另一个shadow相机的view空间
一般情况下矩阵乘法结合在CPU里边做好,在GPU阶段一般不这么做,一是效率不好,另外一个是shader的矩阵直接相乘意思和上面的不一样。
world和View之间的矩阵变换是线性的,但是一旦转到project空间就是非线性的,所以要除以w进行归一化。
常用变量计算方法:
float4 projectpos/=projectpos.w;//归一化 归一化后的NDC坐标范围在-1,1内
float2 uv=projectpos.xy*05.f+0.5f;//project(需要归一化)坐标的xy就是uv坐标的衍射 屏幕图像的uv,uv坐标也可以在顶点阶段用unity的接口ComputeScreenPos()计算,然后在frag里边去归一化就可以用了。
projectpos.z存的是深度缓存0-1 但是unity将其进行了矫正 是非线性的,会导致近处精度高 远处精度低,那么远处效果就不会好,我们ds的深度值用的是view空间的z值0-far。
float depth=-viewpos.z/_ProjectionParams.w;//view坐标的z值存的就是深度值,除以farclip衍射到0-1范围存储,z(<0表示在前方)坐标需要乘以-1衍射深度。但是使用我们自定义的viewmatrix不需要,采用不同坐标系已经定好了方向。projet的z是深度缓存。
渲染深度的时候要记得先把背景图设置成白色,或者把相机clearFlag设置SolidColor然后颜色设置成白色,保证没有被渲染的地方深度为1


关于像素深度值还原坐标:(因为每个点都要矩阵转换,所以效率不高)
float3 viewpos=mul(project_Inverse,float3(projectpos.x,projectpos.y,depth));//若depth=projectpos.z 表示把该模型的顶点还原view空间,取depth代表屏幕处于同一点显示的前后坐标点将project空间转换到view空间、一般用来背景图的深度值还原其具体坐标。
我们的计算方式:(project空间还原知道深度的像素view 坐标点 )
float3 farpos=float3(projectpos.xy,1);//该值代表project空间远裁决上和当前屏幕xy重合的最远坐标点。ps里边计算projectpos的时候如果用系统提供的矩阵一定要在dx下projectpos.y=1-projectpos.y我们自己的矩阵好像是有转换过的。
Vector3 _FarCorner;//c#里边计算的相机的farclip右上方坐标点(x,y,z),其对应的左下方就是(-x,-y,z);这两个坐标的衍射刚好对应于project空间xy的(-1,1)之间。
float3 viewfarpos=farpos*_FarCorner;//该式直接将farpos映射到view空间的远裁决上和当前屏幕xy重合的最远坐标点。
float3 viewpos=viewfarpos*depth;//利用相似三角形原理,在屏幕上重合的两个点,在相机perspective视角下呈现相似三角形,远裁剪上的点(proj.z=1)和求得点(z=depth)是一条线,直接相乘可得坐标点。


ViewPos的理解,以相机为坐标系原点的坐标,显示结果是屏幕坐标中间是(0,0),向上y变大 向下y变小,右边x变大,坐标x变小为负、不要直接与世界坐标空间的一些位置参与计算。
float4 wpos=mul(View2World,float4(vpos,1));//矩阵转换空间的时候一定要带上四维坐标,如果只是三维的mul(View2World,vpos)代表值进行了三维运算的旋转和缩放,没有计算平移。如果要用w参与转换一定要先保证矩阵转换信息里边和原始值的w正确,像我们的farcorner的w是没用的,所以转换后w是无效的。


关于我们屏幕后处理的DrawMesh:
Graphics.DrawMesh(Quad,Vector3.zero,Quaternion.identity,0)//默认情况如果MVP转换该mesh位置在相机原点。视锥范围外 被剔除。
o.pos=float4(v.verter.x*2,v.vertex.y*2,0,1);//简化直接转换到project空间的近裁剪位置。ZTest Less
o.pos=float4(v.verter.x*2,v.vertex.y*2,1,1);//简化直接转换到project空间的远裁剪位置。ZTest Greater
所以根据以上两个pos转换的位置不同,ZTest 也要不同。Quad的坐标是-0.5,0.5 乘2直接到proj空间的-1,1.然后proj.z值0-1代表近裁剪到远裁剪,proj.w=1 代表顶点。


float3 dir=mul((float3x3)Project,vdir);//vdir是float3的时候 矩阵必须转换成三维矩阵,否则可能有些平台会报错。或者把vdir转换为float4也行,点w=1,向量w=0、
float4 dir=mul(Project,float4(vdir,0));//or 


在给shader设置参数的时候:
mat.SetTexture("",_tex);//尽量用这个,只给该材质设置参数
Shader.SetGlobalTexture("",_tex)//全局变量,在cpu阶段查找所有材质,有该变量就设置该参数。效率影响在cpu 应该不是gpu
------------------------
常用光照模型和向量计算方法:
1.漫反射Lambert:float3 diff=max(0,dot(i.normal,i.lightDir));//lightDir是光源方向的单位向量,normal是法向量。C是光源颜色。反射光线的强度与表面法线和光源方向之间的夹角的余弦值成正比。取max防止颜色为负
2.Half-Lambert:float3 diff=dot(i.normal,i.lightDir)*0.5f+0.5f;//将颜色空间映射到0-1而不是采用lambert的截断,lamber模型会使没有光照的地方全黑,可使用环境光避免,HalfLambert会使没有光照的地方显示微光。大部分情况用该模型。
2.高光反射Phong:float3 spec=pow(saturate(dot(reflectdir,viewdir)),_Gross);//视角方向与反射方向点乘截取后的gloss次方。gloss光泽度越大 亮点越小,求反射光用reflect注意光源方向和入射方向相反。
4.Blinn-Phong:float3= pow(max(0,dot(worldNormal,halfDir)),_Gross); float3 halfDir=normalize(worldlightdir,ViewDir);  //对视角方向和光照方向相加再归一化得到halfDir作为参数,再来和法向量点乘 再power。高光看起来更亮更大一些,大部分情况用这个模型
3.输出颜色:float4 col=(diff+spec+ambient)*C
3.光源方向:常说的光源(光照)方向一般指点到光源的方向,而不是入射光线的方向。_WorldSpaceLightPos0.xyz,.w=0表示平行光,.w=1表示点光源或聚光灯,当然不同引擎可能存的方向也不一样,但是unity是这样定义的。
4.视角方向:float worldViewDir=normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); //unity API: UnityWorldSpaceViewDir(worldPos.xyz)一般指点到相机的视角方向
5.View空间视角放向:float3 viewdir=viewpos;//view空间的坐标值等于相机到该点的视角方向。
5.反射方向:float3 reflect = reflect(o.lightDir, o.worldNormal);//入射光线(不是光源方向),表面法线,一般用来求采样cubemap或者天空盒采样的反射信息。场景中的实时反射一般采用单独渲染反射纹理, 把坐标进行变换后合并。
6.视角光线的反射方向:float3 o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);//用视角方向的反向量 反求入射光线(反向),视角方向求视角光线的反射方向,用该反射方向去采样纹理。
7.折射方向: float3 refract  = refract(normalize(o.lightDir), normalize(o.worldNormal), _RefractRatio);//入射光线单位向量,表面法线单位向量,介质比
8.视角光线的折射方向:float3  worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);//用视角方向的反向量 反求折射光线(反向)
9.菲涅尔反射:float fresnel=pow(1-saturate(dot(worldViewDir,normal)),4)//视角方向和法线方向,他们之间的夹角越小,fresnel越弱 反射越越小 折射越强。另外也有反射和漫反射之间fresnel插值公式。为了效率可不执行power操作。
 float3 finalcol=fresnel*reflect+(1-fresnel)*refract;//fresnel插值反射和折射,用来计算反射和折射之间根据视角方向各种占的比重
10:光照衰减公式:float atten= 1/(_WorldSpaceLightPos0.xyz,wpos.xyz);//衰减物理公式与距离的平方成反比,有时候也用距离反比函数做衰减。 
10.高度雾:float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);然后把该值和颜色进行插值。有线性雾,指数雾,指数平方雾。雾对噪声图进行动态采样,可以实现动态雾效的效果。
11.PBR基于物理的着色:输入参数 粗糙度和金属性,ds的完美模式下采用PBR渲染,PBR()接口输入各种方向,返回diff和spec,内部实现了金属材质曲线的方式。但是快速的前向模式下,只是计算了简单的漫反射。
   金属度大,漫反射变小,高光反射变大,金属材质高光反射率RGB分量可能不同
   金属度小,漫反射变大,高光反射变小,非金属材质高光反射率RGB分量是一样的(大多数很接近<0.05, 0.05, 0.05>)
   粗糙度大:光滑变暗,漫反射增多,镜面反射变小
   粗糙度小:越光滑,镜面反射效果增多 漫反射变小
公式:float R=saturate(1-_SmoothBase)
 float3 diff=1/PI *max(0,dot(lightdir,normal))*saturate(1-power((1-R*R)/(1+R*R),2)); 
float h=normalize(worldlightdir,ViewDir);
float nh=saturate(dot(normal,h));
float3 spec=R*R*R*R/power((nh*nh*(R*R*R*R-1)+1),2)*(1+meatal*3)/4PI
12.次表面散射其中一种实现方式环绕光照:diff = ( dot(normal, lightDir) + wrap )/( 1+wrap )     //wrap为环绕参数,最后再计算该点模型的厚度作为参数相乘、输出颜色=col*diff*depthMinus;//diff乘以颜色和厚度(前后深度差)










1 0