Go製の2Dゲームエンジン「Ebitengine」でレイマーチングしてみる

この記事は[クラスター Advent Calendar 2023](https://qiita.com/advent-calendar/2023/cluster) シリーズ2の11日目の記事です。

こんにちは。クラスター株式会社でソフトウェアエンジニアをしている　@catGPT　です。えびのてんぷらとクリエイティブコーディングとおっきいねこが好きです。

前日の記事は もなしー monac(@monac) さんの「[clusterのアバターメイカーで推しカラーコーデ作ってみた！](https://qiita.com/monac/items/14fe53473ea4aa373338)」でした！ 色って表現においても重要な要素ですよね！

普段はサーバーサイドをGoで書いたりしているのですが、趣味では言語やツールを問わず表現の領域が好きだったりします。先日、Go製の2Dゲームエンジン「Ebitengine」のシェーディング言語「Kage」でレイマーチングに入門してみたのでその紹介の記事を書きたいと思います。

なおshaderに関する知識は趣味として得たもので、誤った内容等なとはお目溢しいただければ幸いです。

# Goの2Dゲームエンジン「Ebitengine」
Ebitengineの説明は、公式Webページに一番端的な説明がありましたので引用させていただきます。

>Ebitengine (エビテンジン) (旧称 Ebiten) はプログラミング言語 Go のオープンソースなゲームエンジンです。シンプルな API を使って、マルチプラットフォームな 2D ゲームを開発することができます。

https://ebitengine.org/ja/

個人的には、名前やロゴのかわいさ、そしてシンプルさに惹かれて、半年前くらいから気になっていました。

#  Kage
KageはGoの文法を使って記述することができるEbitengine独自のシェーディング言語で、動的にコンパイルされGPUで実行されます。現時点ではフラグメントシェーダー(pixcel shader)のみ記述できるようです。

GLSLでも見覚えがある組み込み関数などは一通り実装されているように見受けました。Uniform 変数を使って、Go側から任意の値を渡すこともできます。

https://ebitengine.org/ja/documents/shader.html

文法はGoですが、使える型などは限られているので注意が必要そうです。


#  レイマーチングしてみる

Kageでレイマーチングする、おそらく一番かんたんなコードを書いてみました。

Goで書かれた`main.go`と、Kageで書かれた`shader.go` があります。

レイマーチングのコードは　[魔法使いになりたい人のためのシェーダーライブコーディング入門](https://qiita.com/kaneta1992/items/21149c78159bd27e0860)　を参考にさせていただき、Kageのコードとして書いてみました。


```main.go
package main

import (
	_ "embed"

	"github.com/hajimehoshi/ebiten/v2"
)

//go:embed shader.go
var shader []byte

const (
	screenWidth  = 500
	screenHeight = 500
)

type Game struct {
	shader *ebiten.Shader
}

func (g *Game) Update() error {
	return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
	w, h := screen.Bounds().Dx(), screen.Bounds().Dy()
	screen.DrawRectShader(w, h, g.shader, nil)// 描画
}

func (g *Game) Layout(_, _ int) (int, int) {
	return screenWidth, screenHeight
}

func main() {
	ebiten.SetWindowSize(screenWidth, screenHeight)// ウィンドウサイズを指定
	s, err := ebiten.NewShader(shader)//　shaderをコンパイル
	if err != nil {
		panic(err)
	}

	if err := ebiten.RunGame(&Game{shader: s}); err != nil {
		panic(err)
	}
}

```

```shader.go
//go:build ignore

//kage:unit pixels
package main

func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
	imageDstSize := imageDstSize()
	p := (srcPos*2 - imageDstSize) / imageDstSize

	cameraPos := vec3(0, 0, -3.0)
	screenZ := 1.0
	rayDirection := normalize(vec3(p, screenZ))

	col := vec3(0.0)
	depth := 0.0
	for i := 0; i < 100; i++ {
		rayPos := cameraPos + rayDirection*depth
		dist := distanceFunc(rayPos, 1.0)

		if dist < 0.001 {
			col = vec3(1, 1, 1)
			break
		}
		depth += dist
	}
	return vec4(col, 1)
}

func distanceFunc(pos vec3, s float) float {
	d := length(pos) - s
	return d
}
```

実行結果はこのようになりました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634852/6a5fa01b-6217-7275-85c4-3b3314b3c583.png)

無事、結果を出力できています！＼( 'ω')／ウオオオオアアーーーッッ！！！


#  他のKageの公式サンプル
Kageの公式サンプルは下記に公開されており、画像や、複数のshaderを扱ったり、キー入力、マウス位置を使うサンプルを見ることができます。

https://github.com/hajimehoshi/ebiten/tree/main/examples/shader

描画時に渡す`ebiten.DrawRectShaderOptions{}` の `Uniforms` に任意の値を格納してshaderに渡しているとことも注目です。

クリエイティブコーディングでお馴染みの `Time` などの値もここで定義して渡すことができました。


#  楽しい!!
レイトレの先人の方々が公開してくださっているテクニックを見ながら実装して遊んでみました。
楽しい！


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634852/04a79e0a-e34a-4a06-8d89-6db6f7416b62.png" width="350" hight="350">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634852/3ce0df4e-5214-3292-d002-cae3c26959c4.png" width="350" hight="350">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634852/6a69edc2-60d2-ee30-a46a-491f52f9c757.png" width="350" hight="350">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634852/11927a0e-c195-ab70-c8e3-fb8a6aa41390.png" width="350" hight="350">

明日は @FUKUDA_concrete　さんの 「[Cluster Creators Guide」運営四方山話」です！](https://qiita.com/FUKUDA_concrete/items/32c027c7fcaa4067f6a0)お楽しみに

# 参考

- [魔法使いになりたい人のためのシェーダーライブコーディング入門](https://qiita.com/kaneta1992/items/21149c78159bd27e0860)
- [GLSL: レイマーチングでオブジェクトの複製(repetition)を行う](https://wgld.org/d/glsl/g012.html)
- [Signed distance functions 符号付き距離関数｜kynd](https://note.com/kyndinfo/n/n55ccba788f5f)