更新がチョットあいてしまいましたが シェーダ配列関連の検索が多いようですので以前の記事からの変更点をまとめてみます。

Alan Zucconi氏 『Arrays & Shaders in Unity 5.4+』という記事がよくまとまっていますので翻訳と追加情報をすこし加えて記事にしてみました。

元記事のAlan Zucconi氏は

『Unity 5.x Shaders and Effects Cookbook − Alan Zucconi (著), Kenneth Lammers (著)』 の著者の方です。

 

 

■Arrays & Shaders in Unity 5.4+

http://www.alanzucconi.com/2016/10/24/arrays-shaders-unity-5-4/

 

この記事は、Unity 5.4で配列とシェーダを使用する方法を示しています。2016年1月に私はすでに 『Unityでヒートマップ:配列およびシェーダ』と呼ばれる記事でこのトピックをカバーしています 元のアプローチでは、配列をシェーダに渡すことができる文書化されていない機能が公開されていました。それ以来、Unity 5.4はAPIに適切なサポートを導入しました。このチュートリアルは、以前の記事を置き換えるものです。以前のチュートリアルを読んでいればシェーダコードを変更する必要はなくステップ2に進むことができます。

 

  • ステップ1. シェーダ
  • ステップ2. スクリプト
  • ステップ3.  制限事項
■ ステップ1.シェーダ
すべてのシェーダにはプロパティと呼ばれるセクションがあり、マテリアルインスペクタ内の特定の変数を公開することができます。この記事が書かれた時点では、Unityは配列型をサポートしていません。したがって、インスペクタから直接配列にアクセスすることはできません。Unityが2DArrayと呼ばれるタイプをサポートしていますが、これはテクスチャ配列用に予約されています。私たちが望むのは、数字の配列です。

すべての配列は、変数として宣言されスクリプトを介して外部的に初期化されなければなりません。シェーダ内の配列は、あらかじめ定義された長さを持つ必要があります。格納する必要のあるアイテムの数を事前に把握していない場合は、実際にいくつのアイテムが存在するかを示す変数(配列の長さなど _ArrayLength)を保持してスペースを確保します。

 

int _ArrayLength = 0;

float _Array[10];

上記の例では、両方の変数uniformのキーワードが施されています。これは、その値が外部スクリプトから変更され、それらの変更がフレーム間で発生しないと仮定しているためです。

他のタイプの配列と同様にシェーダコードで配列を見ることができます:

 

for (int i = 0; i < _Length; i++){

...

float x = _Array[i];

...

}

■ ステップ2.スクリプト

シェーダを使用する場合は、外部スクリプトを使用して配列を初期化する必要があります。ユニティ5.4+の新しいAPIでは、SetFloatArray、SetMatrixArray  とSetVectorArrayをサポートしています。予想されたように、それらの配列を初期化するためにfloat、Matrix4x4  とVector4がそれぞれ使用されています。これは これらの関数を正しく使用するためのスニペットです。

 

float [] array = new float[] { 1, 2, 3, 4 };

material.SetFloatArray(array);

ここでのマテリアルは  あなたのシェーダで使用するUnityのマテリアルです。インスペクタから直接ドラッグすることも、コードで取得することもできます。

 

Renderer renderer = GetComponent();

Material material = renderer.shaderdMaterial;

Unity 5.4はグローバル配列もサポートしています。それらは一度設定されればその後すべてのシェーダによって共有されるプロパティです。それらは、同様の方法で動作し、 SetGlobalFloatArray、SetGlobalMatrixArray  とSetGlobalVectorArrayといったシグネチャを持っています。しかしながらこれらはシェーダのの静的メソッドのクラスです。

 

v5.4以降で 新しく追加されたarray転送命令が次の関数

 

■ ステップ3.制限事項

あなたが(例えば、他のタイプの配列を渡す必要がある場合はint型、long型、Vector3、...)をあなたが密接にあなたのニーズに合った方法を使用する必要があります。たとえば、あなたがint型をシェーダの配列にあわせる場合  配列内の値をfloatに変換しなければなりません。同様にあなたがVector3をシェーダに割り当てたい場合Vector3をVector4でラップしなければなりません。あなたがVector3をVector4に自動的に割り当てることができるときは Unityが自動的に最後の座標0をセットして、適切にそれらが収まるようにします。ただし、Vector3[ ] を  Vector4 [ ] に割り当てることは出来ません。

2番目に考慮すべきことは、Unityによって行われた設計の選択肢が貧弱であることです。初めて(ローカルまたはグローバルに関係なく)配列を使用すると、Unityは配列自体のサイズを固定しているようです。たとえば、あなたのようにシェーダーで定義された配列の初期化にuniform float _Array[10]; 。 以下のように定義されたC#の配列を持つ float[] array = new float[5]; 。配列に5つ以上の要素を設定することはできません。これがバグかフィーチャーかに関わらず、非常に厄介なバグがあります。これが修正されるのを待って、スクリプトのAwake関数で直接最大サイズで配列を初期化するようアドバイスします:

 

