模板测试(Stencil Test)是现代渲染流水线的一环,其中涉及到的就是模板缓冲(Stencil Buffer),模板缓冲可以用来制作物体的遮罩、轮廓描边、阴影、遮挡显示等等效果

为屏幕上的每一个像素保存一个8位的无符号整数,跟模板缓冲区进行比较并决定是否保留像素称为模板测试

模板测试发生在透明度测试之后,深度测试之前

模板缓冲区默认值为0(测试得到),并且我推测模板缓冲区每帧执行完会进行一个刷新

要加模板测试,就在Shader的Pass开头写Stencil{ }结构体。如果每个Pass都用,则可以提到外面

Stencil 常见语法格式

Stencil{
Ref referenceValue // 参考值 默认值为 0
Comp comparisonFunction // 定义参考值与缓冲值比较的方法 默认值为 Always
Pass stencilOperation // 定义当通过模板测试时,根据参考值对缓冲值的处理方法 默认值为 keep
Fail stencilOperation // 定义当没有通过模板测试时,根据参考值对缓冲值的处理方法 默认为 keep
ZFail stencilOperation // 定义当通过模板测试却没有通过深度测试时,根据参考值对缓冲值的处理方法 默认为 keep
}

举个实际例子

Stencil{
Ref 1
Comp Equal
Pass Keep
}

上述代码的意思是: 我们自己设定了 Ref 参考值为 1。渲染 Pass 得到像素颜色后,拿参考值 1 与模板缓冲中此像素位置的缓冲值比对,只有 Equal 相等才算通过,并且 Keep 保持原有缓冲值,否则丢弃此像素颜色。

关键字

stencil{
Ref referenceValue
ReadMask readMask
WriteMask writeMask
Comp comparisonFunction
Pass stencilOperation
Fail stencilOperation
ZFail stencilOperation
}

Ref

Ref referenceValue

Ref用来设定参考值referenceValue,这个值将用来与模板缓冲中的值进行比较。referenceValue是一个取值范围位0-255的整数。

ReadMask

ReadMask readMask

ReadMask 从字面意思的理解就是读遮罩,readMask将和referenceValue以及stencilBufferValue进行按位与(&)操作,readMask取值范围也是0-255的整数,默认值为255,二进制位11111111,即读取的时候不对referenceValue和stencilBufferValue产生效果,读取的还是原始值。

WriteMask

WriteMask writeMask

WriteMask是当写入模板缓冲时进行掩码操作(按位与【&】),writeMask取值范围是0-255的整数,默认值也是255,即当修改stencilBufferValue值时,写入的仍然是原始值。

Comp

Comp comparisonFunction

Comp是定义参考值(referenceValue)与缓冲值(stencilBufferValue)比较的操作函数,默认值:always

Pass

Pass stencilOperation

Pass是定义当模板测试(和深度测试)通过时,则根据(stencilOperation值)对模板缓冲值(stencilBufferValue)进行处理,默认值:keep

Fail

Fail stencilOperation

Fail是定义当模板测试(和深度测试)失败时,则根据(stencilOperation值)对模板缓冲值(stencilBufferValue)进行处理,默认值:keep

ZFail

ZFail是定义当模板测试通过而深度测试失败时,则根据(stencilOperation值)对模板缓冲值(stencilBufferValue)进行处理,默认值:keep

Comp,Pass,Fail 和ZFail将会应用给背面消隐的几何体(只渲染前面的几何体),除非Cull Front被指定,在这种情况下就是正面消隐的几何体(只渲染背面的几何体)。你也可以精确的指定双面的模板状态通过定义CompFront,PassFront,FailFront,ZFailFront(当模型为front-facing geometry使用)和ComBack,PassBack,FailBack,ZFailBack(当模型为back-facing geometry使用)

自定义一些值

Comp比较函数

Greater Only render pixels whose reference value is greater than the value in the buffer.
GEqual Only render pixels whose reference value is greater than or equal to the value in the buffer.
Less Only render pixels whose reference value is less than the value in the buffer.
LEqual Only render pixels whose reference value is less than or equal to the value in the buffer.
Equal Only render pixels whose reference value equals the value in the buffer.
NotEqual Only render pixels whose reference value differs from the value in the buffer.
Always Make the stencil test always pass.
Never Make the stencil test always fail.

Operation

Keep Keep the current contents of the buffer.
Zero Write 0 into the buffer.
Replace Write the reference value into the buffer.
IncrSat Increment the current value in the buffer. If the value is 255 already, it stays at 255.
DecrSat Decrement the current value in the buffer. If the value is 0 already, it stays at 0.
Invert Negate all the bits.
IncrWrap Increment the current value in the buffer. If the value is 255 already, it becomes 0.
DecrWrap Decrement the current value in the buffer. If the value is 0 already, it becomes 255.

