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 ...
随机推荐
- 牛客网PAT练习场-数素数
题解:就是一道素数筛选法 题目地址:https://www.nowcoder.com/questionTerminal/e0fb49acb75f47e8b6fa2077d9071799 /** * C ...
- java23种设计模式——八、组合模式
目录 java23种设计模式-- 一.设计模式介绍 java23种设计模式-- 二.单例模式 java23种设计模式--三.工厂模式 java23种设计模式--四.原型模式 java23种设计模式-- ...
- 要不是真的喜欢学技术,谁会来用Python爬小姐姐啊
养成习惯,先赞后看!!!不用于任何商业价值,只是自己娱乐.否则 爬虫爬的好,牢饭吃到饱.这是我们这次爬取的网址:https://www.vmgirls.com/ 很多人学习python,不知道从何学起 ...
- android 申请忽略电池节电
fun checkBattery(){ var main = activity as MainActivity if(main.isIgnoringBatteryOptimizations()){ L ...
- Unity透明地形
http://answers.unity3d.com/questions/1162779/unity-5-transparent-terrain-shader.html http://answers. ...
- 记录一个基于Java的利用快排切分来实现快排TopK问题的代码模板
使用快排切分实现快排和TopK问题的解题模板 import java.util.Arrays; public class TestDemo { public static void main(Stri ...
- 01_Python基础知识梳理
1.计算机知识基础 1.计算机组成 计算机底层: 点子电路,计算机只能识别两个数 0 1 硬件: 处理器(CPU), 运行内存(RAM), 主板(总线设备), 外部存储设备(硬盘U盘等 ...
- 20190923-02Linux文件目录类 000 010
pwd 显示当前工作目录的绝对路径 pwd:print working directory 打印工作目录 1.基本语法 pwd (功能描述:显示当前工作目录的绝对路径) 2.案例实操 (1)显示当前工 ...
- Vue指令之条件渲染
1. v-show 根据表达式的真假值,切换元素的 display CSS属性.表达式为false时,p标签被赋予 style="display:none;" <p v-sh ...
- SSH框架下页面跳转入门篇
一.完成目标,因为WEB-INF下面的界面不能通过输入地址的方式直接访问,所以需要在后台定义一个方法跳转过去. 步骤1:.创建普通类继承ActionSupport类,并定义一个方法返回需要跳转的路径 ...