2
2

More than 1 year has passed since last update.

グラフィックス勉強のノード_3.1_Stencil Test & Z Test

Last updated at Posted at 2022-07-14

3.1 Stencil Test & Z Test

1. Stencil Test (ステンシル)

image-20220628141730506

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

1.1 Stencil Test ができること

1.1.1 Portal

image-20220628142410480

1.1.2 MinionsArt の作品

img img

img

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

1.2 Stencil Test は何ですか

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

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

image-20220628143751911

1.2.2 ロジックから考えて

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

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

1.2.3 UnityShaderのStencil

image-20220628150416257

  • 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

  • 最初のScene

    image-20220628173602633

  • 実現原理

    • UnityのStencilBufferValue のデフォルト値は0です

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

      Stencilの設定

      Comp always いつも通過する、

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

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

      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 

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

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

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

      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

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

    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

    • 原理

      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

      image-20220629094157682

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

      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

    • 原理

      SimpleStencilReflection

      //正常描画する
      Pass{}
      //頂点を変換して StencilValueを1を設定して 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を1にする

      Stencil {
          Ref 0       
          Comp always 
          Pass IncrSat
          Fail keep   
          ZFail keep  
      }
      
  • ShadowVolume (シャッドー・ボリューム)

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

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

      2. ShadowVolumeを計算する、

        image-20220629150526200

      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

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

      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

2. 深度テスト(Depth Test/ Z-Test)

2.1 深度テストは何ですか

  • レンダリングパイプライン中、PixelProcessingの中、StecilTestとBlendingの間image-20220628143751911

  • ロジックから理解する

    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

    image-20220630083518424

2.4 深度値

image-20220630092649693

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

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

      image-20220630093256383

      image-20220630093548786

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

      image-20220630094358614

      image-20220630094419116

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

      1. Z-Fight 原因

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

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

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

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

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

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

        • UnityShaderは Offsetを設置することでZFightを避ける

          Offset -1, -1
          

2.5 深度テストを応用する

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

    Ztest 01

    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

    • 原理

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

      • XrayはZTest Greaterを設定する。

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

        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

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

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

      image-20220630135944244

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

      image-20220630140831981

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

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

      image-20220630141228640

2.6 深度テストまとめ

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

2.7 その他の用途

Project

参考資料

Real-Time Rendering 4th Edition

Shadow Algorithms

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2