前言
GPU的渲染流水线:
其中的逐片元操作详细流程:
像素所有权测试→裁剪测试→透明度测试→模板测试→深度测试→透明度混合
- Pixel Ownership Test:
-
- 简单来说就是控制当前屏幕像素的使用权限
- e.g.Unity中的Game和Scene窗口才会显示画面,其他窗口没有使用权限
- Scissor Test(裁剪测试):
-
- 在渲染窗口再自定义要渲染哪一部分
- 和裁剪空间一起理解,也就是只渲染能看到的部分
- e.g.只渲染Unity Game窗口的左下角小地图部分
- AlphaTest(透明度测试):
-
- 提前设置一个透明度预值
- 只能实现不透明效果和全透明效果
- e.g. 例如设置透明度a为0.5,如果片元大于这个值就通过测试,如果小于0.5就剔除掉
- Stencil Test(模板测试)
- Depth Test(深度测试)
- Blending(透明度混合):
-
- 可以实现半透明效果
完成接下来的其他一系列操作后,我们会将合格的片元/像素输出到帧缓冲区(FrameBuffer)和屏幕上。
逐片元操作是说是由管线/硬件自身规定好的,我们只能对里边的内容进行配置。
1. 模板测试 Stencil Test
1.1. 引例
模板测试的一个例子:其中0不显示,为黑色;1显示。
其他例子:
链接:https://www.patreon.com/posts/14832618
每个正方体面显示不同场景(每个面都是一个蒙版,用来来显示不同的场景)
1.2. 伪代码
- 理解:
-
- referenceValue:当前模板缓冲片元的参考值
- stencilBufferValue:模板缓冲区里的值
- comparisonFunction:做一个比较
- 结果:
-
- 如果通过,这个片元就进入下一个阶段
- 未通过/抛弃,停止并且不会进入下一个阶段,也就是说不会进入颜色缓冲区
- 总结:就是通过一定条件来判断这个片元/片元属性执行保留还是抛弃的操作
1.3. 定义
模板缓冲区-FrameBuffer
- 模板缓冲区可以为屏幕上的每一个像素点保存一个无符号整数值(通常为8位int 0-255)。这个值的意义根据程序的具体应用而定。
模板测试
- 渲染过程中,可以用这个值与预先设定好的参考值作(ReferenceValue)比较,根据结果来决定是否更新相应的像素点的颜色值。这个比较的过程就称为模板测试。
- 执行顺序:透明度测试->模板测试->深度测试。如果模板测试通过,相应的像素点更新,否则不更新。
1.4. 代码实现
stencil
{
Ref referenceValue//当前片元的参考值(0-255)
ReadMask readMask//读掩码
WriteMask writeMask//写掩码
Comp comprisonFunction//比较操作函数
Pass stencilOperation//测试通过之后进行操作
Fail stencilOperation//测试未通过进行的操作
ZFail stencilOperation//模板测试通过,深度测试未通过执行的操作
}
ComprisonFunction | |
Greater | 相当于“>”操作,即仅当左边>右边,模板测试通过,渲染像素 |
GEqual | 相当于“>=”操作,即仅当左边>=右边,模板测试通过,渲染像素 |
Less | 相当于“<”操作,即仅当左边<右边,模板测试通过,渲染像素 |
LEqual | 相当于“<=”操作,即仅当左边<=右边,模板测试通过,渲染像素 |
Equal | 相当于“=”操作,即仅当左边=右边,模板测试通过,渲染像素 |
NotEqual | 相当于“!=”操作,即仅当左边!=右边,模板测试通过,渲染像素 |
Always | 不管公式两边为何值,模板测试总是通过,渲染像素 |
Never | 不论公式两边为何值,模板测试总是失败 ,像素被抛弃 |
StencilOperation | |
Keep | 保留当前缓冲中的内容,即stencilBufferValue不变。 |
Zero | 将0写入缓冲,即stencilBufferValue值变为0。 |
Replace | 将参考值写入缓冲,即将referenceValue赋值给stencilBufferValue。 |
IncrSat | stencilBufferValue加1,如果stencilBufferValue超过255了,那么保留为255,即不大于255。 |
DecrSat | stencilBufferValue减1,如果stencilBufferValue超过为0,那么保留为0,即不小于0。 |
Invert | 将当前模板缓冲值(stencilBufferValue)按位取反 |
IncrWrap | 当前缓冲的值加1,如果缓冲值超过255了,那么变成0,(然后继续自增)。 |
DecrWrap | 当前缓冲的值减1,如果缓冲值已经为0,那么变成255,(然后继续自减) 。 |
1.5. 案例
1.5.1. 案例一
透视卡片所用Shader:
//模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1
Shader "Custom/StencilMask"
{
Properties
{
_ID("Mask ID",int)=1
}
SubShader
{
//覆盖了所有的像素,在所有物体的后面被渲染,队列设置为 "Geometry+1 "
Tags{"RenderType"="Opaque""Queue"="Geometry+1"}
//颜色遮罩,0就是什么都不输出
//可以改为RGBA(没有遮罩)、输出单通道R、G、B
ColorMask 0
ZWrite off
//模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1
Stencil
{
Ref[_ID]//当前片元的参考值(0-255)
Comp Always//比较操作函数
Pass replace//测试通过之后将参考值写入缓冲
}
Pass
{
//CGINCLUDE代码块会被插入到每一个Pass中,
//因此我们可以把Pass中的公共的字段,结构和方法等声明在CGINCLUDE代码块中。
CGINCLUDE
struct a2v
{
float4 vertex:POSITION;
};
struct v2f
{
float4 pos:SV_POSITION;
};
v2f vert(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i):SV_Target
{
return fixed4(1,1,1,1);
}
ENDCG
}
}
FallBack "Diffuse"
}
卡片中内容所用Shader:
//模板测试:以ID作为参考值,仅当左边=右边,模板测试通过
Shader "Custom/LitStencilMask"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Ramp("Toon Ramp(RGB)",2D)="white"{}//漫反射色调的渐变纹理
_ID("Mask ID",Int)=1
}
SubShader
{
//在StencilMask.shader之后被渲染
Tags{"RenderType"="Opaque""Queue"="Geometry+2"}
LOD 200
//模板测试:以ID作为参考值,仅当左边=右边,模板测试通过
Stencil
{
Ref [_ID]
Comp equal//仅当左边=右边,模板测试通过,渲染像素
}
CGPROGRAM
#pragma surface surf ToonRamp
#pragma Lighting ToonRamp exclude_path:forward
fixed4 _Color;
sampler2D _MainTex;
sampler2D _Ramp;
int _ID;
//表面函数的输入结构体
struct Input
{
float2 uv:TEXCOORD0;
};
//表面函数
//表面函数定义了表面属性,一个对象的表面属性定义了它的反射率、光滑度、透明度等值。
void surf(Input IN,inout SurfaceOutput o)
{
half4 c=tex2D(_MainTex,IN.uv)*_Color;
o.Albedo=c.rgb;
o.Alpha=c.a;
}
//卡通渲染漫反射光照函数
//光照函数会使用表面函数中设置的各种表面属性,来应用某些光照模型,进而模拟物体表面的光照效果。
inline half4 LightingToonRamp(SurfaceOutput s,half3 lightDir,half atten)
{
#ifdef USING_DIRECTIONAL_LIGHT
lightDir=normalize(lightDir);
#endif
half d=dot(s.Normal,lightDir)*0.5+0.5;//从向量映射到颜色uv
half3 rampColor=tex2D(_Ramp,float2(d,d)).rgb;//漫反射采样颜色
half4 c;
c.rgb=s.Albedo*_LightColor0.rgb*rampColor*atten*2;//albedo反照率,atten光衰
c.a=0;
return c;
}
ENDCG
}
FallBack "Diffuse"
}
实现效果:
案例的效果:
- Unity的模板缓冲区的默认值是0,默认所有物体在应用shader之前都是0。
- 渲染蒙版(mask),ID给了1,并且Pass的设置为replace,也就是说mask所在的模板缓冲区的值变成了1。因此mask外的ID值是0,mask的值是1。
- 渲染物体:场景中的物体ID给了1,只有ID相等的时候才渲染,所以只渲染Mask内所看到的部分。
1.5.2. 案例二
- 透视Quad所用Shader:
//模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1
Shader "Custom/StencilWindow"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_ID("Mask ID",int)=1
[Enum(UnityEngine.Rendering.CompareFunction)] _SComp("Stencil Comp",Float)=8//8种比较操作函数
[Enum(UnityEngine.Rendering.StencilOp)] _SOp("Stencil Op",Float)=2
}
SubShader
{
//覆盖了所有的像素,在所有物体的后面被渲染,队列设置为 "Geometry+1 "
Tags{"RenderType"="Opaque""Queue"="Geometry+1"}
//颜色遮罩,0就是什么都不输出
//可以改为RGBA(没有遮罩)、输出单通道R、G、B
ColorMask 0
ZWrite off
//模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1
Stencil
{
Ref[_ID]//当前片元的参考值(0-255)为_ID
Comp[_SComp]//比较操作函数
Pass[_SOp]//Mask测试通过之后将参考值_SOp写入缓冲
}
Pass
{
//CGINCLUDE代码块会被插入到每一个Pass中,
//因此我们可以把Pass中的公共的字段,结构和方法等声明在CGINCLUDE代码块中。
CGINCLUDE
struct a2v
{
float4 vertex:POSITION;
};
struct v2f
{
float4 pos:SV_POSITION;
};
v2f vert(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i):SV_Target
{
return fixed4(1,1,1,1);
}
ENDCG
}
}
FallBack "Diffuse"
}
- 物体所用Shader:
//模板测试:以ID作为参考值,仅当左边=右边,模板测试通过
Shader "Custom/BumpDiffuseStencil"
{
Properties
{
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_BumpMap ("Normalmap", 2D) = "bump" {}
_ID("Mask ID",int)=1
[Enum(UnityEngine.Rendering.CompareFunction)] _SComp("Stencil Comp",Float)=3
[Enum(UnityEngine.Rendering.StencilOp)] _SOp("Stencil Op",Float)=0
}
SubShader
{
Tags { "RenderType"="Opaque""Queue"="Geometry+2" }
LOD 300
//模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1
Stencil
{
Ref[_ID]//当前片元的参考值(0-255)为_ID
Comp[_SComp]//比较操作函数
Pass[_SOp]//Mask测试通过之后将参考值_SOp写入缓冲
}
CGPROGRAM
#pragma surface surf Lambert
#pragma target 3.0
sampler2D _MainTex;
sampler2D _BumpMap;
fixed4 _Color;
int _ID;
//Float _SComp;
//Float _SOp;
struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
};
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb * _Color.rgb;
o.Alpha = tex.a * _Color.a;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
}
ENDCG
}
FallBack "Legacy Shaders/Diffuse"
}
每个Quad和物体对应的ID不一样
实现效果:
1.6. 总结
- 使用模板缓冲区最重要的两个值:当前模板缓冲值(stencilBufferValue)和模板参考值(referenceValue)
- 模板测试主要就是对这两个值使用特定的比较操作:Never,Always,Less ,LEqual,Greater,Equal等等
- 模板测试之后要对模板缓冲区的值(stencilBufferValue)进行更新操作,更新操作包括:Keep,Zero,Replace,IncrSat,DecrSat,Invert等等
- 模板测试之后可以根据结果对模板缓冲区做不同的更新操作,比如模板测试成功操作Pass,模板测试失败操作Fail,深度测试失败操作ZFail,还有正对正面和背面精确更新操作PassBack,PassFront,FailBack等等
其他可实现效果:
参考资料:
- 官方文档:Unity - Manual: ShaderLab command: Stencil
- UnityShader实例09:Stencil Buffer&Stencil Test_unity shader 螺丝孔-CSDN博客
- Unity Shader: 理解Stencil buffer并将它用于一些实战案例(描边,多边形填充,反射区域限定,阴影体shadow volume阴影渲染)_unity shader stencil-CSDN博客
- Unity3D Stencil Test模板测试详解-腾讯游戏学堂
- 模板测试 - LearnOpenGL-CN
- https://www.patreon.com/posts/14832618
- Just a moment...
2. 深度测试
判断场景中物体之间的遮挡关系
例如以下由深度测试所实现的效果:
2.1. 伪代码
和模板测试的异同:
都要比较值,但是深度测试要额外写入颜色
2.2. 一些定义
2.2.1. 深度测试ZTest、深度缓冲Z-Buffer和深度写入
⭐ 深度测试ZTest
所谓深度测试ZTest,就是针对当前对象在屏幕上(更准确的说是frame buffer)对应的像素点,将对象自身的深度值与当前该像素点缓存的深度值进行比较,如果通过了,本对象在该像素点才会将颜色写入颜色缓冲区,否则不会写入颜色缓冲。
ZTest状态 | 描述 |
Greater | 深度大于当前缓存则通过 |
LEqual | 深度小于等于当前缓存则通过 |
Less | 深度小于当前缓存则通过 |
GEqual | 深度大于等于当前缓存则通过 |
Equal | 深度等于当前缓存则通过 |
NotEqual | 深度不等于当前缓存则通过 |
Always(Off) | 深度不论如何都通过 |
Never | 深度不论如何都不通过 |
⭐ 深度缓冲z-buffer
深度缓冲z-buffer就像颜色缓冲(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,存储的是当前的深度信息,对于每个像素存储一个深度值。并且通常和颜色缓冲有着一样的宽度和高度。深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的。
⭐ 深度写入ZWrite
深度写入ZWrite包括两种状态:ZWrite On与ZWrite Off,开启和关闭
- ZWrite On:当我们开启深度写入的时候,物体被渲染时针对物体在屏幕(更准确地说是frame buffer)上每个像素的深度都写入到深度缓冲区
- ZWrite Off:物体的深度就不会写入深度缓冲区。但是,物体是否会写入深度,除了ZWrite这个状态之外,更重要的是需要深度测试通过,也就是ZTest通过,如果ZTest都没通过,那么也就不会写入深度了
深度测试ZTest分为通过和不通过两种情况,ZWrite分为开启和关闭两种情况的话,一共就是四种情况:
默认情况下:
- Z Write:On
- Z Test:LEqual
- Z Buffer:无穷大
2.2.2. 渲染队列Queue
Unity内置的几种渲染队列,按照渲染顺序从先到后排序,队列数越小,越先渲染。
Unity中设置渲染队列语法:
Tags { “Queue” = “渲染队列名”}
- Unity中不透明物体的渲染顺序:从前往后
- Unity中透明物体的渲染顺序:从后往前(类似画家算法,会造成OverDraw)
2.2.3. Early-Z技术
其中的逐片元操作详细流程:
在传统的渲染管线中,ZTest是在逐片元操作的Blending阶段进行的。其中的一部分像素不会通过ZTest,它们仍然在进行深度测试之前还需要执行前面的一系列操作,这会造成性能的浪费,而Over Draw也是这么产生的。
因此现代GPU中运用了Early-Z的技术,大致的渲染管线可以描述成:
- 应用阶段(CPU)->几何阶段(顶点着色器)->early-z(提前深度测试)->光栅化阶段(片元着色器)->各种测试(深度测试,透明度测试,模板测试等)->颜色缓冲区(buffer)
如果Early-Z深度测试失败,就不必进行fragment阶段的计算了,因此在性能上会有很大的提升。
但是最终的ZTest仍然需要进行,以保证最终的遮挡关系结果正确。
- 前面的一次主要是Z-Cull,为了裁剪以达到优化的目的
- 后一次主要是Z-Check,为了检查,如下图:
2.2.4. 深度值
在深度缓冲区中包含了深度值,它介于0.0
和1.0
之间。而在视图空间中, z 值可以是投影的近平面和远平面之间的任何值。因此我们需要一些方法将这些视图空间的 z 值转换到 [0,1] 的范围内,方法之一就是线性转换 :
下图给出 z 值和其相应的深度值的关系:
然而,在实践中是几乎从来不使用这样的线性转换,而是使用了非线性的映射方法:
正确的投影特性的非线性深度方程是和1/z成正比、和z成反比的 。这样在z很小的时候是高精度(深度值变化快), z 很大的时候是低精度(深度值变化小)。
由于非线性函数是和 1/z 成正比,例如1.0 到 2.0 之间的 z 值,深度值将变为 0 到 0.5之间, 这样在z非常小的时候给了我们很高的精度。而 当z值越大,深度值变化越小,这正是我们想要的。
2.2.5. 深度冲突 Z-fighting
如果两个平面或三角形距离非常近且相互平行,那么深度缓冲区不具有足够的精度以至于无法得到哪一个更加靠前。这会导致两个平面不断切换顺序导致显示出问题。这种情况被称为深度冲突(Z-fighting)。
防止深度冲突
- 让物体之间不要离得太近,以至于他们的三角形重叠。
- 尽可能把近平面设置得远一些,这样可以提高精度。然而把近平面移动的太远会导致近处的物体被裁剪掉。所以不断调整测试近平面的值,为场景找出最好的近平面的距离。
- 放弃一些性能来得到更高的深度值的精度。大多数的深度缓冲区都是24位。但现在显卡支持32位深度值,这让深度缓冲区的精度提高了一大节。
2.3. 案例
2.3.1. 案例一
Shader "Custom/ZTest"
{
Properties
{
[HideInInspector]_MainTex("Texture",2D)="white"{}
_Color("Color",Color)=(1,1,1,1)
[Enum(off,0,On,1)]_ZWriteMode("ZWrite Mode",Float)=1
[Enum(UnityEngine.Rendering.CompareFunction)]_ZComp("ZTest Comp",Float)=4
}
SubShader
{
Pass
{
Tags{"RenderType"="Opaque""Queue"="Geometry"}
ZWrite[_ZWriteMode]
ZTest[_ZComp]
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed4 _Color;
float4 _MainTex_ST;
struct a2v
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float2 texcoord:TEXCOORD0;
UNITY_FOG_COORDS(1)
};
v2f vert(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.texcoord=TRANSFORM_TEX(v.uv,_MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag(v2f i):SV_Target
{
fixed4 col=_Color;
UNITY_APPLY_FOG(i.fogCoord,col);
return col;
}
ENDCG
}
}
FallBack "Diffuse"
}
渲染顺序
对于不透明物体,且Render Queue相等,那么从前往后渲染
ZTest为小于等于,那么深度值越小(越靠近摄像机)则通过测试
- 梳理渲染过程:
-
- 没渲染时,此时Unity的深度缓冲区默认值为无穷大
- 渲染绿色正方体:相对于默认深度缓冲区的无穷大,肯定是小于等于,所以测试通过
- 渲染蓝色正方体
-
-
- 蓝色正方体进行深度测试,深度测试同样是LessEqual,并且蓝色的深度值比绿色正方体的大。
- 结果就是:两个正方体重叠部分是大于深度缓冲区的,也就是测试不通过,所以重叠部分没有写入绿色,还是蓝色的。没有重叠部分,深度当然比无穷大小,所以写入, 渲染出来了绿色正方体未重叠的部分。
-
-
- 红色同理。
关闭绿色正方体的深度写入,即使测试通过,但是并没有写入深度。也就是说,渲染完蓝色正方体时,深度缓冲区的值还是无穷大。而蓝色正方体的深度值小于无穷大,所以先渲染。
将红色也改为总是通过,但因为是从前往后渲染,所以应该如下
蓝色正方体只在大于绿色正方体部分的才通过测试
2.3.2. 案例二
Shader "Custom/XRay"
{
Properties
{
_OutlineColor ("Outline Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
fixed4 _OutlineColor;
sampler2D _MainTex;
float4 _MainTex_ST;
struct a2v
{
float4 vertex:POSITION;
float4 texcoord:TEXCOORD;
float3 normal: NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 normal:TEXCOORD0;
float3 viewDir:TEXCOORD1;
fixed4 color:COLOR;
};
//1. 绘制外轮廓
v2f vertXRay(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.normal=v.normal;
o.viewDir=ObjSpaceViewDir(v.vertex);
float3 normal=normalize(v.normal);
float3 viewDir=normalize(o.viewDir);
float rim=1-dot(normal,viewDir);//外轮廓
o.color=_OutlineColor*rim;
return o;
}
fixed4 fragXRay(v2f i):SV_Target
{
return i.color;
}
///
struct v2f2
{
float4 pos:SV_POSITION;
float2 uv:TEXCOORD0;
};
//2. 正常绘制
v2f2 vertNormal(a2v v)
{
v2f2 o;
o.pos=UnityObjectToClipPos(v.vertex);
o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 fragNormal(v2f2 i):SV_Target
{
return tex2D(_MainTex,i.uv);
}
ENDCG
/
//绘制外轮廓
Pass
{
Tags{"RenderType"="Transparent" "Queue"="Transparent"}
Blend SrcAlpha One
ZTest Greater//核心:当处于其他物体后面时绘制外轮廓
Zwrite Off
Cull Back
CGPROGRAM
#pragma vertex vertXRay
#pragma fragment fragXRay
ENDCG
}
//正常绘制
Pass
{
Tags{"RenderType"="Opaque"}
ZTest LEqual//正常遮挡关系
Zwrite On
CGPROGRAM
#pragma vertex vertNormal
#pragma fragment fragNormal
ENDCG
}
}
FallBack "Diffuse"
}
2.3.3. 案例三
Shader "Unlit/ParticleZTest"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
//对默认Unlit Shader做以下修改
Tags { "RenderType"="Opaque""Queue"="Transparent" }//渲染队列改为透明物体的渲染队列
LOD 100
ZWrite Off//无遮挡,对于透明物体,是有相互叠加关系的,所以关掉写入
ZTest LEqual //如果透明物体前有不透明物体,此时透明物体看不到;如果透明物体后面有不透明物体,此时透明物体可以看到。
Blend One One//加法混合,叠加效果的显示
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
----->
另一个弹簧粒子动画代码,不知道为什么没有效果
Shader "Custom/ParticleAnimation"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_TimeOffset("Noise Offset",Range(0,100))=0.0
}
SubShader
{
Tags { "RenderType"="Opaque""Queue"="Transparent" }
LOD 100
Blend One One
ZWrite Off//无遮挡
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//实现模糊效果
#pragma multi_compile_fog
sampler2D _MainTex;
float4 _MainTex_ST;
float _TimeOffset;
struct a2v
{
float4 vertex:POSITION;
fixed4 color:COLOR;
float3 uv:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 texcoord:TEXCOORD0;
UNITY_FOG_COORDS(1)
fixed4 color:COLOR;
};
v2f vert(a2v v)
{
v2f o;
//动画定义
float sineFrequency=5.0;
float sineAmplitude=4.0;
float time=_Time.y+_TimeOffset;
float sineOffset=sin(time*sineFrequency)*sineAmplitude;
float agePercent=v.uv.z;
float3 vertexOffset=float3(0,sineOffset*agePercent,0);
v.vertex.xyz+=vertexOffset;
o.pos=UnityObjectToClipPos(v.vertex);
o.color=v.color;
o.texcoord.xy=TRANSFORM_TEX(v.uv,_MainTex);
o.texcoord.z=v.uv.z;
UNITY_TRANSFER_FOG(o,o.pos);
return o;
}
fixed4 frag(v2f i):SV_Target
{
fixed4 col=tex2D(_MainTex,i.texcoord);//纹理颜色
col*=i.color;//和粒子颜色相乘
float particleAgePercent=i.texcoord.z;
fixed4 colorRed=fixed4(1,0,0,1);
col=lerp(col,colorRed*col.a,particleAgePercent);//从纹理颜色插值为红色
UNITY_APPLY_FOG(i.fogCoord,col);//应用模糊效果
return col;
}
ENDCG
}
}
}
2.4 总结
- 最重要的两个值:当前深度缓冲区的值(ZBufferValue)和深度参考值(ReferenceValue)。通过比较操作还实现理想的渲染效果
- Unity中的渲染顺序:先渲染不透明物体(从前到后),再渲染透明物体(从后往前)
- Unity中的默认条件:
-
- ZWrite:On
- Ztest:LessEqual
- 渲染队列:Geometry(2000)
- 通过对ZWrite和ZTest的相互组合配置来控制半透明物体的渲染(关闭深度写入,快开启深度测试,透明度混合)
- 引入Early-Z之后深度测试相关的内容(Z-Cull、Z-Check)
- 深度缓冲区中存储的深度值为[0,1]的非线性值
2.5. 深度测试的扩展用法
- 基于深度的着色,如:湖水的效果
- 阴影贴图(shadowma),比较摄像机空间和灯光空间的深度值得到阴影范围
- 透明物体、粒子渲染
- 透视X-Ray效果
- 切边效果
参考资料:
- Unity Shader-渲染队列,ZTest,ZWrite,Early-Z_unity shader ztest zclip-CSDN博客
- 深度测试 - LearnOpenGL-CN
- ShaderLab: Culling & Depth Testing - Unity 手册
- unity-shader深度测试-透视xray_全景图片转透视图片的shader-CSDN博客
- Unity Toon Water Shader Tutorial at Roystan
- 《shader入门精要》
- 《Unity ShaderLab 开发实战详解》
3. 作业
待补充...