Aqoole_Hateenaの技術日記

vulkan+raytraceで色々描いてます

Unityによって予約されているTextureについて

概要

Unityにはいくつかの特別なテクスチャがあり、これらのテクスチャは特定の目的のために予約されている。
つまり、シェーダー内で同じ名前のテクスチャを独自に宣言して使用することは推奨されない。
とくにURPやHDRPのようなSRPでsahderを作成する際に気になるポイントかと思われる。
これらのテクスチャについて調べたので、おそらく他にもあるだろうがまとめておく。

URPのLit Shader

具体例として、URPのLit Shaderで用いられているpropertiesを抜粋する。

Properties
    {
        // Specular vs Metallic workflow
        _WorkflowMode("WorkflowMode", Float) = 1.0

        _WaterHeight("WaterHeight", Float) = 0.55
        _HeightScale("HeightScale", Float) = 1.0

        [MainTexture] _BaseMap("Albedo", 2D) = "white" {}
        [MainColor] _BaseColor("Color", Color) = (1,1,1,1)

        _Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5

        _Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5
        _SmoothnessTextureChannel("Smoothness texture channel", Float) = 0

        _Metallic("Metallic", Range(0.0, 1.0)) = 0.0
        _MetallicGlossMap("Metallic", 2D) = "white" {}

        _SpecColor("Specular", Color) = (0.2, 0.2, 0.2)
        _SpecGlossMap("Specular", 2D) = "white" {}

        [ToggleOff] _SpecularHighlights("Specular Highlights", Float) = 1.0
        [ToggleOff] _EnvironmentReflections("Environment Reflections", Float) = 1.0

        _BumpScale("Scale", Float) = 1.0
        _BumpMap("Normal Map", 2D) = "bump" {}

        _Parallax("Scale", Range(0.005, 0.08)) = 0.005
        _ParallaxMap("Height Map", 2D) = "black" {}

        _OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0
        _OcclusionMap("Occlusion", 2D) = "white" {}

        [HDR] _EmissionColor("Color", Color) = (0,0,0)
        _EmissionMap("Emission", 2D) = "white" {}

        _DetailMask("Detail Mask", 2D) = "white" {}
        _DetailAlbedoMapScale("Scale", Range(0.0, 2.0)) = 1.0
        _DetailAlbedoMap("Detail Albedo x2", 2D) = "linearGrey" {}
        _DetailNormalMapScale("Scale", Range(0.0, 2.0)) = 1.0
        [Normal] _DetailNormalMap("Normal Map", 2D) = "bump" {}

        // SRP batching compatibility for Clear Coat (Not used in Lit)
        [HideInInspector] _ClearCoatMask("_ClearCoatMask", Float) = 0.0
        [HideInInspector] _ClearCoatSmoothness("_ClearCoatSmoothness", Float) = 0.0

        // Blending state
        _Surface("__surface", Float) = 0.0
        _Blend("__blend", Float) = 0.0
        _Cull("__cull", Float) = 2.0
        [ToggleUI] _AlphaClip("__clip", Float) = 0.0
        [HideInInspector] _SrcBlend("__src", Float) = 1.0
        [HideInInspector] _DstBlend("__dst", Float) = 0.0
        [HideInInspector] _ZWrite("__zw", Float) = 1.0

        [ToggleUI] _ReceiveShadows("Receive Shadows", Float) = 1.0
        // Editmode props
        _QueueOffset("Queue offset", Float) = 0.0

        // ObsoleteProperties
        [HideInInspector] _MainTex("BaseMap", 2D) = "white" {}
        [HideInInspector] _Color("Base Color", Color) = (1, 1, 1, 1)
        [HideInInspector] _GlossMapScale("Smoothness", Float) = 0.0
        [HideInInspector] _Glossiness("Smoothness", Float) = 0.0
        [HideInInspector] _GlossyReflections("EnvironmentReflections", Float) = 0.0

        [HideInInspector][NoScaleOffset]unity_Lightmaps("unity_Lightmaps", 2DArray) = "" {}
        [HideInInspector][NoScaleOffset]unity_LightmapsInd("unity_LightmapsInd", 2DArray) = "" {}
        [HideInInspector][NoScaleOffset]unity_ShadowMasks("unity_ShadowMasks", 2DArray) = "" {}
    }

