2013年12月06日

うにばな (Graphics.DrawMesh で高速な描画?)

 

 

drawMeshTest

■webプレイヤー

https://dl.dropboxusercontent.com/u/18978109/DrawMeshTest/DrawMeshTest.html

 

Unity公式のフォーラムで大量のメッシュを描画する方法として有効だというはなしのGraphics.DrawMesh命令を簡単に解説します

Unityの新しいバージョンでプロパティブロックまわりが改良されたそうなので プロパティブロックを使用してサンプルを作成してみました

< どこが新規に変わったのかいまいちわからないのですが >

 

ちなみに情報としては古めなので すでにほかのところに詳しい記事があるかもしれませんが、ここの管理人は怠けものなので国内の記事はあまり見ていません。







■Graphics.DrawMesh

● static function DrawMesh(mesh: Mesh, position: Vector3, rotation: Quaternion, material: Material, layer: int, camera: Camera = null, submeshIndex: int = 0, properties: MaterialPropertyBlock = null): void;

● static function DrawMesh(mesh: Mesh, matrix: Matrix4x4, material: Material, layer: int, camera: Camera = null, submeshIndex: int = 0, properties: MaterialPropertyBlock = null): void;

 

【パラメータ】

mesh描画するメッシュデータ

positionメッシュの位置。

rotation      メッシュの回転。

matrixメッシュの変換行列(位置、回転、その他の変換)。

materialマテリアルを指定する。

layerレイヤを使用する。

camera    :デフォルトはNULL。メッシュはすべてのカメラで描画されますが 指定がある場合指定されたカメラでレンダリングします。

submeshIndex描画するメッシュのどのサブセット。これは、複数の材料で構成されたメッシュに適用される。

propertiesメッシュが描画される直前にマテリアルにプロパティ変更を適用します。※MaterialPropertyBlock

 

DrawMesh命令は1フレームごとにメッシュを描画します。通常のゲームオブジェクトと同様にシェーディングされ影が落ちてプロジェクターの影響を受けることができます。デフォルト状態ではすべてのカメラにレンダリングされますが特定のカメラを指定することもできます。

ゲームオブジェクトの作成と管理のオーバーヘッドを避けて大量のメッシュを描画したい場合DrawMesh命令は有効に機能します。DrawMeshはすぐにメッシュを描画せず Updateの後にレンダリングパイプラインにデータを送り、そのあと通常のレンダリングプロセスの一部としてレンダリングされます。 すぐにメッシュを描画したい場合は、Graphics.DrawMeshNowを使用します。

DrawMeshはすぐにメッシュの描画がされないため、関数の呼び出しの時に材料特性を変更すると、メッシュはそれらをピックアップすることはありません。あなたは、同じマテリアルを用いてメッシュのシリーズを描きたいのですが、すこしだけマテリアルのプロパティ(例えば色を変更)をする場合は、MaterialPropertyBlockのパラメータを使用します。

 





■MaterialPropertyBlock

MaterialPropertyBlockGraphics.DrawMeshによって使用されているRenderer.SetPropertyBlockです。、同じマテリアルで複数のオブジェクトを描画するような場合、たとえばメッシュの色を変更したい場合シェーダ内のプロパティの値を部分的に変更することで対応します。

 

【機能】

AddColor            : カラープロパティを追加します。

AddFloat             :フロート型プロパティを追加します。

AddMatrix         : マトリックス型プロパティを追加します。

AddTexture       :テクスチャ型プロパティを追加します。

AddVector         :ベクター型プロパティを追加します。

Clear                  : プロパティ値のクリア。

GetFloat             : プロパティブロックからフロートを取得します。

GetMatrix          : プロパティブロックから行列を取得します。

GetTexture        : プロパティブロックからテクスチャを取得します。

GetVector          : プロパティブロックからベクトルを取得します。

 

 

【Example】

public class  XXXXX : MonoBehaviour
{

     private MaterialPropertyBlock _PropertyBlock; 
     private int _ColorPropertyId ;

 

void Start()
{

               _PropertyBlock    = new MaterialPropertyBlock();
               _ColorPropertyId = Shader.PropertyToID("_Color");

}

void Update() 
{

               _PropertyBlock.Clear(); 
               _PropertyBlock.AddColor(_ColorPropertyId, Color.white);    
            
  Graphics.DrawMesh( Mesh, Position , Rotation, Material, 0, MainCamera, 0,_PropertyBlock);

  }

}

 

PropertyBlock を効率よく使用するためには1つのブロックを作成してDrawMeshをコールするたびにブロックを再利用する方法です。

Graphics.DrawMesh()の前に使用ブロックをクリアし AddXXX を使用して値を追加します。

指定できるプロパティは使用しているシェーダ内で登録されているプロパティとなります。 複数のシェーダを使用する場合はShader.Find(シェーダ名)で書き換え対象のシェーダを指定する必要があります。

 

 

●DrawMeshManager.cs

using System.Collections.Generic;
using UnityEngine;
using Assets;

