🦐

EbitengineでLive2Dを描画してみた

2023/12/27に公開

EbitengineでLive2Dモデルを描画しようと試行錯誤していて、ある程度形になってきたので公開します。

デモリポジトリを作成したので、詳しくはそちらのmain.goを見ていただければと思いますが、いくつかポイントを解説します。

描画対象

screenに直接描くとスケール周りでしんどくなってしまうので、モデルに設定されているCanvasWidthCanvasHeightに合わせた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