プロパティのオプションについて

プロパティの前に[]を指定して、オプションをつけることができる。
オプションについての公式マニュアルは以下。
ShaderLab: Properties - Unity マニュアル
例えば[MainTexture]があるが、これを指定すると、通常Unityは_MainTexがメインテクスチャであるとして内部で処理しているが、[MainTexture]が指定されたテクスチャをMainTextureであるとしてshaderの処理を実行する。
LitShaderでは_BaseMapがメインテクスチャとして扱われている。

予約されているTexture変数

_MainTex

上にも少し書いたとおりであるが、マテリアルにつけられるメインのテクスチャのことを指している。
[MainTexture]がオプションで付けられていない限り、デフォルトでUnityは_MainTexがメインテクスチャであるとして処理する。

_CameraOpaqueTexture

不透明なオブジェクトのレンダリング結果を格納するために使用される。
FrameDebugを見ると描画している処理順が見れるが、DrawOpaqueObjectsの処理が終わった後に、RendererのOpaqueで指定されているObjectsの描画結果がこのテクスチャに格納される。
Transparentsの処理でblendなどしたい場合、利用できる。

_CameraDepthTexture

カメラの深度情報を表すテクスチャで、デプステストやシャドウマップの生成など、深度に関連する処理を行う際に利用できる。

予約されているわけではないが、一般的に使用されているTexture変数

_BumpMap

Normal Mapとして一般的に使用されている名前。
カスタムする傾向にないビルトインパイプラインでは、この名前をそのまま使用した方がよいかも。

_SpecularMap

Specular Mapとして一般的に使用されている名前。
物体の反射ハイライトの制御に使用される。
ChatGPT先生に聞いてみると

要約すると、Unityのビルトインシェーダーでは_SpecularMapという変数名が一般的に使用されますが、カスタムシェーダーを作成する場合は自由に変数名を選択できます。ただし、シェーダーマテリアルのテクスチャスロットに関連付ける際には、スロットの名前を _SpecularMap として指定する必要があります。

とのことだが、スロットの名前というのが不明。
Unity内部で予約されているわけでなないので、Specular Mapという名前でなくてもよいはず。
開発者以外が使用することを想定して、inspectorに出てくる名前の部分を"Specular Map"にしなさい、ということかもしれない。

_EmissionMap

物体の発光や自己発光を制御するために使用されるが、上記同様_EmissonMapという名前が推奨されるが、この名前でなくともよい。

Shaderの基本についての調査

概要

Unity初心者の私がShaderの構造や関数のエントリーポイントについて調査したのでまとめる。

Shader

ShaderLabやHLSLの用語説明などは、ページによくまとまっているので、以降は個人的に気になったC# ScriptからShaderへのデータの渡し方や関数のエントリーポイント、Passについて知っておくべき知識などをまとめる。
Unity のシェーダの基礎を勉強してみたのでやる気出してまとめてみた - 凹みTips
Unity ShaderLab ノート - Qiita

構造

Shader クラス - Unity マニュアルには以下のように記述がある。

シェーダーオブジェクト
シェーダーオブジェクトには以下が含まれています。

名前など、それ自体の情報
フォールバックシェーダーオブジェクト。Unity がこのシェーダーオブジェクトを使用できない場合に、フォールバックシェーダーオブジェクトが使用されます。
1 つまたは複数のサブシェーダー
また、共有シェーダーコードや、カスタムエディター を使用するかどうかなどの追加情報を定義することもできます。シェーダーオブジェクトの定義については、ShaderLab: シェーダーオブジェクトの定義 を参照してください。

SubShader
SubShader (サブシェーダー) は、シェーダーオブジェクトを、異なるハードウェア、レンダーパイプライン、ランタイム設定に対応する部分に分離することができます。

