Edited at

[Unity] Mesh動的生成 (mesh、sharedMeshの違い), (subMeshを利用した複数Materialのアタッチ)


Meshの構成

Meshは以下のようなデータを元に構成されています.結構おおいですねぇ,,でもこれらすべてを必ずしも設定せずともMeshを使うことは可能です.サンプルコードを見ながら説明していきます.


  • MeshRenderer

  • MeshFilter(mesh, sharedMesh内構成↓)


    • VertexData

    • normalData

    • ColorData

    • IndexData

    • uvData




サンプルコード


MeshSample.cs

using System.Collections;

using System.Collections.Generic;
using UnityEngine;

//必要コンポーネント
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class MeshSample : MonoBehaviour
{
private void Awake()
{
Mesh mesh = new Mesh();
GetComponent<MeshFilter>().mesh = mesh;

Vector3[] vertices = new Vector3[3];
int[] triangles = new int[3];
Color[] colors = new Color[3];

//極座標ベース
for(int i = 0; i < 3; i++)
{
float angle = i * 120.0f * Mathf.Deg2Rad;
vertices[i] = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0.0f);
}

triangles = new int[3] { 0, 2, 1};
colors = new Color[3] { new Color(10.0f, 0.0f, 0.0f), Color.red, Color.green };

mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.colors = colors;

//頂点情報をもとに法線を計算
mesh.RecalculateNormals();
}
}



表裏の判定(indexについて)

頂点にはそれぞれindexが登録した順番に与えられます.なぜそのようなindexが与えられるかというと三角形を作る際に必要になります.「何番と何番と何番の点で三角形になってください~」って感じです.indexが付与されたことで識別が簡単になりますもんね.そこで重要なことがもう1つあります,それは番号を呼ぶ順番です.なんと呼ぶ順番によって表裏が変わってしまうのです.

Unityの座標系は左手座標系です(x,y,zそれぞれが右,上,奥が正の座標系).ポリゴンは左手座標系の場合,+Zの方向に視点を向けた際の時計まわりに頂点のインデックスを指定してあげると+zにを向いているカメラに向かって表向きとなって表示されます.これを反時計周りに登録してしまうとカリング(処理を軽くするために必要としない面の描画をしない最適化手法)の対象になってしまいカメラに向かって面が描画されない場合がありますので注意.


MeshとPolygonの関係性

Polygonは複数の点が集まった多角形のことを示します.それに対してMeshは4点ごと結んだ四角形のポリゴン(2つ三角形ポリゴンで構成された物)を扱っていくことを指します.


MeshFilter内のmesh, sharedMeshの違い

sharedMeshはMeshFilterが持つメッシュを返し,meshの場合はまだメッシュが作成されていない場合(nullのとき)新しくメッシュをインスタンス化し返します.なので以下のコードのように初めはメッシュを持っていないが,meshを呼ぶことにとってインスタンス化されていることが確認できます.


MeshSample.cs

using System.Collections;

using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class MeshSample : MonoBehaviour
{
private void Awake()
{
if(GetComponent<MeshFilter>().sharedMesh == null){
Debug.Log("null");
}else{
Debug.Log("not null");
}

if (GetComponent<MeshFilter>().mesh == null){
Debug.Log("null");
}else{
Debug.Log("not null");
}
}
}


-> null

-> not null


Shader周り


Culling

カリングの種類はsubShader内でレンダリングステートのカスタマイズ時に設定することが出来ます.

- Cull Back : 裏面を描画しない(一般的)

- Cull Front : 表面を描画しない

- Cull Off : どちらの面も描画する

materialを作成して是非Sceneビューなどで確認してみてください.


Cull.shader

Shader "Custom/Culling"

{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Cull Back
Tags { "RenderType"="Opaque" }
LOD 200

CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0

sampler2D _MainTex;

struct Input
{
float2 uv_MainTex;
};

half _Glossiness;
half _Metallic;
fixed4 _Color;

UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)

void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}



頂点ベースの色

初めのサンプルコードの中で頂点に色の属性を持たせていましたが実際に反映はされていませんでした.マテリアル本来の性質を無効にし,頂点の色属性を最優先にするshaderのサンプルを載せておきます.

