EbitengineでLive2Dを描画してみた
EbitengineでLive2Dモデルを描画しようと試行錯誤していて、ある程度形になってきたので公開します。
デモリポジトリを作成したので、詳しくはそちらのmain.go
を見ていただければと思いますが、いくつかポイントを解説します。
描画対象
screen
に直接描くとスケール周りでしんどくなってしまうので、モデルに設定されているCanvasWidth
とCanvasHeight
に合わせたebiten.Image
を作ると良いです。
surface := ebiten.NewImage(int(getCanvasWidthPixel()), int(getCanvasHeightPixel()))
座標
まずvertexPositions
は-1〜1、vertexUVs
は0〜1になっているため、それぞれ変換する必要があります。
vertexPositions
についてはcanvasのサイズに合わせれば良いので、以下のようになります。ここでは上述したsurface
のサイズを利用しています。
x := (pos.X + 1) * float32(surface.Bounds().Dx()) / 2
y := (pos.Y * -1 + 1) * float32(surface.Bounds().Dy()) / 2
vertexUVs
はテクスチャ画像のサイズを元に変換します。
x := uv.X * float32(texture.Bounds().Dx())
y := (1 - uv.Y) * float32(texture.Bounds().Dy())
Y座標にだけなにやら操作を行っているのは、元の座標が左下を原点としているからです。それぞれ値を反転させています。
描画
基本的には上述したような形で作った座標を元にebiten.Vertex
を作り、DrawTriangles
に食わせるだけで描画できます。ただし、以下のように工夫が必要なポイントもあります。
マスク処理
これは若干面倒で、以下のようなKageシェーダを用いてアルファ値をゴニョゴニョする必要があり、そのためバッファとなるebiten.Image
が追加で2枚必要になります。
//kage:unit pixels
package main
func Fragment(dstPos vec4, srcPos vec2, col vec4) vec4 {
maskBuffer := imageSrc0At(srcPos)
frameBuffer := imageSrc1At(srcPos)
return vec4(frameBuffer.r, frameBuffer.g, frameBuffer.b, frameBuffer.a * maskBuffer.a)
}
ただ、この実装方法ではどうしても描画対象の切り替えやDraw系の命令が増える影響で若干コストが高く、より高速化できる方法があるかもしれません。
今後の展望
現状だとCubismNativeFrameworkとCubismCoreを更にラップしたオレオレ動的ライブラリを作っているのですが、これはかなりしんどい道です...。
なのでCubismNativeFramework自体をGolangで書き直し、あとはCoreの動的ライブラリさえあれば動くような手軽に使えるライブラリの形を目指したいです。
また恥ずかしながら現状対応していない機能や操作などのTODOが余りにも多いので、それらも回収しつつ引き続きやっていこうと思います。
Discussion