SubShader には以下が含まれています。

このサブシェーダーが対応できるハードウェア、レンダーパイプライン、ランタイム設定についての情報。
サブシェーダー タグ (サブシェーダーに関する情報を提供するキーと値のペア)。
1 つまたは複数のパス
また、すべてのパスに共通するレンダー状態などの追加情報を定義することもできます。サブシェーダーで定義できるすべての内容については、ShaderLab: サブシェーダーの定義 を参照してください。

Pass
Pass (パス) には以下が含まれています。

Pass タグ (パスに関する情報を提供するキーと値のペア)。
シェーダープログラムを実行する前にレンダー状態を更新するための指示
1 つまたは複数のシェーダバリアントにまとめられたシェーダープログラム
また、名前などの追加情報を定義することもできます。パスで定義できるすべての内容については、ShaderLab: パスの定義 を参照してください。

Shader概観

SubShader

公式ドキュメント
ShaderLab: SubShader の定義 - Unity マニュアル
Shaderを、ハードウェアごとやパイプラインごとに分けて記述するときに、SubShaderにわけて記述できる。
つまりTagやPass, そのほかBlendなどの情報をSubShaderごとに分けて書く。
実際のUniversal Render Pipeline/Litを見ると、Tag情報の中でShaderModelを4.5と2.0でわけてSubShaderで記述しているようだった。

Tagについて

分けて記載するSubShaderの目的や特性をつける。
記述例

SubShader{
    Tag {"RenderPipeline" = "UniversalRenderPipeline"
           "RenderType" = "Opaque"
     }
}

調べてみたところ、以下のような設定値があるらしい。
ShaderLab: タグを SubShader に割り当てる - Unity マニュアル

Tag
RenderPipeline UniversalRenderPipeline
HighDefinitionRenderPipeline
Queue Background
Geometry
AlphaTest
Transparent
Overlay
RenderType Opaque
Transparent
TransparentCutout
Background
Overlay
TreeOpaque
TreeTransparentCutout
TreeBillboard
Grass
GrassBillboard
ForceNoShadowCasting True
False
DisableBatching True
False
LODFading
IgnoreProjector True
False
PreviewType Sphere
Plane
Skybox
CanUseSpriteAtlas True
False

Pass

Passとはグラフィックスのシェーダープログラム内で特定の描画処理を定義するためのセクションまたはブロックのこと。
Universal Render Pipeline/Litを見てみると、物体の描画や影の描画ごとにPassが作成されていた。
各Passにvertex関数とfragment関数があるので、どのPassも条件があえば実行される。

SubShader{
    Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "UniversalMaterialType" = "Lit" "IgnoreProjector" = "True" "ShaderModel"="4.5"}
    Pass{
        Name "ForwardLit"
        Tags{"LightMode" = "UniversalForward"}
        HLSLPROGRAM
        #pragma vertex LitPassVertex
        #pragma fragment LitPassFragment
        ***
        ENDHLSL
    }

    Pass{
        Name "ShadowCaster"
        Tags{"LightMode" = "ShadowCaster"}
        HLSLPROGRAM
        #pragma vertex ShadowPassVertex
        #pragma fragment ShadowPassFragment
        ***
        ENDHLSL
    }

    Pass{
        Name "GBuffer"
        Tags{"LightMode" = "UniversalGBuffer"}
        ***
    }

    Pass{
        Name "DepthOnly"
        Tags{"LightMode" = "DepthOnly"}
        ***
    }

    Pass{
        Name "DepthNormals"
        Tags{"LightMode" = "DepthNormals"}
        ***
    }

    Pass{
        Name "Meta"  //for lightmap baking
        Tags{"LightMode" = "Meta"}
        ***
    }

    Pass{
        Name "Universal2D"
        Tags{ "LightMode" = "Universal2D" }
        ***

    }
}


またHLSLでは、vertex shaderとfragment shaderのメイン関数のエントリーポイントはそれぞれ

#pragma vertex ShadowPassVertex
#pragma fragment ShadowPassFragment

