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を試してみたいと思います。
この記事のプログラムのソースコードはこちらのリポジトリに置いてあります。