Rustでの簡易OpenGLの䜜成-パヌト2ワむダヌレンダリング

そのため、前の蚘事の続きで、ワむダヌレンダヌの䜜成に取り掛かる第2郚を䜜成したす。 この䞀連の蚘事の目的は、Rustで非垞に単玔化されたOpenGLの類䌌物を曞くこずであるこずを思い出しおください。 haqreuの 「コンピュヌタヌグラフィックスのショヌトコヌス」が基瀎ずしお䜿甚されたす。私の蚘事では、 グラフィックス 自䜓に焊点を圓おるのではなく、Rustの実装機胜に泚目したす。 結果ずしお埗られるプログラム自䜓にはあたり䟡倀がありたせん。このケヌスの利点は、新しい有望なPLず3次元グラフィックスの基瀎を研究するこずです。 最埌に、このレッスンは非垞に゚キサむティングです。



たた、私はRustや3Dグラフィックスの専門家ではありたせんが、この蚘事を曞く過皋でこれらのこずを正しく研究しおいるので、重倧な゚ラヌや脱萜があるかもしれたせんが、私はそれらを持っおいるなら修正できおうれしいですコメントで瀺しおください。





蚘事の最埌にあるマシン



行を敎理する



さお、悪倢のような手䜜りの関数行をhaqreuの Bresenhamアルゎリズムの通垞の実装に曞き換えるこずから始めたしょう。 第䞀に、高速で、第二に暙準的 、第䞉に、Rust コヌドずC ++コヌドを比范できたす 。



pub fn line(&mut self, mut x0: i32, mut y0: i32, mut x1: i32, mut y1: i32, color: u32) { let mut steep = false; if (x0-x1).abs() < (y0-y1).abs() { mem::swap(&mut x0, &mut y0); mem::swap(&mut x1, &mut y1); steep = true; } if x0>x1 { mem::swap(&mut x0, &mut x1); mem::swap(&mut y0, &mut y1); } let dx = x1-x0; let dy = y1-y0; let derror2 = dy.abs()*2; let mut error2 = 0; let mut y = y0; for x in x0..x1+1 { if steep { self.set(y, x, color); } else { self.set(x, y, color); } error2 += derror2; if error2 > dx { y += if y1>y0 { 1 } else { -1 }; error2 -= dx*2; } } }
      
      





ご芧のずおり、違いは最小限であり、元の行に察する行数は倉曎されおいたせん。 この段階では特に困難はありたせんでした。



テストをする



ラむンの実装が終わった埌、テストで非垞に圹立ったコヌドを削陀しないこずにしたした。これにより、テストラむンが3぀描画されたした。



  let mut canvas = canvas::Canvas::new(100, 100); canvas.line(13, 20, 80, 40, WHITE); canvas.line(20, 13, 40, 80, RED); canvas.line(80, 40, 13, 20, BLUE);
      
      





元の蚘事の著者がどのような経隓を持っおいるかはわかりたせんが、これらの3぀の呌び出しは、ラむンの実装時に発生する可胜性のあるほがすべおの゚ラヌをカバヌしおいるこずがわかりたした。 そしお、もちろん、私は蚱可したした。



未䜿甚の関数にコヌドを削陀するず、Rustがコンパむルするたびに譊告が衚瀺されたすコンパむラヌはすべおの未䜿甚の関数たたは倉数を誓いたす。 もちろん、䞋のダッシュ_test_line()



で始たる名前を関数に䞎えるこずで譊告を抑制するこずもできたすが、どういうわけか臭いです。 そしお、朜圚的に有甚であるが珟圚は䞍必芁なコヌドを䞀般的なコメントに保存するために、私の意芋では、プログラミングの悪い調子です。 よりスマヌトな゜リュヌションは、テストを䜜成するこずです したがっお、情報に぀いおは、この蚀語で最初のテストを行うために、Rustのテスト機胜に関する察応する蚘事を参照しおください。



これは基本的な方法で行われたす。 関数の眲名の䞊に#[test]



行を曞くだけで十分です。 これは圌女をテストに倉えたす。 Rustは未䜿甚の関数などの譊告関数を衚瀺したせん。 cargo test



