Aqoole_Hateenaの技術日記

vulkan+raytraceで色々描いてます

caustics map

youtu.be


caustics mapという言葉はないかもしれませんが、今回はcausticsの強さを事前に計算するパイプラインを作成し、caustics map作成後rendering用のパイプラインでそれを使用するという方式で実装しました。

caustics mapパイプライン

causticsの強さを計算するだけのパイプラインを作成します。例では光源は単一のもので計算しているので、光源をrayのoriginとし水面の各点にrayを飛ばすようなパイプラインにします。

イメージ図

f:id:Aqoole_Hateena:20210917225240p:plain
イメージ図

地面を一枚のimageに見立てて、水面から屈折したrayがヒットした位置に光の強さに応じた色を格納します。

raygen shader

vec3 origin = pushC.lightPosition;
vec2 relative = vec2(float(gl_LaunchIDEXT.x) / float(gl_LaunchSizeEXT.x), float(gl_LaunchIDEXT.y) / float(gl_LaunchSizeEXT.y));
float waterWidth = righttop.x - lefttop.x;
float waterHeight = lefttop.z - leftbottom.z;
vec3 target = vec3(left + waterWidth * relative.x, seaBase, bottom + waterHeight * relative.y); 
vec3 direction = normalize(target - origin);
float tmin = 0.1;
float tmax = 10000.0;
uint flags = gl_RayFlagsOpaqueEXT;
traceRayEXT(topLevelAS, flags, 0xff, 0, 0, 0, origin, tmin, direction, tmax, 0);

rchit shader

水面にhitした先のrchitシェーダにて光を屈折させ、底面にヒットした座標に対応するimageに色を格納します。

for(uint i = 0; i < 2; i++)
{
  InitPayLoad(vec3(0.0), vec3(0.0));
  traceRayEXT(topLevelAS, flags, 0xFF, 1, 0, 1, origin, tMin, refractD, tMax, 2);
  if(prdBlend.hit)
  {
    vec3 planePos = prdBlend.pos;
    ivec2 imagePos = ImagePos(planePos);
    float d = length(planePos - pushC.lightPosition);
    float l = pushC.lightIntensity / (1.0 + d * d);
    vec4 oldColor = imageLoad(image, imagePos);
    oldColor = vec4(min(1.0, oldColor.x + l));
    imageStore(image, imagePos, oldColor);
    break;
  }
  origin = prdBlend.pos;
  refractRatio = 1.0 / refractRatio;
  refractD = refract(origin, prdBlend.normal, refractRatio);
}

renderingパイプライン

あとはrenderingパイプラインにてcausticsの色を計算するときに、hitした地面の座標に対応するcaustics mapの値をimageLoad関数を用いて参照し、色を計算します。

あとがき

イデア自体はやはりシンプルなのですが、vulkan側でのimageの初期化に手間取ったり、一番はglslでの計算というか、整数->小数点のキャストの詳細を知らなかったので、いつまでたってもある一点にしかrayが飛ばないなどで苦労しました。
基本のルールを把握して、それを積み上げてプログラミングすることが大事ですね。