のようにして指定する。
メイン関数のエントリーポイントを間違えると、たとえばFowardLit Passだと

did not find shader kernel LitPassVertex to comile

が表示されてコンパイルに失敗する。
#pragmaディレクティブは下記に一覧が表示されている。
HLSL のプラグマディレクティブ - Unity マニュアル

その他のshaderの文法については以下に詳細が記載されている。
例えば、fragment shaderで色を出力する関数にはSV_Targetの指定が必要。
シェーダーセマンティクス - Unity マニュアル

URPの概要についての調査

概要

Unity初心者の私がグラフィクスパイプラインの処理について調査したので、まとめたい。
モバイル端末での表示が関心事なので、ビルトインパイプラインとURP(Universal Render Pipeline)について調べてみた。

Unityでのグラフィクスパイプライン

まずは公式でどのように書かれているか、ということでグラフィクスパイプラインについてUnityの公式マニュアルをみると

レンダーパイプラインは、シーンのコンテンツを取得し、それを画面に表示する一連の操作を行います。簡単に説明すると、以下のような操作です。

使用するレンダリングパイプラインの選択
Unity は以下のレンダリングパイプラインを提供します。

ビルトインレンダーパイプラインは Unity のデフォルトの レンダリングパイプラインです。これは汎用レンダリングパイプラインで、カスタマイズのオプションが限られています。
ユニバーサルレンダーパイプライン (URP) はスクリプタブルレンダーパイプラインの 1 つです。素早く簡単にカスタマイズでき、幅広いプラットフォームで最適化されたグラフィックスを作成できます。
HD レンダーパイプライン (HDRP) はスクリプタブルレンダーパイプラインの 1 つです。ハイエンドプラットフォームで最先端の忠実度の高いグラフィックスを作成できます。
Unity のスクリプタブルレンダーパイプライン API を使って、独自の カスタムレンダーパイプライン を作成することができます。
https://docs.unity3d.com/ja/2021.3/Manual/render-pipelines-overview.html

用語説明
カリング:シーンの内、描画するオブジェクトを選別すること
レンダリングピクセル当たりの色を決めること
ポストプロセッシングレンダリングする画像を作成した後に、その画像を加工すること

引用:https://docs.unity3d.com/ja/2021.3/Manual/BestPracticeLightingPipelines.html

ということで、構造的には

  • ビルトインパイプライン
  • SRP (スクリプタブルレンダーパイプライン)
    • URP (ユニバーサルレンダーパイプライン) (ローエンド向け)
    • HDRP (HDレンダーパイプライン) (ハイエンド向け)

という区分になっている。

SRP (URP)でRenderPassの追加について

色々調べてみたが、この記事が一番わかりやすかったので引用する。
https://shibuya24.info/entry/unity-urp-add-pass

基本的なclassの簡単な相関図は以下のようになっている模様。

Scriptable Render Classes

スクリプトリファレンスに各クラスの簡単な説明があるので抜粋する。

Class 説明 リンク
ScriptableRenderer rendering戦略を決めるもので、どのようにcullingが実行されるかやlightingがされるかを決めるもの Class ScriptableRenderer | Universal RP | 7.1.8
ScriptableRenderPass URP rendererで使用されるrendering passを定義するもの Class ScriptableRenderPass | Universal RP | 7.1.8
ScriptableRendererFeature RenderPassをRendererに追加するもの Class ScriptableRendererFeature | Universal RP | 7.1.8
RenderPipeline Unityがframeを描画する一連のコマンドや設定を定義するもの Rendering.RenderPipeline - Unity スクリプトリファレンス
ScriptableRenderContext custom render pipelineが用いるコマンドや状態を定義するもの Rendering.ScriptableRenderContext - Unity スクリプトリファレンス

Frame Debugとドローコールについて

Unityの機能のFrame Debugを使うと、ドローコールの順が見える。
どの順で何の命令が実行されているかが見えるので、処理の理解と最適化に役立つと思う。
https://docs.unity3d.com/ja/current/Manual/FrameDebugger.html
Frame Debugをみると、実行されているRendererが確認できる。

