[Vulkan ray tracing] サンプルコードからの次の一歩　raygenシェーダについて

# 概要
今までは書いたプログラムが動くことを第一にしてきたので、テンプレートな記述はおまじないというか「そういうもの」としてあまり深入りしなかったが、一旦この辺りでサンプルコードによく記述されるraygenシェーダの中味について調べてみたので、記事に残してみる。

#サンプルコード在り処
・Khronos公式　
おそらくみんなが最初に参照するコード
https://github.com/KhronosGroup/Vulkan-Samples/tree/master/samples/extensions/raytracing_basic

・NVIDIA Vulkan Ray Tracing Tutorial
解説付きなので、非常にわかりやすい
NVIDIAはハード面だけでなく、こういった環境面でもやはり一歩先に行ってる模様
https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/#accelerationstructure/top-levelaccelerationstructure/helperdetails:raytracingbuilder::buildtlas()

#raygenシェーダとは
ray tracing pipelineの出発点であり終着点。ここからrayを飛ばし、missシェーダやrchitシェーダなどを通りpixelの色を計算した後、最終結果を2Dimageに格納し、それを表示することで画面を出力する。
上図が一般的と言うかレガシーなグラフィクスパイプラインで、下図がレイトレーシングパイプライン。上図ではVertex、つまりポリゴンの頂点が中心になっており、それをパイプラインに流して(あとはAPIが計算してくれて)世界を描画する形だが、レイトレーシングパイプラインでは世界の中心はrayを飛ばす元(cameraとかeyeとか)である。そこから見える景色をそのまま計算し、2Dimageに描き出す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1446998/656c9e62-8e12-e009-f309-05d5172449f6.png)
引用:https://developer.nvidia.com/blog/vulkan-raytracing/
_Figure 2. Traditional rasterization pipeline versus the ray tracing pipeline_

#テンプレートなraygenシェーダの中味
main関数のみ抽出。シェーダは各ピクセルごとに呼ばれるものと推察できる。座標系の名前は色々あるが、下記の図の名前でここでは統一する。注意点として、これはOpenGLの図なので、座標軸の向きなどに一部誤差がある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1446998/e2a009cb-f33f-7852-b32a-5840be518959.png)
引用:https://learnopengl.com/Getting-started/Coordinate-Systems
_The global picture_



``` raygen.rgen
void main()
{
  const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5);
  const vec2 inUV        = pixelCenter / vec2(gl_LaunchSizeEXT.xy);
  vec2       d           = inUV * 2.0 - 1.0;

  vec4 origin    = cam.viewInverse * vec4(0, 0, 0, 1);
  vec4 target    = cam.projInverse * vec4(d.x, d.y, 1, 1);
  vec4 direction = cam.viewInverse * vec4(normalize(target.xyz), 0);

  uint  rayFlags = gl_RayFlagsOpaqueEXT;
  float tMin     = 0.001;
  float tMax     = 10000.0;

  traceRayEXT(topLevelAS,     // acceleration structure
              rayFlags,       // rayFlags
              0xFF,           // cullMask
              0,              // sbtRecordOffset
              0,              // sbtRecordStride
              0,              // missIndex
              origin.xyz,     // ray origin
              tMin,           // ray min range
              direction.xyz,  // ray direction
              tMax,           // ray max range
              0               // payload (location = 0)
  );

  imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.0));
}
```
段落ごとに見ていく。

##1段落目
``` 1段落目
const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5);
const vec2 inUV        = pixelCenter / vec2(gl_LaunchSizeEXT.xy);
  vec2       d           = inUV * 2.0 - 1.0;
```
gl_LaunchIDEXTにはwindowの左上を原点とした、相対的なピクセルの位置が整数で格納されている(Screen Space)。pixel centerの位置がfloatでほしいので、0.5を足す。
次にScreen SpaceをUV空間としたpixel centerの相対位置を割り出す(inUV)。つまりScreen Spaceを(0, 0)から(1, 1)までの空間とみて、pixcel centerの位置を計算するので、スクリーンサイズ(ウィンドウサイズ)で割る。pixel centerは(0,0)から(1,1)までの値を取る。
最後に上記UV空間からClip Spaceへ変換を行う。UV空間が(0,0)から(1,1)までなのに対して、Clip Spaceは(-1,-1)から(1,1)までである。よって座標変換は

```mathematica
d = inUV * 2.0 - 1.0
```
で計算できる。こうすれば(0,0)の点は(-1,-1)へ射影され、(1,1)の点は(1,1)に射影されるので、Screen SpaceからClip Spaceへの変換が可能である。間の点は連続的に射影される。

##2段落目
```
vec4 origin    = cam.viewInverse * vec4(0, 0, 0, 1);
vec4 target    = cam.projInverse * vec4(d.x, d.y, 1, 1);
vec4 direction = cam.viewInverse * vec4(normalize(target.xyz), 0);

```
最終的にtraceRayEXT関数でrayを飛ばすための変数を求める(origin, direction)。originはWorld Spaceでのcameraの位置のことである。これはuniform変数で外から直接与えても良いし、この例のようにviewInverse行列を与えて計算しても良い。viewInverseは後ほどまた使うので、viewInverseだけを与えたほうが効率は良いかも。View Spaceにおいてcameraの位置は(0,0,0,1)に固定されているので(逆にWorld SpaceからView MatrixによりそうなるようにView Spaceが作成されている)、cameraの位置は

```mathematica
viewInverse * vec4(0,0,0,1)
```
で計算できる。補足として、座標変換の逆行列を掛けているので、上図の変換の逆を行える。
次にdirection、つまりcameraからrayを飛ばす方向を求める。これは将来Screen Spaceに変換したときのpixel centerとなる、World Spaceの位置への方向ことである。つまりray tracingでは、すべてのピクセルに対して、それを一度World Spaceに変換し、そこにcameraからrayを飛ばして見える色を求めるということをやっている(そりゃ計算量増えますわ)。
具体的には、1段落目で求めたdを使う。dはClip Spaceにあるので、View Spaceに持ってくる。

```mathematica
target = projInverse * vec4(d.x, d.y, 1, 1)
```
View Space内での方向ベクトルを算出し、それをWorld Spaceに変換する

```mathematica
direction = viewInverse * vec4(normalize(target.xyz), 0)
```
## ３段落目以降
以上で求めたoriginとdirectionをtraceRayEXT関数を使ってray　tracingし、rayが当たればrchitシェーダへ、当たらなければmissシェーダへの処理に進む。
最終的な結果がpayloadに格納されるので、それをimageStore関数でimageに格納し、処理完了となる。