写在前面:本文只介绍Stencil最基本的三种参数:Ref Comp Pass ,其他的略略带过。
碎碎念:之前用过Stencil,但是没有完全理解,原因在于虽然理解了每项的意思,但是实际的使用效果不如人意,最后得出的结果纯靠运气试出来的。昨晚按照渲染流程走了一遍,豁然开朗,感觉自己好瓜皮qaq

Stencil简介

Stencil是模板测试的意思,通常写在Pass里面最开头,CGPROGRAM之前。Stencil会在该场景生成一个Stencil缓冲区,缓冲值初始为0。在运行中,Stencil会通过读取每个像素的缓冲值,与Ref定义的参考值进行比较,若通过则对该像素缓冲值进行Pass后的操作,并渲染该像素,若不通过则进行Fail后的操作。
stencil完整语法格式如下:

stencil{Ref referenceValueReadMask  readMaskWriteMask writeMaskComp comparisonFunctionPass stencilOperationFail stencilOperationZFail stencilOperation
}

但一般来说,我只取Ref Comp Pass三项

Ref

Ref用来设定参考值,这个值将用来与模板缓冲中的值进行比较。
例:Ref 0意为“设定参考值0”。

Comp

comp等于英语compare,意思是将参考值与缓冲值相比。
例:Ref 0 Comp Less 意为“若参考值0小于缓冲值”,千万不要弄反了。

Pass

Pass意思是通过了就执行xx操作。
例:Pass keep 意思是若通过了就不改变缓冲区的值,并渲染该像素。

Comp和Pass的可取值类型如上1

举例

现在正式进入我们的探索阶段,虽然上面讲得很清楚,但是实践下来可能会发现结果和自己理解的不一样(比如我)。那么现在我们就创建一个平面投影shader2,用这个来测试Stencil。