Frame Debug

終わりに

これからもUnityのグラフィクスについての調査をして、まとめていきたい。
各Classについても詳細に調べたり、Shaderとの関係や処理についても調べてまとめたい。

オブジェクトが途中から描画されない場合

概要

オブジェクトが途中から描画されない場合があった。

原因

Top Level ASの更新漏れ

詳細

Khronos Blog - The Khronos Group Inc

the triangles or axis-aligned bounding boxes (AABBs)

とある通り、ray tracingのオブジェクトの範囲はboxで指定されている。

The upper level, the top-level acceleration structure, contains references to a set of bottom-level acceleration structures, each reference including shading and transform information for that reference.

とあり、TLASはBLASの参照をもっているので、BLAS全体を統合し、描画するAABBを決めるものと思われる。
なので、TLASを更新しなければ、最初のAABBの範囲内しか描画されず、上動画のような挙動になってしまう。

Acceleration Structure Hierarchy

引用:
https://www.khronos.org/blog/ray-tracing-in-vulkan

上記絵の物体は、AABBも更新されることを表しているものと思われる。

3Dオブジェクトをタッチしているかの判定方法

結論

カメラーオブジェクトのベクトルとカメラータッチ位置をワールド座標に変換したベクトルの内積を計算し、1に近ければタッチしていると判定

補足

点と平面の距離を計算して、その距離が0に近ければ、なども考えたが、内積を考えるのが速そう。
Unityであれば、Rayを飛ばすという方法があるらしい。
クリック(スマホではタップ)したゲームオブジェクトを判定する方法 | ソフトライム
3D空間でタッチした位置というのは、1点に定まらないので、カメラータッチ位置の直線上にオブジェクトがあるかどうかを判断するしかないと思う。
レイトレーシングの構造体(AS)を準備しているのであれば、判定するだけのパイプラインを作成してもよいかもしれない。
オブジェクトが重なったときに前面のものだけタッチしているかどうかを判定したければ、空間上のオブジェクトをすべて認識している必要があるので、
レイトレーシングアーキテクチャーを使用するのがよさそうか。

水面の表現に挑戦

水面の反射

水面に反射する草を描画してみました

課題

  1. 反射の計算が重すぎて驚異の2fpsしか出ていない
  2. 水面の頂点をすべて保持しているので、コピーで済む部分はそのようにしたい offsetをもつようにするとか
  3. 水面の頂点の計算はCPUで行っているので、GPUで行うようにcompute shaderを書いた方がよいかも
  4. オブジェクト追加するたびにshader内のオブジェクトidを書き換えなければならないのが面倒 オブジェクトのグループごとのshaderをつくっても良いかも
  5. sbtの設計やvertex bufferの設計をまとめた方がよいかもしれない

glTFモーフィング描画(アルファテストあり)

glTFモーフィング(アルファテストあり)

アルファテストを用いて透明 or notを判定することにより、必要な部分だけを取り出してモーフィングアニメーションすることができた。

モデルの権利表記
https://sketchfab.com/3d-models/yard-grass-3a67e76decc849c694c228eb590a9902
ライセンス:CC Atribution
https://creativecommons.org/licenses/by/4.0/legalcode

レイトレーシングでのアルファテスト

Googleで調べてみると色々な人が書いているが、レイトレーシングでのアルファテストはanyhit shaderを用いることで達成できる。
anyhit shader内でアルファテストを行い、もし透明な部分にヒットしている場合には

ignoreIntersectionEXT

を用いて交差判定を無視する。
これを用いると、その判定は無視してrayは直進し直し、次の判定に進む。

注意点としては
Khronos Blog - The Khronos Group Inc
にある通り、ignoreIntersectionEXT()ではなくignoreIntersectionEXTを用いる。

instead of appearing as ignoreIntersectionEXT(); it is now simply ignoreIntersectionEXT; when used in a shader.

その他、any hit shaderの実装方法についてはnvidia様が作成しているvulkan raytracing tutorialが参考になる。
github.com