Help us understand the problem. What is going on with this article?

モンテカルロ法を用いて拡散反射光を考慮したレイマーチング

More than 1 year has passed since last update.

はじめに

レイマーチングでは,物体に衝突したレイをreflect()で反射させ,再び衝突した他の物体の色を合成することで鏡面反射を実現できます
しかし,単純な鏡面反射のみでは各物体の反射率の設定もできず,下図のような表現ができません

鏡面反射のみでレイマーチング
鏡面反射のみでレイマーチング

拡散反射光も考慮したレイマーチング
拡散反射光も考慮したレイマーチング

今回はモンテカルロ法を用いて拡散反射光も考慮したレイマーチングを行います
尚,framebufferを使用して実装します

拡散反射光とは

拡散反射(かくさんはんしゃ;diffuse reflection)とは、平坦でないかざらざらした表面からの光の反射のことで、入射光が様々な角度で反射しているかのように見える。乱反射(らんはんしゃ)ともいう。

https://ja.wikipedia.org/wiki/%E6%8B%A1%E6%95%A3%E5%8F%8D%E5%B0%84

モンテカルロ法とは

モンテカルロ法 (モンテカルロほう、英: Monte Carlo method, MC) とはシミュレーションや数値計算を乱数を用いて行う手法の総称。

https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%B3%E3%83%86%E3%82%AB%E3%83%AB%E3%83%AD%E6%B3%95

実装手順

  1. レイが物体に衝突したらその物体の反射率に基づいて,鏡面反射かランダムに反射を行う

  2. 描画結果はframebufferでオフスクリーンレンダリング

  3. 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が最終的にレンダリングするプログラムです

ループが回るたびにレイマーチングを行います.その際にフレーム数に基づいた乱数でランダムな反射を行うので,毎回違う結果が得られます.一回一回の結果は汚いものです(下図のようになります).しかし,これらの平均を出力することで拡散反射光の結果が得られるというわけです
 2018-12-26 18.08.59.png

終わりに

サンプルはこちらです
https://ukeyshima.github.io/monteCarlo/
時間が経つにつれて画質が綺麗になります
色々とゴリ押しな実装が多いです.もっとこうしたほうがいいよといった助言をお待ちしております!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away