特定の例を使用してUnity3Dのシェーダーを扱います





理論のビット



シェーダーの動作を理解するには、ビデオカードがイメージを作成する方法に精通している必要があります。 画面上の3Dオブジェクトの視覚化の一般的な構造を次の図に示します。





まず、グラフィックスレンダリングパイプラインのような概念を見てみましょう。 これはコンベアであり、ビデオカードが最終的な画像を作成する段階です。 少し歴史に飛び込みます。 最初のコンピューターはソフトウェアレンダリングを使用しました。 誤算はすべて中央処理装置によって処理されました。 コンベアは次のようになりました。



最初の3Dアクセラレータは、いわゆる固定機能パイプラインを使用していました。 名前から、それが修正され、厳密に一貫していることがわかります。 画像の構築を妨げることは不可能でした。 次に、このコンベヤのすべての段階を検討します。 将来的には、これが重宝します。

  1. 入力データ。 入力パラメーターとして、ビデオカードは、多くの属性を持つ個々の頂点の形でオブジェクトを受け取ります。 たとえば、空間内の頂点の位置、その色、法線、テクスチャ座標など。
  2. 変換と照明。 この段階で、オブジェクトに対して幾何学的操作(変位、回転、スケール)が実行されます。 また、シーンの照明も計算します。 各頂点について、光源の位置とタイプ、およびオブジェクトの表面を特徴付けるパラメーター(反射、吸収)に基づいて照明値が計算されます。
  3. 三角測量。 この段階で、頂点は三角形に結合されます。
  4. ラスタライズ このステップの目的は、すでに準備されているデータに基づいてピクセルの色を計算することです。 頂点の色に関する情報しかないため、ピクセルの色を取得するために、対応する頂点の色の値の間の値を線形補間します。
  5. ピクセル処理。 この段階で、ピクセルの色付けが行われます。 入力は前のレベルのデータです。 また、ここでピクセルに追加の効果を適用できます。 たとえば、テクスチャリング。
  6. 完成した画像の形成。 次に、最終フレームを構築する必要があります。 この段階では、どのオブジェクトがカメラに近いかを判断するために、Zバッファデータが考慮されます。 アルファテストもここで実行されます。 レイヤーごとに、オブジェクトは最終イメージに「適用」されます。 ポストエフェクトもここで適用できます。 その後、完成したフレームがフレームバッファーに配置されます。


そこで、固定機能パイプラインの手順を確認しました。 ご覧のように、特定のステージに特定のオプションを選択することによってのみ最終画像に影響を与えることができますが、たとえば照明モデルを記述することはできません。 当時は、特定のビデオアダプターがサポートする内容に満足する必要がありました。 これは、DirectX 8.0-8.1のハードウェアサポートを備えた最初のグラフィックスカードの前でした。 この時点から、頂点とピクセルを処理するプログラムを書くことができました。 このようなモデルのコンベヤーを次の図に示します。









将来的には、パイプラインのすべての段階をプログラム可能にする予定です。

ビデオカードがどのように画像を作成するかがわかったので、3Dオブジェクトについて少し話す価値があります。 モデルは、頂点、それらの間の関係、およびマテリアル、アニメーションなどのコレクションです。頂点には属性があります。 たとえば、UV座標。 これらは、テクスチャスキャン上のこの頂点の位置を示します。 マテリアルはオブジェクトの外観を決定し、ジオメトリまたはパーティクルを視覚化するために使用されるシェーダーへのリンクを含みます。 したがって、これらのコンポーネントはマテリアルなしでは表示されません。

シェーダーは、グラフィックスパイプラインのいずれかの段階のプログラムです。 すべてのシェーダーは、頂点とフラグメント(ピクセル)の2つのグループに分けることができます。 Unity3Dには、シェーダー-Surface Shaderを記述するための単純化されたアプローチがあります。 それは、単に抽象化のより高いレベルです。 Surfaceシェーダーをコンパイルするとき、コンパイラーは頂点シェーダーとピクセルシェーダーで構成されるシェーダーを作成します。 Unity3Dの言語はあなた自身のものです。 ShaderLabと呼ばれます 。 CGおよびHLSLでの挿入をサポートしています。



練習する



例として、拡散オブジェクトにテクスチャ、法線マップ、反射マップ(Cubemapに基づく)を課し、拡散テクスチャのアルファチャネルに沿ってピクセルをカットするシェーダーの作成を検討します。

開始するには、一般的なShaderLab構文を検討してください。 シェーダーをCGまたはHLSLで記述している場合でも、インスペクターでシェーダーのパラメーターを設定できるように、ShaderLab構文を知る必要があります。



