#はじめに
レイマーチングでは,物体に衝突したレイをreflect()で反射させ,再び衝突した他の物体の色を合成することで鏡面反射を実現できます
しかし,単純な鏡面反射のみでは各物体の反射率の設定もできず,下図のような表現ができません
今回はモンテカルロ法を用いて拡散反射光も考慮したレイマーチングを行います
尚,framebufferを使用して実装します
####拡散反射光とは
拡散反射(かくさんはんしゃ;diffuse reflection)とは、平坦でないかざらざらした表面からの光の反射のことで、入射光が様々な角度で反射しているかのように見える。乱反射(らんはんしゃ)ともいう。
####モンテカルロ法とは
モンテカルロ法 (モンテカルロほう、英: Monte Carlo method, MC) とはシミュレーションや数値計算を乱数を用いて行う手法の総称。
#実装手順
-
レイが物体に衝突したらその物体の反射率に基づいて,鏡面反射かランダムに反射を行う
-
描画結果はframebufferでオフスクリーンレンダリング
-
1.2.3の手順を繰り返して,平均結果をレンダリングする
offScreen-fragmentShader
まず以下のように各物体の反射率を設定します
const float sphereReflectRatio=0.9;
次に,マーチング内で物体に衝突した際に,レイの先端座標とフレーム数に基づいた乱数が反射率よりも低い場合は単純な鏡面反射,それ以外では拡散反射と設定します
if(distance==sphereDistFunc(rPos-sphereCenter,sphereSize)){
color=sphereColor;
ray=random(rPos*frameCount)<sphereReflectRatio?normalize(reflect(ray,normal)):normalize(random3(rPos*frameCount));
}
最終的に,今までにオフスクリーンレンダリングして来たテクスチャの平均を出力するようにします
color=(color+texture(tex,vec2(vTextureCoord.x,-vTextureCoord.y)).rgb*(frameCount-1.0))/frameCount;
texは前回までのテクスチャの平均だと思ってください
前回までの平均に,現在のフレーム数から1を引いた数を掛けることで前回までのテクスチャの総和が計算できます
それに現在の出力を足してフレーム数で割ることで平均結果が出力できるというわけです
#JavaScript
JavaScript側では,オフスクリーンレンダリングによって今までのテクスチャの平均を出力するプログラムと,最終的にレンダリングするプログラムで分けてます
const renderLoop = () => {
gl.useProgram(offRenderProg);
gl.bindFramebuffer(gl.FRAMEBUFFER, fBuffer[dist].f);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, fBuffer[1 - dist].t);
gl.uniform2fv(offRenderUniLocation[0], [canvas.width, canvas.height]);
gl.uniform1i(offRenderUniLocation[1], 0);
gl.uniform1f(offRenderUniLocation[2], frameCount);
set_attribute([vPosition, vTextureCoord],offRenderAttLocation,offRenderAttStride);
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(prog);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, fBuffer[dist].t);
gl.uniform1i(uniLocation[0], 0);
set_attribute([vPosition, vTextureCoord], attLocation, attStride);
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
dist = 1 - dist;
gl.flush();
frameCount++;
}
offRenderProgが平均を出力するプログラムで,progが最終的にレンダリングするプログラムです
ループが回るたびにレイマーチングを行います.その際にフレーム数に基づいた乱数でランダムな反射を行うので,毎回違う結果が得られます.一回一回の結果は汚いものです(下図のようになります).しかし,これらの平均を出力することで拡散反射光の結果が得られるというわけです
#終わりに
サンプルはこちらです
https://ukeyshima.github.io/monteCarlo/
時間が経つにつれて画質が綺麗になります
色々とゴリ押しな実装が多いです.もっとこうしたほうがいいよといった助言をお待ちしております!