マルチUVの検索ワードが多めだったので 今回は予定を変更してUnityのマルチUVと配列の転送についてのおはなしをします。

コード全体を書いている時間がなかったので解説だけなのですが、記事の作成におもいのほか時間がかかってしまいまして

コード書いたほうが楽だったじゃないの という企画倒れな面もありましたがなんとかまとめてみました。

 

  1. 頂点データにデータを付加する方法
  2. データをテクスチャに変換する方法
  3. ストラクチャーバッファなどを使用する方法(DX11)※ DX11を使用する方法は以前記事にしましたので参照して下さい
  4. その他

 

 

現在のところUnityはテクスチャUV0,UV1 2つまでメッシュデータに含むことができますがUV2がライトマップ用に予約されていてそれ以上のマルチUVの値を

通常では送ることが出来ません。シェーダ配列のサポートは未定ということらしいです フォーラムで最初の要望から数年たってますから あきらめムードのような感じに。。。

 

■頂点データに配列データを付加する方法

Meshの構造体の未使用データ部分にUV値を書き込んでシェーダ側で対応させることで2つ以上のマルチUVを実現することは出来ます。

もちろんシェーダで使用する様々なデータを付加できますので Mesh.colors(頂点カラー)、Mesh.tangents(タンジェント)あたりが必要なければ

float4型のデータであればColor型でもVector型でも8888=32ビットのデータとして扱うことが出来ます。

ただしメッシュデータの最大頂点数は65,536までですので全体データ数はそれを超えることは出来ません。

もちろん頂点データの構造体をメッシュ表示をさせるかわりにデータテーブルとしてGPGPU計算に使用することも出来ます。

fig2

 

 

 

□配列データのエンコード・デコード

データ長が8ビットにおさまらない場合にはエンコードしてパックする必要があります。 以下にサンプルのコードを載せておきます。

■floatをカラーデータ(RGB)にエンコード
float3 pack(float f) {
      float b = floor(f / (256 * 256));
      float g = floor((f - b * 256 * 256) / 256);
      float r = floor(f % 256);
      return float3(r, g, b); 
}
■カラーデータ(RGB)をfloatにデコード
float  unpack(float3 color)  { return color.r + color.g * 256 + color.b * 256 * 256; }
※効率よくデータをパックしたい場合はビット演算子{>> 、<<}をつかいます。boolフラグなど1ビットで表現できるものはデータ量が減らせます。

以下にエンコードとデコードのサンプルを載せておきます

http://forum.unity3d.com/threads/95687-Passing-Arrays-to-Shader

<Example1>

inline float4 EncodeFloatRGBA( float v )

{

    float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 160581375.0);

    float kEncodeBit = 1.0/255.0;

    float4 enc = kEncodeMul * v;

    enc = frac (enc);

    enc -= enc.yzww * kEncodeBit;

    return enc;

}

inline float DecodeFloatRGBA( float4 enc )

{

    float4 kDecodeDot = float4(1.0, 1/255.0, 1/65025.0, 1/160581375.0);

    return dot( enc, kDecodeDot );

}

 

<Example2>

// Float to Color in Unity Shader
float4 F2C(float Value)
{
float4 color;
int c1 = 255;
int c2 = 255 * 255;
int c3 = 255 * 255 * 255;
int full = (int)(Value * c3);
int B = (full / c2); full -= B * c2;
int G = (full / c1); full -= G * c1;
int R = full;
color = float4(R / 255.0f, G / 255.0f, B / 255.0f, 1.0f);
return color;
}


// Color from RenderTexture to Float
float C2F(Vector3 Color)
{
int c1 = 255;
int c2 = 255 * 255;
int c3 = 255 * 255 * 255;
return (Color.x * 255 + Color.y * 255 * c1 + Color.z * 255 * c2) / c3;
}

 

 

 

□テクスチャデータに配列データを変換する方法

http://forum.unity3d.com/threads/178682-Passing-Arrays-to-shaders

PassArrayText

<意訳>

これは本当に適切であるかどうかは知りませんが、これが配列を渡す簡単な方法です

1.配列サイズに新しいRenderTextureを設定します。