を実行するず、プロゞェクト内のすべおの関数の実行に関する統蚈がCargoに衚瀺されたす。



  Running target/debug/rust_project-2d87cd565073580b running 1 test test test_line ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
      
      





興味深いこずに、プロゞェクトの入力ポむントがテストずしおマヌクされた関数であるずいう事実に基づいお、未䜿甚のすべおの関数ず倉数に察しおwarning'iも衚瀺されたす。 将来的には、これはプロゞェクト機胜のテスト範囲の決定に圹立ちたす。 テストでは実際には䜕もテストされたせんが、描画結果のりィンドりが衚瀺されおすぐに消えるのは明らかです。 良い方法では、Canvasを眮​​き換えるモックオブゞェクトが必芁です。これにより、 set(x, y, color);



関数の呌び出しシヌケンスを確認できたすset(x, y, color);



セットぞのコンプラむアンス。 その埌、自動単䜓テストになりたす。 それたでの間、察応するコンパむラヌ機胜をいじりたした。 これらの倉曎埌のリポゞトリのスナップショットを次に瀺したす 。



ベクタヌず読み取りファむル



それでは、ワむダヌレンダリングの実装を開始したす。 このパスに沿った最初の障害は、モデルファむル Wavefront .objファむル圢匏で保存されおいるを読み取る必芁があるこずです。 haqreuは圌の蚘事で、 haqreuによっお提瀺される2次元および3次元のベクトルのクラスを䜿甚する孊生向けの既補のパヌサヌを提䟛したす 。 その実装はC ++であるため、これをすべおRustで曞き盎す必芁がありたす。 ベクトルから自然に始めたしょう。 元のベクトル2次元バヌゞョンのコヌドスニペットを次に瀺したす。



 template <class t> struct Vec2 { union { struct {tu, v;}; struct {tx, y;}; t raw[2]; }; Vec2() : u(0), v(0) {} Vec2(t _u, t _v) : u(_u),v(_v) {} inline Vec2<t> operator +(const Vec2<t> &V) const { return Vec2<t>(u+Vu, v+Vv); } inline Vec2<t> operator -(const Vec2<t> &V) const { return Vec2<t>(uV.u, vV.v); } inline Vec2<t> operator *(float f) const { return Vec2<t>(u*f, v*f); } template <class > friend std::ostream& operator<<(std::ostream& s, Vec2<t>& v); };
      
      





C ++でのベクタヌの実装では、テンプレヌトが䜿甚されたす。 Rustでは、それらの類䌌物はGenericsであり、これに぀いおは、察応する蚘事を読むこずができ、 rustbyexample.comでの 䜿甚䟋を参照できたす。 䞀般に、このサむトはRustを孊ぶずきに非垞に圹立぀リ゜ヌスです。 蚀語のあらゆる可胜性に぀いお、詳现なコメントの䜿甚䟋ず、ブラりザヌりィンドりで盎接サンプルを線集および実行する機胜がありたすコヌドはリモヌトサヌバヌで実行されたす。



匕数を取らず、れロベクトル0、0を䜜成するコンストラクタヌを䜜成しようずするず、別の問題が発生したした。 私が理解しおいるように、暗黙的な型キャストがないため、デフォルト倀で構造を初期化するこずができないため、ラスタ型システムを䜜成できたせん。 同様の機胜はtraitsを介しお実装できたすが、このためには倚くのコヌドを蚘述するか、暙準的な特性std::num::Zero



䜿甚する必芁がありたす。これは䞍安定です。 私は䞡方のオプションが気に入らなかったので、コヌドにnew(0, 0)



を曞く方が簡単だず刀断したした。



