コンピュータグラフィックスの短期コース:簡略化されたDIY OpenGLの作成、記事3/6

コース内容





コード改善










公式の翻訳(少し磨き上げたもの)はこちらから入手できます。






それから何? 私はすべてのものを作りました!





記事7と8では、OpenGLで直接プログラミングする方法について説明します。 記事9+でOpenCL / CUDAの短いコースを取得する可能性はゼロではありません。



不可視の表面を削除する



私の友人である抽象的なアフリカ人の頭のz-bufferに会ってください。 これは、前回の記事で示した背面を破棄する視覚的なアーティファクトを削除するのに役立ちます。







ちなみに、私は尾とたてがみで使用するこのモデルが素晴らしいVidar Rappによって親切に提供されたことに言及せざるを得ません。



レンダリングトレーニングの一部としてのみ使用できます。 これは非常に高品質のモデルであり、私は野barに扱われましたが、彼女の目を戻すことを約束します!



理論的には、目に見えない顔を脇に投げることはできませんが、最後から最後まですべてを連続して描くことができます。



これは、アーティストのアルゴリズムと呼ばれます 。 残念ながら、カメラの位置を変更するたびにシーンを並べ替える必要があるため、非常に高価です。 また、動的なシーンもあります...しかし、これでさえ大きな問題ではありません。 問題は、これが常にできるとは限らないことです。






頭を描く前に、もっとシンプルに



カメラが下を向いている3つの三角形の最も単純なシーンを想像してみましょう。三角形を白い画面に投影します。







これは、このシーンのレンダーの外観です。







青い線-赤の後ろですか、それとも前ですか? これもそれも。 アーティストのアルゴリズムはここで分類されます。 さて、つまり、青い面を2つに分割できます。1つは赤の前に、もう1つは後ろにあります。 そして、赤いものの前にあるもの、さらに2つ-緑のものと緑のものの前に...数百万の三角形があるシーンでは、これがすぐに難しいタスクになることは十分に明らかだと思います。 はい、彼女は、たとえば、 スペースのバイナリパーティション使用するソリューションを持っていますが、同時にカメラの位置を変更するときに並べ替えるのに役立ちますが、私たちの生活を複雑にしないでください!






三次元が多すぎる。 Yバッファ!



ディメンションの1つを失いましょう。シーンと黄色の断面の交点によって得られる2次元のシーンを考えてみましょう。



つまり、シーンは3つのセグメント(黄色の平面と各三角形の交差点)で構成され、そのレンダリングは写真です

通常のレンダリングと同じ幅ですが、高さは1ピクセルです。





通常どおり、github コードのスナップショット 。 2次元のシーンがあるので、非常に簡単に描画できます。これらは、初めてプログラミングしたline()関数への3つの呼び出しです。



     {// 2Dシーンをダンプするだけです(十分な寸法があります!)
         TGAImageシーン(幅、高さ、TGAImage :: RGB);

         //シーン「2Dメッシュ」
        ライン(Vec2i(20、34)、Vec2i(744、400)、シーン、赤);
        ライン(Vec2i(120、434)、Vec2i(444、400)、シーン、緑);
        ライン(Vec2i(330、463)、Vec2i(594、200)、シーン、青);

         //スクリーンライン
        ライン(Vec2i(10、10)、Vec2i(790、10)、シーン、白);

         scene.flip_vertically();  //画像の左下隅に原点を置きたい
         scene.write_tga_file( "scene.tga");
     }




これが2次元シーンの外観です。これらのセグメントを上から見ることがタスクです。





レンダリングしましょう。 レンダーとは、シーン全体で幅が広く、高さが1ピクセルの画像であることを思い出してください。 私のコードでは、高さを16に宣言しましたが、これは、高解像度の画面で1ピクセルを見て、目をつぶらないようにするためです。 ラスタライズ関数は、レンダーイメージの最初の行にのみ書き込みます。



         TGAImageレンダリング(幅、16、TGAImage :: RGB);
         int ybuffer [幅];
         for(int i = 0; i <width; i ++){
             ybuffer [i] = std :: numeric_limits <int> :: min();
         }
        ラスタライズ(Vec2i(20、34)、Vec2i(744、400)、render、red、ybuffer);
        ラスタライズ(Vec2i(120、434)、Vec2i(444、400)、render、green、ybuffer);
        ラスタライズ(Vec2i(330、463)、Vec2i(594、200)、レンダリング、青、ybuffer);




そこで、画面のサイズ(幅、1)に合わせて、不可解なybuffer配列を宣言しました。 この配列は初期化されたマイナス無限大です。 次に、レンダリングイメージとこの不可解な配列の両方をラスタライズ関数に渡します。 関数自体はどのように見えますか?



 void rasterize(Vec2i p0、Vec2i p1、TGAImage&image、TGAColor color、int ybuffer []){
     if(p0.x> p1.x){
         std :: swap(p0、p1);
     }
     for(int x = p0.x; x <= p1.x; x ++){
         float t =(x-p0.x)/(float)(p1.x-p0.x);
         int y = p0.y *(1.-t)+ p1.y * t;
         if(ybuffer [x] <y){
             ybuffer [x] = y;
             image.set(x、0、color);
         }
     }
 }