模板测试判断依据

和深度测试一样,在unity中,每个像素的模板测试也有它自己一套独立的依据,具体公式如下:

if(referenceValue&readMask comparisonFunction stencilBufferValue&readMask)

通过像素

else

抛弃像素

轮廓描边

思路+Code

两个Pass,第一个Pass正常渲染,第二个Pass把vertex沿着模型法线膨胀一点然后基于上一个Pass的模板缓冲区来剔除重叠部分

Shader "Unlit/Edge"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color",Color) = (1,1,1,1)
_RefValue("Stencil RefValue",Int) = 0
_Outline("OutLine Width",Range(0,1)) = 0.05
_OutlineColor("OutLineColor",Color) = (0,0,0,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
//stencil buffer if zero default and it will be reset at the end of one frame Render
Stencil{
Ref [_RefValue]
Comp Equal
Pass IncrSat
}
CGINCLUDE
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Color;
float _Outline;
float4 _OutlineColor;
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col*_Color;
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex _vert
#pragma fragment _frag
v2f _vert (appdata v)
{
v2f o;
v.vertex = v.vertex+float4(normalize(v.normal)*_Outline,1);
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 _frag (v2f i) : SV_Target
{
return _OutlineColor;
}
ENDCG
}
}
}

产生的问题

1、边界交融:两个物体物体在屏幕上有z先后关系时相交部分不会有外轮廓线

2、边界竞争:写入了模板缓冲区,并根据模板缓冲区进行剔除,摄像机位置变动,物体的渲染顺序发生变化,先谢了模板缓冲的物体会覆盖后了模板缓冲的物体的模型

解决边界竞争的关键在于模型本体的渲染不能被模板缓冲区影响,所以两个Pass之间使用不同的Stencil测试,第一个Pass渲染本体并对模板缓冲区进行初始化,也就是把Comp设置成Always,第二个Pass做之前一样的模板测试

第一个Pass

Pass
{
Stencil{
Ref [_RefValue]
Comp Always
Pass IncrSat
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//...
ENDCG
}

第二个Pass

 Pass
{
Stencil{
Ref 0
Comp Equal
Pass Keep
}
CGPROGRAM
#pragma vertex _vert
#pragma fragment _frag
//...
ENDCG }

实现Unity遮罩的方法

  • 搞一个渲染队列靠前的平面,然后做模板缓冲区写入,后来的物体做模板测试就好

    • 如何初始化模板缓冲区,用一个渲染队列在前面的物体,调成Always
  • 使用透明物体的写深度方式

非欧几里得空间

那非欧几里得空间,又简单来说:违反现实三维空间几何规律的空间就可以认为是非欧几里得空间

每个面显示一个空间

想要达成非欧几里得的效果,只需要如下设置:

  1. 一个面世界中,只有通过这个四边形面片(Quad),才能看到这个里面的三维物体(GameObjects)。
  2. 各个面世界不相互干扰,一个面只负责显示一个世界。

遮罩的处理

Quad Shader注意点

  1. 渲染顺序 Queue 标签,要比其他物体先渲染。
  2. 关闭 Zwrite 深度写入,否则后面的物体ZTest不过不会显示。

多个面互相不干扰

要想让面世界之间互不干扰:你显示你的,我显示我的。就像上图所显示那样。

其实很简单,只需要为每个面世界设置不同的 Ref 参考值就好了。

比如左边显示圆球的面世界中,四边形面片(Quad)与其中的物体们(GameObjects)的参考值都设置为 1

右边显示圆柱的面世界中,四边形面片(Quad)与其中的物体们(GameObjects)的参考值都设置为 2

代码部分

Mask

 Properties
{
_RefValue("Stencil Value",Int) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" "Opaque" = "Geometry-1"}
Pass
{
Stencil{
Ref [_RefValue]
Comp Always
Pass Replace
}
ZWrite Off
ColorMask 0 CGPROGRAM
//...
ENDCG
}
}

Obj

 Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color",Color) = (1,1,1,1)
_RefValue("Stencil Value",Int) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" "Opaque" = "Geometry"}
Pass
{
Stencil{
Ref [_RefValue]
Comp Equal
Pass Keep
} CGPROGRAM
//...
ENDCG
}
}

基于Stencil的镜面效果

镜面效果往往需要额外创建一个摄像机,根据摄像机的图像反转位置来渲染镜子中的内容,利用stencil进行镜面区域限定,配合顶点镜面反转,也可以实现镜面效果