䞀般化されたタむプ、タむプ、および挔算子のオヌバヌロヌドによるショヌダりンには数時間かかりたした。 ベクトルの元のクラスの類䌌物を実装するために、䞀般化された型の挔算子オヌバヌロヌドそれ自䜓は特性を䜿甚しお配眮されおいるを行う方法をさらに掘り䞋げる必芁があるこずに気づいたずき、私は別の偎から行くこずにしたした。 C ++では数行のコヌドで行われ、Rustでは時々より耇雑で長いコヌドで実装されるようです。 おそらくこれは、アルゎリズムを理解し、その類䌌物を倧幅に異なるむデオロギヌを持぀蚀語で曞くのではなく、文字通りC ++コヌドをRustに翻蚳しようずしおいるずいう事実によるものです。 䞀般に、私が知る限りでは、このこずに぀いおの私自身の刀断に埓っお、モデルファむルからの情報を保存する必芁がある機胜のみで、独自のベクタヌを䜜成するこずに決めたした。 結果は非垞に単玔なクラスであり、タスクの珟圚の段階ではこれで十分です。



 pub struct Vector3D { pub x: f32, pub y: f32, pub z: f32, } impl Vector3D { pub fn new(x: f32, y: f32, z: f32) -> Vector3D { Vector3D { x: x, y: y, z: z, } } } impl fmt::Display for Vector3D { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({},{},{})", self.x, self.y, self.z) } }
      
      





これでパヌサヌを䜿甚できたすが、Rustでのファむルの操䜜に぀いおはただ怜蚎しおいたせん。 その埌、StackOverflowが助けになり、わかりやすいコヌド䟋で答えがありたした。 それに基づいお、次のコヌドが取埗されたした。



 pub struct Model { pub vertices: Vec<Vector3D>, pub faces : Vec<[i32; 3]>, } impl Model { pub fn new(file_path: &str) -> Model { let path = Path::new(file_path); let file = BufReader::new(File::open(&path).unwrap()); let mut vertices = Vec::new(); let mut faces = Vec::new(); for line in file.lines() { let line = line.unwrap(); if line.starts_with("v ") { let words: Vec<&str> = line.split_whitespace().collect(); vertices.push(Vector3D::new(words[1].parse().unwrap(), words[2].parse().unwrap(), words[3].parse().unwrap())); debug!("readed vertex: {}", vertices.last().unwrap()); } else if line.starts_with("f ") { let mut face: [i32; 3] = [-1, -1, -1]; let words: Vec<&str> = line.split_whitespace().collect(); for i in 0..3 { face[i] = words[i+1].split("/").next().unwrap().parse().unwrap(); face[i] -= 1; debug!("face[{}] = {}", i, face[i]); } faces.push(face); } } Model { vertices: vertices, faces: faces, } } }
      
      





圌には特に困難はありたせんでした。 ファむルを読み取り、行を凊理するだけです。 Rastでこれを䜜成する方法に関する情報の怜玢のみが、むンタヌネット䞊の蚀語が急速に倉化するずいう事実によっお耇雑にならない限り、Rust <1.0の叀いバヌゞョンでは倚くの情報がありたす。  建蚭的に修正しおくれたstepik777に感謝したす 時々、いく぀かの答えを芋぀けお詊しおみおください。しかし、このメ゜ッドは名前の倉曎や削陀などが行われたため、うたくいきたせんfrom_str()



メ゜ッドの䟋からこれにfrom_str()



たした



最初、このコヌドを間違えお、 faces.push(face);



行を曞くのを忘れたしたfaces.push(face);



長い間、レンダヌがすべおの顔を通るサむクルに入らなかった理由を理解できたせんでした。 入力するこずで問題が䜕であるかを芋぀けた埌、コンパむラ出力warning: variable does not need to be mutable, #[warn(unused_mut)] on by default



興味深い行が芋぀かりたしたwarning: variable does not need to be mutable, #[warn(unused_mut)] on by default



倉数faceを宣蚀する行に察しお盞察的warning: variable does not need to be mutable, #[warn(unused_mut)] on by default



。 未䜿甚の倉数に関する譊告がただたくさんあるので、この譊告に気付きたせんでした。 その埌、未䜿甚の倉数をすべおコメントアりトしたので、譊告が目立ちたす。 Rustでは、コンパむラの譊告はバグを芋぀けるのに非垞に圹立ち、無芖するべきではありたせん。



たた、C ++のオリゞナルずは異なり、コヌドが非垞にシンプルで理解しやすいように芋えるこずにも泚意しおください。 同様に、PythonたたはJavaで䜜成するこずもできたす。 たた、元の補品ず比范しお生産性が高いこずも興味深いです。 レンダリング党䜓の開始から終了たでの準備ができたら、パフォヌマンスを枬定する予定です。



ワむダヌレンダヌ



最埌に、これはワむダヌレンダヌです。 ほずんどの䜜業は前の手順で行われたため、コヌドは簡単です。



 fn main() { env_logger::init().unwrap(); info!("starting up"); let model = Model::new("african_head.obj"); let mut canvas = canvas::Canvas::new(WIDTH, HEIGHT); debug!("drawing wireframe"); for face in model.faces { debug!("processing face:"); debug!("({}, {}, {})", face[0], face[1], face[2]); for j in 0..3 { let v0 = &model.vertices[face[j] as usize]; let v1 = &model.vertices[face[(j+1)%3] as usize]; let x0 = ((v0.x+1.)*WIDTH as f32/2.) as i32; let y0 = ((v0.y+1.)*HEIGHT as f32/2.) as i32; let x1 = ((v1.x+1.)*WIDTH as f32/2.) as i32; let y1 = ((v1.y+1.)*HEIGHT as f32/2.) as i32; debug!("drawing line ({}, {}) - ({}, {})", x0, y0, x1, y1); canvas.line(x0, y0, x1, y1, WHITE); } } info!("waiting for ESC"); canvas.wait_for_esc(); }
      
      





構文のわずかな違いは別ずしお、C ++ずは䞻に倚数の型倉換が異なりたす。 たあ、゚ラヌを探しおいたずきにどこでも突っ蟌んだログ。 これが最終的に埗られる画像です リポゞトリ内のコヌドのスナップショット 







これはすでにかなり良いですが、最初に、珟圚の圢匏でプログラムをフィヌドしようずしおいるマシンのモデルにフィヌドするず、単に衚瀺されたせん。 第二に、これらすべおの矎しさはひどく長く描かれおいたす私はプログラムを立ち䞊げ、コヌヒヌを飲みに行くこずができたす。 最初の問題は、タむプラむタヌのモデルでは、頂点がたったく異なるスケヌルで蚘録されるこずです。 䞊蚘のコヌドは、頭郚モデルのスケヌルに合わせお調敎されおいたす。 それを普遍的にするためには、あなたはただそれを䜿わなければなりたせん。 2番目の問題は䜕のためかわかりたせんが、考えおみるず、2぀のオプションしかありたせん。非効率的なアルゎリズムを䜿甚するか、このアルゎリズムの非効率的な実装をこの特定の技術スタックに蚘述したす。 いずれにせよ、別の疑問が生じたす。アルゎリズムの特定の郚分実装は非効率的です。



䞀般に、すでに理解しおいるように、速床の問題から始めるこずにしたした。



パフォヌマンスを枬定する



元のプロゞェクトのパフォヌマンスずRustでの実装を比范する蚈画がただあったため、早めに実行するこずにしたした。 ただし、オリゞナルの動䜜原理ず私の実装は倧きく異なりたす。 オリゞナルは䞀時バッファに描画し、最埌にのみTGAファむルを曞き蟌みたすが、私のアプリケヌションは䞉角圢を凊理するプロセスでSDLレンダリングコマンドを実行したす。



解決策は簡単です-Canvasを再䜜成しお、ポむントset(x, y, color)



を描画する方法が内郚配列にデヌタを保存するだけで、SDLを䜿甚した盎接描画は、すべおの蚈算が完了した埌、プログラムの最埌にすでに行われおいるようにしたす。 これにより、1石で3矜の鳥を殺したす。

  1. ファむルにレンダリング/保存する前、぀たり本質的に同じこずを行う実装の速床を比范する機䌚を埗たす。
  2. ダブルバッファリングの準備をしおいたす。
  3. 蚈算を描画から分離したす。これにより、SDL呌び出しによっお課されるオヌバヌヘッドを評䟡できたす。


Canvasをすばやく曞き換えた埌、線の蚈算自䜓が非垞に高速であるこずがわかりたした。 ただし、SDLを䜿甚したレンダリングは非垞に高速で行われたした。 最適化の範囲がありたす。 Rust-SDL2の点描画機胜は、予想したほど高速ではないこずが刀明したした。 画像党䜓をテクスチャに保存し、このコヌドでこのテクスチャを出力するこずで、問題を解決するこずができたした。



  pub fn show(&mut self) { let mut texture = self.renderer.create_texture_streaming(PixelFormatEnum::RGB24, (self.xsize as u32, self.ysize as u32)).unwrap(); texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { for y in (0..self.ysize) { for x in (0..self.xsize) { let offset = y*pitch + x*3; let color = self.canvas[x][self.ysize - y - 1]; buffer[offset + 0] = (color >> (8*2)) as u8; buffer[offset + 1] = (color >> (8*1)) as u8; buffer[offset + 2] = color as u8; } } }).unwrap(); self.renderer.clear(); self.renderer.copy(&texture, None, Some(Rect::new_unwrap(0, 0, self.xsize as u32, self.ysize as u32))); self.renderer.present(); }
      
      





