弾としての表現
は色々とありますが(要するにシューティングゲームの弾です)
- 球状だと進行方向がわかりにくい、味気ない
- 板状は視線と平行になるとつぶれてしまう
ので、私は楕円体の描画を好んでよく使っています。ビルボードと同様に少ない頂点で描画でき、カメラの位置で破綻することもありません。
今回は Godot での実装例を紹介します。
(Unityの実装例は・・・だいぶ前にソースを公開しただけだったかも)
頂点の算出
進行方向を持つ弾は、2点で表現されます。
カメラに対してこのような位置関係にある場合、頂点シェーダで
このようなオレンジ色の頂点を算出します。このようにカメラに対して垂直な点を作れば、ワールド座標の2点が視線方向に重なっても
こうなりますから、潰れることはありません。楕円体を真横から見れば円になる理屈に沿っていますね。
GDScript
func _ready():
var vertices := PackedVector3Array([
Vector3(0,0,0),
Vector3(0,0,1),
Vector3(0,0,0),
Vector3(0,0,1),
])
var normals := PackedVector3Array([
Vector3(-0.5,-0.5,-1),
Vector3(-0.5, 0.5, 1),
Vector3( 0.5,-0.5,-1),
Vector3( 0.5, 0.5, 1),
])
var UVs := PackedVector2Array([
Vector2(0, 0),
Vector2(0, 1),
Vector2(1, 0),
Vector2(1, 1),
])
var indices := PackedInt32Array([
0, 1, 2,
2, 1, 3,
])
頂点シェーダの計算に都合の良い4頂点のメッシュを用意します。どうせ頂点は計算で出すので、ここがモデルとしてどんな形をしていようがかまいません。
手元では、大量に出すために instancing の設定をしていますが、本筋ではないので割愛。
シェーダー
shader_type spatial;
render_mode cull_disabled, blend_add, depth_draw_never, unshaded, specular_disabled, shadows_disabled, ambient_light_disabled, skip_vertex_transform;
void vertex()
{
vec3 objSpaceViewDir = normalize(transpose(MODEL_NORMAL_MATRIX) * (CAMERA_POSITION_WORLD - NODE_POSITION_WORLD) + VERTEX);
float width = 1.0;
vec3 forward = vec3(0, 0, 1);
vec3 side = normalize(cross(objSpaceViewDir, forward));
vec3 vert = normalize(cross(objSpaceViewDir, side));
vec3 tv = NORMAL.x * width * side;
tv += NORMAL.y * width * vert;
float len = 2.0 - width;
tv -= VERTEX.xyz * len;
VERTEX = (MODELVIEW_MATRIX * vec4(tv, 1.0)).xyz;
}
void fragment()
{
float x = (UV.x-0.5)*2.0;
float y = (UV.y-0.5)*2.0;
float dist2 = x*x + y*y;
ALBEDO = COLOR.rgb;
ALPHA = (1.0-dist2)*0.5;
}
こんなふうに頂点シェーダで外積を多用すると望んだ結果が得られます。
わざわざNORMALを用意せずUVの値で計算してもいいのですが、UVの値はテクスチャなしで円を描画するのに使用しているため、このような実装になっています。
おまけ: objSpaceViewDir
vec3 objSpaceViewDir = normalize(transpose(MODEL_NORMAL_MATRIX) * (CAMERA_POSITION_WORLD - NODE_POSITION_WORLD) + VERTEX);
これ。カメラの方向をオブジェクト空間で欲しいってことはよくありますが・・
Unityのシェーダならすぐにとってこれる値なのですが、Godotだとなかなか複雑な演算を必要とします(それなりに探しましたが他に方法を見つけられなかった)。
イディオムとして使いまわしていきましょう。これを標準で用意すべきとも、あまり思いませんね・・書けばいいのですよ。
そんな小ネタでした
というか Advent Calendar の援護射撃でした。