グラフィックス勉強のノード＿3.1＿Stencil Test & Z Test

# 3.1  Stencil Test & Z Test

## 1. Stencil Test （ステンシル）

![image-20220628141730506](https://vip2.loli.io/2022/06/28/Rj8SimOWwT1C2bu.png)

条件が満たされた時、何かの効果を入れる。

### 1.1 Stencil  Test ができること

#### 1.1.1 Portal

![image-20220628142410480](https://vip2.loli.io/2022/06/28/3O6hxRILN8oUjAH.png)

#### 1.1.2 MinionsArt の作品

<img src="https://github.com/WangYiTao0/GrapichStudyNote/blob/master/Image/3dCard.gif?raw=true"/>

<img src="https://vip2.loli.io/2022/06/28/MOFlBt6Dmk83S1o.gif" alt="img"  />

<img src="https://vip2.loli.io/2022/06/28/DrqyvUsB2gPExI3.gif" alt="img"  />

![img](https://vip2.loli.io/2022/06/28/OtRTvzYZVLyhlcj.gif)

以上の例をまとめると、これらのエフェクトは基本的３つのレイヤーで構成されました。Portalの例では、ドア外、ドア中、ドア。二つのオブジェクトやシーンと一つのマスクで構成された。



### 1.2  Stencil Test は何ですか	

#### 1.2.1 レンダリングパイプラインから考えて

- StencilTestはPixel Processing 段階中の一環です。
- StencilTest はプログラムすることができない、配置することができる。

![image-20220628143751911](https://vip2.loli.io/2022/06/28/MIHWDejyZotp6O1.png)

#### 1.2.2 ロジックから考えて

```c#
if(referenceValue & readMask comparisonFunction stencilBufferValue&readMask)
    //ピクセル通過
else
    //ピクセル除外

```

- referenceValue StencilBuffer中の参考値
- stencilBufferValue　現在StencilBuffer中の値
- StrencilBuffer 一般的は８ビット

#### 1.2.3 UnityShaderのStencil

![image-20220628150416257](https://vip2.loli.io/2022/06/28/t8UADhuimalKfpT.png)

- referenceValue 参考値
- readMask writeMask 
- ComparisonFunction 比較方法
  - Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never 
- Pass　StencilTest通過した後の操作
- Fail　StencilTest通過しなかった後の操作
- ZFail StencilTest通過したが、深度テスト通過しなかった
- StencilOperation  
  - Keep  StencilBufferValueを保留する
  - Zero　StencilBufferValue = 0
  - Replace StencilBufferValue  = referenceValue
  - IncrSat StencilBufferValue  += 1; if(StencilBufferValue  >=255) StencilBufferValue  = 255;
  - DecrSat StencilBufferValue   -= 1; if(StencilBufferValue   <=0) StencilBufferValue   = 0;
  - Invert   StencilBufferValue  = ~StencilBufferValue  
  - IncrWrap StencilBufferValue  += 1; if(StencilBufferValue  >255) StencilBufferValue  = 0;
  - DecrWrap StencilBufferValue   -= 1; if(StencilBufferValue   < 0) StencilBufferValue   = 255;

### 1.3 .  Stencil Test を応用する

#### 1.3.1 3DカードDemoを真似する

- 効果![stencil01](https://vip2.loli.io/2022/06/28/DVFnWcu4ofTPHgY.gif)

- 最初のScene

  ![image-20220628173602633](https://vip2.loli.io/2022/06/28/vA1LUfYDJajRqrx.png)

- 実現原理

  - UnityのStencilBufferValue のデフォルト値は０です

  - Maskオブジェクトを レンダリングする

    Stencilの設定

    Comp always いつも通過する、

    Pass replace　通過してから、StencilBufferValue とReferenceValueを交換する 

    Mask部分のStencilBufferValue １になります。

    ```shader
    Properties{
     
    	_ID("Mask ID", Int) = 1
    }
    SubShader
    {
        Tags
        { 
            "RenderType" = "Opaque"
            "Queue" = "Geometry+1"  //default + 1
        }
        ColorMask 0 //RGBA RGB R,G,B,A,0
        ZWrite off	
        Stencil
        {
            Ref[_ID]
            Comp always //default always  いつも通過する
            Pass replace // default keep　そして値をキープする　
            //Fail keep
            //ZFail Keep
        }
     }
    ```

  - Maskedオブジェクトをレンダリングする  

    Stencilの設定

    Comp equal　

    ```c#
    if(currentStencilBufferValue == referenceValue)
    {
        //レンダリングする
    }
    else{
        //レンダリングしない
    }
    ```

    つまり、Mask以外は０ですから、Maskedオブジェクトはレンダリングしない。Mask以内は１ですから。レンダリングする。簡単効果をてきった。

    Pass　Keep　通過したら、変化なし。StencilBufferValue = 1

    ```c#
    Properties
    {
        _Color("Color",Color) = (0,0,0,1)
        _MainTex ("Texture", 2D) = "white" {}
        _Ramp("Toon Ramp(RGB)", 2D) = "Gray" {}
        _ID("Mask ID",Int) = 1
    }
    SubShader
    {
        Tags
        { 
            "RenderType"="Opaque"
            "Queue" = "Geometry+2" //Default + 2  after StencilMask  
        }
        Stencil 
        {
    		Ref [_ID]
    		Comp equal 
            //Pass Keep default
    	}
    ```

  - レンダリング順番　Geometry -> Mask(Geometry+1) -> Masked Object(Geometry+2)


#### 1.3.2 Stencil Box 

- 効果

  ![stencilbox.gif](https://vip2.loli.io/2022/06/29/CPc2rAFU7GE4Vlu.gif)

- 実現原理は上と同じ、Enumを使ってマテリアルを単独設置する。

  ```shader
  Properties
  {
  		_Color ("Color", Color) = (1,1,1,1)
  		_StencilRef("Stencil Ref",float) = 1
  		[Enum(UnityEngine.Rendering.CompareFunction)] _SComp("Stencil Comp", Int) = 8
  		[Enum(UnityEngine.Rendering.StencilOp)] _SOp("Stencil Op", Int) = 2
  }
  SubShader
  {
  	Tags 
  	{
  		"RenderType" = "Opaque"
  		"Queue" = "Geometry+1"
  	}
  	ColorMask 0
  	ZWrite off
  		
  	Stencil
  	{
  		Ref[_StencilRef]
  		Comp[SComp]
  		Pass[_SOp]
  	}
  }
  ```

### 1.4 . StencilTest まとめ

- Stencilbufferを用いて一番重要な二つの値：現在StencilBufferの値(StencilBufferValue) とStencilBuffer参考値(referenceValue)。
- StencilTestは、主にこの二つの値に対して、Never，Always，Less などの比較演算をすること。
- StencilTest後はその現在StencilBufferの値(StencilBufferValue)に対して更新操作をする、Keep，Zero，Replace，IncrSat，DecrSatなどを使用する。
- StencilTest後はピクセルを操作こと、Pass、Fail、ZFailなど

### 1.5. その他の用途

- OutLine

  - 効果

    ![image-20220629085348566](https://vip2.loli.io/2022/06/29/UaEDnXNm3Bz8T4C.png)

  - 原理

    ```shader
    Stencil {
         Ref 0          //0-255
         Comp Equal     //default:always
         Pass IncrSat   //default:keep
         Fail keep      //default:keep
         ZFail keep     //default:keep
    }
    /*
    Pass 1 オブジェクトをレンダリングする、
    そしてStencilTest通過して、IncrSat操作
    StencilBufferValue + 1
    */
    Pass{}
    /*
    Pass 2 頂点を拡大する
    StencilBufferValueは1ですから、StencilTestを失敗て。
    拡大の部分だけは現在StencilBufferValueは0の部分でレンダリングする
    */
    Pass
    {
    	...
    	 o.vertex= v.vertex+ normalize(v.normal)*_OutLineWidth;
    	...
    	return _OutLineColor;
    	...
    }
    ```

    

- ポリコン充填

  - 効果

    ![image-20220629092330429](https://vip2.loli.io/2022/06/29/twSc8rU2YBIbf1V.png)

    ![image-20220629094157682](https://vip2.loli.io/2022/06/29/MntjgI8W3v9ENCU.png)

  - 原理 Pass 1　レンダリング　StencilTest通過　+1、残ったPassはstencilValue2,3,4でレンダリングする

    ```shader
    Pass{
        Stencil {
            Ref 0          
            Comp always    
            Pass IncrWrap  
        }
    }
    Pass{
        Stencil {
            Ref 2     
            Comp Equal
            Pass keep 
            Fail keep 
            ZFail keep
        }
    }
    Pass{
        Stencil {
            Ref 3     
            Comp Equal
            Pass keep 
            Fail keep 
            ZFail keep
        }
    }
    Pass{
        Stencil {
            Ref 4     
            Comp Equal
            Pass keep 
            Fail keep 
            ZFail keep
        }
    }
    ```

- 反射　

  - 効果

    ![image-20220629100859033](https://vip2.loli.io/2022/06/29/rhesDS1PNnBMRJl.png)

  - 原理 

    SimpleStencilReflection

    ```shader
    //正常描画する
    Pass{}
    //頂点を変換して　StencilValueを１を設定して　CompをEqualにする
    Pass
    {
         Stencil {
                    Ref 1          //0-255
                    Comp Equal     //default:always
                    Pass keep   //default:keep
                    Fail keep      //default:keep
                    ZFail keep     //default:keep
           }
           ZTest Always // 深度テストalways通過する
           
          ...
           //頂点をReflect
           v.vertex.xyz=reflect(v.vertex.xyz,float3(-1.0f,0.0f,0.0f));
    	   v.vertex.xyz=reflect(v.vertex.xyz,float3(0.0f,1.0f,0.0f));
          ...
    }
    ```

    SimpleMirror　//Mirro部分のStencilValueを１にする

    ```shader
    Stencil {
        Ref 0       
        Comp always 
        Pass IncrSat
        Fail keep   
        ZFail keep  
    }
    ```

- ShadowVolume (シャッドー・ボリューム)

  - 原理 : 方法はいくつありますが。このDemoはDepthFailを使います。Carmack’s reverseと呼ばれます

    1. 通常レンダリングします

    2. ShadowVolumeを計算する、

       ![image-20220629150526200](https://vip2.loli.io/2022/06/29/tdb9QR6Olq25oNk.png)

    3. Pass1はCull Front, StencilTest中で、深度テスト失敗した場合、このピクセル見えない。StencilValue+1

    4. Pass2はCull Back  深度テスト失敗した場合、StencilValue - 1

       対象A、CullFront　深度テスト失敗 +1 Cull Back 深度テスト失敗 -1 結果0

       対象B、CullFront　深度テスト失敗 +1 CullBack 深度テスト成功 +0　結果1

       対象C、CullFront　深度テスト成功 +0 CullBack 深度テスト成功 +0　結果0

       ![](https://vip2.loli.io/2022/06/30/li7dBTH9amzMfwR.jpg)

    5. この二つのPass経過して、StencilBufferValue = 1の部分で影を描画する。

    ```shader
    Pass
    {
        Cull Front          
        Stencil {           
            Ref 0           //0-255
            ZFail IncrWrap  //default:keep
        }
        ColorMask 0         //Color write Off
    }
    pass
    {
         Cull Back          
         Stencil {
             Ref 0           //0-255
             ZFail DecrWrap  //default:keep
         }
         ColorMask 0        
    }
    pass
    {
          Cull Back          
          Stencil {
             Ref 1          //0-255
             Comp equal     //default:always
           }
    }
    ```

    

  - 効果 

    これはStencilTest勉強のため、StencilBufferの役割を示すだけです。ShadowVolumeを動的生成ではない、Unityのデフォルトジオメトリの円柱を使ってShadowVolumeを粗雑に模倣しました。

    ![image-20220629175444858](https://vip2.loli.io/2022/06/29/93Ee1bfDyuLNVnR.png)

    

## 2. 深度テスト（Depth Test/ Z-Test）

### 2.1 深度テストは何ですか

- レンダリングパイプライン中、PixelProcessingの中、StecilTestとBlendingの間![image-20220628143751911](https://vip2.loli.io/2022/06/28/MIHWDejyZotp6O1.png)

- ロジックから理解する

  ```c#
  if(ZWrite On && (currentDepthValue ComparisionFunction DepthBufferValue)){
    	DepthValue更新する
  }else{
      無視する
  }
  
  if(currentDepthValue ComparisionFunction DepthBufferValue){
      色バッファに書き込み
  }
  else
  {
       色バッファに書き込まない
  }
  ```

  StencilTestと似ている、通過したかどうか深度値を基づいて判断する、通過したら、色バッファに書き込む、しなかったら、無視する。

### 2.2 基本原理と使用方法

- Z-buffer 一般的は24ビット

- ZWrite On && ZWrite Off && ZTest

  1. ZTest pass Zwrite On : Z-bufferに書き込み、色バッファに書き込み
  2. ZTest pass Zwrite Off : Z-bufferに書き込まない、色バッファに書き込み
  3. ZTest fail Zwrite On : Z-bufferに書き込まない、色バッファに書き込まない
  4. ZTest fail Zwrite Off : Z-bufferに書き込まない、色バッファに書き込まない

- ZTestの比較操作

  | ZTest の比較ルール |                           |
  | :----------------: | :-----------------------: |
  |      Greater       | 現在の深度＞ZBufferの深度 |
  |       LEqual       |            <=             |
  |        Less        |             <             |
  |      GEqueal       |            >=             |
  |       Equal        |            ==             |
  |      NotEqual      |            !=             |
  |    Always(Off)     |      いつも通過する       |
  |       Never        |     いつも通過しない      |

  デフォルトの状態

  - ZWrite : On
  - ZTest : LEqual
  - Zbufferのデフォルト値は1.0

- Unityのレンダリングキュー

  数値は小さいほうのキューは先にレンダリングする。

  | Unity内蔵キュー    |                                                              |
  | ------------------ | ------------------------------------------------------------ |
  | Background（1000） | 最初でレンダリングされたオブジェクトの待ち行列               |
  | Geometry(2000)     | 不透明なオブジェクトをレンダリングする、UnityShaderのデフォルト行列 |
  | AlphaTest(2450)    | 透明なオブジェクト&&AlphaTestが必要なオブジェクトでここでレンダリングするとGeometryより効率がいい |
  | Transparent(3000)  | 半透明のオブジェクト、一般的はZWriteOffのオブジェクト、AlphaBlendなど |
  | Overlay(4000)      | 最後でレンダリングされたオブジェクトの待ち行列、一般的はカメラエフェクトなと |

  - 不透明なオブジェクトのレンダリング順番：前からレンダリングする
  - 透明なオブジェクトのレンダリング順番：後ろからレンダリングする(OverDraw)

### 2.3 EazyZ 技術紹介

- 従来のレンダリングパイプラインは、ZTestはBlendingステージにある、深度テストが実行されると、すべてのオブジェクトのピクセルシェーダが一度計算され、性能向上はなく、正しい効果を得るためだけに多くの無駄な計算が行われます（深度テストに失敗したピクセルは、すでに計算されたものであり、つまり、次のステップでテストが失敗して破棄されるまでは、それまでの計算は無意味です）

- このような不要な計算を減らすために、現代のGPUでは、頂点とピクセルステージの間（ラスタライズ後、ピクセルステージの前）で深度テストを行うEarly-Zテクニックを採用しています

  - もしここの深度テスト失敗した、後のピクセル計算は必要ないです。性能向上になります
  - 正しいオブジェクトの前後関係を確認するために、最終的なZTestをまだ実行する必要があります
  - 前のZ-Cullは性能向上のため、後ろのZ-Checkは正しいオブジェクトの前後関係を確認するため

  ![image-20220630082959278](https://vip2.loli.io/2022/06/30/gMKiAvLbwEIde1f.png)	

  ![image-20220630083518424](https://vip2.loli.io/2022/06/30/fBliOox7F18C9y6.png)

### 2.4 深度値

![image-20220630092649693](https://vip2.loli.io/2022/06/30/dH9kWXVDnTCZjwe.png)

- 何でz/wを深度バッファに書き込むですか。

  1. 深度バッファの範囲は[0~1]の間、深度値を[0~1]の間に変換するには一つの方法はリニア変換(near/far はカメラのfrustum)

     ![image-20220630093256383](https://vip2.loli.io/2022/06/30/jRdgNhDP1sCMF7x.png)

     ![image-20220630093548786](https://vip2.loli.io/2022/06/30/ea6IVlTkxyhPOw5.png)

     

     実際にはノンリニア変換を使う

     ![image-20220630094358614](https://vip2.loli.io/2022/06/30/OorTpYIk8VbSvel.png)		

     ![image-20220630094419116](https://vip2.loli.io/2022/06/30/V6b4hgimANQDyo3.png)

     人の目は近いものを見るち精度が高い、遠いものを見ると精度が低い、ノンリニアグラフは人の目と似ている。

     2. Z-Fight 原因

        二つの三角形が互いに密接に平行である場合、深度バッファの精度が足りない、どれが前かわからないことがある。

        ノンリニアを使うと、近いもの精度が高いで、ZFightは出現しにくい、遠いものはZ-Fightがあったとしても、遠いものだから、気づかない。

        もちろんZFightを避けるが必要です。

        - 物理的、オブジェクトを近すぎように注意する。

        - CameraのNearPlaneを近くに設置する

        - すこし性能を犠牲し、深度バッファを精度を上げる。深度バッファの値は一般的は24ビットです。現代のGPUは32ビットの深度バッファもう支持する。

        - UnityShaderは [Offset](https://docs.unity3d.com/Manual/SL-Offset.html)を設置することでZFightを避ける

          ```c#
          Offset -1, -1
          ```

### 2.5 深度テストを応用する

- ZWrite ZTest　をいくつ実験をする。

  ![Ztest 01](https://vip2.loli.io/2022/06/30/DNwxA7JqKbRh6Ou.png)

  1. 正常描画
  2. 赤をZWriteOffにして。赤部分の深度値はデファクト値1.0、青の深度値＜＝1.0、青の深度値を深度バッファに書き込む
  3. 青のZTest をAlways通過する、比較なしで、青の深度値を深度バッファに書き込む
  4. 青と緑もZTestをAlways通過する、比較なしで、青と緑の深度値を深度バッファに書き込む、同じレンダリングキュー（Geometry）のレンダリング順番は前から始まるので、緑が青の深度値を上書きした。
  5. 青と緑もZTestをAlways通過する、青のレンダリングキュー+1（Geometry+1）,青が緑の深度値を上書きした。
  6. 青のZTest をGreaterに設定、デフォルト値＞緑の深度値＞青の深度値＞赤の深度値、青と赤が交差する部分だけ、青深度テスト成功した。

- XRay

  - 効果

    ![ZTestXray.gif](https://vip2.loli.io/2022/06/30/NmnqCG1e2KjfoT8.gif)

  - 原理

    - 三つの部分：壁、オブジェクト、X-Ray効果

    - XrayはZTest Greaterを設定する。

    - オブジェクト　は　デフォルト値

      ```shader
      Pass //RenderXray
      {
      	　Tags{"RenderType" = "Transparent" "Queue" = "Transparent"}
            Blend SrcAlpha One // 
            ZTest Greater
            ZWrite Off
            Cull Back
      }
      Pass //Normal　Rendering
      {}
      ```

  - 一つ現象があります。XRayPassを "Queue" = "Transparent"設定しましたが、FrameDebugの中はOpaqueGeometry中にいる、そしてXRay効果はNormalRenderingの前です。

    実はオブジェクトはmulti-passシェーダーを使うとUnityはそのシェーダーの中一番小さいキューを選択する、オブジェクトをこのキューに入れる、（Geometry(2000)＜TransParent(3000)）このオブジェクトはGeometryの中でレンダリングする、それからPassの書き順番を基づいてPassを実行する。XRay効果はNormalRenderingの前になります。

    ![image-20220630133318610](https://vip2.loli.io/2022/06/30/ouM5awiqhB6z1RI.png)

- パーティークルのテスト

  - デファクトのパーティークル、透明です。

    ![image-20220630135944244](https://vip2.loli.io/2022/06/30/5Rj6QOErAKwp4TZ.png)

  - 理解を深めるため、UnlitShaderを作って、パーティークルのデフォルトマテリアルを変える。

    ![image-20220630140831981](https://vip2.loli.io/2022/06/30/vLFmxiPKqlEkDGO.png)

  - どうすればデフォルトの状態に戻るか

    - まずQueue　= Transparent
    - ZWrite Off 透明オブジェクトですから
    - ZTest LEqual (default) 
    - Blend 操作　one one　

    ![image-20220630141228640](https://vip2.loli.io/2022/06/30/vBCGVmeLc79Y5jS.png)

  

### 2.6 深度テストまとめ

- 深度テスト重要な二つの値、現在の深度バッファ値(ZBufferValue)と深度参考値(reference)
- Unityのレンダリング順番、不透明オブジェクト、前から、透明なオブジェクト、後ろから
- ZWriteとZTestの使用透明なオブジェクトのレンダリングをコントロールできる。
- EarlyZ技術
- 深度バッファの値は「o～１」、ノンリニア。

### 2.7 その他の用途

- [Toon Water Shader](https://roystan.net/articles/toon-water.html)

  ![image-20220630144544565](https://vip2.loli.io/2022/06/30/4dRTojQFsXfkWyA.png)

- [ShadowMap](https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping)

  ![image-20220630144652239](https://vip2.loli.io/2022/06/30/BVFGd5WqCOkEeDv.png)

## Project

https://github.com/WangYiTao0/GraphicLearningLegacy

## 参考資料

Real-Time Rendering 4th Edition

[Shadow Algorithms](chrome-extension://efaidnbmnnnibpcajpcglclefindmkaj/http://web.cse.ohio-state.edu/~wang.3602/courses/cse5542-2013-spring/19-shadow.pdf)

https://learnopengl.com/

https://www.patreon.com/posts/14832618

https://docs.unity3d.com/Manual/SL-Stencil.html

https://en.wikipedia.org/wiki/Shadow_volume

https://roystan.net/articles/toon-water.html