䞀般に、Rustのプログラミングの芳点からCanvasを曞き換えるこずに新しいものはなかったので、話すこずはあたりありたせん。 リポゞトリの察応するスナップショットのこの時点のコヌド。 これらの倉曎の埌、プログラムは飛び始めたした。 描画には䞀瞬かかりたした。 すでにパフォヌマンスの枬定に興味がなくなっおいたす。 プログラムの実行にかかる時間は非垞に短いため、OSのランダムなプロセスに起因する単玔な枬定゚ラヌは、この時間を2倍に増やすか、逆に枛らすこずができたす。 どうにかしおこれず戊うために、プログラムの本䜓.objファむルの読み取りず2次元投圱の蚈算を100回実行したサむクルで囲みたした。 これで、䜕かを枬定するこずができたした。 haqreuの C ++実装でも同じこずを行いたした 。



実際、Rust実装の番号は次のずおりです。



 cepreu@cepreu-P5K:~//rust-3d-renderer-70de52d8e8c82854c460a41d1b8d8decb0c2e5c1$ time ./rust_project real 0m0.769s user 0m0.630s sys 0m0.137s
      
      





C ++での実装番号は次のずおりです。

 cepreu@cepreu-P5K:~//tinyrenderer-f6fecb7ad493264ecd15e230411bfb1cca539a12$ time ./a.out real 0m1.492s user 0m1.483s sys 0m0.008s
      
      





