初歩的レンダラー

目次

概要

今回は、Unityとは全く関係がない内容になりますが、最近流行りのレイトレーシングの初歩を学ぼう! という事でC++レイトレーシング レンダラーを作って行きたいと思います。

レイトレーシングとは?

最近、RTXがリアルタイムレイトレーシングが出来ると話題になっていますよね。 大体のゲームは、ラスタライズという方法で描画していましたが、RTXではラスタライズと一部レイトレーシングで描画しています。 一部でしかレイトレーシングが使われないのは、本来1フレームを描画するのは数時間、何日もかかる処理なので、それをリアルタイムで描画するのはまだ今の性能では無理な話です。なので、ガラスや水面などレイトレーシングが適してる所だけレイトレーシングで描画しているので、なので一部レイトレーシングなのです。

レイトレーシング ON OFF比較

https://www.4gamer.net/games/419/G041969/20181219082/TN/005.jpghttps://www.4gamer.net/games/419/G041969/20181219082/TN/009.jpghttps://www.4gamer.net/games/419/G041969/20181219082/

RTXデモ動画


Battlefield V: Official GeForce RTX Trailer Battlefield V, NVIDIA RTX Ray Tracing, And GeForce RTX Combine To Deliver Next-Gen Graphics in Games – See It In Action In Our Exclusive Trailer

レイトレーシングの描画流れ

https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Ray_trace_diagram.svg/640px-Ray_trace_diagram.svg.png?download Ray tracing (graphics) - Wikipedia

現実世界では、光源から光が放射して物体(以下、「オブジェクト」と言う。)などに反射、屈折などをして、人間の目に光が入り光の周波数によって色を認識しています。 レイトレーシングはその逆の流れで計算をしています。理由は、光源の光のごく一部しか目に入らないので、光源の光を全部計算すると無駄が多くなってしまうからです。

レイトレーシング実装

今回のブログでは、レイトレーシング法で簡単な反射のみ実装していきます。

1.レイの生成

f:id:aizu-vr:20190106103642p:plain

std::vector<Ray> rays;
for (int i = 0; i < getScreen.w * getScreen.h; i++) {
    double x = getScreen.GetWidth(i) - getScreen.w * 0.5;
    double y = getScreen.GetHigh(i) - getScreen.h * 0.5;

    rays.push_back(Ray(getScreen.cameraRay.o, getScreen.cameraRay.d + V(x * getScreen.pov, y * getScreen.pov, 0))); 
}

raysにこれから計算するレイをプッシュバックしてます。 x,yは for文の i に対してスクリーン上のピクセルかを計算しています。x,yでカメラの方向をズラし、それをレイとしてプッシュバックしています。 例) iが getScreen.w * 0.5 + getScreen.h * 0.5、つまりシーンの中心の時、x、yは0になります。その時はカメラの方向をそのままレイとしてプッシュバックします。

応用として、視野角も後々考えないといけませんね。

2.レイの反射

f:id:aizu-vr:20190106114404p:plain f:id:aizu-vr:20190106114219p:plain 光源とオブジェクト間での反射を考えます。

