UnityShader学习笔记- Stencil Buffer
模板测试(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
- 使用透明物体的写深度方式
非欧几里得空间
那非欧几里得空间,又简单来说:违反现实三维空间几何规律的空间就可以认为是非欧几里得空间

每个面显示一个空间
想要达成非欧几里得的效果,只需要如下设置:
- 一个面世界中,只有通过这个四边形面片(Quad),才能看到这个里面的三维物体(GameObjects)。
- 各个面世界不相互干扰,一个面只负责显示一个世界。
遮罩的处理
Quad Shader注意点
- 渲染顺序 Queue 标签,要比其他物体先渲染。
- 关闭 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的更多相关文章
- UnityShader实例09:Stencil Buffer&Stencil Test
http://blog.csdn.net/u011047171/article/details/46928463 Stencil Buffer&Stencil Test 在开始前先吐槽下uni ...
- 深入浅出Oracle学习笔记:Buffer Cache 和Shared pool
Buffer cache 和 share pool 是sga中最重要最复杂的部分. 一.Buffer Cache 通常数据的读取.修改都是通过buffer cache 来完成的.buffer cach ...
- (转载)UnityShader学习笔记(七) 让贴图纹理动起来(河流瀑布特效、精灵序列帧实现)
大家好,我是Zander.这一章我们将使用纹理贴图实现动画.混合和真实特效来达到理想的效果. 纹理贴图可以使我们的着色器快速的实现逼真的效果,但是如果添加的纹理贴图过多,会非常影响游戏性能,特别是在移 ...
- UnityShader学习笔记1 — — 入门知识整理
注:资料整理自<Unity Shader入门精要>一书 一.渲染流程概念阶段: 应用阶段:(1)准备好场景数据:(如摄像机位置,物体以及光源等) (2)粗粒度剔除(Culling): ...
- MySQL学习笔记-cache 与 buffer
Cache和Buffer是两个不同的概念,简单的说,Cache是加速"读",而 buffer是缓冲"写",前者解决读的问题,保存从磁盘上读出的数据,后者是解决写 ...
- Java NIO 学习笔记(一)----概述,Channel/Buffer
目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...
- Java IO学习笔记一:为什么带Buffer的比不带Buffer的快
作者:Grey 原文地址:Java IO学习笔记一:为什么带Buffer的比不带Buffer的快 Java中为什么BufferedReader,BufferedWriter要比FileReader 和 ...
- 【D3D12学习手记】4.3.8 Create the Depth/Stencil Buffer and View
我们现在需要创建深度/模板缓冲区. 如§4.1.5所述,深度缓冲区只是一个2D纹理,用于存储最近的可见对象的深度信息(如果使用模板(stencil),则也会存储模板信息). 纹理是一种GPU资源,因此 ...
- DirectX 总结和DirectX 9.0 学习笔记
转自:http://www.cnblogs.com/graphics/archive/2009/11/25/1583682.html DirectX 总结 DDS DirectXDraw Surfac ...
随机推荐
- 从 BIO、NIO 聊到 Netty,最后还要实现个 RPC 框架!
大家好,我是 「后端技术进阶」 作者,一个热爱技术的少年. 觉得不错的话,欢迎 star!ღ( ´・ᴗ・` )比心 Netty 从入门到实战系列文章地址:https://github.com/Snai ...
- python3.6和pip3:Ubuntu下安装升级与踩坑之路
本文以Ubuntu16.x系统为例,演示如何安装python3.6和相应环境.安装Python3的机器必须要能访问外网才能进行如下操作! 1. 安装方式 在Ubuntu下安装python有两种方式: ...
- Combine 框架,从0到1 —— 1.核心概念
本文首发于 Ficow Shen's Blog,原文地址: Combine 框架,从0到1 -- 1.核心概念. 内容概览 前言 核心概念 RxSwift Combine 总结 参考内容 ...
- JS学习阶段性总结-1
各种函数的声明 /** * 函数的声明 */ // 声明一个方法,任意调用 function aaa(args){...} // 声明一个函数并以变量的形式展示出去,因此无法再声明前调用 var fn ...
- Java高级特性——反射机制(第一篇)
——何为动态语言,何为静态语言?(学习反射知识前,需要了解动态语言和静态语言) 动态语言 >是一类在运行时可以改变其结构的语言,例如新的函数.对象.甚至是代码可以被引进,已有的函数可以被删除或者 ...
- python3+pyqt5+opencv3简单使用
python3+pyqt5+opencv3简单使用(转载) 关于python3下搭建pyqt5(pycharm)参考这条链接. 对于pyqt的使用个人比较建议ui设计与逻辑功能分开开发. 下面介绍下简 ...
- Java 与 Mysql连接,并分页显示
这是我第一个上规模的Java项目,我们必须在一周内完成的作业,零基础学习Java,网上收集了很多资料,逐渐对面向对象的思想有所了解,但还是半灌水,后期打算结合项目系统地学习一遍Java.老师布置的任务 ...
- Postman发送POST请求到Spring Boot的正确姿势
最近用Spring Boot搭建了一些restful api,写起来真的很爽.但是当用Postman测试一些POST请求的接口的时候却遇到一些问题,上网冲浪查了一堆博客资料,发现都讲得不清不楚,于是记 ...
- HTML语言基本单词与css基本单词
DOCTYPE 文档 html 网页 head 头部 body 主体 title 题目 p 段落 color 颜色 style 样式 backgroun ...
- DVWA_sql injection(low)
这里主要记录下做题时的思路以及步骤,不查看源码也不对源码进行分析.(我这个dvwa是一个在线靶场,所以我也不确定是否与本地靶场有些许出入) 1.Low 将难度调为Low级别后,来到如下界面 首先输入一 ...