コンピュータグラフィックスレイトレーシングアルゴリズム分析

1.求める

レイトレーシングの主な計算量は、多数の交差計算から得られます。Oを光線の開始点、D方向、Pを円上の点、Cを円の中心、rの半径を表します。ボールの方程式は次のとおりです:(P-C)(P-C)= r * r、直線のパラメーター方程式:p(t)= O + tD。

線形方程式をD 2t 2 + 2(OC)Dt +(OC)2-r 2 = 0に代入してから、1つの変数の2次方程式を使用して根の式を求め、解があるかどうかを判断します。2つの解がある場合は、> 0と小さいt。

交差の基本原理は、光線のパラメトリック方程式を円の関数に代入し、tの値を見つけることです。

  1. P(t)= O + tDを円方程式に代入すると、tの1次元2次方程式が得られます。
  2. 最初にVec opを見つけます。opは、球の中心pから光線の開始点(O-C)を引いたものの座標です。
  3. b = op.dot(rd)は「D *(O-C)」を指します
  4. detを検索します。ここでは、原理のbと原理のbの違いに注意を払う必要があるため、
    double det = b * b-op.dot(op)+ rad * rad を直接使用できます。det
    <0の場合、解はありません。直接0を返します。
    それ以外の場合は、ルート番号のdetを見つけます。
  5. 最終的な解は1つまたは2つあります。t= b-det、またはt = b + detの場合があります。0より大きいtと2つの小さいtを選択します。

2.描く

  1. 6つの大きな球を平面として使用します(DIFF属性、拡散反射のみ)。これは、半径が大きい場合、近くの距離を見ると、球は平面に似ているためです。
    作成者は、これを実行して、平面交差、平面クラス、およびその他の関数を記述しないようにする必要があります。
  2. 光源を表すために1つのボールを使用します。つまり、Lite、1つのミラーボール(完全反射)、1つのガラスボール(屈折と反射の両方)で
    すべてのボール横断し、交点を見つけます。
inline bool intersect(const Ray &r, double &t, int &id) {
    
    
    double n = sizeof(spheres) / sizeof(Sphere), d, inf = t = 1e20;
    for (int i = int(n); i--;) {
    
    
        if ((d = spheres[i].intersect(r)) && d < t) 
        {
    
    
            t = d;
            id = i;
        }
    }
    return t < inf;
}
  1. この光線は発射し、すべての球の交点を見つけます。
  2. カメラに最も近い交点を見つけます。これは、後で画面に描画される主要な点です。

3.主な機能の説明

  1. カメラの位置は(50、52、295.6)で、z軸の負の方向を向いています。
int w = 1024, h = 768, samps = argc == 2 ? atoi(argv[1]) / 4 : 10; // # samples 
Ray cam(Vec(50, 52, 295.6), Vec(0, -0.042612, -1).norm()); // cam pos, dir 
Vec cx = Vec(w*.5135 / h), cy = (cx.cross(cam.d)).norm()*.5135, r, *c = new Vec[w*h];
  1. 各ピクセルをトラバースし、ランダムサンプリングを使用して、放出される光の方向dを見つけます。
for (int y = 0; y<h; y++) {
    
                           // Loop over image rows
    fprintf(stderr, "\rRendering (%d spp) %5.2f%%", samps * 4, 100.*y / (h - 1));
    for (unsigned short x = 0, Xi[3] = {
    
     0,0,y*y*y }; x<w; x++)   // Loop cols 
        for (int sy = 0, i = (h - y - 1)*w + x; sy<2; sy++)     // 2x2 subpixel rows 
            for (int sx = 0; sx<2; sx++, r = Vec()) {
    
            // 2x2 subpixel cols 
                for (int s = 0; s<samps; s++) {
    
    
                    double r1 = 2 * erand48(Xi), dx = r1<1 ? sqrt(r1) - 1 : 1 - sqrt(2 - r1);
                    double r2 = 2 * erand48(Xi), dy = r2<1 ? sqrt(r2) - 1 : 1 - sqrt(2 - r2);
                    Vec d = cx*(((sx + .5 + dx) / 2 + x) / w - .5) +
                            cy*(((sy + .5 + dy) / 2 + y) / h - .5) + cam.d;
                    r = r + radiance(Ray(cam.o + d * 140, d.norm()), 0, Xi)*(1. / samps);
                    } // Camera rays are pushed ^^^^^ forward to start in interior 
                    c[i] = c[i] + Vec(clamp(r.x), clamp(r.y), clamp(r.z))*.25;
                }
    		}
    	}
    }
    FILE *f = fopen("image.ppm", "w");         // Write image to PPM file. 
    fprintf(f, "P3\n%d %d\n%d\n", w, h, 255);
    for (int i = 0; i<w*h; i++)
        fprintf(f, "%d %d %d ", toInt(c[i].x), toInt(c[i].y), toInt(c[i].z));
}