如何反转?

给镜子下建立一个子物体,子物体的某一条轴垂直镜面方向,然后把世界空间的物体变换到建立的子物体的空间下,再反转垂直的轴,即可形成虚像

虚像的处理需要关闭深度测试,或者让他总是通过也行



Quad物体就是Mirror,有一条轴垂直镜面的子物体WtoMW_Object:

传送矩阵的工具物体

子物体上挂载一个脚本,用于传送矩阵给材质

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//Set World to Mirror World Matrix
public class SetWtoMWMatrix : MonoBehaviour
{
//WtoMW_Object 的 transform;
Transform refTransform;
//”Wrold“ To ”MirrorWorld“ Matrix(世界转换到镜子世界的矩阵)
Matrix4x4 WtoMW;
public Material material;
//Y 轴对称反转矩阵
Matrix4x4 YtoNegativeZ = new Matrix4x4(
new Vector4(1, 0, 0, 0),
new Vector4(0, 1, 0, 0),
new Vector4(0, 0, -1, 0),
new Vector4(0, 0, 0, 1));
private void Start()
{
//material采用拖拽赋值的形式
refTransform = GameObject.Find("WtoMW_Object").transform;
}
void Update()
{
//模型的坐标,从世界空间转到镜子空间(本质就是把一个要镜像的物体变换到目前建立的子物体的空间上),再经由反转Y轴得到镜子空间的镜像,
//反转Y轴是因为子物体的y轴即是镜面朝向,其实子物体哪个轴朝外反转到那个轴就行,然后把镜像再转换回世界坐标
WtoMW = refTransform.localToWorldMatrix * YtoNegativeZ * refTransform.worldToLocalMatrix;
material.SetMatrix("_WtoMW", WtoMW);
}
}

MirrorObj

对于要被镜子照到的物体我们需要形成虚像,所以需要两个Pass,一个虚像一个实像

Shader "Unlit/MirrorObj"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_RefValue("Ref Value",Int) = 1
}
SubShader{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
//渲染队列在后一点
CGINCLUDE
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldNormal:TEXCOORD1;
};
#include "UnityCG.cginc"
float4x4 _WtoMW; //矩阵
sampler2D _MainTex;
float4 _MainTex_ST;
ENDCG //这里渲染虚像的 Pass
Pass
{
Stencil{
Ref [_RefValue]
//由于stencil buffer默认是0,所以建议给个1,等于1时说明在镜面区域内,则可以显示虚像
Comp Equal
Pass keep
ZFail keep
}
ZTest Off
Cull Front //镜面显示背面而不显式正面 CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//顶点函数
v2f vert (appdata v)
{
v2f o;
//首先将模型顶点转换至世界空间坐标系
float4 worldPos = mul(unity_ObjectToWorld,v.vertex);
//再把顶点从世界空间转换至镜子空间
float4 mirrorWorldPos = mul(_WtoMW,worldPos);
//最后就后例行把顶点从世界空间转换至裁剪空间
o.vertex = mul(UNITY_MATRIX_VP,mirrorWorldPos);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
//frag 函数和实体的是一样的..
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
//这里渲染实体的 Pass
Pass
{
CGPROGRAM
// ...
ENDCG }
}
}

Mirror

没什么好说的,就模板缓冲区初始化,然后搞成透明的

Shader "Unlit/Mirror"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_RefValue("Ref Value",Int) = 1
_Color("Color Tint",Color) = (0,0,0,1)
}
SubShader
{
//注意渲染队列
Tags { "RenderType"="Opaque" "Queue"="Geometry-1" }
Stencil{
Ref [_RefValue]
Comp Always
Pass Replace
}//所谓模板缓冲区初始化
Pass{
//这里镜子的正常渲染(默认我使用 Unlit 的代码
ZWrite Off
ColorMask 0
//不让他往颜色缓冲区写东西,这样就是一个透明的镜子了
CGPROGRAM
//不写主要流程也没关系,想给镜子写点光照反射就写,然后记得把上面的ColorMask 0去掉
ENDCG
}
}
}



[文中案例来自INDIENOVA阿创]: https://indienova.com/u/1149119967

