UnityでGraphicsやGLを使ってレンダリングしてみる
はじめに
以前に書いた記事では独自のシェーダを書いてレンダリングをしてみました。
シェーダの処理自体は自作しましたが、描画命令の発行はMeshRenderer任せでした。
今回はMeshRendererを使わずにレンダリングする方法として、Graphics.DrawMeshと
Graphics.DrawMeshNow、そしてGLの3種類試してみます。
Unityのバージョン:2018.3.0b1
Graphics.DrawMesh
まずはGraphics.DrawMeshを試してみます。
次のコードを適当なGameObjectにアタッチします。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawMesh : MonoBehaviour {
Mesh mesh;
Material material;
void Start () {
mesh = new Mesh ();
mesh.name = "TestMesh";
var vertices = new List<Vector3> {
new Vector3 (0.5f, 0.5f, 0),
new Vector3 (-0.5f, 0.5f, 0),
new Vector3 (0.5f, -0.5f, 0),
new Vector3 (-0.5f, -0.5f, 0),
};
var triangles = new List<int> {
1,
0,
2,
1,
2,
3
};
mesh.SetVertices (vertices);
mesh.SetTriangles (triangles, 0);
material = new Material (Shader.Find ("Standard"));
}
void Update () {
Graphics.DrawMesh (
mesh,
transform.position,
transform.rotation,
material,
LayerMask.NameToLayer ("Default")
);
}
}Start内で正方形のMeshを作成しマテリアルの取得を行っています。
UpdateではGraphics.DrawMeshを呼び出しています。
アタッチしたGameObjectのtransformを反映するようにしています。
実行結果は次のとおりです。
無事にレンダリングされました。
この方法でレンダリングを行うと、MeshFilterとMeshRendererをつけた
GameObjectを用意せずにMeshを描画できます。
大量のMeshを描画したい場合にはGameObjectのオーバーヘッドが馬鹿にならなくなるので
スクリプトから描画処理を走らせるのが有効だそうです。
Graphics.DrawMeshNow
次はGraphics.DrawMeshNowを試してみます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawMeshNow : MonoBehaviour {
Mesh mesh;
Material material;
void Start () {
mesh = new Mesh ();
mesh.name = "TestMesh";
var vertices = new List<Vector3> {
new Vector3 (0.5f, 0.5f, 0),
new Vector3 (-0.5f, 0.5f, 0),
new Vector3 (0.5f, -0.5f, 0),
new Vector3 (-0.5f, -0.5f, 0),
};
var triangles = new List<int> {
1,
0,
2,
1,
2,
3
};
mesh.SetVertices (vertices);
mesh.SetTriangles (triangles, 0);
material = new Material (Shader.Find ("Unlit/TestShader"));
}
void OnRenderObject () {
material.SetPass (0);
Graphics.DrawMeshNow (mesh, transform.position, transform.rotation);
}
}Graphics.DrawMeshと比べるとUpdateではなくOnRenderObjectで
呼び出している点が異なります。
OnRenderObjectは通常のシーンのレンダリングが終わったあとに呼ばれるものです。
また、Graphics.DrawMeshNowはGraphics.DrawMeshと違って
マテリアルを引数に渡していません。
Graphics.DrawMeshNowを呼び出す前に、使うマテリアルでmaterial.SetPassを
呼び出しておく必要があります。
SetPassではマテリアルの使うパスのインデックス番号を0始まりで指定します。
今回は次のようなシェーダを用意しました。
Shader "Unlit/TestShader"
{
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(1, 0, 0, 1);
}
ENDCG
}
}
}Passは1つで単純に赤で塗りつぶすものです。
これのスクリプトを適当なGameObjectにつけて実行してみます。
Graphics.DrawMeshと比べてみると影が描画されていないのがわかります。
Graphics.DrawMeshNowの描画処理をOnRenderObjectではなくUpdateで描画すると、
その後の通常のレンダリング処理でクリアされて消えてしまいます。
フレームデバッガを確認してみます。
これがOnRenderObjectでGraphics.DrawMeshNowを呼び出した場合です。
他のオブジェクトが描画されたあとでOnRenderObjectのタイミングで描画されてます。
こちらがUpdateで呼び出した場合です。
一番最初に描画処理が走っています。
その後の通常の描画処理でクリアされてしまいます。
GL
次はGLを使って描画してみます。
GLはOpenGLのような非常に低レベルなAPIです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawWithGL : MonoBehaviour {
Material material;
void Start () {
material = new Material (Shader.Find ("Unlit/TestShader"));
}
void OnRenderObject () {
GL.PushMatrix ();
GL.MultMatrix (transform.localToWorldMatrix);
material.SetPass (0);
GL.Begin (GL.TRIANGLES);
GL.Vertex (new Vector3 (-0.5f, 0.5f, 0));
GL.Vertex (new Vector3 (0.5f, 0.5f, 0));
GL.Vertex (new Vector3 (0.5f, -0.5f, 0));
GL.Vertex (new Vector3 (-0.5f, 0.5f, 0));
GL.Vertex (new Vector3 (0.5f, -0.5f, 0));
GL.Vertex (new Vector3 (-0.5f, -0.5f, 0));
GL.End ();
GL.PopMatrix ();
}
}Graphics.DrawMeshNowと同じ理由でUpdateではなくOnRenderObjectで呼び出しています。
GL.BegineからGL.Endの間でモデルの頂点を組み立てていきます。
三角形を作る順番でGL.Vertexを呼び出しています。
こちらもGraphics.DrawMeshNowと同じで、
使うマテリアルは事前にmaterial.SetPassを呼び出しておきます。
これを適当なGameObjectにつけて実行してみます。
比較
3種類の描画方法を比較してみます。
Graphics.DrawMeshは即座にメッシュを描画するわけではありません。
DrawMeshに渡されたメッシュは通常のレンダリングパイプラインの中で呼び出されます。
フレームデバッガを確認してみると次のようになっています。
Shadowパスでもきちんと呼び出されています。
レンダリングパイプラインで適切に呼び出されるおかげで影なども適切にレンダリングされます。
一方で、Graphics.DrawMeshNowとGLは呼び出したときに直接レンダリングを行います。
そのためレンダリングパイプラインから外れてしまいます。
先のGraphics.DrawMeshNowのプログラムのフレームデバッガは次のとおりです。
OnRenderObjectで描画しているため通常の描画処理がすべて終わったあとで呼び出されています。
レンダリングパイプラインから外れるため影の描画などがきちんとおこなわれません。
おわりに
MeshRendererに頼らない描画方法について3つ試してみました。 次回はこの記事では試さなかったCommandBufferを試してみたいと思います。
この記事のプログラムのソースコードはこちらのリポジトリに置いてあります。