2017年04月05日

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

 

 

今回は前回記事の関連事項となりますが Jsonデータを使用して各ツールでのデータのやりとり関するお話です。

 

すでにあるデータ形式に関しては必要以上に手を加えず サポート外のデータはJsonでやりとりするのが効率が良いという本家フォーラム(英語)のほうでアドバイスを読みまして おおきくはこのような理由が挙げられているようです。

  • Jsonは軽量で各種ゲームエンジン、DCCツール、WebGLなど ほとんどの開発ツールでサポートされているためやりとりに都合が良い
  • データ形式ごとにコンバータを作成する場合 バージョンアップなどのたびに手を入れ続けなければいけないため作業効率が良くない。
  • すでにあるデータ形式の改造を繰り返すことでツール間でデータの互換性が失われてしまうことがある。

 

 

そこで今回はサンプルとしてアンリアルエンジンで提供されているgridExport.mel pythonにリライトし Jsonで書き出しする機能を追加してみました。連番も対応しています。

Pythonを選択する理由は、メジャーなDCCツールではほとんどでPythonに対応されているためです。元がmelで記述されているため一旦pythonでリライトします。

 

 

 

【Python -Maya側】

gridExporter2

gridExportergridExporter3

 

 

『gridExportU.py』

# Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
# Export velocity grid data from a Maya fluid container
# Description: Writes out velocity grid data in a custom formatted ascii file


import pymel.core as pm
import maya.cmds as cmds
import json
from collections import OrderedDict


def gridExportU():
    # Check to see if the UI window already exists. If it does, it is deleted
    if pm.window('gridExport', exists=1):
        pm.deleteUI('gridExport', window=1)
    # Create new UI Window
    pm.window('gridExport', rtf=1, t="gridExport", widthHeight=(300, 300))
    pm.columnLayout('rootLayout')
    pm.frameLayout(marginHeight=5, labelVisible=False, marginWidth=5)
    pm.columnLayout('verticalSubframe')
    pm.setParent('..')
    pm.text(label="UE4 & UnityX Vector Field Exporter")
    pm.radioButtonGrp('myRadBtnGrp', numberOfRadioButtons=2, label="Export Mode",
                      select=1,
                      labelArray2=("Single Frame", "Sequence"),
                      onCommand1=lambda *args: pm.intFieldGrp(
                          'endFrame', edit=1, enable=0),
                      onCommand2=lambda *args: pm.intFieldGrp('endFrame', edit=1, enable=1))

    pm.radioButtonGrp('myRadBtnGrp1', numberOfRadioButtons=2, label="Export Type",
                      select=1,
                      labelArray2=("Fga", "Json"))

    pm.checkBoxGrp('isCached', v1=0, l="Cached Fluid?")
    pm.intFieldGrp('startFrame', l="Start Frame")
    pm.intFieldGrp('endFrame', v1=3, enable=0, l="End Frame")
    #pm.intFieldGrp('increment', v1=1, enable=0, l="Increment")
    pm.textFieldGrp('folderPath', text="C:\\", l="Path")
    pm.textFieldGrp('filename', text="vel", l="Filename prefix")
    pm.columnLayout('exportButton', adjustableColumn=True,
                    columnAttach=("both", 0))
    pm.button(c=lambda *args: iterateExport(), l="export")
    pm.showWindow('gridExport')