Shader "Custom/PlanarShadow"
{Properties{_Tint("_Tint", Color) = (1,1,1,1)_MainTex("_MainTex (albedo)", 2D) = "white" {}[Header(Alpha)][Toggle(_CLIPPING)] _Clipping ("Alpha Clipping", Float) = 1_Cutoff("_Cutoff (Alpha Cutoff)", Range(0.0, 1.0)) = 0.5 // alpha clip threshold[Header(Shadow)]// _GroundHeight("_GroundHeight", Range(-100, 100)) = 0_GroundHeight("_GroundHeight", Float) = 0_ShadowColor("_ShadowColor", Color) = (0,0,0,1)_ShadowFalloff("_ShadowFalloff", Range(0,1)) = 0.05// Blending state[HideInInspector] _SrcBlend("__src", Float) = 1.0[HideInInspector] _DstBlend("__dst", Float) = 0.0[HideInInspector] _ZWrite("__zw", Float) = 1.0[HideInInspector] _Cull("__cull", Float) = 2.0}SubShader{Pass {CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"struct a2v {float4 vertex : POSITION;float3 normal : NORMAL;};struct v2f {float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldPos : TEXCOORD1;};v2f vert(a2v v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;return o;}fixed4 frag(v2f i) : SV_Target {fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);fixed3 diffuse = _LightColor0.rgb * max(0, dot(worldNormal, worldLightDir));return fixed4(ambient + diffuse , 1.0);}ENDCG}// Planar Shadows平面阴影Pass{Name "PlanarShadow"//用使用模板测试以保证alpha显示正确Stencil{Ref 0Comp GequalPass DecrWrap// Pass Keep// Fail Keep// ZFail Keep}Cull Off//透明混合模式Blend SrcAlpha OneMinusSrcAlpha//关闭深度写入ZWrite off//深度稍微偏移防止阴影与地面穿插Offset -1 , 0CGPROGRAM#pragma shader_feature _CLIPPING#pragma shader_feature _ALPHATEST_ON#pragma shader_feature _ALPHAPREMULTIPLY_ON#include "UnityCG.cginc"#pragma vertex vert#pragma fragment fragfloat _GroundHeight;float4 _ShadowColor;float _ShadowFalloff;half4 _Tint;sampler2D _MainTex;float4 _MainTex_ST;float _Clipping;half _Cutoff;struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float4 vertex : SV_POSITION;float4 color : COLOR;float2 uv : TEXCOORD0;};float3 ShadowProjectPos(float4 vertPos){float3 shadowPos;//得到顶点的世界空间坐标float3 worldPos = mul(unity_ObjectToWorld , vertPos).xyz;//灯光方向// float3 lightDir = normalize(_LightDir.xyz);float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//阴影的世界空间坐标(低于地面的部分不做改变)shadowPos.y = min(worldPos .y , _GroundHeight);shadowPos.xz = worldPos .xz - lightDir.xz * max(0 , worldPos .y - _GroundHeight) / lightDir.y; return shadowPos;}float GetAlpha (v2f i) {float alpha = _Tint.a * tex2D(_MainTex, i.uv.xy).a;return alpha;}v2f vert (appdata v){v2f o;//得到阴影的世界空间坐标float3 shadowPos = ShadowProjectPos(v.vertex);//转换到裁切空间o.vertex = UnityWorldToClipPos(shadowPos);//得到中心点世界坐标float3 center = float3(unity_ObjectToWorld[0].w , _GroundHeight , unity_ObjectToWorld[2].w);//计算阴影衰减float falloff = 1-saturate(distance(shadowPos , center) * _ShadowFalloff);//阴影颜色o.color = _ShadowColor;o.color.a *= falloff;o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;}fixed4 frag (v2f i) : SV_Target{if (_Clipping){float alpha = GetAlpha(i);i.color.a *= step(_Cutoff, alpha);}return i.color;}ENDCG}}
}

在场景中加入多个物体,为它们挂载上Shader,瞎摆一通使它们的阴影重叠。使用不同的Stencil设置,会出现不同的阴影重叠效果,从上到下分别为:无阴影、无重叠阴影、重叠阴影。



我用Ref 0试了所有的CompPass参数,结果如下:

当然,Comp没有包括Alwaysnever,因为它俩的情况比较简单,Always就是“无论如何都渲染”,never就是“无论如何都不渲染”。
我们简单分析一下上面的表格,在分析之前我们要明确三点:

  1. 需要把渲染顺序考虑进去
  2. 缓冲值实际上是8位二进制数,范围在0-255之间
  3. IncrWrap\DecrWrap 中,0-1=255,255+1=0IncrSat\DecrSat 中,0-1=0,255+1=255

首先,如果Comp后面的值是NotEqual/Greater/Less,那么都会得到无阴影的结果。可是阴影明明有重叠的范围,如果写了Pass IncrWrap ,为什么在选择Less的时候不会显示重叠区?那么我们过一遍测试流程,首先第一个物体进行模板测试,它的所有缓冲值都是0,那么比较的结果就是全部不通过,缓冲区没有任何更改,所以第二个物体进入测试的时候,和第一个物体阴影重叠部分的缓冲值仍然为0,这样比较下来依然是全部不通过,层层递推下来就完全没有通过的,最后的结果就是全部不显示。

然后,经过刚刚的推导,表上大部分的情况都可以顺下来了。还有一个问题是,为什么IncrWrapDecrWrap 的结果相同呢?这就是因为Wrap的特性,0-1=255,255+1=0。设Ref 0 Comp Lequal,当Pass DecrWrap时,第一个全部渲染后Stencil缓冲值-1,该阴影所在区域,其像素的Stencil缓冲值均等于255,因此第二个进入测试时,非重叠部分缓冲值为0,重叠部分缓冲值为255,全部比较通过,渲染并将缓冲值减1;第三个进入测试时,有两次重叠的部分缓冲值为254,一次重叠部分缓冲值为255,无重叠部分缓冲值为0,它的测试也是全部通过的;当Pass IncrWrap时,第一个全部渲染后Stencil缓冲值+1,那么第二个测试时,与第一个阴影重叠的部分全部比较通过,全部渲染,接下来的也是如此。

最后一个问题,为什么DecrWrapDecrSat 的结果不同呢?我们先考虑DecrSat,它在测试通过的时候,缓冲值-1,但前面说到,IncrSat\DecrSat 中,0-1=0,所以缓冲值依然为0,那么不通过的时候,如果不写Fail,则默认为keep,所以还是缓冲值还是0,如此算下来,DecrSat=zeroDecrWrap前面已经推导过,这里不再赘述。

写在后面:还是写了蛮多的,都是因为我的蠢问题> <估计没有人会像我这么傻哈哈哈


  1. https://gameinstitute.qq.com/community/detail/127404 ↩︎

  2. https://zhuanlan.zhihu.com/p/31504088 ↩︎

【Unity Shader】关于Stencil的理解小记相关推荐

  1. Unity Shader - Planar Shadow - 平面阴影

    文章目录 整体运行效果 思路 Shader 问题 Z-Fighting,解决:按法线方向偏移一丢丢 绘制 Alpha 混合重叠,解决:使用 stencil buffer 来规避 为何出现这个问题 解决 ...

  2. unity shader入门精要_Unity Shader 入门(一):渲染流水线

    一.参考与说明(需要写在开始东西): 1.1 Unity Shader 入门紧要学习 candycat1992/Unity_Shaders_Book​github.com 1.2 还有一些图形学的历史 ...

  3. unity Shader Lab(cg hlsl glsl)着色器入门教程 以及 vs2019 支持unity shader语法(更新中2019.9.5)

    前言: 如果你对cg glsl hlsl 顶点着色器 片段着色器 表面着色器 固定渲染管线 等等有所疑惑,或是想学会unity的渲染,看这一篇就足够了.另外我博客的shader分类中还有很多shade ...

  4. Unity Shader中各部分定义内容详解

    Unity Shader中各部分定义内容详解 样板 Shader "Practice/Unlit/SimpleUnlit" {Properties{_MainTex (" ...

  5. Unity shader入门精要(学习总结)

    (学习总结) 一,基础概念 什么是shader? Unity Shader定义了渲染所需的各种代码(如顶点着色器和片元着色器).属性(如使用哪些纹理等)和指令(渲染和标签设置等),而材质则允许我们调节 ...

  6. 2d shader unity 阴影_【Unity Shader】平面阴影(Planar Shadow)

    来介绍一种适用于移动平台的高性能实时阴影解决方案--平面阴影(Planar Shadow). 由于Unity内置的实时阴影实现方式是屏幕空间阴影贴图(Screen Space Shadow Map)非 ...

  7. Unity Shader知识点(二)写一个基础漫反射Shader

    前言 我们刚学习Shader时,往往会被计算机图形学相关复杂的数学.物理知识所困扰,并在学习过程中丧失兴趣.个人以为,在代码以外,学习Shader应当有本科线性代数课程基础的概念框架,在这一基础上,完 ...

  8. Unity Shader - URP ShadowCast ShadowRecieve - 投影 和 接受阴影

    文章目录 Shadow Caster Using URP Shadow Caster Pass Using Custom Shadow Caster Pass 先来看看 [没有] apply shad ...

  9. 【Unity Shader】屏幕后处理3.0:均值模糊和高斯模糊

    发现之前学习记录的太过详细,导致整理的过程占用太长的时间了,这篇之后博客重要的是掌握实现过程,关于基础的理论会更多的放上别人写得更好的文章. 参考:[Unity Shader编程]之十五 屏幕高斯模糊 ...

最新文章

  1. 第二十六天 iptables的nat功能
  2. 近世代数--极大理想--I是R的极大理想↔R/I是域
  3. Hadoop配置机架感知(python脚本)
  4. 一般区域二重、三重积分MATLAB计算方法
  5. Java 项目开发及管理常用工具收集
  6. java8 list转map
  7. Linux常见问题三则:Executable Path Is Not Absolute
  8. svn添加提交备注限制和自动发布web项目
  9. android最新文献,android开发参考文献
  10. Linux: dnf
  11. kindle看pdf不清楚_你不知道的kindle技巧:如何优雅地用kindle看pdf文档?
  12. 链表初始化typedef struct LNode{}LNode,*linklist的理解
  13. Ubuntu系统下搭建C/C++ gcc开发环境
  14. location属性和prototype属性介绍
  15. 医院客户关系管理系统
  16. 传iPhone8将搭载裸眼3D技术
  17. 45岁了社保交了25年不交了行吗?
  18. python脚本控制ios手机app_iOS自动打包ipa(Python脚本)
  19. 抖音素材哪里下-抖音素材哪里找-短视频素材库
  20. BGP(1):BGP 的基本机制

热门文章

  1. ElasticSearch(6.3.0)的配置和使用全过程
  2. java空指针异常是什么、怎么发生、如何处理
  3. 【EARLIER/EARLIEST函数】引用不存在的更早的行上下文 报错解决
  4. 我的形码输入法[C语言] 之一:输入法的字词编码
  5. 转换工具推荐:如何将PDF文档转换为PPT演示文稿
  6. Python调用pywin32模拟触屏滑动 刷宝视频,自动刷视频python
  7. 徐亚波博士出席暨南大学企管ME论坛,讲述数说“超级飞轮”的故事
  8. JavaScript(WebAPI) (前端)
  9. unity3d 批量替换模型材质的脚本 一键替换模型及子物体材质
  10. UE4 Random Unit Vector In Cone