UnityShader学习笔记- Stencil Buffer的更多相关文章

  1. UnityShader实例09:Stencil Buffer&Stencil Test

    http://blog.csdn.net/u011047171/article/details/46928463 Stencil Buffer&Stencil Test 在开始前先吐槽下uni ...

  2. 深入浅出Oracle学习笔记:Buffer Cache 和Shared pool

    Buffer cache 和 share pool 是sga中最重要最复杂的部分. 一.Buffer Cache 通常数据的读取.修改都是通过buffer cache 来完成的.buffer cach ...

  3. (转载)UnityShader学习笔记(七) 让贴图纹理动起来(河流瀑布特效、精灵序列帧实现)

    大家好,我是Zander.这一章我们将使用纹理贴图实现动画.混合和真实特效来达到理想的效果. 纹理贴图可以使我们的着色器快速的实现逼真的效果,但是如果添加的纹理贴图过多,会非常影响游戏性能,特别是在移 ...

  4. UnityShader学习笔记1 — — 入门知识整理

    注:资料整理自<Unity Shader入门精要>一书 一.渲染流程概念阶段:  应用阶段:(1)准备好场景数据:(如摄像机位置,物体以及光源等)   (2)粗粒度剔除(Culling): ...

  5. MySQL学习笔记-cache 与 buffer

    Cache和Buffer是两个不同的概念,简单的说,Cache是加速"读",而 buffer是缓冲"写",前者解决读的问题,保存从磁盘上读出的数据,后者是解决写 ...

  6. Java NIO 学习笔记(一)----概述,Channel/Buffer

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  7. Java IO学习笔记一:为什么带Buffer的比不带Buffer的快

    作者:Grey 原文地址:Java IO学习笔记一:为什么带Buffer的比不带Buffer的快 Java中为什么BufferedReader,BufferedWriter要比FileReader 和 ...

  8. 【D3D12学习手记】4.3.8 Create the Depth/Stencil Buffer and View

    我们现在需要创建深度/模板缓冲区. 如§4.1.5所述,深度缓冲区只是一个2D纹理,用于存储最近的可见对象的深度信息(如果使用模板(stencil),则也会存储模板信息). 纹理是一种GPU资源,因此 ...

  9. DirectX 总结和DirectX 9.0 学习笔记

    转自:http://www.cnblogs.com/graphics/archive/2009/11/25/1583682.html DirectX 总结 DDS DirectXDraw Surfac ...

随机推荐

  1. 史上!最最最简洁明了的 Java JDK 安装目录及其子目录含义 10分钟详解 - 精简归纳

    Java JDK 安装目录及其子目录含义 10分钟详解 - 精简归纳 JERRY_Z. ~ 2020 / 8 / 30 转载请注明出处!️ 目录 Java JDK 安装目录及其子目录含义 10分钟详解 ...

  2. 封装Vue Element的dialog弹窗组件

    我本没有想着说要封装一个弹窗组件,但有同行的朋友在问我,而且弹窗组件也确实在项目开发中用的比较多.思前想后,又本着样式统一且修改起来方便的原则,还是再为大家分享一个我所封装的弹窗组件吧. 其实,并不是 ...

  3. vim编辑器 与etc目录

    第1章         目录结构 1.1  vim  vim故障 vim  是vi的升级版本 vi类似于文本文档  vim类似于notepad++ 编辑 必须先安装vim命令 yum -y insta ...

  4. Kubernetes K8S之资源控制器Daemonset详解

    Kubernetes的资源控制器Daemonset详解与示例 主机配置规划 服务器名称(hostname) 系统版本 配置 内网IP 外网IP(模拟) k8s-master CentOS7.7 2C/ ...

  5. 据说是最好的记忆工具——Anki

    http://www.ankichina.net/ .u1s1,确实挺好用,自建题库,全程自助. 可以插入文字.图片.音频,会安排合理的复习频率,可以随时同步,电脑手机版本全.

  6. Sequence (矩阵快速幂+快速幂+费马小定理)

            Holion August will eat every thing he has found. Now there are many foods,but he does not wa ...

  7. Codeforces 1324E Sleeping Schedule DP

    题意 给你一个长度为\(n\)的数组\(a\)和3个数字\(h,l和r\).\(t\)初始为0,每次可以使\(t=(t+a_i) \% h\)或者\(t=(t+a_i-1)\%h\),如果这时\(t\ ...

  8. Istio 的配置分析

    Istio 的配置分析 目录 Istio 的配置分析 Analyzer 的消息格式 ConflictingMeshGatewayVirtualServiceHosts 问题解决 举例 Conflict ...

  9. vue相关知识点及面试

    ### vue #### vue生命周期 beforeCreated `实例初始化,数据观察和event/watch事件配置之前被调用` created `实例创建后立即调用,数据观测,数据和方法运算 ...

  10. ZT:C/C++ 字符串与数字相互转换

    转载地址:https://www.cnblogs.com/happygirl-zjj/p/4633789.html 一.利用stringstream类 1. 字符串到整数     stringstre ...