この記事はクラスター 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のコードとして書いてみました。
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)
}
}
//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
}
無事、結果を出力できています!\( 'ω')/ウオオオオアアーーーッッ!!!
他のKageの公式サンプル
Kageの公式サンプルは下記に公開されており、画像や、複数のshaderを扱ったり、キー入力、マウス位置を使うサンプルを見ることができます。
描画時に渡すebiten.DrawRectShaderOptions{}
の Uniforms
に任意の値を格納してshaderに渡しているとことも注目です。
クリエイティブコーディングでお馴染みの Time
などの値もここで定義して渡すことができました。
楽しい!!
レイトレの先人の方々が公開してくださっているテクニックを見ながら実装して遊んでみました。
楽しい!
明日は @FUKUDA_concrete さんの 「Cluster Creators Guide」運営四方山話」です!お楽しみに