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の値を見つけることです。
- P(t)= O + tDを円方程式に代入すると、tの1次元2次方程式が得られます。
- 最初にVec opを見つけます。opは、球の中心pから光線の開始点(O-C)を引いたものの座標です。
- b = op.dot(rd)は「D *(O-C)」を指します
- detを検索します。ここでは、原理のbと原理のbの違いに注意を払う必要があるため、
double det = b * b-op.dot(op)+ rad * rad を直接使用できます。det
<0の場合、解はありません。直接0を返します。
それ以外の場合は、ルート番号のdetを見つけます。 - 最終的な解は1つまたは2つあります。t= b-det、またはt = b + detの場合があります。0より大きいtと2つの小さいtを選択します。
2.描く
- 6つの大きな球を平面として使用します(DIFF属性、拡散反射のみ)。これは、半径が大きい場合、近くの距離を見ると、球は平面に似ているためです。
作成者は、これを実行して、平面交差、平面クラス、およびその他の関数を記述しないようにする必要があります。 - 光源を表すために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;
}
- この光線は発射し、すべての球の交点を見つけます。
- カメラに最も近い交点を見つけます。これは、後で画面に描画される主要な点です。
3.主な機能の説明
- カメラの位置は(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];
- 各ピクセルをトラバースし、ランダムサンプリングを使用して、放出される光の方向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を鈍角(球の外側を指す法線ベクトル)にします。
- 交差するかどうかを決定し、交点を見つけ、サーフェス法線を見つけます
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;
}
- 拡散反射(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));
}
- 鏡面反射(材料は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));
- 反射と屈折(材質は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が必要です。