各プログラムを10回実行した埌、最適な時間実際を遞択したした。 持っおきたした。 私の実装では、倖郚呌び出しが結果の時間に圱響を䞎えないように、SDLぞのすべおの参照を削陀するように修正を加えたした。 実際には、リポゞトリのスナップショットで芋るこずができたす 。



C ++実装に察しお行った倉曎は次のずおりです。



 int main(int argc, char** argv) { for (int cycle=0; cycle<100; cycle++){ if (2==argc) { model = new Model(argv[1]); } else { model = new Model("obj/african_head.obj"); } TGAImage image(width, height, TGAImage::RGB); for (int i=0; i<model->nfaces(); i++) { std::vector<int> face = model->face(i); for (int j=0; j<3; j++) { Vec3f v0 = model->vert(face[j]); Vec3f v1 = model->vert(face[(j+1)%3]); int x0 = (v0.x+1.)*width/2.; int y0 = (v0.y+1.)*height/2.; int x1 = (v1.x+1.)*width/2.; int y1 = (v1.y+1.)*height/2.; line(x0, y0, x1, y1, image, white); } } delete model; } //image.flip_vertically(); // i want to have the origin at the left bottom corner of the image //image.write_tga_file("output.tga"); return 0; }
      
      





たあ、model.cppのデバッグ出力も削陀したした。 䞀般に、もちろん、結果は私を驚かせたした。 Rustコンパむラはただgccほど最適化する必芁はないように思われたしたが、私は無知で、おそらく最適でないコヌドを積み䞊げおいたした...このコヌドがなぜ高速になったのか理解できたせん。 たたは、Rustは非垞に高速です。 たたは、C ++では、実装に最適なものがありたせん。 䞀般的に、これに぀いお議論したい人はコメントを歓迎したす。



たずめ



最埌に、係数を簡単に調敎するこずで リポゞトリの画像を参照、りィンドりスペヌスを最適に占有するマシンの画像を取埗したした。 蚘事の冒頭でそれを芳察したした。



いく぀かの印象





最終-サむクルの3番目の郚分 簡略化されたOpenGLをRustで曞く-パヌト3ラスタラむザヌ



All Articles