def iterateExport():

    startFrame = int(pm.intFieldGrp('startFrame', q=1, v1=1))
    endFrame = int(pm.intFieldGrp('endFrame', q=1, v1=1))
    #increment=int(pm.intFieldGrp('increment', q=1, v1=1))
    dataName = ["velocity"]
    n = startFrame
    sel = pm.ls(sl=1)
    if len(sel) > 1 or len(sel) < 1:
        print "ERROR: Please select a single fluid container \n"

    else:
        fluidShape = pm.listRelatives(sel[0], s=1)
        objectCheck = str(pm.objectType(fluidShape[0]))
        if objectCheck == "fluidShape":
            doit = int(pm.checkBoxGrp('isCached', q=1, v1=1))
            print doit
            if doit == 0:
                sceneCurTime = int(pm.currentTime(q=1))
                sceneMinTime = int(pm.playbackOptions(q=1, minTime=1))
                if sceneCurTime > startFrame:
                    pm.currentTime(sceneMinTime)
                    runupToStart(sceneMinTime, startFrame)

                elif sceneCurTime < startFrame:
                    runupToStart(sceneCurTime, startFrame)

            if pm.radioButtonGrp('myRadBtnGrp', q=1, select=1) == 1:
                pm.currentTime(n)
                folder = str(pm.textFieldGrp('folderPath', q=1, text=1))
                filename = str(pm.textFieldGrp('filename', q=1, text=1))
                filePath = folder + "\\" + filename + "." + str(n)
                # print "Wrote: " + filePath + "\n"

                if pm.radioButtonGrp('myRadBtnGrp1', q=1, select=1) == 1:
                    dataExport(dataName[0], filePath, fluidShape[0])
                elif pm.radioButtonGrp('myRadBtnGrp1', q=1, select=1) == 2:
                    dataExportJson(dataName[0], filePath, fluidShape[0])

            else:
                for n in range(startFrame, (endFrame + 1)):
                    pm.currentTime(n)
                    folder = str(pm.textFieldGrp('folderPath', q=1, text=1))
                    filename = str(pm.textFieldGrp('filename', q=1, text=1))
                    #filePath=folder + "" + filename + "." + str(n) + ".fga"
                    filePath = folder + "" + filename + "." + str(n)
                    # print "Wrote: " + filePath + "\n"
                    if pm.radioButtonGrp('myRadBtnGrp1', q=1, select=1) == 1:
                        dataExport(dataName[0], filePath, fluidShape[0])
                    elif pm.radioButtonGrp('myRadBtnGrp1', q=1, select=1) == 2:
                        dataExportJson(dataName[0], filePath, fluidShape[0])

        else:
            print "ERROR: Please select a fluid container \n"


def dataExport(dataName, filePath, myfluidShape):

    filePath += ".fga"
    voxCount = 0
    # Grab the Grid resolution
    res = map(int, pm.getAttr(myfluidShape + ".res"))
    # switch back to parent transform
    fluidShapeParent = pm.listRelatives(myfluidShape, p=1)
    # Grab the voxel container bounding box
    bb = pm.xform(fluidShapeParent[0], q=1, ws=1, bb=1)
    # create and open the output file in write mode
    fileId = open(filePath, "w")
    # Write voxel res
    fileId.write(("" + str(res[0]) + ","))
    fileId.write(("" + str(res[1]) + ","))
    fileId.write(("" + str(res[2]) + ","))
    # Write bounding Box info
    fileId.write(("" + str(bb[0]) + ","))
    fileId.write(("" + str(bb[1]) + ","))
    fileId.write(("" + str(bb[2]) + ","))
    fileId.write(("" + str(bb[3]) + ","))
    fileId.write(("" + str(bb[4]) + ","))
    fileId.write(("" + str(bb[5]) + ","))
    x = 0
    y = 0
    z = 0

    for z in range(0, res[2]):
        for y in range(0, res[1]):
            for x in range(0, res[0]):
                v = pm.getFluidAttr(xi=x, yi=y, zi=z, at=dataName)

                fileId.write(
                    (str(v[0]) + "," + str(v[1]) + "," + str(v[2]) + ","))

    fileId.close()


def runupToStart(baseframe, exportFirstFrame):

    i = 0
    for i in range(baseframe, exportFirstFrame):
        # print($i+"...\n")
        pm.currentTime(i)


def dataExportJson(dataName, filePath, myfluidShape):

    filePath += ".json"

    voxCount = 0

    # Grab the Grid resolution

    res = map(int, pm.getAttr(myfluidShape + ".res"))
    # switch back to parent transform
    fluidShapeParent = pm.listRelatives(myfluidShape, p=1)
    # Grab the voxel container bounding box
    bb = pm.xform(fluidShapeParent[0], q=1, ws=1, bb=1)
    # create and open the output file in write mode

    x = 0
    y = 0
    z = 0
    dict = []

    for iz in range(0, res[2]):
        for iy in range(0, res[1]):
            for ix in range(0, res[0]):
                v = pm.getFluidAttr(xi=x, yi=y, zi=z, at=dataName)
                myVelocity = {"x": v[0], "y": v[1], "z": v[2]}
                dict.append({"velocity": OrderedDict(
                    sorted(myVelocity.items()))})

    exportData = {
        'metadata': {
            'formatVersion': 1.0,
            'generatedBy': 'VectorFieldExporter'
        },
        "Data": {
            "Resolution": OrderedDict(sorted({"resX": res[0], "resY": res[1], "resZ": res[2]}.items())),
            "BBOX": {
                "Min": OrderedDict(sorted({"minX": bb[0], "minY": bb[1], "minZ": bb[2]}.items())),
                "Max": OrderedDict(sorted({"maxX": bb[3], "maxY": bb[4], "maxZ": bb[5]}.items()))
            }
        }, "DataArray": dict

    }

    writeJsonFile(exportData, filePath)