public class DrawMeshManager : MonoBehaviour
{
   
    public Mesh ObjMesh;
    public Material Material1;
   
    public Camera MainCamera;
    public Transform Player;
    public float ForceDrawWithinDistance = 50.0f;
   
    public int Maxval = 100;
    public float  Scaler = 2.0f;   
   
    private List Objs = new List();
   
    private MaterialPropertyBlock _PropertyBlock;
    private int _ColorPropertyId ;
   
   
    private void Start()
    {
       
        MainCamera = Camera.main;                
        float Offset=(Maxval*Scaler/2.0f)-Maxval*Scaler;
        int ObjectType = 0;
       
        for(var x=0 ; x        {                
            ObjectType = x%2;          
            for(var y=0 ; y            {                   
                for(var z=0 ; z                {    
                                                                
                    ObjData newObj = new ObjData( ObjectType,
                                            new Vector3((float)x*Scaler+Offset,(float)y*Scaler+Offset,(float)z*Scaler+Offset),
                                            Quaternion.Euler(0, 0 ,0));

                    Objs.Add(newObj);                                       
        
                }
           }
           
        }
       
        _PropertyBlock = new MaterialPropertyBlock();
        _ColorPropertyId = Shader.PropertyToID("_Color");       
        
    }

   
    private void Update()
    {
       

        Vector3 playerPosition = Player.position;
        var FrustumPlanes = GeometryUtility.CalculateFrustumPlanes(MainCamera);

        
        float _timer = Time.timeSinceLevelLoad;               
        var ObjBounds = ObjMesh.bounds;
       
        foreach (var Obj in Objs)
        {
                        
        //    int ObjType   = Obj.ObjType; // メッシュオブジェクトを複数定義する場合に使用する
            
                ObjBounds.center = Obj.WorldPosition;

       
            float distance = Vector3.Distance(Obj.WorldPosition, playerPosition);

       
            if (distance < ForceDrawWithinDistance || GeometryUtility.TestPlanesAABB(FrustumPlanes, ObjBounds))
            {
                   
                float ofst = Mathf.Sin((Obj.WorldPosition.y + _timer)/4.0f)*1.5f;
                                
                _PropertyBlock.Clear();
                _PropertyBlock.AddColor(_ColorPropertyId, Obj.Color);   
                Graphics.DrawMesh(ObjMesh, Obj.WorldPosition + new Vector3(ofst,0,ofst) , Obj.Rotation, Material1, 0, MainCamera, 0,_PropertyBlock);
                                          
                    
            }
        }
    }
}

●ObjData.cs 【構造体クラス】

using System;
using UnityEngine;


namespace Assets
{
    public class ObjData
    {
        private  int _ObjType;
        private  Vector3 _WorldPosition;
        private  Quaternion _Rotation;
        private  Color _Color;
       

        public ObjData(int ObjType, Vector3 worldPosition, Quaternion rotation)
        {
            _ObjType = ObjType;
            _WorldPosition = worldPosition;
            _Rotation = rotation;
           
            if (ObjType == 0)
            {
               
                _Color = Color.white;
               
            }
            else
            {

               _Color = new Color(1.0f,0.2f,0.05f);
               
            }      
            
        }

        public Vector3 WorldPosition
        {
            get { return _WorldPosition; }
        }
        public Quaternion Rotation
        {
            get { return _Rotation; }
        }
        public int ObjType
        {
            get { return _ObjType; }
        }       
        public Color Color
        {
            get { return _Color; }
        }
       
    }
}

いままでは配列で管理して結合した後GPUに転送という方法が知られていましたが それにに比べると メモリ消費も少なくて なにより頭を悩ませることがないのがいいですね。

画像のStatisticsで表示されるDrawCall数は3000を越えていますが FPS値はふつうに高いままなので Unityの中身はわからないので想像ですが、おそらくCPU側でGraphics.Draw関数をコールした回数だと思われます。VRAMへの描画は1フレームで行われているらしいです。

左のGUIのFPS表示はエディタから実行した数値です。 PCの環境にもよりますがwebプレイヤーでは60フレーム程度はでていると思います。

Unityのツリーシステムなどでは メモリ使用量はモデルの頂点数分の合計となるためかなり大きめな数値となっています おそらくは内部でメッシュをマージして転送する方法を行っているのだと思いますが、今回のDrawMesh関数を使用するサンプルはアップデート内部でシェーダにモデル1つ分のデータを送るだけなのでGPU側のメモリはほとんど消費されていません

それからスキンモデルにもDrawMwshを使用してみたいところなのですが現時点ではスキンウェイトのモデルはダイナミックバッチがかからない仕様になっているそうなのです

スキニングのフィルターがブラックボックスなので調べてみないとわかりませんが、アニメーションするキャラクターを大量に配置したい場合はスキンウェイト用のシェーダを自作する必要があるかもしれません。



akinow at 09:46│Comments(0)TrackBack(0) Clip to Evernote Unity3d | シリーズ講座

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