std::optional<HitInfo> RayHit(Screen &getScreen, const Ray &getRay,  double rayPower) {
    //再帰中止
    if (rayPower < 0.01) {
        return std::nullopt;
    }

    for (int s = 0; s < getScreen.spheres.size(); s++) {
        double dotA = Dot(getRay.d, getRay.d);
        double dotB = -2 * Dot(getRay.d  ,getScreen.spheres[s].p - getRay.o);
        double dotC = Dot(getScreen.spheres[s].p - getRay.o, getScreen.spheres[s].p - getRay.o) - std::pow(getScreen.spheres[s].r,2);
        double D_4 = (std::pow(dotB, 2) - 4 * dotA*dotC) / 4;
        double t1 = (-dotB / 2 + std::pow(D_4, 0.5)) / dotA;
        double t2 = (-dotB / 2 - std::pow(D_4, 0.5)) / dotA;
        if (t1 >= DBL_EPSILON) {
            V Q1 = V(getRay.o + t1 * getRay.d);
            HitInfo hit;
            hit.hitObject = getScreen.spheres[s];
            hit.position = Q1;
            hit.hitObjectNormal = Normalize(Q1 - getScreen.spheres[s].p);   
            hit.dot = Dot(hit.position, hit.hitObject.p) / Magnitude(hit.position) / Magnitude(hit.hitObject.p);
            hit.ray = Ray(hit.position, Normalize(getRay.d) + hit.hitObjectNormal);
            //光源の場合、直ちに光を反映
            if (hit.hitObject.emission.Power() > 0) {
                hit.color = hit.dot* hit.hitObject.emission;
                return hit;
            }
            auto hitRecursive = RayHit(getScreen , Ray(hit.position, Normalize(getRay.d) + hit.hitObjectNormal),rayPower*hit.dot);
            if (hitRecursive) {
                if (hitRecursive.value().hitObject.emission.Power() > 0) {
                    hit.color = hitRecursive.value().dot * hitRecursive.value().hitObject.emission;
                }
                else {
                    hit.color = hitRecursive.value().color * hit.dot;
                }
            }
            else {
                //色の影響がごくわずかである
                //あるいは、rayが接触しなかった
                hit.color = Color(0,0,0);
            }
            return hit;
        }
    }
    //結果衝突を検出出来なかった
    return std::nullopt;
}

反射を繰り返し、ピクセルの色を決定する時の色の影響率が低いレイは再帰の途中で停止してます。

3.画像ファイルの書き出し

今回はPPMファイルを出力しています。PPMファイルの中身はテキストで書かれています。全てのピクセルがテキストで表現できるので、余計なライブラリを使わずに簡単に書き出しが出来ます。

計算結果

f:id:aizu-vr:20190106115606j:plain 上手くいきました👍

りぽじとりー

github.com

ノード型シェーダーエディタOSS

初めに

今回は、OSS(オープンソースソフトウェア)のお題で簡単なシェーダーエディタを作ってみました。

環境

Unity 2018.2.14f1

 

 

ノードの決め事

計算結果を毎回変数に格納して、その計算結果を後のコードに使える状態にします。

例)

float add1 = 1 + 2;

float add2 = add1 + 7;

 

ダメな例)

float add2 = add1 + 7;

float add1 = 1 + 2;

1行目でadd1にアクセスしますが、定義前のadd1にはアクセス出来ません。

 

定義前の変数にアクセスしない様、今回は深さ優先探索を使いノードの書き出し順を決めていきます。

下の画像は深さ優先探査の図です。折り返した時に定義するとします。

f:id:koutei7penguin-jp:20190126200119g:plain

 

ノード処理

fixed4をfixedに分解するノードを「ExportColor」と言うことにします。

下の図のコメントはシェーダーの書き出し部分です。

処理の流れ

ExportColorノードのインポートとして接続されたノードの変数(f4)を取得し、ExportColorで決めた変数名で定義します。また、インポート先とエクスポート先の変数の型を確認する必要があります。理由は、仮にExportColorのインポートノードがfixed4以外時のエラーを回避するためです。fixed3の変数のアルファ値をアクセスしますとエラー、想定外の処理が起きます。

f:id:koutei7penguin-jp:20190323105504j:plain

ID決定

その他にもExportColorノードが複数あった場合、定義変数が重複しエラーが出るので、ノード毎にIDを割り当てて定義変数に追加します。IDは上で書いた深さ優先探索でノードで訪れた順にします。

 

リポジトリ

github.com

 

初ハッカソンに出ました!!

大学のVR部としてMashup Awards 2017に出場しました!

作品のデモ動画

デモ中にリープモーションによる攻撃が出ないというアクシデントがありました💦 

プレゼンのデモの前に焦ってビルドしたのが良くなかったですね。

これからは気を付けますw

 受賞!!

Mashup Awards 2017で「データスパイダー賞」を受賞しました!!

 

このVRコンテンツをデジゲー博に向けて作って行きたいと思います!

プログラムによる分居モデル

分居モデルをC言語で作って遊んでみました。プログラムが少し甘い所があり、空所が少ないと無限ループします。

 

ルール

始めに各色をランダムに配置し、次に1ドットの周りのドットが中心のドットとどれほど色違いであるか計算して、あまりにも色違いであったら空いてる所(黒)に移動する、これを全てのドットが満足するまでループさせます。


分居モデルシミュレーション_01