def writeJsonFile(dataToWrite, fileName):
    if ".json" not in fileName:
        fileName += ".json"

    print "> write to json file is seeing: {0}".format(fileName)

    with open(fileName, "w") as jsonFile:
        json.dump(dataToWrite, jsonFile, indent=4, separators=(',', ': '))

    print "Data was successfully written to {0}".format(fileName)

    return fileName


gridExportU()

 

GUI部分を除けばJsonのエクスポートをする部分とgetAttrを使用してシーンからデータを配列にコピーするだけなので、Maya以外に対応させるときは、使用するツールごとになんらかのエクスポーターを参考にしてJsonデータを書き出しする箇所を追記するだけで同様なスクリプトが書けると思います。

”import json” でJsonモジュールを読み込んで key[ ]とValue[ ]を別々に配列として登録した後に zip(key,value)で辞書型配列に登録するのが一般的な書き方のようなのですが、データの整列部分をはさんで今回は直接データを辞書型に変換しています。

通常の配列を辞書型の配列に変換すると要素の順番が保持されないため これを整列するためにOrderedDictモジュールをインポートしています。 これによってアルファベット順に辞書型配列内のデータを整列させて読みやすくなります ただしJsonをインポートするときには構造体のKeyを参照して読み込まれるためデータ順はどのようになっていても良いので この部分は無くてもかまいません。

  

OrderedDict(sorted({"resX": res[0], "resY": res[1], "resZ": res[2]}.items()))

 

”Python側”のexportDataが書き出しデータを整形するための配列となります。、カスタマイズしたい場合はこの配列内の記述を参考に、書き出されたデータの対応は”Python側” ”Json側"のスクリプトを参考にしてください

python(json)での表記は { }:辞書型配列 、[ ]:リスト配列型、():タプル型 となります。

Jsonファイルに書き出しをする部分は以下のように

 

with open(fileName, "w") as jsonFile:
        json.dump(dataToWrite, jsonFile, indent=4, separators=(',', ': '))

 

それぞれ 書き出しは json.dump( )   読み込みは json.loads( ) を使用します

参考:YoheiM.NET:[Python] JSONを扱う http://www.yoheim.net/blog.php?q=20150901

 

”Python側”

  exportData = {
        'metadata': {
            'formatVersion': 1.0,
            'generatedBy': 'VectorFieldExporter'
        },
        "Data": {
            "Resolution": OrderedDict(sorted({"resX": res[0], "resY": res[1], "resZ": res[2]}.items())),
            "BBOX": {
                "Min": OrderedDict(sorted({"minX": bb[0], "minY": bb[1], "minZ": bb[2]}.items())),
                "Max": OrderedDict(sorted({"maxX": bb[3], "maxY": bb[4], "maxZ": bb[5]}.items()))
            }
        }, "DataArray": dict

    }

”Json側"