4.レイトレーシングの再帰の説明

_Vector radiance:関数が再帰的に呼び出されるレイトレーシング処理フローを実現します。レイトレーシングの再帰プロセスの終了条件は、ライトが環境内のオブジェクトと交差しないか、純粋な拡散サーフェスで交差することであり、トレースされたライトによって返される輝度値は、ピクセルの色にほとんど寄与せず、特定の深さまで再帰します。関数は2つのパラメーターを渡します。1つは光線の参照で、もう1つは再帰の深さです。

まず、光線が交差するオブジェクトの距離と、光線が交差するオブジェクトのIDを見つけます。交差がない場合は、emission(0,0,0)のベクトルが返されます。それらが交差する場合は、オブジェクトがヒットしたポイントを見つけ、法線ベクトルnormal_realを計算し、ベクトルのユニット化を実行します。次に、再帰が特定の深さに達し、深さが100を超えて終了するかどうかを判断します。深度が5より大きい場合、0〜1のランダムな浮動小数点数がRGBカラーコンポーネントの最大値Pと比較され、乱数がPより小さい場合、現在のカラー値が返されます。それ以外の場合、反射や屈折などの計算は、球のマテリアルタイプに応じて実行されます。その中で、拡散反射は、ランダムな拡散反射光を見つけるために、乱数と、w、u、vの3つの直交ベクトルを受け取り、反復的に継続します。鏡面反射は、反射光の角度を直接計算します。反射と屈折は、最初にnormalとnormal_realが同じ方向にあるかどうかを決定し、次に屈折率と入射角余弦を計算し、フレネル屈折と反射計算を実行し、最後に再帰呼び出しのルーレットアルゴリズムを使用してカラー値を返します。

再帰的出口(深さの値)を設定し、各球をライトと交差させ、法線ベクトルとray._directを鈍角(球の外側を指す法線ベクトル)にします。

  1. 交差するかどうかを決定し、交点を見つけ、サーフェス法線を見つけます
Vec radiance(const Ray &r, int depth, unsigned short *Xi) {
    
    
    double t;                               // distance to intersection 
    int id = 0;                               // id of intersected object 
    if (!intersect(r, t, id)) 
        return Vec(); // if miss, return black 
    const Sphere &obj = spheres[id];        // the hit object 
    Vec x = r.o + r.d*t, n = (x - obj.position).norm(); // calculate vector n,球面法向量
    Vec nl = n.dot(r.d) < 0 ? n : n*-1, f = obj.color;
    double p = f.x>f.y && f.x>f.z ? f.x : f.y>f.z ? f.y : f.z; // max refl 
    if (++depth>5||!p) 
        if (erand48(Xi)<p) 
            f = f*(1 / p); 
        else  
            return obj.emission; 
}
  1. 拡散反射(DIFF)
    マテリアルが拡散反射の場合、拡散反射に対してランダムな方向が生成されます。
    法線ベクトルwとベクトル(0,1,0)または(1,0,0)を使用して外積演算を実行してベクトルuを取得し、次にwとuの外積を実行してベクトルvを取得し、外積演算の方向を取得して正規直交基底w、u、vを設定します。ランダム関数drand48()を使用して2つの乱数r1、r2を取得し、2つの演算を通じて3つの座標を取得してから、正規直交基底w、u、vの直下にあるランダムベクトルを取得します。光を拡散し、再帰を続けます。
if (obj.refl == DIFF) {
    
                      // Ideal DIFFUSE reflection 
    double r1 = 2 * M_PI*erand48(Xi), r2 = erand48(Xi), r2s = sqrt(r2);
    Vec w = nl, u = ((fabs(w.x)>.1 ? Vec(0, 1) : Vec(1)).cross(w)).norm(), v = w.cross(u);  //w,v,u为正交基
    Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1 - r2)).norm();
    return obj.emission + f.mult(radiance(Ray(x, d), depth, Xi));
}
  1. 鏡面反射(材料はSPEC)
    は、鏡面反射の方向を計算して
    から再帰を続行します。拡散反射と鏡面反射はどちらも反射の法則に従うため、反射光の方向は反射の法則に従って計算され、再帰は継続します。
else if (obj.refl == SPEC)            // Ideal SPECULAR reflection 
    return obj.emission + f.mult(radiance(Ray(x, r.d - n * 2 * n.dot(r.d)), depth, Xi));
  1. 反射と屈折(材質はREFR)
    ガラス材質、一部の光は反射、一部の光は屈折。
    ここではルーレット方式を採用しています。
    まず、相対屈折率を計算します。屈折角の正弦は、式n1sinn1 = n2 sinn2によって計算できます。同時に、屈折の方向は、入射光の方向、法線方向、および屈折光を生成するための屈折角に従って計算できます。フレネルに従って近似式を計算して、フレネル反射と屈折の比率(Fr + Fe = 1)を計算し、再帰を続けることができます。
    光線reflRay(x、rd-n * 2 * n.dot(rd)); //理想的な誘電率屈折率反射光の方向は、平行四辺形法によって取得されます