void Awake () {

material.SetFloatArray("_Points", new float[10]);

}

※ 一部のユーザーから 配列が初期化されたら サイズをリセットできるようにエディタを再起動する必要があると報告されています。

 

 

 

■Unity5.4+以降のシェーダ配列周りの変更による考察

https://forum.unity3d.com/threads/passing-array-to-shader.392586/

 

■MaterialPropertyBlock

Unity - Scripting API- MaterialPropertyBlock

シェーダのインスタンシング描画がサポートされましたのでMaterialPropertyBlockによる配列操作もサポートされました。

MaterialPropertyBlockの記述方法が2種類あり 設定した変数名を直接指定する場合と nameIDを指定する場合でnameIDはより高速に実行できます。 ここらへんは以前からある命令ですがセットで覚えると理解しやすいので一応

 

■スクリプト側 <マニュアルから抜粋>

『シェーダで設定した変数名を使用する場合』

using UnityEngine;

// Draws 3 meshes with the same material but with different colors. public class ExampleClass : MonoBehaviour { public Mesh mesh; public Material material; private MaterialPropertyBlock block;

void Start() { block = new MaterialPropertyBlock(); } void Update() { // red mesh block.SetColor("_Color", Color.red); Graphics.DrawMesh(mesh, new Vector3(0, 0, 0), Quaternion.identity, material, 0, null, 0, block);

// green mesh block.SetColor("_Color", Color.green); Graphics.DrawMesh(mesh, new Vector3(5, 0, 0), Quaternion.identity, material, 0, null, 0, block);

// blue mesh block.SetColor("_Color", Color.blue); Graphics.DrawMesh(mesh, new Vector3(-5, 0, 0), Quaternion.identity, material, 0, null, 0, block); } }

『nameIDを使用する場合』

using UnityEngine;

// Draws 3 meshes with the same material but with different colors. public class ExampleClass : MonoBehaviour { public Mesh mesh; public Material material; private MaterialPropertyBlock block; private int colorID;

void Start() { block = new MaterialPropertyBlock(); colorID = Shader.PropertyToID("_Color"); } void Update() { // red mesh block.SetColor(colorID, Color.red); Graphics.DrawMesh(mesh, new Vector3(0, 0, 0), Quaternion.identity, material, 0, null, 0, block);

// green mesh block.SetColor(colorID, Color.green); Graphics.DrawMesh(mesh, new Vector3(5, 0, 0), Quaternion.identity, material, 0, null, 0, block);

// blue mesh block.SetColor(colorID, Color.blue); Graphics.DrawMesh(mesh, new Vector3(-5, 0, 0), Quaternion.identity, material, 0, null, 0, block); } }
 

 

1つのシェーダに複数の配列を持つことができます。この制限は、ハードウェアよりも内部実装によるものです。定数バッファに配列を置くと、定数バッファの最大サイズ(D3D11では64KB、OpenGLでは16KB)によっても制限されます。

■ シェーダ側でコンスタントバッファ(定数バッファ)をネームアップする場合 定数バッファの最大サイズ(D3D11では64KB、OpenGLでは16KB)によっても制限されます。これは CBUFFERをいくつかの塊に分けて記述することで回避します。

※ 未使用のバッファが存在すると処理速度の低下につながりますので、リリース時にはチェックしておきましょう。

 
UNITY_INSTANCING_CBUFFER_START(MyProperties)
                    UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
                    UNITY_DEFINE_INSTANCED_PROP(float4x4, _m)
UNITY_INSTANCING_CBUFFER_END


UNITY_INSTANCING_CBUFFER_START(MyProperties1)

                    ……
                    ……
UNITY_INSTANCING_CBUFFER_END


UNITY_INSTANCING_CBUFFER_START(MyProperties2)
                    ……
                    ……
UNITY_INSTANCING_CBUFFER_END

 

■その他Tipsなど

■通常のテクスチャを使用する場合のTips:

シーン全体で同じテクスチャ配列を設定する場合は 配列のGrobal化と同様にShader.SetGlobalTextureを使用します。

 

Texture配列からデータの読み取りをする場合はUnity標準APIではGetPixels()命令などを使用することになるのですが、御存知の通りたいへんな低速です。 ただし128×128程度のサイズであればそれなりに使えなくもないないようです。

テクスチャが格納されているメモリはCPUとGPUに分かれていますが、GetPixels()という関数は発生した次点でGPUでレンダリングされているテクスチャをCPU側にコピーしようとするため レンダリングが途完了していない場合タスクが非常に重くなるという欠点があります。

IEnumeratorを使用してテクスチャレンダリングが終了しているかのチェックとフレームエンドのチェックを行うことである程度速度は向上できますがどちらにしろお勧めはしません。 < if( render_texture )  … if( End of Frame ) みたいな感じで チェックしますがテクスチャのチェックだけでもいけます>

以前に参照した記事によれば、スマホなどDirectXを使用しない環境であればOpenGLモードで起動(Unity起動バッチにforce-openGL オプション)

"C:\Program Files (x86)\Unity\Editor\Unity.exe" -force-opengl

GL関数のglReadPixels()する方法が有効かもしれないということです。

C++などでプラグインからGLをたたく事もできますが、マルチプラットフォーム開発の場合プラグインの調整の時間が取られることから これもあまりお勧め出来ないとか。

 

5.4以降で すでにシェーダ側と配列をやり取りする機能がありますので texture配列は用途が少なくなりそうですが GPU計算のスワップバッファなどの描画周りでは用途がありますのでそこら辺の解説は後日、

 

 

■TextureArrayの追加もありました

hlslの命令のサポートになりますが通常のtextureとほぼ同じもので2Dスプライトのアトラスを実装するためにサポートされたようです。

  • TextureとTextureArrayは 両方共アクセス速度は差がないのでTextureで配列の転送をしている場合は特に変更する必要はないようです。
  • TexutreArray はDX9世代(SM3.0以前)には対応していないので注意が必要です。
  • 内部的にはTexture1Dもサポートしているふうなんですが 本家のフォーラムでUnityのエンジニアの方が Texture2Dで代用しても速度的には変わらないので2D を使用してもらいたいという 発言がありました。 最適化の記事でよく1DTextureは2DTextureより高速なので積極的に使用すべきなどとある という質問に対しての回答です。

 

使用するファンクションは通常のテクスチャと同様に

  • Apply Actually apply all previous SetPixels changes.
  • GetPixels Returns pixel colors of a single array slice.
  • GetPixels32 Returns pixel colors of a single array slice.
  • SetPixels Set pixel colors for the whole mip level.
  • SetPixels32 Set pixel colors for the whole mip level.

などのファンクションと テクスチャイメージをコピーするGraphics.CopyTexture()が使用できます。

■ Graphics.CopyTexture()のサンプル

https://forum.unity3d.com/threads/how-do-you-use-graphics-copytexture.428008/

//Graphics.CopyTexture()

void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
       // Texture2D dest = new Texture2D(Screen.width, Screen.height);
        for (int i = 0; i < charData.charData.Length; i++)
        {
            Rect r = charData.GetNormalizedRectFromId(charData.charData[i].id);
            float scale = 1f;
            Rect DestRect = new Rect(charData.charData[i].x * scale, charData.charData[i].y * scale, charData.charData[i].width * scale, charData.char Data[i].height * scale);
//            Graphics.DrawTexture(DestRect, charData.FontImage, r, 0, 0, 0, 0, Color.red, renderMat);
            Graphics.CopyTexture(charData.FontImage, 0, 0,
                charData.charData[i].x,
                charData.charData[i].y,
                charData.charData[i].width,
                charData.charData[i].height, dest, 0, 0, charData.charData[i].x, charData.charData[i].y);
        }
        Graphics.Blit(dest, destination,renderMat);
    }

 

■ Sample2DArrayTexture

マニュアルのサンプルコードと同じものです

Shader "Example/Sample2DArrayTexture"
{
    Properties
    {
        _MyArr ("Tex", 2DArray) = "" {}
        _SliceRange ("Slices", Range(0,16)) = 6
        _UVScale ("UVScale", Float) = 1.0
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // to use texture arrays we need to target DX10/OpenGLES3 which
            // is shader model 3.5 minimum
            #pragma target 3.5
           
            #include "UnityCG.cginc"

            struct v2f
            {
                float3 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            float _SliceRange;
            float _UVScale;

            v2f vert (float4 vertex : POSITION)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, vertex);
                o.uv.xy = (vertex.xy + 0.5) * _UVScale;
                o.uv.z = (vertex.z + 0.5) * _SliceRange;
                return o;
            }
           
            UNITY_DECLARE_TEX2DARRAY(_MyArr);

            half4 frag (v2f i) : SV_Target
            {
                return UNITY_SAMPLE_TEX2DARRAY(_MyArr, i.uv);
            }
            ENDCG
        }
    }
}

 

【関連記事】

 

 

今回は以上ですが、また新しい情報があれば追記するかもしれまん。

体調がすぐれないのでこの辺で... ではまた