模板测试(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. 牛客网PAT练习场-数素数

    题解:就是一道素数筛选法 题目地址:https://www.nowcoder.com/questionTerminal/e0fb49acb75f47e8b6fa2077d9071799 /** * C ...

  2. java23种设计模式——八、组合模式

    目录 java23种设计模式-- 一.设计模式介绍 java23种设计模式-- 二.单例模式 java23种设计模式--三.工厂模式 java23种设计模式--四.原型模式 java23种设计模式-- ...

  3. 要不是真的喜欢学技术,谁会来用Python爬小姐姐啊

    养成习惯,先赞后看!!!不用于任何商业价值,只是自己娱乐.否则 爬虫爬的好,牢饭吃到饱.这是我们这次爬取的网址:https://www.vmgirls.com/ 很多人学习python,不知道从何学起 ...

  4. android 申请忽略电池节电

    fun checkBattery(){ var main = activity as MainActivity if(main.isIgnoringBatteryOptimizations()){ L ...

  5. Unity透明地形

    http://answers.unity3d.com/questions/1162779/unity-5-transparent-terrain-shader.html http://answers. ...

  6. 记录一个基于Java的利用快排切分来实现快排TopK问题的代码模板

    使用快排切分实现快排和TopK问题的解题模板 import java.util.Arrays; public class TestDemo { public static void main(Stri ...

  7. 01_Python基础知识梳理

    1.计算机知识基础 1.计算机组成 计算机底层: 点子电路,计算机只能识别两个数 0 1         硬件: 处理器(CPU), 运行内存(RAM), 主板(总线设备), 外部存储设备(硬盘U盘等 ...

  8. 20190923-02Linux文件目录类 000 010

    pwd 显示当前工作目录的绝对路径 pwd:print working directory 打印工作目录 1.基本语法 pwd (功能描述:显示当前工作目录的绝对路径) 2.案例实操 (1)显示当前工 ...

  9. Vue指令之条件渲染

    1. v-show 根据表达式的真假值,切换元素的 display CSS属性.表达式为false时,p标签被赋予 style="display:none;" <p v-sh ...

  10. SSH框架下页面跳转入门篇

    一.完成目标,因为WEB-INF下面的界面不能通过输入地址的方式直接访问,所以需要在后台定义一个方法跳转过去. 步骤1:.创建普通类继承ActionSupport类,并定义一个方法返回需要跳转的路径 ...