☀️

Goでレイトレーシングを作った話

2022/06/20に公開

Goでレイトレーシングを作りました。repoは下記です。
https://github.com/stone-like/RayGo

Q.レイトレーシングとは

下記の説明がわかりやすかったです。
簡単に言ってしまえば光源を用意して影や鏡面の映り込みをリアルに表現できるようにする技術...といっていいと思います。
https://www.itmedia.co.jp/pcuser/articles/2202/11/news032.html

元ネタの本

元ネタはThe Ray Tracer ChallegeというJamis Buckさんによって書かれた本で、TestDrivenでRayTracerをスクラッチで書いてみよう!という趣旨の本
下記リンクから購入できます。
https://pragprog.com/titles/jbtracer/the-ray-tracer-challenge/

疑似コードでかかれているため、特に実装する際の言語は指定されていないので今回Goで書いてみました。

何ができるのか?

基本的に大体の3Dオブジェクトは作れると思います。
ただ、基本的な形(三角錐、正方体、球、円柱)を組み合わせる必要があるので複雑なオブジェクトを自力でつくるのは面倒です。

複雑なオブジェクトを自力で作るのは面倒なので、PPMファイルという3Dオブジェクトのレシピがあるので、PPMファイルを読み込むことで複雑なオブジェクトを描画して楽しむこともできます。

実際できるのは下記みたいなやつで、映り込みがうまく表現できて実物らしくなっていますね。

遊び方

コマンドラインとかには全然対応していないのでとても遊び辛いのですが、もし遊ぶ方がいらっしゃったらmain.goを適当に弄ってgo run main.goをすればtest.ppmが生成されるので、
test.ppmをgimp等でppmファイルを読み取れば画像として見れます。

下記がフォルダ構造です。

Calc(行列計算用)
Files(PPMファイル読み取り用、今回は関係ない)
Scene(球や正方形などのShape、光源、カメラ、すべての元になるWorld)
Task(元ネタの本の課題用、今回は関係ない)
Util(floatの比較するための関数とか入っている、今回は関係ない)

となっていて、必要なのは大本となるWorldと、光源、カメラ、主役のShapeです。
まずWorldを作ってあげて、光源とカメラ、Shapeをどこに置くかを設定していく感じですね。

具体的には下記のように設定していきます。


        //球の作成
	sphere := scene.NewSphere(1)
	
	sphere.SetTransform(calc.NewTranslation(-0.5, 1, 0.5))
	sphereMaterial := scene.DefaultMaterial()
	sphereMaterial.Color = scene.NewColor(0.1, 1, 0.5)
	sphereMaterial.Diffuse = 0.7
	sphereMaterial.Specular = 0.3
	sphere.SetMaterial(sphereMaterial)
        
	
	//Worldの作成,NewLightの部分は光源
	world := scene.NewWorld(scene.NewLight(calc.NewPoint(-10, 10, -10), scene.NewColor(1, 1, 1)),sphere)
        
	//カメラの作成
	camera := scene.NewCamera(600, 900, math.Pi/3)
	camera.Transform = scene.ViewTransform(
		calc.NewPoint(0, 1.5, -5),
		calc.NewPoint(-1.25, -0.7, 0),
		calc.NewVector(0, 1, 0),
	)
        
	//世界の描画
	canvas, err := world.Render(camera)
	if err != nil {
		fmt.Println(err)
		return
	}
        
	//ファイルに書き込み
	fp, err := os.Create("test.ppm")
	if err != nil {
		fmt.Println(err)
		return
	}

	defer fp.Close()

	fp.WriteString(canvas.ToPPM())

上から順に解説していきます。

行列の計算

SetTransForm部分は行列を使ってShapeを動かしています。
すべてのShapeはWorldの原点(0,0,0)に配置されるようになっているので、そこから行列を使って動かしていきます。
Translation(単純な移動)、Scale(縮小、拡大)、RotateX,Y,Z(X,Y,Z軸での回転)、
Shearing(x,y,zがそれぞれ残りの二つに対して変化する,複雑なのであまり使わないかも)

材質

MaterialはShapeの材質を設定するところです。
material.Colorは色を変更できます。
また透明にもできるのですが、複雑なのでここでは設定方法だけとさせてください。
下記のようにすれば透明なShapeができます。
material.Diffuse = 0.1
material.Ambient = 0.1
material.Transparency = 1.0
material.RefractiveIndex = 1.5

光源

光源はNewLightで作ってNewWorldの第一引数に渡します。
NewLightには光源の位置と色を与えてあげます。

カメラ

カメラはNewCameraで作って、縦と横の解像度、FOV(視野)を与えます。ゲームを遊んだりしている人なら馴染み深いかもしれないです。
https://spacely-support.zendesk.com/hc/ja/articles/360032915851-FOVとは何ですか-

カメラの位置も行列で動かせます。
camera.Transformに直接ViewTransformを与えてあげる形ですが、SetTransformする形であったり、ViewTransform以外の行列を受け付けない形にしてあげたり改善した方が良いですね。

あとは世界の描画とファイルに書き出しで終わりです。お疲れさまでした。

感想

Build your own X系を漁っていて何かいい感じのやつないかな~と思っていたらこの本に出会いました。
なにをどうしたら3Dオブジェクトが作られるのか全く想像もついていなかったのもあり、とても楽しくできました。
やっぱり事前に想像もつかないものが一つずつ自分の近くに来ると面白いですね。

個人的に一番ぐっと来たのがShapeの反射と透明を実装するところです。
リアルに近ければ近いほどやってやった感が増していくので、ここが一番楽しいポイントでした。

余談ですが、リアル感と言えば少し前に話題になったUNREAL ENGINE5で作成された駅はどれだけ凄いんだって改めて自分で作って少しわかった気がします。

https://news.denfaminicogamer.jp/news/220510c

疑似コードで言語も選ばないので、得意な言語で書けておすすめです。皆さんもぜひやってみてください!

Discussion