bool into = n.dot(nl)>0;                // Ray from outside going in? 
double nc = 1, nt = 1.5, nnt = into ? nc / nt : nt / nc, ddn = r.d.dot(nl), cos2t;
if ((cos2t = 1 - nnt*nnt*(1 - ddn*ddn))<0)    // Total internal reflection 
    return obj.emission + f.mult(radiance(reflRay, depth, Xi));
Vec tdir = (r.d*nnt - n*((into ? 1 : -1)*(ddn*nnt + sqrt(cos2t)))).norm();
double a = nt - nc, b = nt + nc, R0 = a*a / (b*b), c = 1 - (into ? -ddn : tdir.dot(n));
double Re = R0 + (1 - R0)*c*c*c*c*c, Tr = 1 - Re, P = .25 + .5*Re, RP = Re / P, TP = Tr / (1 - P);
return obj.emission + f.mult(depth>2 ? (erand48(Xi)<P ?   // Russian roulette 
radiance(reflRay, depth, Xi)*RP : radiance(Ray(x, tdir), depth, Xi)*TP) :
radiance(reflRay, depth, Xi)*Re + radiance(Ray(x, tdir), depth, Xi)*Tr);

5.シーンの説明

0.5135は、カメラの視野角を設定します。つまり、値が大きいほど、視野角が大きくなり、視野円錐が太くなります。Sx、syはピクセルグリッドの4つの頂点です。このメソッドは、各ピクセルをトラバースし、ランダムサンプリングを使用して、放出される光の方向dを見つけます。(sx + .5 + dx)/ 2の値の範囲は[-0.25、0.75]です。この値は主に、ランダムサンプリング中にxをオフセットするために使用されます。もし彼を無視すれば。次に、(((sx + .5 + dx)/ 2 + x)/ w-.5)の値は、実際には[-0.5、0.5]です。

dに140を掛けてカメラの原点に追加する必要がある理由は、カメラの原点が「前面」の壁の外側にあるためです。追加されない場合、すべての光が放出されたときにこの壁に直接当たります。オン、直接壁の色に戻りました。

注:モデルが決定したパラメーター:

空間視点:(x_e、y_e、z_e)表示
距離:D = 140cos⁡θ(θ= 0.5135)
視線方向:eyedir(x_d、y_d、z_d)=(0、0.042612、-1);
視線の上:視線(X_u、y_u、z_u)=(1、0、0)とeyedirの外積の結果
画面の中心:opoint(x_o、y_o、z_o)= eyedir + D
eyedir;

6.座標系の説明:

カメラ座標系から画像座標系まで、透視投影関係に属し、3Dから2Dに変換されます。
スケール係数uを計算して、投影点の位置座標を計算します。
スケール係数は次のとおりです:u =(0.0-eyePos._z)/(A._z-eyePos._z);
注:この時点では、投影点pの単位はまだピクセルではなくmmであり、さらにピクセル座標系に変換する必要があります。

ピクセル座標系とイメージ座標系はどちらもイメージング平面上にありますが、それらの原点と測定単位は異なります。画像座標系の原点は、カメラの光軸と画像平面の交点です。これは通常、画像平面の中点です。画像座標系の単位はmmです。原点は画像の左上隅であり、ピクセル座標系の単位はピクセルです。通常、ピクセルは複数の行と複数の列で表されます。したがって、2つの間の変換は次のようになります。dxとdyは、各列と各行が表すmmの数を示します。つまり、1ピクセル=
正規化の場合はdx mm です。

Lw =(幅)/(140 * sin(0.5135));
Lh =(高さ)/(140 * sin(0.5135));

注:現在、実際には平面ではなく、eyePosポイントから球体140に描画されているため、一定の比率の調整が必要です。

Lw = Lw * 0.5135 / sin(0.5135); //弧の長さより弦の長さ
Lh = Lh * 0.5135 / sin(0.5135); //弧の長さより弦の長さ

x [i + 1] =(int)((((b._x-eyePos._x)* u + eyePos._x)* Lw + 0.5)+幅/ 2;
y [i + 1] =高さ/ 2-(int)(((b._y-eyePos._y)* u + eyePos._y)* Lh + 0.5);

ピクセル座標系の左上隅が(0,0)ポイントであり、画像座標系の画像の中心が原点であるため、変換にはwidth / 2およびheight / 2が必要です。

おすすめ

転載: blog.csdn.net/qq_43405938/article/details/104251127
おすすめ