7.ShaderLab-ShaderLab语法规则-Shader的子着色器-States渲染状态
7.1 知识点
知识回顾
SubShader子着色器基本构成为:
- Tags(渲染标签)
- States(渲染状态)
- Pass(渲染通道1)
- Pass(渲染通道2)
- …
渲染状态:
通过状态来确定渲染时的剔除方式、深度测试方式、混合方式等等内容
片元:
在渲染管线中,片元是指在光栅化阶段生成的像素或像素片段。片元是渲染管线中进行像素级别操作和计算的基本单位。每个片元代表了屏幕上的一个像素,并且具有位置信息和与之相关的属性,比如:颜色、深度值、法线等等
渲染状态的语法结构
基本语法:渲染状态 状态类型
例如:
LOD 100
渲染状态是通过 渲染状态关键词+空格+状态类型 决定的。如果存在多个渲染状态,可以通过空行隔开
剔除方式
//剔除方式
//Cull Back //背面剔除
//Cull Front //正面剔除
Cull Off //不剔除
- 默认是背面剔除
- 当背面剔除时,在Scene窗口鼠标右键wasd进入Cube内部是看不见东西的。
- 改成不剔除时,进入Cube内部可以看到四面白色(Cube内部)。因为使用了不剔除。
深度测试流程前置知识
深度测试流程如下
假设有两个立方体,他们存在遮挡关系。因为立方体都挂着对应的着色器,着色器会对他们每一个点进行转换,但其实着色器也不知道两个立方体之间的前后关系,所以需要深度测试。深度测试决定重合部分最终渲染的颜色是什么。
可以理解为每一个像素点都有一份内存区域,记录着最终要渲染的片元信息。默认为一个Zmax值,可以简单理解为离摄像机的远近。
当后面的立方体渲染时,会用后面立方体对应像素的Z1深度值和Zmax深度值进行比较.如果Z1小于Zmax,可以认为离摄像机更近,就算是通过了深度测试。
通过深度测试后,可以选择是否把Z1值写入到缓存区覆盖掉Zmax值,即将深度值写入到深度缓冲区。这一步是我们可以控制是否要进行的,可以不写入缓冲区的。
现在假设前面的立方体对应像素的深度值是Z2,Z2小于Z1,那么也会继续覆盖。
假设先把Z2存进去了,在把Z1进行比较,肯定通不过测试的,会直接舍弃该片元,不会被渲染。
注意:只要通过了深度测试,都能被渲染。渲不渲染这个片元信息只取决于是否通过深度测试。就算没有开启深度写入还是会渲染。深度写入只是用于和之后的信息进行比较。对于一些特殊效果,是可以不用存入深度缓冲区的。
假设有三个方块ABC被渲染。C如果先进行深度测试并写入,那么AB都无法通过深度测试,最终只会渲染C的颜色。
什么时候希望不进行深度写入呢?就是当C是半透明或者透明的时候。因为半透明或者透明时,还是需要用到AB的颜色,可能会对ABC的颜色进行混合再渲染,这涉及到混合流程。所以我们需要自己控制是否进行胜读写入。
注意:深度测试流程在渲染管线光栅化中的逐片元操作中进行
深度缓冲
主要作用:
- 是否写入深度缓冲
- 深度缓冲(Depth Buffer):
- 深度缓冲是一个与屏幕像素对应的缓冲区,用于存储每个像素的深度值(距离相机的距离)。
- 在渲染场景之前,深度缓冲被初始化为最大深度值,表示所有像素都在相机之外。
- 最后留在深度缓冲中的信息会被渲染
API
- ZWrite On 写入深度缓冲
- ZWrite Off 不写入深度缓冲
- 不设置的话,默认为写入
- 一般情况下,我们在做透明等特殊效果时,会设置为不写入
//深度缓冲
ZWrite On //写入深度缓冲
//ZWrite Off //不写入深度缓冲 在做透明等特殊效果时,会设置为不写入
深度测试
主要作用:
- 设置深度测试的对比方式
- 深度测试的主要目的是确保在渲染时,像素按照正确的深度(距离相机的距离)顺序进行绘制,从而创建正确的遮挡关系和透视效果。
- 在渲染场景之前,深度缓冲被初始化为最大深度值,表示所有像素都在相机之外。
- 在渲染过程中,对于每个像素,深度测试会将当前像素的深度值与深度缓冲中对应位置的值进行比较。
- 一般情况下
- 如果当前像素的深度值小于深度缓冲中的值,说明当前像素在其他物体之前,它会被绘制,并更新深度缓冲。
- 如果当前像素的深度值大于等于深度缓冲中的值,说明当前像素在其他物体之后,它会被丢弃,不会被绘制,并保持深度缓冲不变。
API
- ZTest Less 小于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
- ZTest Greater 大于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
- ZTest LEqual 小于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
- ZTest GEqual 大于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
- ZTest Equal 等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
- ZTest NotEqual 不等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
- ZTest Always 始终通过深度测试写入深度缓冲中
- 不设置的话,默认为LEqual 小于等于
一般情况下,我们只有在实现一些特殊效果时才会区修改深度测试方式,比如透明物体渲染会修改为Less,描边效果会修改为Greater等
//深度测试
//ZTest Less //小于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
ZTest Greater //大于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest LEqual //小于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中 不设置的话,默认为LEqual 小于等于
//ZTest GEqual //大于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest Equal //等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest NotEqual //不等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest Always //始终通过深度测试写入深度缓冲中
- 举个例子:改成ZTestGreater大于当前深度缓冲中的值这个渲染状态后,由于深度缓冲中的值很大,不可能大于,挂上这个shader材质的Cube就不会被渲染。在Game窗口就看不到了。
混合流程前置知识
混合流程在深度测试之后去进行的。
混合过后就会把这个颜色存入到一个颜色缓冲区中,这个颜色缓冲区其实就是一个内存空间,它会存储屏幕上的这些像素的颜色是什么样子的。
开始混合的时候,判断是否开启混合
- 开启混合的话,先得到源颜色,即该片元的颜色。再得到目标颜色,即已经存在于颜色缓冲区当中的颜色值,进行混合。混合过后就会更新颜色缓冲区当中的这个值。
- 没有开启这个混合的话,就会直接使用该片元的这个颜色值。
混合其实就是把当前的这个颜色和我们这个颜色缓冲区当中的颜色来进行一个混合计算,当计算完成过后,再重新的存入到我们的颜色缓冲区当中。
经过几轮的这样的计算下来,最终留在这个颜色缓冲区当中的这个内容,就是我们最终会在屏幕上面看到的一个颜色
在深度测试前置知识时举了ABC方块C半透明的例子,实际上用到了混合流程。
注意:混合流程也是在渲染管线光栅化中的逐片元操作中进行
混合方式
- 主要作用:
- 设置渲染图像的混合方式(多种颜色叠加混合,比如透明、半透明效果和遮挡的物体进行颜色混合)
- API
- Blend One One 线性减淡
- Blend SrcAlpha OneMinusSrcAlpha 正常透明混合
- Blend OneMinusDstColor One 滤色
- Blend DstColor Zero 正片叠底
- Blend DstColor SrcColor X光片效果
- Blend One OneMinusSrcAlpha 透明度混合
- 等等
- 不设置的话,默认不会进行混合
- 一般情况下,我们需要多种颜色叠加渲染时,就需要设置混合方式,具体情况具体处理
//混合方式
//Blend One One //线性减淡
//Blend SrcAlpha OneMinusSrcAlpha //正常透明混合
//Blend OneMinusDstColor One //滤色
//Blend DstColor Zero //正片叠底
//Blend DstColor SrcColor //X光片效果
//Blend One OneMinusSrcAlpha //透明度混合
其他渲染状态
- LOD
控制LOD级别,在不同距离下使用不同的渲染方式处理 - ColorMask
设置颜色通道的写入蒙版,默认蒙版为RGBA - 等等…
- 我们目前主要掌握剔除方式、深度缓冲、深度测试、混合方式即可
// 其他渲染状态
LOD 100 // 控制LOD级别,在不同距离下使用不同的渲染方式处理
ColorMask RG // 设置颜色通道的写入蒙版,默认蒙版为RGBA
- Shader中设置了ColorMask RG,这意味着只允许红色和绿色通道写入到帧缓冲区,而蓝色和alpha通道被禁用。因此,当渲染时,蓝色通道的值始终为0。如果你的纹理本身包含RGB信息,当蓝色通道被强制为0时,组合红色和绿色通道的值将导致物体呈现黄色,因为黄色是由红色和绿色混合而成的。
渲染状态的注意事项
- 以上这些状态不仅可以在SubShader语句块中声明,之后讲解的Pass渲染通道语句块中也可以声明这些渲染状态。
- 如果在SubShader语句块中使用会影响之后的所有渲染通道Pass;如果在Pass语句块中使用只会影响当前Pass渲染通道,不会影响其他的Pass
总结
- 渲染状态对于我们来说很重要
- 它可以影响最终我们看到的渲染效果
- 其中
- 剔除方式决定了 模型正面背面是否能够被渲染
- 深度缓冲和深度测试 决定了景深关系的确定以及透明效果的正确表达等
- 混合方式 决定了透明半透明颜色的正确表现,以及一些特殊颜色效果的表现
7.2 知识点代码
Lesson07_ShaderLab_ShaderLab语法规则_Shader的子着色器_States渲染状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson07_ShaderLab_ShaderLab语法规则_Shader的子着色器_States渲染状态 : MonoBehaviour
{
void Start()
{
#region 知识回顾
//1.
//SubShader子着色器基本构成为:
// --------Tags(渲染标签)
// |--------States(渲染状态)
//SubShader---|--------Pass(渲染通道1)
// |--------Pass(渲染通道2)
// --------....(渲染通道n)
//渲染状态:通过状态来确定渲染时的剔除方式、深度测试方式、混合方式等等内容
//2.
//片元:
//在渲染管线中,片元是指在光栅化阶段生成的像素或像素片段
//片元是渲染管线中进行像素级别操作和计算的基本单位
//每个片元代表了屏幕上的一个像素,并且具有位置信息和与之相关的属性
//比如:颜色、深度值、法线等等
#endregion
#region 知识点一 渲染状态的语法结构
//渲染状态 状态类型
//渲染状态是通过 渲染状态关键词+空格+状态类型 决定的
//如果存在多个渲染状态
//可以通过空行隔开
#endregion
#region 知识点二 剔除方式
//主要作用:
//设置多边形的剔除方式,有背面剔除、正面剔除、不剔除
//所谓的剔除,就是不渲染,背面剔除就是背面不渲染,正面剔除就是正面不渲染,不剔除就是都渲染
//Cull Back 背面剔除
//Cull Front 正面剔除
//Cull Off 不剔除
//不设置的话,默认为背面剔除
//一般情况下,我们需要两面渲染时,会设置为不剔除
#endregion
#region 知识点三 深度缓冲
//主要作用:
//是否写入深度缓冲
//深度缓冲(Depth Buffer):
//深度缓冲是一个与屏幕像素对应的缓冲区,用于存储每个像素的深度值(距离相机的距离)。
//在渲染场景之前,深度缓冲被初始化为最大深度值,表示所有像素都在相机之外。
//最后留在深度缓冲中的信息会被渲染
//ZWrite On 写入深度缓冲
//ZWrite Off 不写入深度缓冲
//不设置的话,默认为写入
//一般情况下,我们在做透明等特殊效果时,会设置为不写入
#endregion
#region 知识点四 深度测试
//主要作用:
//设置深度测试的对比方式
//深度测试的主要目的是确保在渲染时,像素按照正确的深度(距离相机的距离)顺序进行绘制,
//从而创建正确的遮挡关系和透视效果
//在渲染场景之前,深度缓冲被初始化为最大深度值,表示所有像素都在相机之外。
//在渲染过程中,对于每个像素,深度测试会将当前像素的深度值与深度缓冲中对应位置的值进行比较。
//一般情况下
//1.如果当前像素的深度值小于深度缓冲中的值,说明当前像素在其他物体之前,它会被绘制,并更新深度缓冲。
//2.如果当前像素的深度值大于等于深度缓冲中的值,说明当前像素在其他物体之后,它会被丢弃,不会被绘制,并保持深度缓冲不变。
//ZTest Less 小于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest Greater 大于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest LEqual 小于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest GEqual 大于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest Equal 等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest NotEqual 不等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest Always 始终通过深度测试写入深度缓冲中
//不设置的话,默认为LEqual 小于等于
//一般情况下,我们只有在实现一些特殊效果时才会区修改深度测试方式,比如透明物体渲染会修改为Less,描边效果会修改为Greater等
#endregion
#region 知识点五 混合方式
//主要作用:
//设置渲染图像的混合方式(多种颜色叠加混合,比如透明、半透明效果和遮挡的物体进行颜色混合)
//Blend One One 线性减淡
//Blend SrcAlpha OneMinusSrcAlpha 正常透明混合
//Blend OneMinusDstColor One 滤色
//Blend DstColor Zero 正片叠底
//Blend DstColor SrcColor X光片效果
//Blend One OneMinusSrcAlpha 透明度混合
//等等
//不设置的话,默认不会进行混合
//一般情况下,我们需要多种颜色叠加渲染时,就需要设置混合方式,具体情况具体处理
#endregion
#region 知识点六 其他渲染状态
//1.LOD 控制LOD级别,在不同距离下使用不同的渲染方式处理
//2.ColorMask 设置颜色通道的写入蒙版,默认蒙版为RGBA
//等等
//我们目前主要掌握剔除方式、深度缓冲、深度测试、混合方式即可
#endregion
#region 知识点七 渲染状态的注意事项
//以上这些状态不仅可以在SubShader语句块中声明
//之后讲解的Pass渲染通道语句块中也可以声明这些渲染状态
//如果在SubShader语句块中使用会影响之后的所有渲染通道Pass
//如果在Pass语句块中使用只会影响当前Pass渲染通道,不会影响其他的Pass
#endregion
#region 总结
//渲染状态对于我们来说很重要
//它可以影响最终我们看到的渲染效果
//其中
//剔除方式决定了 模型正面背面是否能够被渲染
//深度缓冲和深度测试 决定了景深关系的确定以及透明效果的正确表达等
//混合方式 决定了透明半透明颜色的正确表现,以及一些特殊颜色效果的表现
//这些内容,大家可以将其记入自己的笔记当中
//之后在使用他们时,翻看笔记进行复习
#endregion
}
}
Lesson07_NewUnlitShader.shader
Shader "ShaderTeach/Lesson07_NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
//剔除方式
//Cull Back //背面剔除
//Cull Front //正面剔除
Cull Off //不剔除
//深度缓冲
ZWrite On //写入深度缓冲
//ZWrite Off //不写入深度缓冲 在做透明等特殊效果时,会设置为不写入
//深度测试
//ZTest Less //小于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest Greater //大于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest LEqual //小于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中 不设置的话,默认为LEqual 小于等于
//ZTest GEqual //大于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest Equal //等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest NotEqual //不等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
//ZTest Always //始终通过深度测试写入深度缓冲中
//混合方式
//Blend One One //线性减淡
//Blend SrcAlpha OneMinusSrcAlpha //正常透明混合
//Blend OneMinusDstColor One //滤色
//Blend DstColor Zero //正片叠底
//Blend DstColor SrcColor //X光片效果
//Blend One OneMinusSrcAlpha //透明度混合
// 其他渲染状态
LOD 100 // 控制LOD级别,在不同距离下使用不同的渲染方式处理
ColorMask RG // 设置颜色通道的写入蒙版,默认蒙版为RGBA 设置成意味着只允许红色和绿色通道写入到帧缓冲区 蓝色通道的值始终为0
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
}
}
}
7.3 练习题
Unity的 ShaderLab 中 Cull 渲染状态的作用是什么?
用于控制渲染剔除(Culling)的行为,可以用于决定哪些面(正面或背面)会被剔除(不渲染)
深度测试、深度写入基本规则举例说明
深度测试、深度写入基本规则思考题
- 需要逐步分析每个物体的渲染和深度测试过程。根据给定的条件,我们将逐个检查每个物体如何影响最终的深度缓冲区和颜色缓冲区。
- 渲染队列值决定渲染顺序,值小的先渲染。
- 深度测试根据测试方式**对深度缓冲区的值和当前对象的深度进行比较,判断深度测试是否通过。
- 只要深度测试通过就会把当前对象的颜色写入颜色缓冲区。
- 深度写入是否开启决定深度测试通过后是否更新深度缓冲区。
深度测试和深度写入规则
假设默认深度缓冲区初始值为无限大,颜色缓冲区初始为空。
渲染顺序和深度测试分析
天空盒
- 渲染队列: 1000
- 深度: 无限大
- 测试方式: 默认(小于比较)
- 深度写入: 默认开启
结果:
- 天空盒深度(无限大)< 缓冲区默认值(无限大): 不成立(但因为深度缓冲区默认也是无限大,所以不影响)
- 深度缓冲区: 无限大(未改变)
- 颜色缓冲区: 天空盒颜色(被写入)
红色物体
- 渲染队列: 2800
- 深度: 100
- 测试方式: Always
- 深度写入: 开启
结果:
- Always通过深度测试
- 深度缓冲区: 100(被更新)
- 颜色缓冲区: 红色(被写入)
蓝色物体
- 渲染队列: 3000
- 深度: 200
- 测试方式: >
- 深度写入: 关闭
结果:
- 蓝色物体深度(200)> 当前深度缓冲区值(100): 成立
- 深度缓冲区: 100(不改变,因为深度写入关闭)
- 颜色缓冲区: 蓝色(被写入,因为深度测试通过,但深度不写入)
绿色物体
- 渲染队列: 3200
- 深度: 150
- 测试方式: >
- 深度写入: 开启
结果:
- 绿色物体深度(150)> 当前深度缓冲区值(100): 成立
- 深度缓冲区: 150(被更新)
- 颜色缓冲区: 绿色(被写入)
最终结果
经过上述分析,最终每个缓冲区的值为:
- 深度缓冲区: 150(绿色物体的深度)
- 颜色缓冲区: 绿色(绿色物体的颜色)
结论
因此最终在Game窗口中央显示的颜色应该是绿色,如果是透视摄像机,可能会看到绿色边缘有红色
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com