growCrystalsでやっていること[コード]

六角柱を作る方法に重きを置くので、ジオメトリーシェーダーの書き方については説明しません。ご了承ください。

growCrystalsでやっていることの要点のみ実装したシンプルなものをgithubに用意しました。
github.com
こちらのジオメトリーシェーダー部を上から全部解説します。

g2f v = (g2f)0;

[unroll]
for(int j = 0;j < 3;j++){
	v.vertex = UnityObjectToClipPos(ip[j].vertex);
	v.uv = ip[j].uv;
	v.normal = ip[j].normal;
	OutputStream.Append(v);
}
OutputStream.RestartStrip();

元のポリゴンを描画するだけです。


//六角柱の各頂点の位置を決める係数群
float3 needle = float3(1./3.,1./3.,1./3.);
float3 tri =  float3(2./4.,1./4.,1./4.);
float3 itri = float3(2./5.,2./5.,1./5.);

float3 pp0 = (p0*needle.x  + p1*needle.y + p2*needle.z );
float3 pp1 = (p0*tri.x   + p1*tri.y  + p2*tri.z);
float3 pp2 = (p0*itri.x  + p1*itri.y + p2*itri.z );
float3 pp3 = (p0*tri.y   + p1*tri.x  + p2*tri.z );
float3 pp4 = (p0*itri.z  + p1*itri.x + p2*itri.y );
float3 pp5 = (p0*tri.z   + p1*tri.y  + p2*tri.x );
float3 pp6 = (p0*itri.y  + p1*itri.z + p2*itri.x );

考えたこと にあったように各頂点にどれだけ近いかを係数として与えて、それらをかけたものを座標にしています。
tri をfloat3(1,0,0)とすると元のポリゴンの頂点上に六角形の頂点が移動します。
一度float3にしているのはこういった調整がしやすいからです。


//中心から頂点へ向かう単位ベクトルを計算
float3 pv1 = normalize(pp1-pp0);
float3 pv2 = normalize(pp2-pp0);
float3 pv3 = normalize(pp3-pp0);
float3 pv4 = normalize(pp4-pp0);
float3 pv5 = normalize(pp5-pp0);
float3 pv6 = normalize(pp6-pp0);

コードの中のコメントにもありますが考えたことで出てきたことです。
六角柱の中心から六角柱の各頂点へ向いたベクトルを出し、正規化しています。
これに任意の数をかけることで、中心→頂点へ任意の数のぶんだけ移動させられる便利なものです。

float size1 = _CrystalSize1;
float size2 = _CrystalSize2;

//中心から見て頂点方向に伸ばす
pp1 += pv1 * size1;
pp2 += pv2 * size1;
pp3 += pv3 * size1;
pp4 += pv4 * size2;
pp5 += pv5 * size2;
pp6 += pv6 * size2;

ここで六角柱の太さを設定しています。
size1,size2の大きさだけ中心→頂点方向へ大きくなります。
大きさはInspectorから触れるようにしてあります。

float3 dir = getNormal(p0,p1,p2);
float len =_CrystalLength;

float3 pp0d = pp0 + dir * _Sharpness;
//法線の方向にlenの長さ分伸ばす
float3 pp1d = pp1 + dir * len;
float3 pp2d = pp2 + dir * len;
float3 pp3d = pp3 + dir * len;
float3 pp4d = pp4 + dir * len;
float3 pp5d = pp5 + dir * len;
float3 pp6d = pp6 + dir * len;

ここで六角柱の長さを設定しています。
dirに代入されているgetNormal()は、考えたこと にあった外積を用いて法線を出して返すものです。

pp0dには_Sharpnessがかけられています。
先端のとんがりの長さ≒鋭さとしてパラメータを分けています。

六角柱の作成

ここからいよいよ六角柱を作っていきます。
f:id:tonoshake:20191114214129p:plain
六角柱はこのように三角形のポリゴンが組み合わさってできています。
そのため、側面の四角い部分も三角形二つを組み合わせて作ることになります。

どうやって作るかをpp1,pp2,pp1d,pp2dを例にとって示します。
f:id:tonoshake:20191114215052p:plain
黄色の矢印でpp1→pp2→ppd1と辿って1つ目
白色の矢印でpp2→ppd1→ppd2と辿って2つ目になります。

実装部分のみ抜き出すと以下のコードになります。

v4o = float4(pp1,1);
o.vertex = UnityObjectToClipPos(v4o);
OutputStream.Append(o);

v4o = float4(pp2,1);
o.vertex = UnityObjectToClipPos(v4o);
OutputStream.Append(o);

v4o = float4(pp1d,1);
o.vertex = UnityObjectToClipPos(v4o);
OutputStream.Append(o);

OutputStream.RestartStrip();

v4o = float4(pp2,1);
o.vertex = UnityObjectToClipPos(v4o);
OutputStream.Append(o);

v4o = float4(pp1d,1);
o.vertex = UnityObjectToClipPos(v4o);
OutputStream.Append(o);

v4o = float4(pp2d,1);
o.vertex = UnityObjectToClipPos(v4o);
OutputStream.Append(o);

OutputStream.RestartStrip();

v4oに入れているfloat4のなかを見ると分かりますが、上の矢印で辿ったように頂点が入っています。

実装されているコードには法線情報を入れている部分がありますが、六角にするだけならこれでも十分です。
これを頂点を変えながら側面6つ分繰り返します。

六角柱の先端部分

f:id:tonoshake:20191114220612p:plain
六角中の先端は2つの頂点と真ん中の頂点を結んだ三角形を並べることで作られます。
pp1d,pp2d,pp0dを例にとって示します。

//top1
v4o = float4(pp1d,1);
o.vertex = UnityObjectToClipPos(v4o);
OutputStream.Append(o);

v4o = float4(pp2d,1);
o.vertex = UnityObjectToClipPos(v4o);
OutputStream.Append(o);

v4o = float4(pp0d,1);
o.vertex = UnityObjectToClipPos(v4o);
OutputStream.Append(o);

OutputStream.RestartStrip();

pp1d→pp2d→pp0dとなっているのが分かると思います。
これを先端の面の数である6回頂点を変えながら繰り返すことで六角柱を作ることが出来ます。

一通り解説しましたが、おかしなところやわからないところがあれば教えてください。

11/20
続編 色の付け方の解説を書きました
tonoshake.hateblo.jp