LoginSignup
4
0

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

Last updated at Posted at 2023-12-10

この記事はクラスター Advent Calendar 2023 シリーズ2の11日目の記事です。

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

前日の記事は もなしー monac(@monac) さんの「clusterのアバターメイカーで推しカラーコーデ作ってみた!」でした! 色って表現においても重要な要素ですよね!

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

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

Goの2Dゲームエンジン「Ebitengine」

Ebitengineの説明は、公式Webページに一番端的な説明がありましたので引用させていただきます。

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

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

Kage

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

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

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

レイマーチングしてみる

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

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

レイマーチングのコードは 魔法使いになりたい人のためのシェーダーライブコーディング入門 を参考にさせていただき、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

無事、結果を出力できています!\( 'ω')/ウオオオオアアーーーッッ!!!

他のKageの公式サンプル

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

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

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

楽しい!!

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

明日は @FUKUDA_concrete さんの 「Cluster Creators Guide」運営四方山話」です!お楽しみに

参考

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0