2.GL.LoadPixelMatrixを使用して、quadの束(各quad= 1ピクセル)をレンダリングします。

3.各ピクセルごとにをフロート値をRGBAカラーに符号化します。ルックアップテーブルを作成しておくとより速度があがるかもしれません または別のフレームで事前計算をします。

camera.renderを呼び出します。

4.RenderTextureにすべてのクワッドピクセルをblitします 。

5.RenderTextureをデバイスに転送します。

デバイスにテクスチャレンダリングするには最良の方法ではありませんが、それは下位互換性を考慮するとsetPixelよりもはるかに高速だと思います。

 

Unityのフォーラムのほうに配列データを転送するための有効な方法として挙げられていたレスなのですが、いまのところ

この方法が最も有効そうな感じがしますので 今回はこれを参考に説明してみます。

■スクリプト側

テクスチャデータの配列参照を端的にいうと頂点データにインデックスをつけてテクスチャ化した配列テーブルを参照させる方法です。

インデックスはさきほどのデータをメッシュデータに追加するやり方でどれか一つデータを利用して頂点番号インデックスを入れ込みます。

インデックスを登録するデータはUV2でもよいのですがすでに作成されているデータとの兼ね合いも考えて、あまり使用されていない頂点カラーあたりを使用すると良いかも

しれません。

インデックスに利用したデータを使用したい場合はテクスチャの方にデータとして焼き込んでそちらからデータを参照すれば良いのでとくにデメリットはないのでは

1.まず テクスチャからデータを取得するときにUV計算のための頂点データのインデックスの登録をします。

2バイトのデータ長があれば65,536までの数値が表現できますので頂点データ数はカバーできそうです、ここでは頂点カラーRGBA のうちRGを使用した場合を考えてみます。

前項でやったように頂点インデックスをエンコードします。

この方法だとテクスチャメモリが許す限り配列が格納できますので、かなりアドバンテージがあるのではないでしょうか。

 

 

□RenderTextureの生成とオプション指定

RenderTextureを作成するときにデータをいれたテクスチャにカラー補間がかからないようにする必要があります。

次のようにオプションを指定してください

RenderTexture renderTexture = RenderTexture.GetTemporary( width, height );

※以下の2行を追加します

renderTexture.filterMode = FilterMode.Point;
renderTexture.wrapMode = TextureWrapMode.Clamp;

RenderTexture.active = renderTexture;


