1.获得renderTexture上的4个角的近裁面位置
cam = GetComponent
Matrix4x4 inverseViewProjectionMatrix = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true);
inverseViewProjectionMatrix *= cam.worldToCameraMatrix;
inverseViewProjectionMatrix = inverseViewProjectionMatrix.inverse;
Vector3 leftBottom = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(-1, -1, 1));
Vector3 rightBottom = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(1, -1, 1));
Vector3 leftTop = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(-1, 1, 1));
Vector3 rightTop = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(1, 1, 1));
2.获得renderTexture上的4个角的远裁面位置[相机空间的位置]
cam = GetComponent
Matrix4x4 inverseViewProjectionMatrix = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true);//根据相机的投影矩阵计算GPU投影矩阵
inverseViewProjectionMatrix *= cam.worldToCameraMatrix;//unity_MatrixVP
inverseViewProjectionMatrix = inverseViewProjectionMatrix.inverse;//unity_MatrixVP矩阵的逆矩阵
Vector3 leftBottom = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(-1, -1, 0));
Vector3 rightBottom = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(1, -1, 0));
Vector3 leftTop = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(-1, 1, 0));
Vector3 rightTop = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(1, 1, 0));
3. GL.GetGPUProjectionMatrix
GL.GetGPUProjectionMatrix函数获得差异处理后的投影矩阵,并且远裁面深度为0,近裁面深度为1。
Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(mCamera.projectionMatrix, true);
unity_MatrixVP = projectionMatrix * mCamera.worldToCameraMatrix;
DX左上角为(0,0)且值域为(0,1),GL左下角为(0,0)且值域为(-1,1)
GL.GetGPUProjectionMatrix:用于处理DX和GL的坐标差异性。
这个函数,当使用DX渲染的时候(底层统一用的是Opengl方式的)就是把DX 的Z轴,从-1,-1映射会(0,1)。y轴进行翻转了。
解释原文链接:https://blog.csdn.net/liuwumiyuhuiping/article/details/52524317
5.shdaer看属性数据,可以通过帧调试窗口看
6.矩阵
E00~E03
E10~E13
...
...
7.shader中获取长度
length(pos1 - pos2)
8.多一个drawcall一般来讲比在shader中进行复杂的运算更为节省性能
9.
[loop]
[unroll]
[Header(test)] 括号内即为头标题的显示文字 不要加引号,不支持中文
[NoScaleOffset] 隐藏贴图的Tilling和Offset选项
[Toggle] 模拟开关,0为假,1为真,同[MaterialToggle]
10.
static int _MaxRayLength = Shader.PropertyToID("_MaxRayLength");
11.
//当前激活的渲染目标
//获取当前状态的RenderTexture(对应于OnRenderImage的source)
UnityEngine.Rendering.BuiltinRenderTextureType.CurrentActive
12.CommandBuff将光源的shadowmap贴到屏幕上
public Light ml;
public RenderTexture rt;
CommandBuffer buf = null;
buf = new CommandBuffer();
buf.SetShadowSamplingMode(BuiltinRenderTextureType.CurrentActive, ShadowSamplingMode.RawDepth);
RenderTargetIdentifier id = new RenderTargetIdentifier(rt);
buf.Blit(BuiltinRenderTextureType.CurrentActive,id );
ml.AddCommandBuffer(LightEvent.AfterShadowMap, buf);
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(rt,destination);
}
14.
_material.EnableKeyword("HEIGHT_FOG");
_material.DisableKeyword("HEIGHT_FOG");
15 两向量之间夹角弧度计算,当direction1 和direction2 都是单位向量时
radian=Acos(Dot(direction1,direction2))
Mathf.Rad2Deg - 弧度到度转换
16.
///
/// 相机是否在点光源范围内
private bool IsCameraInPointLightBounds()
{
float distanceSqr = (_light.transform.position - Camera.current.transform.position).sqrMagnitude;
float extendedRange = _light.range + 1;
if (distanceSqr < (extendedRange * extendedRange))
return true;
return false;
}
///
/// 相机是否在Spot光源范围内
private bool IsCameraInSpotLightBounds()
{
// check range
float distance = Vector3.Dot(_light.transform.forward, (Camera.current.transform.position - _light.transform.position));
float extendedRange = _light.range + 1;
if (distance > (extendedRange))
return false;
// check angle
float cosAngle = Vector3.Dot(transform.forward, (Camera.current.transform.position - _light.transform.position).normalized);
if ((Mathf.Acos(cosAngle) * Mathf.Rad2Deg) > (_light.spotAngle + 3) * 0.5f)
return false;
return true;
}
17.cos 和acos
Dot( direction1,direction2)=Cos(radian)*Length(direction1)*Length(direction2)=direction1.x*direction2.x+direction1.y*direction2.y
Cos(radian)=(direction1.x*direction2.x+direction1.y*direction2.y)/(Length(direction1)*Length(direction2)
radian=Acos((direction1.x*direction2.x+direction1.y*direction2.y)/(Length(direction1)*Length(direction2)))
radian=Acos((direction1.x*direction2.x+direction1.y*direction2.y)/(Length(direction1)*Length(direction2)))
float radian=Mathf.Acos(Mathf.Dot(direction1,direction2)) -> 得到弧度
float angle=Mathf.Acos(Mathf.Dot(direction1,direction2)) * Mathf.Rad2Deg -> 得到角度
18.投影向量
Vector3.Project(toP_foDirect, toP_toDirect);
public static Vector3 Project(Vector3 vector, Vector3 onNormal)
{
//pow(onNormal, 2)
float num1 = Vector3.Dot(onNormal, onNormal);
if ((double) num1 < (double) Mathf.Epsilon)
return Vector3.zero;
float num2 = Vector3.Dot(vector, onNormal);
return new Vector3(
onNormal.x * num2 / num1,
onNormal.y * num2 / num1,
onNormal.z * num2 / num1
);
}
19.OnPreRender和OnPostRender代替OnRenderImage
private void OnPreRender()
{
if(QualitySettings.antiAliasing == 0)
cameraRenderTex = RenderTexture.GetTemporary(Screen.width, Screen.height, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default);
else
cameraRenderTex = RenderTexture.GetTemporary(Screen.width, Screen.height, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default, QualitySettings.antiAliasing);
mainCamera.targetTexture = cameraRenderTex;
}
private void OnPostRender()
{
Graphics.Blit(cameraRenderTex, null as RenderTexture);
RenderTexture.ReleaseTemporary(cameraRenderTex);
}
20.射线追踪
https://zhuanlan.zhihu.com/p/21425792
简单的for循环:
//ro视线起点,rd是视线方向
vec3 raymarch(vec3 ro, vec3 rd)
{
const int stepNum= 100;
//光源强度
const float lightIntense = 100.;
//推进步幅
float stepSize= 250./stepNum;
vec3 light= vec3(0.0, 0.0, 0.0);
//光源位置
vec3 lightPos = vec3(2.,2.,.5);
float t = 1.0;
vec3 p = vec3(0.0, 0.0, 0.0);
for(int i=0; i
vec3 p = ro + t*rd;
//采样点光照亮度
float vLight = lightIntense /dot(p-lightPos,p-lightPos);
light + =vLight;
//继续推进
t+=stepSize;
}
return light;
}
积分求解
float InScatter(vec3 start, vec3 rd, vec3 lightPos, float d)
{
vec3 q = start - lightPos;
float b = dot(rd, q);
float c = dot(q, q);
float iv = 1.0f / sqrt(c - b*b);
float l = iv * (atan( (d + b) * iv) - atan( b*iv ));
return l;
}
21.
特定关键字
in , out , inout,
uniform (被修饰的变量从外部传入), const
Vertex Shader的输入:appdata
POSITION, NORMAL, BINORMAL, BLENDINDICES, BLENDWEIGHT, TANGENT, PSIZE,
TEXCOORD0 ~ TEXCOORD7, SV_VertexID
Vertex Shader的输出, 也就是Pixel Shader的输入 v2f
POSITION, PSIZE, FOG,COLOR0 ~ COLOR1, TEXCOORD1 ~ TEXCOORD7,SV_POSITION
22.SV_VertexID
struct VSInput
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
uint vertexId : SV_VertexID;
};
23.移位操作符只能作用在int上
int2 a = int2(0.0,0.0);
int2 b = a>>1;
如果使用如下代码,会出现错误提示信息
float2 a = int2(0.0,0.0);
float2 b = a>>1;
24.三目运算符
if(a < 0){b = a}
else{c = a}
等价
a < 0 ) ?(b = a) :( c = a); expr2 和 expr3 必须是与 expr1 长度相同的向量
25.in out inout
in : 修辞一个形参只是用于输入,进入函数体时被初始化,且该形参值的改变不会影响实参值,这是典型的值传递方式。
out : 修辞一个形参只是用于输出的,进入函数体时并没有被初始化,这种类型的形参一般是一个函数的运行结果;
inout : 修辞一个形参既用于输入也用于输出,这是典型的引用传递。
举例如下:
void myFunction(out float x); // 形参 x ,只是用于输出
void myFunction(inout float x); // 形参 x ,即用于输入时初始化,也用于输出数据
void myFunction(in float x); // 形参 x ,只是用于输入
void myFunction(float x); / 等价与 in float x ,这种用法和 C/C++ 完全一致
也可以使用 return 语句来代替 out 修辞符的使用
26.
使用几何体着色器(#pragma geometry)将编译目标设置为4.0。
使用曲面细分着色器(#pragma hull或#pragma domain)将编译目标设置为4.6。
#pragma target 3.0:派生指令,纹理LOD采样,10个插值器,允许使用更多的数学/纹理指令。
27.
sampler2D_half - 低精度采样器
纹理包含HDR,颜色,则可能要使用半精度采样器
28.
sampler2D _MainTex;
half4 color = tex2D(_MainTex, uv);
unity允许声明贴图和采样器时使用DX11风格的HLSL语法,用一个特殊的命名惯例来将他们匹配起来;拥有名字为“sampler”+贴图名字 的采样器会对这个纹理进行取样
Texture2D _MainTex;
SamplerState sampler_MainTex; // "sampler" + “_MainTex”
half4 color = _MainTex.Sample(sampler_MainTex, uv);
29.替换着色器
camera.SetReplacementShader (EffectShader, "RenderType");
相机上着色器所有的有"RenderType"="Opaque"的SubShader,会被EffectShader的"RenderType"="Opaque"的subShader替换,SomethingElse同理替换
Shader "EffectShader" {
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
...
}
}
SubShader {
Tags { "RenderType"="SomethingElse" }
Pass {
...
}
}
...
}
30.定点翻转
Direct3D的:顶部的坐标为0,向下则增加。这适用于Direct3D,Metal和控制台。
OpenGL:底部的坐标为0,向上增大。这适用于OpenGL和OpenGL ES。
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
uv.y = 1-uv.y;
#endif
31.
为了使Shaders在所有平台上都能正常工作,某些Shader值应使用以下语义:
顶点着色器输出(剪切空间)位置:SV_POSITION。有时,着色器会使用POSITION语义来使着色器在所有平台上都能正常工作。请注意,这不适用于Sony PS4或具有镶嵌效果。
片段着色器输出颜色:SV_Target。有时,着色器会使用COLOR或COLOR0使着色器在所有平台上正常工作。请注意,这在Sony PS4上不起作用。
32.lod
Unity中的内置着色器通过以下方式设置其LOD
VertexLit着色器= 100 VertexLit kind of shaders = 100
贴花,反射性顶点光= 150 Decal, Reflective VertexLit = 150
漫射= 200 Diffuse = 200
漫反射细节,反射凹凸未照明,反射凹凸VertexLit = 250 Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
凹凸,镜面反射= 300 Bumped, Specular = 300
凹凸镜面反射= 400 Bumped Specular = 400
视差= 500 Parallax = 500
视差镜面反射= 600 Parallax Specular = 600
33.纹理数组
_MyArr ("Tex", 2DArray) = "" {}
#pragma require 2darray
34.Visual Studio调试着色器
https://docs.unity3d.com/Manual/SL-DebuggingD3D11ShadersWithVS.html
35.深度专题
https://www.jianshu.com/p/80a932d1f11e
35.1 开启
Camera.main.depthTextureMode = DepthTextureMode.Depth;
35.2 声明
sampler2D _CameraDepthTexture;
35.3 访问
//1.如果是后处理,可以直接用uv访问
//vertex
//当有多个RenderTarget时,处理UV翻转问题
#if UNITY_UV_STARTS_AT_TOP //DirectX之类的
if(_MainTex_TexelSize.y < 0)
o.uv.y = 1 - o.uv.y; //满足上面两个条件时uv会翻转,因此需要转回来
#endif
//fragment
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv));//获取像素的深度
//2.其他:利用投影纹理采样
//vertex
o.screenPos = ComputeScreenPos(o.vertex);
//fragment
float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos));
float linear01Depth = Linear01Depth(depth); //转换成[0,1]内的线性变化深度值
float linearEyeDepth = LinearEyeDepth(depth); //转换到视野空间
35.4 NDC坐标反推世界坐标,利用VP矩阵重建世界坐标
35.4.1
// _InverseViewProjectionMatrix UNITY_MATRIX_VP矩阵的逆矩阵
float worldPos = mul(_InverseViewProjectionMatrix, float3(i.uv * 2 - 1, depth));//通过NDC坐标反推世界坐标
35.4.2
unity中
Matrix4x4 currentVP = VPMatrix;
Matrix4x4 currentInverseVP = VPMatrix.inverse;
mat.SetMatrix("_CurrentInverseVP", currentInverseVP);
Graphics.Blit(source, destination, mat);
shader中
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv.zw));
float4 NDC = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, depth * 2 - 1, 1); //NDC坐标
float4 WorldPosWithW = mul(_CurrentInverseVP, NDC);//VP逆矩阵乘以NDC坐标得到世界坐标
float4 WorldPos = WorldPosWithW / WorldPosWithW.w; //公式推导可知需除以w,参考:https://www.cnblogs.com/sword-magical-blog/p/10483459.html
//因为经过了 投影矩阵, 分量w 就不在为1, 所以要求得具体的世界坐标, 就需要 除以 分量w 的影响
35.4.3 利用视椎方向向量重建
unity中
float halfHeight = near * tan(fov/2);
float halfWidth = halfHeight * aspect;
Vector3 toTop = up * halfHeight;
Vector3 toRight = right * halfRight;
Vector3 toTopLeft = forward + toTop - toRight;
Vector3 toBottomLeft = forward - toTop - toRight;
Vector3 toTopRight = forward + toTop + toRight;
Vector3 toBottomRight = forward - toTop + toRight;
toTopLeft /= cam.nearClipPlane;//视锥体近平面的四个点
toBottomLeft /= cam.nearClipPlane;
toTopRight /= cam.nearClipPlane;
toBottomRight /= cam.nearClipPlane;
Matrix4x4 frustumDir = Matrix4x4.identity;
frustumDir.SetRow(0, toBottomLeft);
frustumDir.SetRow(1, toBottomRight);
frustumDir.SetRow(2, toTopLeft);
frustumDir.SetRow(3, toTopRight);
mat.SetMatrix("_FrustumDir", frustumDir);
shader中
//vertex
int ix = (int)o.uv.z;
int iy = (int)o.uv.w;
o.frustumDir = _FrustumDir[ix + 2 * iy];
//fragment
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv.zw));
float linearEyeDepth = LinearEyeDepth(depth);
float3 worldPos = _WorldSpaceCameraPos + linearEyeDepth * i.frustumDir.xyz;
35.5 ComputeScreenPos和COMPUTE_EYEDEPTH
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.scrPos = ComputeScreenPos(o.pos);//将返回片段着色器的屏幕位置。 ComputeScreenPos 的参数是剪裁空间下的位置
COMPUTE_EYEDEPTH(o.scrPos.z);//计算顶点摄像机空间的深度:距离裁剪平面的距离,线性变化;
35.6 相交判断
//vertex
o.eyeZ = COMPUTE_EYEDEPTH(o.eyeZ);
o.screenPos = ComputeScreenPos(o.vertex);
//fragment
float screenZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
float halfWidth = _IntersectionWidth / 2;
float diff = saturate(abs(i.eyeZ - screenZ) / halfWidth); //除以halfWidth来控制相交宽度为_IntersectionWidth
fixed4 finalColor = lerp(_IntersectionColor, col, diff);//相交程度越高的越偏_IntersectionColor
return finalColor;
35.7
深度图的使用时比较耗费批次的, 场景中的 不透明 ( opaque ) 物体需要渲染两遍, 第一遍 ( 也就是多出来的那一倍 ) 渲染是为了的得到深度纹理.
对于自身带有 ShadowCaster Pass 或者 FallBack 中含有,并且 Render Queue 小于等于 2500 的渲染对象才会出现在深度纹理中
即使关了 深度写入 ZWrite Off , 深度图 还是有这个对象的深度信息
深度图 不等于 深度缓存
深度图是提前把 不透明 ( opaque ) 物体全都渲染了一遍从而得到的
深度缓存 是在渲染每一个物体是根据给定的 深度比较条件 从而写进去的值.
_CameraDepthTexture 中只保存了场景中不透明物体的深度信息,因此这个时候无法从CameraDepthTexture 中获取 透明队列 的深度信息,可在vert中用COMPUTE_EYEDEPTH计算深度
COMPUTE_EYEDEPTH 计算该片段在视野空间下的z
LinearEyeDepth,可以用在 物体a 需要与场景的 深度值 作比较, 进而判断出是否相交. 此时需要保证 物体a 不会渲染都深度图中, 不然不能做比较.
Linear01Depth,把 深度值 约束在 [0, 1] ,与材质暴露出来的控制值做比较
渲染先后由队列的数值决定,越小则优先渲染
而不是透明的一定显示在最前,将透明物体队列设置为1000,不开启深度写入时,透明物体在不透明物体前,也会被不透明物体遮挡
不开启深度时,透明物体在不透明物体前,不透明不会被透明遮挡
透明物体写入深度,不写入颜色
参考代码,原文:https://blog.csdn.net/yangxuan0261/article/details/90182143
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
// 仅仅是把模型的深度信息写入深度缓冲中
// 从而剔除模型中被自身遮挡的片元
Pass {
// 开启深度写入
ZWrite On
// 用于设置颜色通道的写掩码(Wirte Mask)
// ColorMask RGB|A|0|(R/G/B/A组合)
// 当为0时意味着该Pass不写入任何颜色通道,也就不会输出任何颜色
ColorMask 0
}
35.8 颜色写入掩码
ColorMask RGB 可写入RGB的颜色
ColorMask 0意味着不输出任何颜色
35.9 深度偏移
Offset 3000,0
36.后处理可直接用uv取深度,其他情况不可以
37.垂直雾
float fogDensity = (worldPos.y - _StartY) / (_EndY - _StartY);
fogDensity = saturate(fogDensity * _FogDensity);
fixed3 finalColor = lerp(_FogColor, col, fogDensity).xyz;
38.边缘检测
38.1 视角和法线点乘
38.2 取当前像素的附近4个角,分别计算出两个对角的深度值差异,将这两个差异值相乘就得到我们判断边缘的值。缺少法线效果不太好
//vertex
//Robers算子 取四个角的uv
o.uv[1] = uv + _MainTex_TexelSize.xy * float2(-1, -1);
o.uv[2] = uv + _MainTex_TexelSize.xy * float2(-1, 1);
o.uv[3] = uv + _MainTex_TexelSize.xy * float2(1, -1);
o.uv[4] = uv + _MainTex_TexelSize.xy * float2(1, 1);
float sample1 = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv[1])));
float sample2 = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv[2])));
float sample3 = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv[3])));
float sample4 = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv[4])));
float edge = 1.0;
//对角线的差异相乘
edge *= abs(sample1 - sample4) < _EdgeThreshold ? 1.0 : 0.0;
edge *= abs(sample2 - sample3) < _EdgeThreshold ? 1.0 : 0.0;
return lerp(0, col, edge); //描边
40.ZWrite Off和ZTest Always
有ZWrite Off 和 ZTest Always RenderType 为 Transparent的物体会显示在最前方
当ZWrite为On时,ZTest通过时,该像素的深度才能成功写入深度缓存,同时因为ZTest通过了,该像素的颜色值也会写入颜色缓存。
当ZWrite为On时,ZTest不通过时,该像素的深度不能成功写入深度缓存,同时因为ZTest不通过,该像素的颜色值不会写入颜色缓存。
当ZWrite为Off时,ZTest通过时,该像素的深度不能成功写入深度缓存,同时因为ZTest通过了,该像素的颜色值会写入颜色缓存。
当ZWrite为Off时,ZTest不通过时,该像素的深度不能成功写入深度缓存,同时因为ZTest不通过,该像素的颜色值不会写入颜色缓存。
注意:
ZTest为Off时,表示的是关闭深度测试,等价于取值为Always,而不是Never!
Always是直接将当前像素颜色(不是深度)写进颜色缓冲区中;而Never指的是不要将当前像素颜色写进颜色缓冲区中,相当于消失
天空盒混合模式为one zero
41.在TtoW0,TtoW1,TtoW2中存放切线,副切线,法线,坐标点
struct appdata
{
fixed4 vertex : POSITION;
float2 uv : TEXCOORD0;
fixed3 normal:NORMAL;
float4 tangent : TANGENT;
};
struct v2f
{
fixed4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _Albedo);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
//转换矩阵本身也包含信息如:worldpos
//切线到世界坐标的 转换矩阵
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
42.
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));//视角方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//获取指向光源的方向
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));//获取光源射向该物体的方向
43.获得切线坐标下的法线
fixed3 normal = UnpackNormal(tex2D(_Normal, i.uv));
44.前向渲染和延迟渲染的区别
假设有1个光源和1000个具有光照反射的三角形在view coordinate沿着z轴正方形延伸摆放,法线与z轴平行,即所有三角形xy全相同,只有z不同,但是这里增加一个条件:摆放顺序是无序的。
屏幕上只能看到一个带光照的三角形,其他的都被挡住了。
前向渲染会这样做:
遍历1000个三角形片元
进行深度检测,没通过的忽略
通过检测的进行光照计算
更新帧缓冲区
返回1继续直到遍历结束
由于上面的要求是无序摆放,那么如果运气差一点 1000次深度检测全部都能通过,那么光照会计算1000次,可是因为只能看见最上面的,那么999次光照计算都是多余的。如果光源越多第三步的重复次数越多,整体复杂度也会越高。
延迟渲染引入了GBuffer,它会这样做:
遍历1000个三角形片元
进行深度检测,没通过的忽略
通过的将坐标、光照等信息写入GBuffer
返回1继续直到遍历结束
遍历Gbuffer
利用Gbuffer中的数据进行光照计算
更新帧缓冲区
返回5继续直到遍历结束
延迟渲染先把可以显示在屏幕上的像素点的相关参数保存下来,然后只进行了一次光照计算就实现了最终效果。这样大大节约了光照计算复杂度。每增加一个光源,只会增加一次整体的光照计算。所以延迟渲染的好处显而易见了。
缺点:GBuffer只能给屏幕上的每一个点保存一份光照数据,所以无法处理Blend
由于Gbuffer存的都是像素值,无法体现出每个像素对应的原始模型,那么也不支持多重采样抗锯齿功能
如果对Blend混合和抗锯齿有要求,那么Gbuffer可能就不太适合了
45.Buffer 参考:http://blog.csdn.net/colorapp/article/details/36899341
1. buffer分为frame buffer和render buffer两大类,其中frame buffer相当于render buffer的管理者,frame buffer object即称为FBO,常用于做离屏渲染缓冲等。render buffer则又可分为三类,color buffer / depth buffer / stencil buffer。
46.ComputeShader
c#侧
ComputeBuffer _drawArgsBuffer;
var kernel = _compute.FindKernel("Update");
_compute.SetVector("NoiseOffset", _noiseOffset);
_compute.SetBuffer(kernel, "NormalBuffer", _normalBuffer);
_compute.Dispatch(kernel, ThreadGroupCount, 1, 1);//设置xyz维度分别几个线程组,最后一个一般都是1
FindKernel函数按照名称找到Compute Shader中定义的一个运算unit,这里叫"Update"
SetFloat SetVector函数把脚本的输入和Compute Shader的输入关联在一起
SetBuffer把Compute Shader中定义的Buffer关联到外部,给其它阶段的shader提供数据
Dispatch函数是启动运算unit,这里就是启动名为"Update"的Compute Shader Unit
computeShader侧
#pragma kernel Update
RWStructuredBuffer
CBUFFER_START(Params)
float3 NoiseOffset;
CBUFFER_END
[numthreads(64, 1, 1)]//设置工作组大小
void Update(uint id : SV_DispatchThreadID)
{
int idx1 = id * 3;
int idx2 = id * 3 + 1;
int idx3 = id * 3 + 2;
//计算
}
#pragma kernel Update定义一个名为Update的运算kernel。一个kernel是数个Compute Shader unit运算模块的集合体,可以用来实现各种计算功能。
RWStructureBuffer定义了可读写的buffer,可以用来把信息传到其它渲染阶段。
CBUFFER定义了Compute Shader内部使用的固定变量,从脚本获得了输入值。
numthreds(64,1,1)]定义了线程是如何分配的,如果是(8,8,1)代表每个线程组8个线程,共有8个线程组所以一共也是64个线程。
SV_DispatchThreadID当前所处的线程ID
因为每个线程处理一个triangle三个顶点,所以顶点ID idx1 idx2 idx3 为id*3 + 0 1 2
AppendStructuredBuffer
ConsumeStructuredBuffer
RWStructuredBuffer
这三种结构的用法参看场景TestComputeShader
47.判断是否在相机裁截体内
var planes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
var isContain = GeometryUtility.TestPlanesAABB(planes, bounds);
if(isContain)
{
//in frustum..
}
或者
bool IsCanCulling(Transform tran)
{
//必要时候,摄像机的视域体的计算 放置在裁剪判断之外,避免多次坐标变换开销,保证每帧只有一次
Vector3 viewVec = Camera.main.WorldToViewportPoint(tran.position);
var far = Camera.main.farClipPlane ;
var near = Camera.main.nearClipPlane;
if (viewVec.x > 0 && viewVec.x < 1 && viewVec.y > 0 && viewVec.y < 1 && viewVec.z > near && viewVec.z < far)
return false;
else
return true;
}
45.fwidth,ddx,ddy的理解
偏导应用:
1.mipmap的核心在选择到底用那一块mipmap的level时,靠的就是偏导数。屏幕空间的贴图UV偏导数过大的时候代表贴图离我们过远,就会选择低等级的mipmap
2.根据顶点求法线
3.锐化
当代GPGPU在像素化的时候一般是以2x2像素为基本单位,那么在这个2x2像素块当中,右侧的像素对应的fragment的x坐标减去左侧的像素对应的fragment的x坐标就是ddx;下侧像素对应的fragment的坐标y减去上侧像素对应的fragment的坐标y就是ddy。ddx和ddy代表了相邻两个像素在设备坐标系当中的距离,据此可以判断应该使用哪一层的贴图LOD(如果贴图支持LOD,也就是MIPS)。这个距离越大表示三角形离开摄像机越远,需要使用更小分辨率的贴图;反之表示离开摄像机近,需要使用更高分辨率的贴图。
简单说起来,就是取相邻两个像素(物理设备)该变量的差值,ddx就是x轴的差值,ddy就是y轴的差值,对应的也就是偏导数
fwidth是相邻两像素的最大偏差值
任何依赖于像素与其邻居之间的变化率的某种度量的过滤器都可以从该功能中受益。 例子是抗锯齿,边缘检测,各向异性过滤
fwidth是求的偏导数,但因为显卡不知道解析解,所以实际上是用一阶差分替代了偏导数
链接:
https://www.zhihu.com/question/329521044/answer/717906456
http://blog.sina.com.cn/s/blog_7cb69c550102xvog.html
46 kernel 滤波
kernel的用处不仅仅是抗锯齿,还可以进行边界检测等等
47 用顶点求出法线(未验证)
normalize( cross(ddy(pos), ddx(pos)))
Unity里面显示法线如下效果:
void surf (Input IN, inout SurfaceOutput o)
{
o.Albedo = normalize(cross(ddy(IN.worldPos),ddx(IN.worldPos)));
}
48.贴图勾边锐化(对贴图颜色求偏导的例子)
void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D(_MainTex, IN.uv_MainTex);
//c += ddx(c)*2 + ddy(c)*2;这行代码开启和关闭的效果
o.Albedo = c.rgb;
o.Alpha = c.a;
}
49 对uv四舍五入
floor(uv+0.5)
50 抗锯齿
https://blog.csdn.net/candycat1992/article/details/44673819
50.1 Point Style Sampling
左四和左五效果较好
vec4 AntiAlias_None(vec2 uv, vec2 texsize) {
return texture2D(iChannel0, uv / texsize, -99999.0);
}
vec4 AntiAliasPointSampleTexture_None(vec2 uv, vec2 texsize) {
return texture2D(iChannel0, (floor(uv+0.5)+0.5) / texsize, -99999.0);
}
vec4 AntiAliasPointSampleTexture_Smoothstep(vec2 uv, vec2 texsize) {
vec2 w=fwidth(uv);
return texture2D(iChannel0, (floor(uv)+0.5+smoothstep(0.5-w,0.5+w,fract(uv))) / texsize, -99999.0);
}
vec4 AntiAliasPointSampleTexture_Linear(vec2 uv, vec2 texsize) {
vec2 w=fwidth(uv);
return texture2D(iChannel0, (floor(uv)+0.5+clamp((fract(uv)-0.5+w)/w,0.,1.)) / texsize, -99999.0);
}
vec4 AntiAliasPointSampleTexture_ModifiedFractal(vec2 uv, vec2 texsize) {
uv.xy -= 0.5;
vec2 w=fwidth(uv);
return texture2D(iChannel0, (floor(uv)+0.5+min(fract(uv)/min(w,1.0),1.0)) / texsize, -99999.0);
}
左一:AntiAlias_None对应的是最普通的纹理采样,直接使用Vertex传过来的插值后UV坐标对纹理采样,没有进行point sample。
从效果来看,放大后的效果过于模糊。
左二:AntiAliasPointSampleTexture_None对应的是普通的点采样,即我们确保采样的是UV对应的纹理上某个定点像素的颜色,这是通过对UV值四舍五入得到整数值,再加上0.5偏移做到的。
从效果来看,放大后有明显的锯齿,缩小后纹理细节仍很多。
左三:AntiAliasPointSampleTexture_Smoothstep对应用fwidth+smoothstep进行点采样,这里的smoothstep是三次插值方法。在对UV值下取整之后,加上0.5偏移,之后还会利用进行smoothstep抗锯齿。fwidth的返回值表明UV值在该点和临近像素之间的变化,这个值帮助我们判断模糊的大小范围。最后根据UV的小数部分进行模糊。
从效果上来看,这种方法的模糊程度相比于后三种来说最高(也不一定都是好事)。
左四:AntiAliasPointSampleTexture_Linear对应用fwidth+clamp进行点采样,之所以叫线性方法(Linear)是由于clamp是一个线性插值方法。同样是对UV值下取整之后,加上0.5偏移,然后使用了除法比较来计算模糊区间。clamp方法的是将模糊边界直接与0和1比较得到。
从效果上来看,效果比上一种模糊程度小一点。
左五:AntiAliasPointSampleTexture_ModifiedFractal和上一种方法很像,不同的是它直接将UV小数部分和w相除判断结果。
51.间隔划线,通常用于比较效果
if (floor(uv.x)==split || floor(uv.x)==split*2. || floor(uv.x)==split*3. || floor(uv.x)==split*4.) {
fragColor=vec4(1.); return ;
}
52.旋转uv
// rotate the uv with time
float c=cos(iTime*0.1),s=sin(iTime*0.1);
uv=uv*mat2(c,s,-s,c)*0.05;
53.gpu instance
合批的优先级是: static batching > instancing > dynamic batching.
go标记为static batching,则即使用了instancing shader,也不会生效
用了instancing shader,则 dynamic batching不会生效
#pragma multi_compile_instancing
struct appdata
{
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
UNITY_VERTEX_OUTPUT_STEREO//与VR有关,特别是VR的单程立体声渲染。如果未启用VR,则它们不执行任何操作
};
v2f vert(appdata v)
{
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_OUTPUT(v2f, o);//与VR有关,特别是VR的单程立体声渲染。如果未启用VR,则它们不执行任何操作
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);//与VR有关,特别是VR的单程立体声渲染。如果未启用VR,则它们不执行任何操作
}
54.
在 surf 和 frag 以外采样贴图只能使用 tex2Dlod
55.SubShader模板
SubShader
{
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex:POSITION;
fixed2 uv:TEXCOORD0;
};
struct v2f
{
fixed4 pos : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
return o;
}
fixed4 frag(v2f o)
{
}
ENDCG
}
56 (-1,1)->(0,1)
/w归一化后范围是(-1,1)
i.shadowCoord.xy = i.shadowCoord.xy/i.shadowCoord.w;
float2 uv = i.shadowCoord.xy;
uv = uv*0.5 + 0.5; //(-1, 1)-->(0, 1)
float depth = i.shadowCoord.z / i.shadowCoord.w;
#if defined (SHADER_TARGET_GLSL)
depth = depth*0.5 + 0.5; //(-1, 1)-->(0, 1)
#elif defined (UNITY_REVERSED_Z)
depth = 1 - depth; //(1, 0)-->(0, 1)
#endif
57.世界法线,反射使用
float3 wolrdN = UnityObjectToWorldNormal(v.normal);
o.RefDir = reflect(-WorldSpaceViewDir(v.vertex), wolrdN);
58.将法线从模型空间转换到观察空间
float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);//法线变换到视角空间
参考:https://www.cnblogs.com/flytrace/p/3379816.html
59
o.worldViewDir = _WorldSpaceCameraPos.xyz - worldPos;//得到世界空间的视线方向,顶点指向相机
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);//世界法线
60 albedo和diffuse的区别
albedo是无光贴图,diffuse是albedo加上光照形成的漫反射
61.暗部提亮
diff = (diff * 0.5 + 0.5) * atten;
62.frag中获取光照衰减
float4 frag(v2f i) : SV_Target
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//阴影值计算
之后就可以使用衰减atten了
63.
shader中法线向量为什么要乘以模型视图矩阵的逆转置矩阵 - https://blog.csdn.net/christina123y/article/details/5963679
MultiplyPoint,MultiplyVector 用于显示模型的切线和副切线时
Vector3 vertexData = vertexMatrix.MultiplyPoint (vertexs[i]);//MultiplyPoint 通过此矩阵(通用)转换位置。
Vector3 vectorData = vectorMatrix.MultiplyVector(vectors[i]);//MultiplyVector 通过此矩阵变换方向
矩阵逆的用处:
当我们将一个向量经过旋转或其他的变换后,我们想撤销这个变换,就乘以变换矩阵的逆,因为矩阵乘以它的逆等于单位矩阵,任何矩阵乘以单位矩阵都得原矩阵。
矩阵是正交的,那么它的转置就是他的逆
正交矩阵
任意列/行向量都为单元长度向量. 并相互垂直.
这意味着任意两个向量乘以该向量, 向量间的角度不会发生任何变化.
只有旋转和镜像矩阵是正交的
仅仅在观察矩阵进行旋转或者移动,我们的当前模型矩阵仍为正交矩阵
64 shader宏定义优化
两种优化方法:判断贴图是否为空控制宏定义是否启用,用暴露的属性开关控制宏定义是否启用
public class NPRShaderGUI : ShaderGUI
{
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
base.OnGUI(materialEditor, properties); // 显示默认面板
Material targetMat = materialEditor.target as Material;
bool isEnable = false;
// emission
MaterialProperty emissionMap = ShaderGUI.FindProperty("_EmissionTex", properties);
isEnable = emissionMap.textureValue != null;
if (isEnable)
{
targetMat.EnableKeyword("_EMISSION_ON");
}
else
{
targetMat.DisableKeyword("_EMISSION_ON");
}
// receive shadow
bool isRecvShadow = Array.IndexOf(targetMat.shaderKeywords, "_SHADOW_ON") != -1;
EditorGUI.BeginChangeCheck();
isRecvShadow = EditorGUILayout.Toggle("RecevieShadow", isRecvShadow);
if (EditorGUI.EndChangeCheck())
{
if (isRecvShadow)
targetMat.EnableKeyword("_SHADOW_ON");
else
targetMat.DisableKeyword("_SHADOW_ON");
}
}
}
65.普通的阴影显示
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
SHADOW_COORDS(1)
};
v2f vert (appdata v)
{
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed shadow = SHADOW_ATTENUATION(i);
col = fixed4(1,1,1,1) * shadow;
return col;
}
66.CommandBuffer的使用
void OnEnable(){
Append();
}
void OnPreRender(){
if(buffer == null)
Append();
BufferBilt();
}
void Append()
{
buffer = new CommandBuffer();
buffer.name = "Screen Space Subsurface Scattering";
GetComponent
}
void BufferBilt(){
int maskRT = Shader.PropertyToID("_MaskTex");
buffer.GetTemporaryRT(maskRT, -1, -1, 24, FilterMode.Bilinear, RenderTextureFormat.ARGB32);
buffer.Blit(BuiltinRenderTextureType.None, maskRT, CopyDepthMaterial);
buffer.SetRenderTarget(maskRT);
buffer.Blit(BuiltinRenderTextureType.None, maskRT, CopyDepthMaterial);
buffer.SetGlobalTexture("_MaskTex", maskRT);//将_MaskTex的引用设置为maskRT对应的RenderTexture
buffer.ReleaseTemporaryRT(maskRT);//释放
}
void OnDisable(){
GetComponent
}
buffer.GetTemporaryRT的参数
第四个参数是对应shader的pass,如果填写-1则所有pass都绘制
方法第二和三个参数指定了分辨率,-1为默认值,-2为一半大小分辨率,-3为1/3以此类推
67.深度相关
UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);//清除相机深度图
float4 frag (v2f i, out float outDepth : SV_Depth) : SV_Target
{
outDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.texcoord);//返回采样到的深度
return float4(0, 0, 0, 0);
}
68.ComputeScreenPos
https://www.jianshu.com/p/df878a386bec
ComputeScreenPos返回的值是齐次坐标系下的屏幕坐标值,范围是[0, w]
去除宏定义后的核心代码
inline float4 ComputeScreenPos (float4 pos)
{
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
o.zw = pos.zw;
return o;
}
shader中使用方法:
o.pos = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.pos);
从齐次坐标变换到屏幕坐标:
顶点在变换到齐次坐标后,其x和y分量的范围在[-w, w]。假设目前屏幕的宽度为width,高度为height,那么屏幕坐标的计算方法为:
screenPosX = ((x / w) * 0.5 + 0.5) * width
screenPosY = ((y / w) * 0.5 + 0.5) * height
从顶点在变换到齐次坐标的公式推出unity内置的实现
screenPosX / width = ((x / w) * 0.5 + 0.5)
screenPosY / height = ((y / w) * 0.5 + 0.5)
两边同时乘w:
screenPosX / width * w = ((x * 0.5 + w * 0.5)
screenPosY / height * w = ((y * 0.5 + w * 0.5)
为什么Unity要这么做呢?
本意是该ComputeScreenPos返回的坐标值用作tex2Dproj指令的参数值,tex2Dproj会在对纹理采样前除以w分量
也可以自己除以w分量后进行采样,但是效率不如内置指令tex2Dproj
69.Camera.projectionMatrix
做了以下步骤,投影到屏幕坐标系上
1. 用透视变换矩阵把顶点从视锥体中变换到裁剪空间的规则观察体(CVV)中
2. 使用CVV进行裁剪
3. 屏幕映射:将经过前述过程得到的坐标映射到屏幕坐标系上。
屏幕坐标系,又称为像素坐标系,屏幕像素为1920*1080,则鼠标位置在屏幕右上角的时候,打印的信息是(1920,1080,0)
视图坐标系,范围是[0,1]
70.
unity_CameraProjection是相机的投影矩阵,里面的第2行第2个元素存储的就是相机FOV的一半的正切值(tan)
71.[-1,1] => [0,1]
/2.后再+0.5
也可以
+1.后再/2.
72.shader中float2 + float代表了float2里的每个数字都加上float
例如
scrPos.xy = float2(scrPos.x, scrPos.y) + scrPos.w;
等价于
scrPos.xy = float2(scrPos.x + scrPos.w, scrPos.y + scrPos.w);
73.
在差值器,vert,frag中分别加以下三行,添加雾计算
UNITY_FOG_COORDS(1)
UNITY_TRANSFER_FOG(o,o.pos);
UNITY_APPLY_FOG(i.fogCoord, col);
74.
UsePass
Name "OUTLINE2PASS2"
UsePass "OUTLINE2PASS2"
Pass内CGPROGRAM外写别名,别名必须大写
75.2018.2.4f1的cubemap点击isReadable unity会崩溃
可以修改meta文件的m_IsReadable为1解决
76.2×2矩阵的行列式的绝对值是这两个向量组成的平行四边形的面积
所以在shader中可以通过以下代码计算点是否落在三个点组成的面内
struct Triangle {
int2 a, b, c;
int2 ab() { return b - a; }
int2 ac() { return c - a; }
int2 bc() { return c - b; }
//determinant返回矩阵的行列式
half area() { return abs(determinant(half2x2(ab().x, ab().y, ac().x, ac().y))) / 2.0; }
};
Triangle NewTriangle(int2 a, int2 b, int2 c) {
Triangle r; r.a = a; r.b = b, r.c = c;
return r;
}
//点p是否落在a,b,c组成的面内
float PointInTriangle(int2 a, int2 b, int2 c, int2 p) {
Triangle abc = NewTriangle(a, b, c);
half sabc = NewTriangle(a, b, c).area(),
spbc = NewTriangle(p, b, c).area(),
spba = NewTriangle(p, b, a).area(),
spac = NewTriangle(p, a, c).area();
return step(spbc + spba + spac - sabc, 0);
}
77.AlphaToMask On(这个设置影响MSAA抗锯齿的效果)
打开alpha-to-coverage,当使用MSAA(多重采样抗失真),alpha-to-coverage会根据像素shader的alpha结果值适当的修改多重采样覆盖遮罩。这通常比常规的alpha测试的外边框失真更少;
对于植物和经过alpha测试的shader有用
79.TRANSFER_VERTEX_TO_FRAGMENT 宏定义一个坑
这个宏定义定义在AutoLight.cginc文件中,它会与宏LIGHTING_COORDS协同工作,它会根据该pass处理的光源类型( spot 或 point 或 directional )来计算光源坐标的具体值,以及进行和 shadow 相关的计算等。
在pc平台的时候,它会去调用一个在UnityCG.cginc文件中的ComputeScreenPos方法,在使用vertex作为顶点变量时会报错,因为在内置cginclude文件中有使用v.pos
LIGHTING_COORDS(4,5) -- 初始化光照和阴影
struct v2f
{
//half4 vertex : SV_POSITION; //这样会报错
half4 pos : SV_POSITION;//应该改为这样
half2 uv : TEXCOORD0;
half3 normal : TEXCOORD1;
half3 viewDir : TEXCOORD2;
half3 lightDir : TEXCOORD3;
LIGHTING_COORDS(4, 5)
};
80.Unity Shader中获取模型中心点世界坐标的几种写法
unity的世界变化矩阵最后一列是存的Transform里的Position
float3 center = float3(unity_ObjectToWorld[0].w, unity_ObjectToWorld[1].w, unity_ObjectToWorld[2].w);
float3 center = float3(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23);
float3 center = mul(unity_ObjectToWorld , float(0,0,0,1)).xyz;
float3 center = unity_ObjectToWorld._14_24_34;
81.
Unity的矩阵转换顺序,缩放→旋转Z→旋转X→旋转Y→平移
My·Mx·Mz * 向量
对于Mz:float cz = cos(angles.z); float sz = sin(angles.z);
对于Mx:float cx = cos(angles.x); float sx = sin(angles.x);
对应My:float cy = cos(angles.y); float sy = sin(angles.y);
将这6个值带入3个矩阵,进行复杂的矩阵相乘运算后:
float4x4( //横着填充
cy * cz + sy * sx * sz,
-cy * sz + sy * sx * cz,
sy * cx,
0, //第一排末端
cx * sz,
cx * cz,
-sx,
0, //第二排末端
-sy * cz + cy * sx * sz,
sy * sz + cy * sx * cz,
cy * cx,
0, //第三排末端
0, 0, 0, 1 //第四排
);
82.instancing相关
https://acgmart.com/unity/ugp1-3/
Instancing相关的宏可以参考:
Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl
#pragma instancing_options procedural:setup
有这个定义则之后可以用unity_InstanceID直接取id
否则需要
uint instanceID : SV_InstanceID
通过查看UnityInstancing.hlsl可以看到:
unity_InstanceID = inputInstanceID + unity_BaseInstanceID
使用了Unity封装的宏以后,我们只能通过unity_InstanceID来访问per-instance数据,inputInstanceID来自于SV_InstanceID,那么unity_BaseInstanceID是什么呢?
unity_BaseInstanceID
仅用于在(同material的)多个instanced draw call之间共享instancing array。
假设场景中有100个Cube和100个Sphere,他们共享一个material,在渲染循环自动处理的情况下,一次upload 200个实例数据到GPU,用2个draw call分别绘制Cube和Sphere。第1个draw call中unity_BaseInstanceID为0,第二个draw call中unity_BaseInstanceID为100。
通常情况下unity_BaseInstanceID在每次draw call都会重置,因此它不会随帧或时间积累
83.
shader上显示枚举
Properties中
[KeywordEnum(Off, On,Back)] _IsS3("是否开启SSS", Float) = 0
Pass中
#pragma multi_compile _ISS3_OFF _ISS3_ON _ISS3_BACK
shader上显示开关
[Toggle(S_DEVELOP)] S_DEVELOP("开发者模式", Float) = 0
使用枚举设置CullMode
Properties中
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 2
Pass中
Cull [_Cull]
84.shader变体和打包shader的SVC方案
https://blog.csdn.net/RandomXM/article/details/88642534
https://blog.csdn.net/cgy56191948/article/details/103703254
#pragma multi_compile 支持定义多个关键字,其中第一个关键字为默认项
#pragma shader_feature 一般在关键字仅用于某个shader时使用
#pragma shader_feature A其实是 #pragma shader_feature _ A的简写,下划线表示未定义宏(nokeyword)。因此此时shader其实对应了两个变体,一个是nokeyword,一个是定义了宏A的
加__可以省略一个关键字,可以使用双下划线代替第一个关键字。(关键字有限制)
#pragma multi_compile __ FOO_ON
整个工程中可用的关键字数量为 256 - 60 = 194
内置的multi_compile
multi_compile_fwdbase
multi_compile_fwdadd
multi_compile_fwdadd_fullshadows
multi_compile_fog
实验变体是否被打进了资源包里
为了对比shader_feature 和multi_compile 以及shader依赖和非依赖打包,做如下实验:
#pragma shader_feature RED GREEN BLUE
将选中RED关键字的prefab打包,加载bundle和其中的prefab,显示了红色,此时改变此材质的keyword为GREEN或者BLUE,没有效果(打包时只会打包当前选中的变体)
#pragma multi_compile RED GREEN BLUE
将选中RED关键字的prefab打包,加载bundle和其中的prefab,显示了红色,此时改变此材质的keyword为GREEN或者BLUE,可以显示绿色和蓝色(打包时会打包所有变体)
结论:
shader_feature只有引用了的关键字会被打包
multi_compile所有组合的Shader变体都会被打包
Fog和LightMapping模式的变体,如果场景中没有引用不会被打进包里
只打包指定变体
项目中一般使用:SVC + multi_compile + 依赖打包
收集变体工具:
https://blog.uwa4d.com/archives/USparkle_Shadervariant.html
https://github.com/lujian101/ShaderVariantCollector
在SVC配置好需要生成的变体后,将collection与shader打在同一个包中,便能准确生成面板中所配置的shader变体。
启用关闭关键字
shader_feature,最好使用Material.EnableKeyWord和Material.DisableKeyWord来完成,这个是针对某个材质来控制关键字使用的,范围更小的,只针对某个材质的,它是一个public方法;
multi_compile,最好使用Shader.EnableKeyWord和Material.DisableKeysWord来完成,这个是做全局设置的,它是一个static方法
减少变体
可使用 #pragma skip_variants减少变体(使用IPreprocessShaders.OnProcessShader更合理)
在使用ShaderVariantCollection收集变体打包时,只对shader_feature定义的宏有意义,multi_compile的变体不用收集也会被全部打进包体
变体的数量还跟SubShader的个数相关,2个SubShader的变体数会想加
warmup优化第一次加载shader时的编译引起的卡顿
shader如果没有被渲染的话不会触发CreateGPUProgram操作
如果在加载资源之前对ShaderVariantCollection执行了warmup的话,会绘制一个看不见的三角形,之后再第一次渲染时就不会再CreateGPUProgram,就不会引起卡顿了
打包速度优化,编译时shader变体排除
Unity 2018.2引入了一个可编程的Shader变体移除管道:IPreprocessShaders.OnProcessShader,有了这个接口,我们就可以在Unity编译Shader的时候收到回调通知,我们可以实现自己的Shader变体删除逻辑,进一步减少编译时间
类似这样,这个代码还不够完整
class BuiltinShaderPreprocessor : IPreprocessShaders {
static ShaderKeyword[] s_uselessKeywords;
public int callbackOrder {
get { return 0; } // 可以指定多个处理器之间回调的顺序
}
static BuiltinShaderPreprocessor() {
s_uselessKeywords = new ShaderKeyword[] {
new ShaderKeyword( "DIRLIGHTMAP_COMBINED" ),
new ShaderKeyword( "LIGHTMAP_SHADOW_MIXING" ),
new ShaderKeyword( "SHADOWS_SCREEN" ),
};
}
public void OnProcessShader( Shader shader, ShaderSnippetData snippet, IList
for ( int i = data.Count - 1; i >= 0; --i ) {
for ( int j = 0; j < s_uselessKeywords.Length; ++j ) {
if ( data[ i ].shaderKeywordSet.IsEnabled( s_uselessKeywords[ j ] ) ) {
data.RemoveAt( i );
break;
}
}
}
}
}
前期规划
项目前期美术需定好宏定义使用规范,防止多个美术为了效果过度使用宏定义
生成变体的规则和关系
总的来说都是求交集,以下链接对着几个情况进行了说明:
ForwardBase,ForwardAdd等PassType 和 Material的Keywords之间的生成变体规则
运行时修改material中的keywords之后会调用哪个变体
1个shader在多个pass时会生成哪些变体
链接:https://blog.csdn.net/RandomXM/article/details/88642534
Shader编写规范
1。建议使用shader_feature时将定义语句写成完整模式,并且不要在一个语句中定义多个宏
如:#pragma shader_feature _ A B C,若一定要定义多个宏,请务必将其写成完整模式,不使用完整模式在切换shader时可能会与想要的效果不一致
2.若在shader中使用shader_feature,请为这个shader指定一个c#的XXXEditor,在shader的最后一个}前加上CustomEditor "XXXGUI"
3.如果需要在代码中开关宏,请使用multi_compile来定义这个宏,以免变体丢失
85.
Material mat
mat.CopyPropertiesFromMaterial(targetMat);
86.Unity贴图的sRGB选线
在linear空间下
勾选,shader会自动将读到的像素作gramma矫正,即0.45次方
不勾选,shader读到的就是原始的颜色值,相当于已经在linear下,不进行矫正
在gramma空间下(则勾选与否都按下面的这个处理)
会自动将输出颜色做一个伽马矫正,偏暗,相当于拍照对图片的自动处理。
透明通道的值不会受到伽马编码的影响,勾选与否都不会影响其线性值
写入 ColorBuffer 会做 Gamma 0.45 的校正
计算过程linear,最终输出转换到gamma
Gamma矫正例子:
A和B一样为(127,0,0),C 为(187,0,0)
A 导入选项为 Linear,不勾选 sRGB,B 勾选 sRGB,C勾选 sRGB
unity选linear
最终结果A和C都是(127,0,0),B为(55,0,0)
计算过程
A 贴图为 Linear,采样后不会被硬件 Gamma 2.2 而维持原样,Shader 计算输出到 ColorBuffer 时 Gamma 0.45 提亮,最终输出到显示器再 Gamma 2.2,最终输出颜色(127,0,0);
B 贴图为 sRGB,采样后被硬件 Gamma 2.2 为 ((127/255)^2.2)*255=55,然后 Shader 中计算输出 ColorBuffer 时 Gamma 0.45 变回到127,最终被显示器 Gamma 2.2,输出颜色(55,0,0)
86.UnityDynamicBatch 动态合批的规则及注意事项
可以简单概括为以下几条:
一般情况下,Unity仅支持对Meshes小于900顶点的物体进行Dynamic Batching,如果Shader里使用了顶点位置,法线,UV值,则仅支持300顶点以下的物体;如果使用顶点位置,法线,UV0,UV1和Tangent向量,则仅支持180顶点以下的物体。
如果两个物体的scale刚好是呈镜像的,如scale分别为(-1,-1,-1)或(1,1,1),他们不会被Dynamic Batching。
引用材质实例不同的物体不会被Dynamic Batching,即使两个物体的材质本质上没有任何不同。这句话的理解有点绕,简单举例就是说,相同的材质实例化了两份,分别被A和B引用了,那么A和B是不会被Dynamic Batching的,因为他们引用的是两个不同的实例。
拥用lightmaps的物体将不会被Dynamic Batching,除非他们指向了lightmap的同一部分。
拥有多Pass通道的Shader的物体不会被Dynamic batching。
合批之后的单个mesh的顶点数不能超过64K
86.ui相关
渲染排序说明
UI 合批规则说明
多分辨率适配方案
ui优化
87.
step(a,b) b>=a 返回1,否则返回0
88.获取灯光贴图的值
fixed3 lm = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv2));
89.没有定义的写法
#ifndef LIGHTMAP_OFF
90.切线,副切线计算
NTBYAttribute GetWorldNormalTangentBitangent(in float3 normal ,in float4 tangent)
{
NTBYAttribute o = (NTBYAttribute)0;
o.normal = UnityObjectToWorldNormal(normal);
o.tangent = normalize(mul(unity_ObjectToWorld, float4(tangent.xyz, 0.0)).xyz);
//v.tangent.w
//值为-1或者1,由DCC软件中的切线自动生成,和顶点的环绕顺序有关。
//unity_WorldTransformParams.w
//定义于unityShaderVariables.cginc中.
//模型的Scale值是三维向量,即xyz,当这三个值中有奇数个值为负时(1个或者3个值全为负时),unity_WorldTransformParams.w = -1,否则为1.
//副切线有2条分为2个方向,通过tangentSign去区别是哪一条
half tangentSign = tangent.w * unity_WorldTransformParams.w;
o.bitangent = normalize(cross(o.normal, o.tangent) * tangentSign);
return o;
}
91. 在;号后面加个\
在TEXCOORD##idx1
92.
LIGHTING_COORDS在AutoLight.cginc里定义
本质上就是一个#define指令
93.##idx中的##是占位符的意思,idx1,idx2,idx3传入的是数字,如果传入的是1,2,3,则以下代码
#define NORMAL_TANGENT_BITANGENT_COORDS(idx1,idx2,idx3) \
half3 normal : TEXCOORD##idx1;\
half3 tangent : TEXCOORD##idx2;\
half3 bitangent : TEXCOORD##idx3;
等价于
half3 normal : TEXCOORD1;\
half3 tangent : TEXCOORD2;\
half3 bitangent : TEXCOORD3;
94.TRANSFER_VERTEX_TO_FRAGMENT(o);
这一句是根据世界坐标在光源空间下的坐标 以及 阴影坐标的计算
95.相机相关矩阵
Matrix4x4 depthProjectionMatrix = depthCamera.projectionMatrix;
Matrix4x4 depthViewMatrix = depthCamera.worldToCameraMatrix;
Matrix4x4 depthVP = depthProjectionMatrix * depthViewMatrix;
Matrix4x4 depthVPBias = biasMatrix * depthVP;//缩放vp矩阵
96.软阴影 PCF(Percentage Closer Filtering)
https://zhuanlan.zhihu.com/p/45653702
均值模糊PCF,通过对附近像素多次采样求平均来实现阴影边缘抗锯齿,达到软阴影的效果。下面是个3x3的PCF:
float PCFSample(float depth, float2 uv)
{
float shadow = 0.0;
for (int x = -1; x <= 1; ++x)
{
for (int y = -1; y <= 1; ++y)
{
float4 col = tex2D(_gShadowMapTexture, uv + float2(x, y) * _gShadowMapTexture_TexelSize.xy);
float sampleDepth = DecodeFloatRGBA(col);
shadow += sampleDepth < depth ? _gShadowStrength : 1;
}
}
return shadow /= 9;
}
在预计算中用了EncodeFloatRGBA
EncodeFloatRGBA用32位保存深度,提高了深度精度
所以在采样提取的时候用对应的DecodeFloatRGBA。
97.世界坐标转为光源空间坐标的2种方法
97.1
Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(dirLightCamera.projectionMatrix, false);
Shader.SetGlobalMatrix("_gWorldToShadow", projectionMatrix * dirLightCamera.worldToCameraMatrix);
GL.GetGPUProjectionMatrix来处理不同平台投影矩阵的差异性
97.2
unity_WorldToShadow[0] - 内置的世界坐标转换到光源空间坐标
98
#pragma fragmentoption ARB_precision_hint_fastest
意思是会用低精度(一般是指fp16)进行运算
99.法线贴图的切线空间下存储发现的方式
法线编码到法线贴图中需要转换:result = (normal+1)/2;
从法线贴图解码需要转换: result = normal * 2 - 1;
传入未解码的法线,计算解码后的法线
inline fixed3 UnpackNormalRG(fixed4 packednormal)
{
fixed3 normal;
normal.xy = packednormal.xy * 2 - 1;
normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
return normal;
}
100._WorldSpaceLightPos0
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); 用来获取 指向光源的方向
_WorldSpaceLightPos0.w为0,则表示该光源为平行光,_WorldSpaceLightPos0.xyz为此平行光的方向向量。反之_WorldSpaceLightPos0.w不为0,则表示光源为点光源,_WorldSpaceLightPos0.xyz为此点光源
101.smoothstep和lerp,step,clamp
smoothstep(min,max,x):非线性插值,–2*(( x – min )/( max – min ))3 +3*(( x – min )/( max – min ))2
lerp(a,b,f):线性插值函数,返回值为(1-f)*a+b*f
step(a,x):如果x或=a返回1
clamp(x,a,a):如果xb返回b;如果在a和b之间就返回x
102.?:运算符,例如以下的test为float3,比较时是用test的xyz分别和0进行比较,得到三个1或0,再与起来作为(test > 0.0f)的结果
float3 test = float3(0,0,0.5);
fixed3 col1 = fixed3(1,1,1);
fixed3 col2 = fixed3(0,0,0);
color = (test > 0.0f) ? col1 : col2;
103.Camera.projectionMatrix
不使用默认的fieldOfView时,可以替换这个矩阵,例如平面反射,水渲染的时候:
//用逆转置矩阵将平面从世界空间变换到反射相机空间
var viewSpacePlane = reflectionCamera.worldToCameraMatrix.inverse.transpose * plane;
//计算相机和一个平面得出的哦一个斜视锥体裁剪投影矩阵,也就是远裁剪面改为平面,被平面遮挡的部分会被裁减掉
var clipMatrix = reflectionCamera.CalculateObliqueMatrix(viewSpacePlane);
reflectionCamera.projectionMatrix = clipMatrix;
104.表示平面的方程
点法式,N为平面法向量,P0为平面上一点
d = -Dot(N,P0); //原点到平面上任意点在法线上的投影长度
105.矩阵
如果一个矩阵可逆,那么这个矩阵的转置的逆等于逆的转置
如果我们已知一个View空间的平面Pv,要想将其转化到裁剪空间Pc,就需要投影矩阵M的逆转置矩阵:
Pc = Pv(M的逆)的转置
Pv = Pc((M的逆)的转置)的逆
参考:https://blog.csdn.net/puppet_master/article/details/80808486
106.推导
https://blog.csdn.net/puppet_master/article/details/54000951
描边效果,内有法线空间变换的推导
https://blog.csdn.net/puppet_master/article/details/80317178
内有投影矩阵推导
107.捕捉图像的
设置Camera加layer来拍摄RTT,然后给shader处理,如果不是所有layer都拍摄,就不会处理遮挡,如果为了遮挡所有层都拍摄效率就很低了,其实也可以通过前景、背景的深度比较来处理遮挡。
用RenderWithShader,处理遮挡,相当于场景从新渲染遍,效率低。
GrabPass,使用简单,但不是所有硬件都支持。
CommandBuffer:通过在渲染循环中插入自己渲染内容来实现,
int screenCopyID = Shader.PropertyToID("_ScreenCopyTexture");
buf.GetTemporaryRT (screenCopyID, -1, -1, 0, FilterMode.Bilinear);
buf.Blit (BuiltinRenderTextureType.CurrentActive, screenCopyID);
buf.Blit (screenCopyID, blurredID);
ReflectProbe:速度快、操作麻烦,要矫正指标需要使用射线和包围盒求交点,Unity中可以用boxProjectCubemapDirection。
后处理:类似扭曲这种不要求精准对像素,直接不拍摄了,放在后处理中来实现
108
#pragma fragmentoption ARB_precision_hint_fastest 用低精度,提升fragment着色器的运行速度
#pragma fragmentoption ARB_precision_hint_nicest 用高精度,可能会降低运行速度,增加时间
_MainTex_TexelSize表示_MainTex这张贴图一个像素/素纹的大小,是一个四元数,是 unity 内置的变量,它的值为 Vector4(1 / width, 1 / height, width, height),
UNITY_UV_STARTS_AT_TOP
表示纹理的uv坐标原点是在左下还是在左上,Direct3D 和opengl等不同平台的定义不一样,在 Direct3D 中,坐标顶部为零,向下增加。 在 OpenGL 和 OpenGL ES 中,坐标底部为零,向上增加。UNITY_UV_STARTS_AT_TOP 总是1或0,Direct3D类似平台使用1;OpenGL类似平台使用0。
v2f_simple vertBloom (appdata_img v)
{
v2f_simple o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord.xyxy;
#if UNITY_UV_STARTS_AT_TOP
//如果开启了抗锯齿,则xxx_TexelSize.y 会变成负值
if (_MainTex_TexelSize.y < 0.0)
o.uv.w = 1.0 - o.uv.w;
#endif
return o;
}
OnRenderImage函数
3d渲染已完成,通过source传入
对source的处理,并把结果整合到destination中。这个函数所在的脚本一般绑定在Camera上。此函数只有在Unity Pro版本中才能够使用。
Graphics.Blit函数
static void Blit(Texture source, RenderTexture dest);
static void Blit(Texture source, RenderTexture dest, Material mat, int pass = -1);
static void Blit(Texture source, Material mat, int pass = -1);
指定材质和shaderpass进行计算
RenderTexture.GetTemporary函数 和RenderTexture.ReleaseTemporary函数
GetTemporary获取临时的RenderTexture。ReleaseTemporary用来释放指定的RenderTexture,unity内部对RenderTexture做了池化操作
109.
表示距离的平方
Dot(vec,vec)
表示衰减,分子+1是为了让光在最接近顶点时,衰减度为1,不加1的话光照会爆掉
1/(1+Dot(vec,vec))
110.
Unity中的几种内置的渲染队列,按照渲染顺序,从先到后进行排序,队列数越小的,越先渲染,队列数越大的,越后渲染。
Background(1000) 最早被渲染的物体的队列。
Geometry (2000) 不透明物体的渲染队列。大多数物体都应该使用该队列进行渲染,也是Unity Shader中默认的渲染队列。
AlphaTest (2450) 有透明通道,需要进行Alpha Test的物体的队列,比在Geomerty中更有效。
Transparent(3000) 半透物体的渲染队列。一般是不写深度的物体,Alpha Blend等的在该队列渲染。
Overlay (4000) 最后被渲染的物体的队列,一般是覆盖效果,比如镜头光晕,屏幕贴片之类的。
RenderType
Opaque: 用于大多数着色器(法线着色器、自发光着色器、反射着色器以及地形的着色器)。
Transparent:用于半透明着色器(透明着色器、粒子着色器、字体着色器、地形额外通道的着色器)。
TransparentCutout: 蒙皮透明着色器(Transparent Cutout,两个通道的植被着色器)。
Background: 天空盒着色器。
Overlay: GUITexture,镜头光晕,屏幕闪光等效果使用的着色器。
TreeOpaque: 地形引擎中的树皮。
TreeTransparentCutout: 地形引擎中的树叶。
TreeBillboard: 地形引擎中的广告牌树。
Grass: 地形引擎中的草。
GrassBillboard: 地形引擎何中的广告牌草。
不透明物体从前往后渲,半透明物体从后往前渲
某个不透明物体的像素阶段操作较费,我们就可以控制它的渲染队列,让其渲染更靠后,这样可以通过其他不透明物体写入的深度剔除该物体所占的一些像素
Tags { "RenderType"="Opaque" "Queue" = "Geometry+1"}
111.代码设置骨骼的Optimize Game Object
https://answer.uwa4d.com/question/5c0a256a28c4e32cba3192eb
第一种方法
public static string[] NodeNames = new string[]
{
"node_head",
"node_arm",
"node_leg",
};
void OnPreprocessModel()
{
ModelImporter importer = assetImporter as ModelImporter;
importer.optimizeGameObjects = true;
//若实际骨骼中没有以下节点,可能会报错,但不影响使用
importer.extraExposedTransformPaths = EWEquipmentsBase.nodeNames;
}
第二种方法
private static readonly string[] s_BindPoints = new string[]
{
"node_head",
"node_arm",
"node_leg",
};
private Animator m_Animator = null;
private void Awake()
{
m_Animator = GetComponent
Assert.IsNotNull(m_Animator);
foreach (var bindPoint in s_BindPoints)
{
GameObject bindPointObj = new GameObject(bindPoint);
bindPointObj.layer = gameObject.layer;
bindPointObj.transform.SetParent(transform);
}
m_Animator.Rebind(); // important!
}
112
不输出A
ColorMask RGB
113.
LinearEyeDepth 负责把深度纹理的采样结果转换到视角空间下的深度值,也就是相对于相机的距离
Linear01Depth 则返回一个范围在[0,1]的线性深度值
114.V2F_SHADOW_CASTER
在没有使用SHADOWS_CUBE,或者使用了SHADOWS_CUBE_IN_DEPTH_TEX(虽然目前并不知道这两个是干什么的)时,V2F_SHADOW_CASTER等于float4 pos:SV_POTISION;
而在使用SHADOWS_CUBE并且不使用SHADOWS_CUBE_IN_DEPTH_TEX时,V2F_SHADOW_CASTER等于float4 pos:SV_POTISION;float3 vec : TEXCOORD0;
115
A和B是矩阵,AB的逆,等于 B的逆乘以A的逆
116.[Delayed]特性
用于在脚本中使float,int或string变量的属性被延迟。只有按了回车或焦点离开字段才会返回新值。
117.模版测试参数(没有通过测试的虽然不绘制,但会有overDraw)
使用模板缓冲区最重要的两个值:当前模板缓冲值(stencilBufferValue)和模板参考值(referenceValue)
模板测试主要就是对这个两个值使用特定的比较操作:Never,Always,Less ,LEqual,Greater,Equal等等。
模板测试之后要对模板缓冲区的值(stencilBufferValue)进行更新操作,更新操作包括:Keep,Zero,Replace,IncrSat,DecrSat,Invert等等。
模板测试之后可以根据结果对模板缓冲区做不同的更新操作,比如模板测试成功操作Pass,模板测试失败操作Fail,深度测试失败操作ZFail,还有正对正面和背面精确更新操作PassBack,PassFront,FailBack等等。
Stencil
{
Ref referenceValue //参考值
ReadMask readMask //读取掩码,取值范围也是0-255的整数,默认值为255,二进制位11111111,即读取的时候不对referenceValue和stencilBufferValue产生效果,读取的还是原始值
WriteMask writeMask //输出掩码,当写入模板缓冲时进行掩码操作(按位与【&】),writeMask取值范围是0-255的整数,默认值也是255,即当修改stencilBufferValue值时,写入的仍然是原始值
Comp comparisonFunction //比较函数,关键字有,Greater(>),GEqual(>=),Less(<),LEqual(<=),Equal(=),NotEqual(!=),Always(总是满足),Never(总是不满足)
Pass stencilOperation //条件满足后的处理
Fail stencilOperation //条件不满足后的处理
ZFail stencilOperation //深度测试失败后的处理
}
ReadMask,默认值为255,即对读取值不作修改
WriteMask 默认值为255,即对写入值不作修改
模板缓冲中的值默认是0
Comp Always:模板测试始终通过
Pass Replace:将参考值赋值给缓冲值
是否通过模板测试的判断方式:
if (参考值 & readMask comparisonFunction 缓冲值 & readMask) ,则像素通过,否则像素丢弃
readMask默认值为255时,简化为:if (参考值 comparisonFunction 缓冲值),则像素通过,否则像素丢弃
118.ColorMask
ColorMask 0 //不输出颜色
119.AO
AO时由于表面凹下去的地方,在光照时受到面片的遮挡,导致颜色压暗
AO只对间接光产生影响,不影响直接光否则整体会被压暗
120.参数默认值
Zwirte:是否开启深度缓存,默认为On。Zwrite On时会开启深度缓存和深度测试
Ztest:深度测试的模式,默认为Lequal(小于等于)。
Queue:渲染队列,默认为Geometry
Cull:默认是On的,也就是只渲单面,要渲双面需要Cull Off
121.
一般半透明开启深度测试,关闭深度写入
开启深度测试是因为被不透明物体遮挡的半透明物体不用显示,不开启的话不需要的半透明物体也会绘制一遍
关闭深度写入是因为被半透明的物体遮挡的不透明物体需要显示,开启了的话不透明物体就会被裁掉
开启深度测试且开启深度写入
可处理模型自身交叉情况下(例如持盾的战士,透明时盾牌不能和后面的身体混合)的渲染,缺点是:模型相对于自身是不透明的
122.植被优化
1是关闭双面渲染,Cull默认就是单面渲染,也就是不加Cull关键词
2是利用EarlyZ,代价是VertexShader部分翻倍,优点是优化了OverDraw
第一个pass通过ZTest Lqual写入最上层的深度,第二个pass使用ZTest Equal只对与深度缓冲中相同的进行处理
Name "EarlyZ"
Tags { "LightMode" = "ForwardBase" }
ColorMask 0
ZWrite On
ZTest LEqual
Name "FORWARD"
Tags { "LightMode" = "ForwardBase" }
ColorMask RGB
ZWrite Off
ZTest Equal
123.将切线空间法线图采用得到的值 转为 世界空间法线
//切线,法线,副切线放入矩阵中
float3x3 GetNormalTranform(in float3 wNormalDir, in float3 wTangentDir,in float3 wBitangentDir)
{
return float3x3(wTangentDir, wBitangentDir, wNormalDir);;
}
//传入的packednormal是从切线空间下的法线图中采用得到的值
inline fixed3 UnpackNormalRG(fixed4 packednormal)
{
fixed3 normal;
normal.xy = packednormal.xy * 2 - 1;
normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
return normal;
}
fixed3 worldNormal = o.normal;
fixed3 worldTangent = UnityObjectToWorldDir( v.tangent.xyz );
//这一句是为了解决建模时法线镜像的问题(也就是有时给模型加光照时,中间有条缝隙的问题)
//模型的左右手系存储在v.tangent.w里,unity的左右手系存储在unity_WorldTransformParams.w里,两者都需要考虑
fixed tangentSign = v.tangent.w * unity_WorldTransformParams.w;
fixed3 worldBinormal = cross( worldNormal, worldTangent ) * tangentSign;
o.T2WRow0 = float3( worldTangent.x, worldBinormal.x, worldNormal.x); // * tNormal.xyz
o.T2WRow1 = float3( worldTangent.y, worldBinormal.y, worldNormal.y);
o.T2WRow2 = float3( worldTangent.z, worldBinormal.z, worldNormal.z);
half3 _normal_val = UnpackNormalRG(e);
//如果有normal的缩放,也只能对xy缩放,只改变斜率
//half3 _normal_val = UnpackScaleNormal(e,_BumpScale);
float3x3 tangentTransform = GetNormalTranform(i.worldNormal, i.worldTangent, i.worldBinormal);
half3 normal = normalize(mul(_normal_val, tangentTransform));
优化
o.tangent = float3( worldTangent.x, worldTangent.y, worldTangent.z);
o.binormal = float3( worldBinormal.x, worldBinormal.y, worldBinormal.z);
normalVal = i.tangent * normalVal.x + i.binormal * normalVal.y + i.normal * normalVal.z ;
o.Normal = normalize(normalVal);
124.切线空间视野方向
float3 objBinormal = cross(v.normal, v.tangent.xyz) * v.tangent.w;
float3x3 objectToTangent = float3x3(v.tangent.xyz,objBinormal,v.normal);
o.tangentViewDir = mul(objectToTangent, ObjSpaceViewDir(v.vertex));
//float3x3的排布如下
// v.tangent.xyz,
// objBinormal, * dir
// v.normal
125.a是邻边,b是斜边,此处为b投影到a上的投影向量长度
dot(a,b);
126.去色
float3 desaturateIceColor = dot( iceCol, float3( 0.299, 0.587, 0.114 ));
127.视差贴图的uv计算
void DoParallaxMap(inout v2f i){
#if defined (_PARALLAX_MAP)
//切线空间视野方向
i.tangentViewDir = normalize(i.tangentViewDir);
//除以z来矫正uv偏移的向量,+0.41是防止分母太小,值太大,导致图像不正确
//除以z得到的是视线方向和地平面的交点线段的xy
i.tangentViewDir.xy /=(i.tangentViewDir.z + 0.41);
//_Parallax是视差值的缩放
i.tangentViewDir.xy *= _Parallax;
i.uv.xy += (i.tangentViewDir.xy /(i.tangentViewDir.z + 0.42) ) * height;
//以上4句与unity内置的ParallaxOffset等价
//float2 offset = ParallaxOffset(height,_Parallax,i.tangentViewDir);
//i.uv.xy += offset;
#endif
}
128.
要接收阴影需要加上这两句
Tags {"LightMode"="ForwardBase"}
#pragma multi_compile_fwdbase_fullshadows
要投射阴影需要加上这几句
Tags {"LightMode"="ShadowCaster"}
#pragma multi_compile_shadowcaster
v2f
V2F_SHADOW_CASTER;
vert
TRANSFER_SHADOW_CASTER(o)
frag
SHADOW_CASTER_FRAGMENT(i)
.杂
WorldSpaceViewDir(模型空間坐标) 返回世界坐标指向相机坐标的向量
UnityWorldSpaceViewDir(世界空間坐标) 返回世界坐标指向相机坐标的向量
//正确混合两个法线混合应该是斜率相加(凹凸感不变),而不是直接相加(凹凸感变弱)
float3 n1 = UnpackNormal( tex2D(_BumpMap,i.uv));
float3 n2 = UnpackNormal( tex2D(_BumpMap,i.uv * 64));
o.Normal = BlendNormals(n1,n2);//等价于BlendNormals normalize(half3(n1.xy + n2.xy.n1.z*n2.z));
切线空间的法线贴图范围是[0,1],解码法线贴图如下,从[0,1]->[-1,1]:
float3 n1 = UnpackNormal( tex2D(_BumpMap,i.uv));
分支语句优化
float visible = texture(VisibleTexture, index).x;
gl_Vertex.z = (visible > threshold) ? 9999 : gl_Vertex.z;
改为
float visible = texture(VisibleTexture, index).x;
visible = sign(visible - threshold) * .5 + .5; // 1=visible, 0=invisible
gl_Vertex.z += 9999 * visible; // original value only for visible
线性衰减
float distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
atten = 1.0/distance;
----------------
常见api记录
o.vertex = UnityObjectToClipPos(v.vertex);
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
o.worldNormal = UnityObjectToWorldDir(v.normal);
o.worldViewDir = UnityWorldSpaceViewDir(worldPos);
//齐次坐标系下的屏幕坐标值,范围是[0, w]
//ComputeScreenPos函数,得到归一化前的视口坐标xy
//z分量为裁剪空间的z值,范围[-Near,Far]
o.screenPos = ComputeScreenPos(o.vertex);
//COMPUTE_EYEDEPTH函数,将z分量范围[-Near,Far]转换为[Near,Far]
COMPUTE_EYEDEPTH(o.eyeZ);
o.uv = TRANSFORM_TEX(v.uv,_MainTex);
saturate(dot(worldNormal, worldViewDir))
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldViewDir = WorldSpaceViewDir(v.vertex);
o.reflectionDir = reflect(-worldViewDir, worldNormal);
struct v2f
{
float4 uv : TEXCOORD0;//xy为主纹理uv,zw为法线纹理uv
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
}
v2f vert (appdata_full v)
{
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
}
五种获取深度的方式
1.
float2 depth_uv = float2(i.uv_MainTex.x, 1-i.uv_MainTex.y); 或者 o.depth_uv = TRANSFORM_TEX(v.uv, _MainTex);
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, depth_uv);
2.float zx = SAMPLE_DEPTH_TEXTURE_LOD(_CameraDepthTexture, float4(depth_uv, 0, 0));
zx = Linear01Depth(zx);
3.v2f vert(appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.screen = ComputeScreenPos(o.pos);
COMPUTE_EYEDEPTH(o.screen.z);
o.uv_MainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
float d2 = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screen));
d2 = LinearEyeDepth(d2);
4.取光源空间下的深度
float4 lightPos = mul(unity_WorldToShadow[0], float4(worldPos, 1));
float depth = lightPos.z ;
5.
float eyeDepth = UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, i.screenPos));
eyeDepth = LinearEyeDepth(eyeDepth);
取阴影
float shadow = UNITY_SAMPLE_DEPTH(tex2Dlod(_ShadowMapTexture, float4(lightPos.xy,0,0)));
//指向光源
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos1));
//指向相机
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos1));
//从世界空间转到光源空间得矩阵
// GL.GetGPUProjectionMatrix是处理多平台得问题得,api描述是:从Camera的投影矩阵得到GPU(图形处理器)的投影矩阵(该函数在跨平台是有用)
Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(dirLightCamera.projectionMatrix, false);
Shader.SetGlobalMatrix("_gWorldToShadow", projectionMatrix * dirLightCamera.worldToCameraMatrix);
//uv.xy * scaleAndOffset.xy + scaleAndOffset.zw;
o.uv = UnityStereoScreenSpaceUVAdjust(v.texcoord, _MainTex_ST);
//uv y轴反向
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0.0)
o.uv.y = 1.0 - o.uv.y;
#endif
//UNITY_SAMPLE_DEPTH返回的值范围是[0,1]
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.screenPos));
float z = -lerp(near, far, depth);
//屏幕坐标和ndc坐标计算
o.vertex = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.vertex);
float4 ndcPos = (o.screenPos / o.screenPos.w) * 2 - 1;
// x = 1 or -1 (-1 if projection is flipped)
// y = near plane
// z = far plane
// w = 1/far plane
float4 _ProjectionParams;
// x = 1-far/near
// y = far/near
// z = x/far
// w = y/far
// or in case of a reversed depth buffer (UNITY_REVERSED_Z is 1)
// x = -1+far/near
// y = 1
// z = x/far
// w = 1/far
float4 _ZBufferParams;
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 0
Cull [_Cull]
o.lightDir = WorldSpaceLightDir( v.vertex );
o.viewDir = WorldSpaceViewDir( v.vertex );
o.tangentWorld = UnityObjectToWorldDir(v.tangent.xyz);