{
    "Data": {
        "Resolution": {
            "resX": 30,
            "resY": 30,
            "resZ": 30
        },
        "BBOX": {
            "Max": {
                "maxX": 5.0,
                "maxY": 5.0,
                "maxZ": 5.0
            },
            "Min": {
                "minX": -5.0,
                "minY": -5.0,
                "minZ": -5.0
            }
        }
    },
    "DataArray": [
        {
            "velocity": {
                "x": -0.0008264329517260194,
                "y": 0.03183583915233612,
                "z": 0.014935646206140518
            }
        },
        {
            "velocity": {
                "x": -0.0008264329517260194,
                "y": 0.03183583915233612,
                "z": 0.014935646206140518
            }

 

   …………

 

],
    "metadata": {
        "formatVersion": 1.0,
        "generatedBy": "VectorFieldExporter"
    }
}

 

 

【C# Unity側】

『 SerializeBuffer.cs 』

using UnityEngine;
using System.Collections;
using System.IO;
using System.Collections.Generic;


[System.Serializable]
public class HeaderData
{
    public Vector3 Res;
    public Vector3 Max;
    public Vector3 Min;
}

[System.Serializable]
public class TransformData
{
    public Vector3 velocity;
}

[System.Serializable]
public class HeaderCollection
{
    public HeaderData[] HeaderArray;
}

[System.Serializable]
public class TransformCollection
{
    public TransformData[] DataArray;
}


public class SerializeBuffer : MonoBehaviour
{  
    public int texWidth = 1024;
    public int texHeight = 1024;


    public Vector3 Resolution;
    public Vector3 BBOX_Min;
    public Vector3 BBOX_Max;

    
    public string _FilePath = "Assets/Resources/";
    public string _FileName = "TexturedBuffer.png";

   
    public HeaderCollection MyHeaderData;
    public TransformCollection MyData;
    public const int MaxFileNum = 10;

    TransformCollection[] MyDataList = new TransformCollection[ MaxFileNum]; 
  



    void Awake()
    { 
    
        MyHeaderData = new HeaderCollection();
        MyData = new TransformCollection();
  

    }

    public void ConvertData()
    {
       
        string JSONString;
   
        DirectoryInfo dir = new DirectoryInfo(_FilePath);
        FileInfo[] info = dir.GetFiles("*.json");

        var index = 0;

        foreach (FileInfo f in info)
        {
                  
            JSONString = File.ReadAllText(f.FullName); // FileInfo.Name  or FileInfo.FullName
            Debug.Log("LoadPath: " + f);

            MyHeaderData = JsonUtility.FromJson(JSONString);
            MyData = JsonUtility.FromJson(JSONString);
            MyDataList[index] = MyData;
               
            index++;
           
        }
           
        Texture2D texture = new Texture2D(texWidth, texHeight, TextureFormat.RGB24, false, false);
        texture.filterMode = FilterMode.Point;
        texture.wrapMode = TextureWrapMode.Clamp;

        texture.Apply();

        int mip = 0;
        int maxIndex = (int)Mathf.Ceil(MyData.DataArray.Length / texWidth) * texWidth;

        Color[] cols = new Color[3];
        cols = texture.GetPixels(mip);

        int offset = 0;

        for (int j = 0; j < info.Length; j++)
        { 
           
            offset = j * maxIndex;

            for (int i = 0; i < maxIndex; ++i)
            {
               
                if (i < MyData.DataArray.Length)
                {

                    cols[i + offset].r = MyDataList[j].DataArray[i].velocity.x;
                    cols[i + offset].g = MyDataList[j].DataArray[i].velocity.y;
                    cols[i + offset].b = MyDataList[j].DataArray[i].velocity.z;

                }
                else
                {
                    cols[i + offset].r = 0;
                    cols[i + offset].g = 0;
                    cols[i + offset].b = 0;
                  
                }
                            
            }

        }

        texture.SetPixels(cols, mip);
         
        string saveFilePath = Path.Combine(_FilePath, _FileName);
        byte[] bytes = texture.EncodeToPNG();
        File.WriteAllBytes(saveFilePath, bytes);

        DestroyObject(texture);
  

       
        Resolution = MyHeaderData.HeaderArray[0].Res;
        BBOX_Min = MyHeaderData.HeaderArray[0].Min;
        BBOX_Max = MyHeaderData.HeaderArray[0].Max;

        Debug.Log("Textured_Buffer Exported : " + saveFilePath);
        Debug.Log("Resolution:" + MyHeaderData.HeaderArray[0].Res);
        Debug.Log("BBOX Min:" + MyHeaderData.HeaderArray[0].Min);
        Debug.Log("BBOX Max:" + MyHeaderData.HeaderArray[0].Max);

        Debug.Log("\n Buffer Export Completed ! ");
    }


    //------------------------------------------
    void Update()
    {
         if (Input.GetKeyDown(KeyCode.Space))
        {
            ConvertData();
            return;
        }
    }

}

 

動作は単純で指定したフォルダ内にあるJsonファイルを全て読み込んでデータをテクスチャに変換するだけです。

使用方法はJsonファイルへのフォルダパスと書き出すテクスチャの名前を設定した後スペースキーを押してください Jsonファイルと同じフォルダパスにTextureBufferのPngデータが書き込まれます。

当初サブフォルダまでサーチする仕様でしたが、作業中にサブフォルダに一旦ファイルを避けておくような用途も考えてやめました。

コマ抜きやら順序入れ替えやらのオプション操作も考えましたがwindows上で必要なファイルだけを選ればいいでしょうしツールは単純な方が使い勝手が良いので必要なしと判断しました。

 

Jsonから読み込んだデータの内容は以下の通り

        Resolution (バッファの解像度) MyHeaderData.HeaderArray[ ].Res 
        BBOX_Min (バウンディングボックス最小座標) MyHeaderData.HeaderArray[ ].Min 
        BBOX_Max (バウンディングボックス最大座標) MyHeaderData.HeaderArray[ ].Max

        velocity.x (x要素)           MyDataList[ ].DataArray[ ].velocity.x; 
        velocity.y (y要素)           MyDataList[ ].DataArray[ ].velocity.y; 
        velocity.z (z要素)            MyDataList[ ].DataArray[ ].velocity.z;


DataArray[ ]内の要素がvelocity.X、velocity.Y、velocity.Z、でMyDataList[ ]がDataArray[ ]をJsonの数だけ格納されています。

MyHeaderDataは解像度は共通なため一つ分だけ存在します。

 

 

【テクスチャデータについて】

【TextureBuffer.png】

TexturedBuffer

 

テクスチャへのデータのパックはUnity側はGL系なのでUV座標のV方向がフリップしています そのため見た目で下から上に向かって配置されます。 シェーダ側でDirectXを使用する場合はV方向は反転されるためテクスチャは上下が反転した見た目となります。

Velocityのデータはインポートした時すでに0~1の長さに正規化されているので、Color型配列にそのまま代入してテクスチャに書き込んでいます。

1以上の範囲を取るデータの場合はな正規化してから代入してください。詳しくは 以前の配列の転送に関する記事を参考にしてください。

 

【ファイルパス操作について】

セーブファイル名のパスとファイル名の連結にPathクラスを使用しています。

 

Path.Combine(_FilePath, _FileName);

 

Pathクラスの標準関数はマニュアルと同じですが次の通り

  • Combine                                   2つのパスストリングを結合してファイルパスに変換。
  • GetDirectoryName                       ディレクトリパスを返す.
  • GetExtension                              ファイルの 拡張子を返す.
  • GetFileName                              拡張子を含めたファイルネームを返す.   
  • GetFileNameWithoutExtension    拡張子抜きのファイルネームを返す.

    Path.GetFileNameWithoutExtension("/Some/File/At/foo.extension"));
    Path.GetFileName("/Some/File/At/foo.extension"));
    Path.GetDirectoryName("/Path/To/A/File/foo.txt"));
    Path.GetExtension("/Some/File/At/foo.extension"));
    Path.Combine("/First/Path/To", "Some/File/At/foo.txt"));