□FilterMode

  • Point - テクスチャのピクセル補間をしない
  • Bilinear -バイリニア補間(平均化サンプリング)
  • Trilinear -トライリニア補間 テクスチャサンプル平均とミップマップレベルによるブレンド。

    □TextureWrapMode

  • Repeat -テクスチャタイリング時にタイリングの継ぎ目を補間
  • Clamp ? タイリングの継ぎ目を補間しない.

    FilterModeはPointを選択します 隣接するピクセルでカラー補間をすると値が正確になりません。 
    WrapModeはClampを選択します Wrapはテクスチャタイリング時にタイリングの境目を補完するオプションですがこれも補間なし。

     

     

    □GLを使用したテクスチャへの描画Unity Script Reference-GL.html

    material.SetPass( 0 ); シェーダパスの指定

    GL.PushMatrix(); マトリクスを更新する前に上書きを避けるために現在のカメラのビューマトリクスを退避させます

    GL.LoadPixelMatrix( 0, width, height, 0 );

    使用するマトリクスの指定 LoadOrtho:正射影行列 LoadPixelMatrix:ピクセル行列 LoadProjectionMatrix:プロジェクション行列>

    GL.Begin( GL.QUADS); GLの描画を開始

    for( int i=0; i {

    GL.Color( new Color( □, □, □, □) ); 描画カラーの指定

            GL.Vertex3(□,□,□); ピクセル4角型の頂点を指定する 左まわり座標で記述しないとカメラに向かって裏面になるので描画されない

    GL.Vertex3(□,□,□);

    GL.Vertex3(□,□,□);

    GL.Vertex3(□,□,□);

            }

    GL.End(); GLの描画を終了

    GL.PopMatrix(); 退避させたカメラビューマトリクスを元にもどします

    □RenderTexture から2DTextureへの変換

     

    RenderTexture から2DTextureへの変換でよくサンプルになっている方法にGraphics.Blitを使用する場合と Texture2D.ReadPixels

    使用した場合が挙げられているのですが、ケースによって選択したほうがよさそうなので 2種類のサンプルコードを載せておきます

     

    1.Graphics.Blit を使用する例

    Graphics.DrawTexture on RenderTexture not working...- - Unity Answers

     

    using UnityEngine;

    using System.Collections;

    public class Test : MonoBehaviour

    {

    public Texture2D texture; //初期化テクスチャ.

    public Texture2D stampTexture; //Graphics.DrawtextureでRenderTextureに変換するためのテクスチャ.

    public float posX = 256f; //DrawTexture で使用するテスト用座標.

    public float posY = 256f; //DrawTexture で使用するテスト用座標.

    RenderTexture rt; //RenderTexture を bufferとして定義

    void Start ()

    {

    rt = new RenderTexture (512, 512, 32); // 512x512 pixels サイズのRenderTextureで初期化します

    renderer.material.SetTexture ("_MainTex", rt); //RenderTexure をシェーダのメインテクスチャとして設定

    Graphics.Blit (texture, rt); //Blit命令でtextureをRenderTextureに焼き付けます

    }

    void Update ()

    {

    RenderTexture.active = rt; //RenderTextureを activeに設定  DrawTexture命令が書込できるようにします

    GL.PushMatrix (); //プロジェクション行列とモデルビュー行列をスタック(退避)させます。

    GL.LoadPixelMatrix (0, 512, 512, 0); // ピクセル描画用マトリックスのセットアップ.

    //stampTexture をRenderTexture  座標(posX 、 posY)に配置

    Graphics.DrawTexture (new Rect (posX - stampTexture.width / 2, (rt.height - posY) - stampTexture.height / 2, stampTexture.width, stampTexture.height), stampTexture);

    GL.PopMatrix (); //プロジェクション行列とモデルビュー行列をスタックから呼び戻します。

    RenderTexture.active = null; //RenderTextureを描画できないように.

    }

    }


     

    メインカメラでレンダリング作業をしてはいけませんのでレンダーテクスチャ用のメインカメラのクローンを作成します。

    以下のコードを参考にしてください

    if (!shaderCamera)

    {

                shaderCamera = new GameObject("ShaderCamera", typeof(Camera));

                shaderCamera.camera.enabled = false;

                shaderCamera.hideFlags = HideFlags.HideAndDontSave;

    }

            Camera cam = shaderCamera.camera;

            cam.CopyFrom(camera); //   MainCameraからプロパティコピー

            cam.backgroundColor = new Color(0, 0, 0, 0);

            cam.clearFlags = CameraClearFlags.SolidColor;       

            cam.targetTexture = buffer; // buffer=RenderTextureの指定

          

       

     

  •  

     

     

    2.Texture2D.ReadPixels を使用する例

     

  • ”GL2TextureExample2.cs” Rendering GL to a Texture2D immediately in Unity4

    を参考にTexture2D.ReadPixels を使用した場合を考えます。

  • // Initialize and render

    RenderTexture rt = new RenderTexture(width, height, 24);

    activeCamera.targetTexture = rt;

    activeCamera.Render();

    RenderTexture.active = rt;

    Texture2D newTexture = new Texture2D( width, height );

    newTexture.ReadPixels( new Rect( 0, 0, width, height ), 0, 0 );

    bool applyMipsmaps = false;

            newTexture.Apply( applyMipsmaps );

    bool highQuality = true;

            newTexture.Compress( highQuality );

    // Clean up

    activeCamera.targetTexture = null;

    RenderTexture.active = null; // RenderTextreをオフにする

    DestroyImmediate(rt);

     

    □Texture2D.ReadPixels(Rect( 0, 0, width, height ), 配置ピクセル座標x, y、ミップマップの再計算(bool値))

    スクリーンからテクスチャにキャプチャする命令 ミップマップ再計算フラグは無視してもよいです。

    □Texture2D.Apply (ミップマップの生成を行うかの指定(bool値)、テクスチャをGPUにつづけて転送しない(bool値))

    最後に呼び出してGPU側にテクスチャを更新 ミップマップの生成をfalseにすれば省メモリになります 変換速度もあがる はず。

    テクスチャの解像度を階乗 8,16・・・256・・・と指定しないとGPUに転送したときテクスチャサイズが変換されてから再転送されるため遅延が発生します。

    ハードウェアとライブラリのバージョンによるため必ず起きるものではなく バージョンの高いライブラリでは問題ないとか。。

    矩形テクスチャで縦横比が違う場合も古いライブラリでは1:1に変換する手順が入るばあいがあり

    Texture2D.Compress(highQuality: bool値)

    テクスチャ圧縮オプション。 highQualityをセットすると圧縮がかからないためテクスチャの変換速度が向上します。

    ※テクスチャの圧縮はハードウェアでサポートされていない場合は無視されます。

     

     

    ■ シェーダ側

    ■テクスチャフェッチ (テクスチャ参照)

    http://forum.unity3d.com/threads/63231-How-to-use-vertex-texture-fetch

    Shader "tex2Dlod" {

        Properties {

            _MainTex ("Base (RGB)", 2D) = "white" {}

        }

        SubShader {

            Tags { "RenderType"="Opaque" }

            LOD 200

           

            CGPROGRAM

            #pragma target 3.0

            #pragma surface surf BlinnPhong vertex:vert

            #pragma glsl

            struct Input {

                float2 uv_MainTex;

            };

     

            void vert (inout appdata_full v) {

                #if !defined(SHADER_API_OPENGL)

                float4 tex = tex2Dlod (_MainTex, float4(v.texcoord.xy,0,0));

                v.vertex.y += tex.r * 2.0;

                #endif

            }         

            ENDCG

        }

        FallBack "Diffuse"

    }

     

    tex2Dlod (_MainTex, float4( (float2)テクスチャUV,0,LOD値<0~7 0で最大解像度> ));

    以前にも説明しましたが頂点シェーダ(vertexシェーダ)でテクスチャフェッチする場合には頂点データの計算がされていないため

    LOD値が確定していませんそのため tex2Dlodを使用して強制的にLOD値を割り付ける必要があります。

    ピクセルシェーダ(fragmentシェーダ)ではふつうにtex2d()を使用すればいいです。

      tex2Dlod はglの命令なので #pragma glsl を記述するかまたは以下のように書いてください

     

          #if !defined(SHADER_API_OPENGL)

             float4 tex = tex2Dlod (_MainTex, float4(v.texcoord.xy,0,0));

             v.vertex.y += tex.r * 2.0;

           #endif

     

     

    □UV値の計算

    uv

     

    UV値は1をテクスチャサイズで割って求めます V方向(uv.y)は0.5を掛けてテクスチャの中央あたりをサンプルするように

    このサンプルではテクスチャサイズが u方向に配列サイズ分のピクセル数 v方向に1ピクセルのテクスチャをイメージしています。

     

    uint   idx = color.r*255+color.g*255*255; //頂点インデックスのデコード

    float2 uv = 1.0 / TextureSize;
    uv.y *= 0.5;
    float4 texCoord = float4( ( float )
    idx * uv.x, uv.y, 0, 1 );

     

    ※DX10以降(シェーダモデル4)では SV_で始まるシステム値セマンティックスを使用することができて (uint)SV_VertexID

    Mesh頂点の順番に対応しているようなので頂点シェーダ内でUV値の計算をすることが出来るようです。

    セマンティクス (DirectX HLSL)

    <Example>

    vert2f vert(appdata_full v , uint id : SV_VertexID)
                {
                    vert2f o;

    float2 uv = 1.0 / TextureSize;
    uv.y *= 0.5;

    float4 texCoord = float4((float) id * uv.x, uv.y, 0, 1 );

           赤字の部分がuint型の頂点インデックスIDで代用
                }  

     

    GLでサポートされていれば便利そうなのですが シェーダの後方互換を保つためであれば、いまはあまり使わないほうがいいのかもとも思います。

     

    ちなみに SV_で始まるシステム値セマンティクス「SV_POSITION は POSITION と同じ値が入っているのはなぜ ていうかSV_って何?」という質問に

    ユニテイの開発者Aras氏「よくわからないけど DX11以降と互換を取るためじゃないかな?」 ということで皆よくはわかってないらしい。ちょっと安心?

     

     

     

    □テクスチャフェッチでループを使用する時の注意

    テクスチャフェッチにループを使用する場合HLSLではfor ステートメントを記述する場合コンパイル方法を制御するパラメーターとして

    [loop]または[unroll]を指定することが出来ます。

    Unityのシェーダで予約語あつかいで記述出来ないようですが シェーダを移植する際などに留意する点として書いておきます。<コンピュートシェーダでは有効みたい>

    ※Unityではコンパイルが自動最適化されているため[loop][unroll]2通りの最適化コンパイルをして実行速度が速いほうを選択しているそうです。

     

    [unroll]

    for ( i=1;i{

            Statement Block;

    }

    forStatement

     

    この2つのオプションはコンパイル後のコード記述のされかたが違います.

    [loop]指定時はコンパイル後にアセンブラコードでループがそのまま記述されます メモリサイズは抑えられますが実行速度はやや低速となります。

    [unroll]指定時はループ内のコードがループ回数分記述されます メモリサイズは大きくなりますが 実行速度は高速になります。

    このオプションは通常は省略可能ですがループ内でテクスチャフェッチ(テクスチャを参照)する場合は[unroll]を強制すると動作が安定するということです

    ちなみにHLSLシェーダでは、ほぼC言語の命令セットとおなじ命令がサポートされているため ループにwhileステートメントも使用できますが

    この場合シェーダが最適化されませんので実行速度は低下する場合があります。

    シェーダの実行速度は条件によるのですが、 最適化された計算時間 [unroll]指定時がPCが100として [loop]ではモバイルで200 xbox360で160 程度に遅延

    という参考数値の記述がありました。

    シェーダ内でループをするときには、なるべくforステートメントを使用するほうが良いかもしれません。

     

     

    □テクスチャ化したデータの書き出し

    一度データ化したデータはテクスチャリソースとして書き出してしまえばデータ生成の初期化手順を省けますので データがだいたいフィックスした段階で

    アセットデータから読み出すかたちでシェーダにセットすると良いかと思います。

    以下のUnityコミュニティWikiのスクリプトを参考にしてみてください。※Application.CaptureScreenshot を使うやりかたもあるとかないとか

    Unity Script Reference-Texture2D.EncodeToPNG.html

    ExportNormalmap - Unify Community Wiki

    □ExportNormalmap.js

    @MenuItem ("Assets/Export Normalmap...")  
     static function ExportNormalmap () {
    	var tex = Selection.activeObject as Texture2D;
    	if (tex == null) {
    		EditorUtility.DisplayDialog("No texture selected", "Please select a texture.", "Cancel");
    		return;
    	}   // Force the texture to be readable so that we can access its pixels
    	var texPath = AssetDatabase.GetAssetPath(tex);
    	var texImport : TextureImporter = AssetImporter.GetAtPath(texPath);
    	if (!texImport.isReadable) {
    		texImport.isReadable = true;
    		AssetDatabase.ImportAsset(texPath, ImportAssetOptions.ForceUpdate);
    	}   var bytes = tex.EncodeToPNG();
    	var path = EditorUtility.SaveFilePanel("Save Texture", "", tex.name+"_normal.png", "png");
    	if (path != "") {
    		System.IO.File.WriteAllBytes(path, bytes);
    		AssetDatabase.Refresh(); // In case it was saved to the Assets folder
    	}
    }





    ■追記 2014 12.18

    文字数制限で入らなかった部分を同時期にアップしたのですが、あまり閲覧されていないようなのでリンクしておきます。

    うにばな(シェーダへ配列データの転送 マルチUVなど) その2

    うにばな(シェーダへ配列データの転送 マルチUVなど) その3

    うにばな (シェーダへ配列データの転送) その4 JsonでMayaからUnityへ