ColorMaterialを用いると,マテリアルにて設定された色の代わりに頂点ごとの色を使用します.AmbientAndDiffuse は、マテリアルの Ambient,Diffuse 値を置き換えます. Emission はマテリアルの Emission 値を置き換えます.つまりこの状態ではライティングなどの効果も受けません.


VertexColored.shader

Shader "Custom/Vertex Colored" {

Properties{
_Color("_Color", Color) = (0.0, 0.0, 0.0, 0.0)
_Emission("Emmisive Color", Color) = (0,0,0,0)
}
SubShader{
Pass {
ColorMaterial AmbientAndDiffuse
//Emission
}
}
}


 複数Materialのアタッチ

1つのメッシュに対して一部分だけ異なるMaterialを適応させることも可能です.こんな感じ↓



全体のソースは後にあげます,重要な部分から先に説明します.

subMeshCountはメッシュに適応させるマテリアルの数を指定することが出来ます.今回の場合は2種類のマテリアルを適応させてあげたいので2を代入してあげています.またMeshRendererのmaterial.Sizeを2に増やしてあげてください.

その後,setTriangleを呼ぶのですが第1引数には三角形を構成するインデックスのリスト,第2引数には何番目のマテリアルを使用するかを指定してあげます.(今回の場合は先にインデックスを作っておき,3の倍数の時に区切ってあげています.何故3の倍数かというと三角形を構成するインデックスなので3つごとに区切りが来るからです.)

複数マテリアルをアタッチできる円柱をMeshを生成するソース↓


MeshSample.cs

mesh.subMeshCount = 2;

int triangleCount = 50;
mesh.SetTriangles(triangles.GetRange(0, triangles.Count-triangleCount*3), 0);
mesh.SetTriangles(triangles.GetRange(triangles.Count - triangleCount * 3, triangleCount * 3), 1);


MeshSample.cs

using System.Collections;

using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class DynamicMesh : MonoBehaviour
{
MeshFilter meshFilter;
MeshRenderer meshRenderer;

[SerializeField] [Range(1, 96)] int wSegments = 16, hSegment = 8;
[SerializeField] [Range(3.0f, 100.0f)] float radius = 5.0f, height = 1.0f;
const float PI2 = Mathf.PI * 2f;

private void Awake()
{
meshFilter = GetComponent<MeshFilter>();
meshRenderer = GetComponent<MeshRenderer>();

}

void Start()
{
this.meshFilter.mesh = CreateMesh();
}

Mesh CreateMesh()
{
var mesh = new Mesh();
var vertices = new List<Vector3>();
var uvs = new List<Vector2>();

for (int i = 0; i < hSegment; i++)
{
for (int j = 0; j < wSegments; j++)
{
var _theta = (PI2 / wSegments) * (float)j;
var x = radius * Mathf.Cos(_theta);
var y = height * i;
var z = radius * Mathf.Sin(_theta);
vertices.Add(new Vector3(x, y, z));
uvs.Add(new Vector3(x / wSegments, y / height, z / wSegments));
}
}

var triangles = new List<int>();
for (int y = 0; y < hSegment - 1; y++)
{
for (int x = 0; x < wSegments; x++)
{
int index = y * wSegments + x;
var a = index;
var b = index + 1;
var c = index + wSegments;
var d = index + wSegments + 1;

if (x != wSegments - 1)
{
triangles.Add(a);
triangles.Add(d);
triangles.Add(c);
triangles.Add(a);
triangles.Add(b);
triangles.Add(d);
}
else if (x == wSegments - 1)
{
b = y * wSegments;
d = b + wSegments;

triangles.Add(a);
triangles.Add(d);
triangles.Add(c);
triangles.Add(a);
triangles.Add(b);
triangles.Add(d);
}
}
}
mesh.SetVertices(vertices);
mesh.subMeshCount = 2;
int triangleCount = 50;
mesh.SetTriangles(triangles.GetRange(0, triangles.Count-triangleCount*3), 0);
mesh.SetTriangles(triangles.GetRange(triangles.Count - triangleCount * 3, triangleCount * 3), 1);
mesh.RecalculateBounds();
mesh.RecalculateNormals();

return mesh;
}
}