Shader "Group/SomeShader" { // properties that will be seen in the inspector Properties { _Color ("Main Color", Color) = (1,0.5,0.5,1) } // define one subshader SubShader { Pass { } } } Fallback "Diffuse" }
      
      





最初のキーワードはShaderです。 その後、シェーダーの名前が引用符で示されます。 さらに、エディターでマテリアルを設定するときに、ドロップダウンメニューでシェーダーが配置されるパスを「/」で指定できます。 その後、インスペクターに表示され、ユーザーが操作できるプロパティ{}パラメーターの説明があります

Unity3Dの各シェーダーには、本体に少なくとも1つのサブシェーダーが含まれています。 ジオメトリを表示する必要がある場合、エンジンは必要なシェーダーを検索し、ビデオカードが処理できるリストの最初のサブシェーダーを使用します。 これは、同じシェーダーが異なるシェーダーモデルをサポートする異なるビデオカードで正しく表示できるようにするために行われます。 Pass {}キーワードは、シングルパス命令ブロックを定義します。 シェーダーには、1〜複数のパスを含めることができます。 複数のパスを使用すると、たとえば、古いハードウェア用にシェーダーを最適化する場合や、特殊効果(アウトライン、トゥーンシェーディングなど)を実現する場合に便利です。

Unity3Dがシェーダーボディにジオメトリを正しく表示できるSubShaderを見つけなかった場合、 フォールバック命令が使用された後に宣言された別のシェーダーにロールバックします。 上記の例では、ビデオカードが現在のシェーダーを正しく表示できない場合、拡散シェーダーが使用されます。



次に、特定の例を考えてみましょう。



 Shader "Example/Bumped Reflection Clip" { Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("Bumpmap", 2D) = "bump" {} _Cube ("Cubemap", CUBE) = "" {} _Value ("Reflection Power", Range(0,1)) = 0.5 } SubShader { Tags { "RenderType" = "Opaque" } Cull Off CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float2 uv_BumpMap; float3 worldRefl; INTERNAL_DATA }; sampler2D _MainTex; sampler2D _BumpMap; samplerCUBE _Cube; float _Value; void surf (Input IN, inout SurfaceOutput o) { float4 tex = tex2D (_MainTex, IN.uv_MainTex); clip (tex.a - 0.5); o.Albedo = tex.rgb; o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); float4 refl = texCUBE (_Cube, WorldReflectionVector (IN, o.Normal)); o.Emission = refl.rgb * _Value * refl.a; } ENDCG } Fallback "Diffuse" }
      
      







[ プロパティ]フィールドには、Unity3Dインスペクターに表示される4つの変数が含まれています

_MainTex-括弧内は、インスペクターに表示される名前、タイプ、およびデフォルト値を示します。

_MainTex_BumpMapはテクスチャ、 _Cubeは反射用のキューブ、 _Valueは反射効果の度合い用のスライダーです。

タグ{"RenderType" = "Opaque"}は、シェーダーを不透明としてマークします。 これはレンダリングキューに影響します。

Cull Offは、シェーダーが法線方向に沿ってクリッピングされないことを示します。 ポリゴンをクリップするための3つのオプションがあります:その法線はカメラからカメラに向けられ、クリップなしです。 最後のオプションは、ポリゴンを2つの側面から見ることを意味します。

コードを記述するときは、CG挿入を使用します。 挿入は、2つのキーワードCGPROGRAMENDCGで囲まれています。

#pragma surface surf Lambert-サーフェスシェーダー関数と追加パラメーターの宣言。 この場合、関数はsurfと呼ばれ、Lambertライティングモデルは追加パラメーターとして示されます。

次に、入力構造を検討します。 入力構造のすべての可能な変数は、 ヘルプで見つけることができます。 使用されるもののみを考慮します。

変数uv_MainTexおよびuv_BumpMapは、シェーダーがオブジェクトを適切にテクスチャリングするために必要なUV座標です。 これらの変数は、テクスチャ変数の名前と同じ方法で、最初と2番目のチャネルのそれぞれに接頭辞uv_またはuv2_を付けて呼び出す必要があります。 worldReflおよびINTERNAL_DATAは、反射に使用されます。

次に、 surfシェーダー関数を検討します。

最初のステップは、テクスチャのピクセルカラーに関する情報を格納する4コンポーネントベクトルを取得することです。 tex変数は、それに関する情報を保存します。 その後、 クリップ関数を使用して、レンダリング時にスキップするピクセルを指定します。 なぜなら アルファチャネルに保存されている情報に従ってカットオフし、パラメーターでtex.aを指定します。

クリッピング後、次の行を使用してオブジェクトにメインテクスチャをオーバーレイします。
 o.Albedo = tex.rgb;
      
      



変数oは出力構造です。 すべてのフィールドは、SurfaceShadersヘルプにあります。

次のステップは、法線マップを使用することです:
 o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
      
      



その後、反射を適用します。
 float4 refl = texCUBE (_Cube, WorldReflectionVector (IN, o.Normal)); o.Emission = refl.rgb * _Value * refl.a;
      
      



ここで、インスペクタで効果の程度を制御できるように、受信したリフレクションに関する情報に_Valueを掛けることに注意する価値があります。

最後に、ビデオカードがこのシェーダーを正しく表示できない場合に備えて、フォールバックを追加することを忘れないでください。



そして、ここに私たちが得たものがあります:




All Articles