非常に簡単です。p0.xとp1.xの間のすべてのx座標を調べて、対応するラインのy座標を計算します。

次に、このx座標でybuffer配列にあるものを確認します。 現在のピクセルがそこに保存されているものよりもカメラに近い場合、

それを絵に描いて、新しいy座標をゲームバッファーに入れます。



ステップごとに理解しましょう:最初の(赤)行のラスタライザーを呼び出した後、これは私たちが念頭に置いているものです:



画面の内容:





yバッファの内容:



ここで、非常に紫色の色はマイナス無限大を示し、これらは単一のピクセルがまだ描画されていない場所です。

それ以外はすべてグレースケールです ybufferは色ではなく、指定されたピクセルの深さです。 白く、カメラに近いほど、このピクセルが画面に描画されました。



次に、緑色の線を描画します。これは、ラスタライザーを呼び出した後のメモリです。



画面の内容:







yバッファの内容:







そして最後に、青いもの:



画面の内容:







yバッファの内容:







おめでとう、2次元のシーンを描きました! もう一度、最終的なレンダリングを賞賛します。












三次元-これはちょうどいいです。 Zバッファ!



github のコードのスナップショット


注:この記事では、前のものと同じバージョンの三角形ラスタライザーを使用します。 ラスタライザーの改良版(説明する長方形のすべてのピクセルの通過)は、間もなく親切に提供され、尊敬されるgbgによって別の記事で説明されます ! お楽しみに。






画面が2次元になったため、zバッファーも2次元になります。

 int * zbuffer = new int [width * height];


2次元配列を1次元にパックしました。通常どおり変換できます。

2つの座標から1つの座標まで:

 int idx = x + y * width;




その逆:

 int x = idx%width;
 int y = idx / width;




次に、コードですべての三角形を調べて、ラスタライザーを呼び出して、画像とzバッファーの両方を渡します。



三角形(screen_coords [0]、screen_coords [1]、screen_coords [2]、image、TGAColor(強度* 255、強度* 255、強度* 255、255)、zbuffer);  [...] void triangle(Vec3i t0、Vec3i t1、Vec3i t2、TGAImage&image、TGAColor color、int * zbuffer){if(t0.y == t1.y && t0.y == t2.y)return;  //縮退三角形を気にしませんif(t0.y> t1.y)std :: swap(t0、t1);  if(t0.y> t2.y)std :: swap(t0、t2);  if(t1.y> t2.y)std :: swap(t1、t2);  int total_height = t2.y-t0.y;  for(int i = 0; i <total_height; i ++){bool second_half = i> t1.y-t0.y ||  t1.y == t0.y;  int segment_height = second_half?  t2.y-t1.y:t1.y-t0.y;  float alpha =(float)i / total_height;  float beta =(float)(i-(second_half?t1.y-t0.y:0))/ segment_height;  //注意してください:上記の条件では、ここではゼロによる除算はありませんVec3i A = t0 + Vec3f(t2-t0)* alpha;  Vec3i B = second_half?  t1 + Vec3f(t2-t1)*ベータ:t0 + Vec3f(t1-t0)*ベータ。  if(Ax> Bx)std :: swap(A、B);  for(int j = Ax; j <= Bx; j ++){float phi = Bx == Ax?  1 .:(フロート)(jA.x)/(フロート)(Bx-Ax);  Vec3i P = Vec3f(A)+ Vec3f(BA)* phi;  int idx = P.x + Py *幅;  if(zbuffer [idx] <Pz){zbuffer [idx] = Pz;  image.set(Px、Py、色);  }}}} 


前の記事のコードがどれだけラスタライザのように見えるかはひどいです。 何が変わった? (vimdiffを使用して見てください)。

関数呼び出しでVec2はVec3に置き換えられ、(zbuffer [idx] <Pz)がチェックされた場合。

それだけです! これは、目に見えないサーフェスを切り取る傷のない実際のレンダリングです。







私のコードのバックフェースカリングは残っていることに注意してください。

 if(強度> 0){
    三角形(screen_coords [0]、screen_coords [1]、screen_coords [2]、image、TGAColor(強度* 255、強度* 255、強度* 255、255)、zbuffer);
 }


この図を取得する必要はありません。計算を加速するだけです。








やめて、z座標を補間しました。 ロードに何か他のものを追加できますか?



テクスチャー! 宿題になります。



.objファイルにはvt uv行があり、テクスチャ座標の配列を指定します。

fx / x / xx / x / xx / x / xのスラッシュ間の平均数は、この三角形のこの頂点のテクスチャ座標です。 それらを三角形内で補間し、テクスチャファイルの幅と高さを掛けて、テクスチャファイルからピクセルカラーを取得します。

ここで拡散テクスチャを取ります



発生する可能性のある例を次に示します。





更新:



宿題のソリューションはこちらから入手できます。



All Articles