Pathクラスのvalue

  • AltDirectorySeparatorChar       ディレクトリレベルを区切るための代替文字。 (Read Only) '/' Windows 、 '/'  macOS
  • DirectorySeparatorChar           ディレクトリレベルを区切るために使用されるデフォルトの文字。 (Read Only) '\' Windows 、 '/'  macOS


 

 

【その他】

"maya-json-exporter" https://github.com/Jam3 https://github.com/Jam3/maya-json-export

Ttree.js など MayaのJsonエクスポータはいくつか公開されていますので参考にすると良いかと思います。

melとPython両方に対応されていますが、pythonがOpenMayaで記述されている箇所があり、非プログラマには若干ハードルが高いかもしれないです。 今回の解説をPython+Pymelだけで書いた理由でもあります 他プラットフォームへの移植もめんどうですし。

データが大きく 処理時間がかかりすぎるようであればOpenMayaかC++で書き換えする必要があるかもしれませんが、データをGetする以外は特に重たい処理をしているわけでもないので、わりと速度が気になるほどではないような気がします。

現在すでに手が入っている部分はそのままでよいかと思いますが ちょっとデータが必要だけどコンバータを書くのがめんどくさいとか言う場合に手軽にデータを送ることができるので便利かもしれません。

 

シェーダ側での配列の扱いについては、次回以降 余裕があれば、ぼちぼち書いていく 予定。

 

ではまた



akinow at 09:50|PermalinkComments(0)TrackBack(0) Clip to Evernote Unity3d | シリーズ講座

2017年01月24日

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

 

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

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
        }
    }
}

 

【関連記事】

 

 

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

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



akinow at 14:50|PermalinkComments(0)TrackBack(0) Clip to Evernote Unity3d | シリーズ講座