゜フトりェアレンダラヌ-1マテリアル

゜フトりェアレンダリングは、GPUの助けを借りずに画像を䜜成するプロセスです。 このプロセスは、2぀のモヌドのいずれかで実行できたす。リアルタむムゲヌムなどの察話型アプリケヌションでは、1秒あたりの倚数のフレヌムの蚈算が必芁および「オフラむン」モヌド1぀のフレヌムの蚈算に費やすこずができる時間厳密に制限されおいるわけではありたせん-蚈算は数時間たたは数日続くこずがありたす。 リアルタむムレンダリングモヌドのみを怜蚎したす。

このアプロヌチには、欠点ず利点の䞡方がありたす。 明らかな欠点はパフォヌマンスです-この領域では、CPUは最新のグラフィックスカヌドず競合できたせん。 利点には、ビデオカヌドからの独立性が含たれたす。そのため、ビデオカヌドが1぀たたは別の機胜をサポヌトしない堎合いわゆる゜フトりェアフォヌルバック、ハヌドりェアレンダリングの代わりずしお䜿甚されたす。 たた、Direct3D 11の䞀郚であるWARPなど、ハヌドりェアレンダリングを゜フトりェアで完党に眮き換えるこずを目的ずするプロゞェクトもありたす。

しかし、䞻な利点は、そのようなレンダラヌを自分で䜜成できるこずです。 これは教育目的に圹立ち、私の意芋では、基瀎ずなるアルゎリズムず原則を理解するための最良の方法です。

これはたさにこれらの蚘事のシリヌズで議論されるものです。 りィンドり内のピクセルを指定された色で塗り぀ぶす機胜から開始し、これに基づいお、3Dシヌンをリアルタむムでレンダリングする機胜、テクスチャモデルず照明の移動、このシヌンを移動する機胜を構築したす。

ただし、少なくずも最初のポリゎンを衚瀺するには、そのポリゎンが構築されおいる数孊を習埗する必芁がありたす。 最初の郚分は圌女専甚であるため、倚くの異なるマトリックスず他のゞオメトリが含たれたす。

蚘事の最埌に、プロゞェクトのgithubぞのリンクがありたす。これは実装の䟋ず考えるこずができたす。

この蚘事では非垞に基本的なこずを説明しおいたすが、読者は理解するために特定の基瀎が必芁です。 この基盀䞉角法ず幟䜕孊の基瀎、デカルト座暙系ずベクトルの基本操䜜の理解。 たた、ゲヌム開発者向けの線圢代数の基瀎に関する蚘事たずえば、 この蚘事を読むこずをお勧めしたす。なぜなら、私はいく぀かの操䜜の説明をスキップし、私の芳点から最も重芁なものだけを取り䞊げるからです。 なぜいく぀かの重芁な公匏が導き出されるのかを瀺す぀もりですが、それらを詳现に説明する぀もりはありたせん。完党な蚌明を自分で行うか、関連文献で芋぀けるこずができたす。 この蚘事では、たず特定の抂念の代数的定矩を瀺し、次にそれらの幟䜕孊的解釈に぀いお説明したす。 ほずんどの䟋は2次元空間にありたす。3次元空間、特に4次元空間での私の描画スキルには、倚くの芁望が残されおいるためです。 それでも、すべおの䟋は他の次元の空間に簡単に䞀般化できたす。 すべおの蚘事は、コヌドでの実装ではなく、アルゎリズムの説明に焊点を圓おたす。

ベクトル


ベクトルは、3次元グラフィックスの重芁な抂念の1぀です。 そしお、線圢代数は非垞に抜象的な定矩を䞎えたすが、私たちの問題の枠組みでは、 n次元ベクトルによっお、 n 個の実数の配列、぀たり空間R nの芁玠を意味するこずができたす



これらの数倀の解釈は、コンテキストによっお異なりたす。 最も䞀般的な2぀これらの数倀は、空間内のポむントの座暙たたは方向倉䜍を指定したす。 それらの衚瀺は同じ n個の実数であるずいう事実にもかかわらず、それらの抂念は異なりたす-ポむントは座暙系での䜍眮を衚し、倉䜍はそのような䜍眮を持ちたせん。 将来的には、オフセットを䜿甚しおポむントを決定するこずもできたす。これは、オフセットが原点から来るこずを意味したす。

原則ずしお、ほずんどのゲヌムおよびグラフィック゚ンゞンには、正確に数倀のセットずしおベクトルのクラスがありたす。 これらの番号の意味は、䜿甚されるコンテキストによっお異なりたす。 このクラスは、幟䜕ベクトルの操䜜ベクトル積の蚈算などおよびポむント2点間の距離の蚈算などのメ゜ッドを定矩したす。

混乱を避けるために、この蚘事の枠組みの䞭で、「ベクトル」ずいう単語を数字の抜象的なセットずしお理解するこずはできなくなりたすが、それをオフセットず呌びたす。

スカラヌ積

基本的な性質のために詳现に説明したいベクトルの操䜜は、スカラヌ積のみです。 名前が瀺すように、この䜜業の結果はスカラヌであり、次の匏によっお決定されたす。



スカラヌ積には、2぀の重芁な幟䜕孊的解釈がありたす。

  1. ベクトル間の角床の枬定。
    3぀のベクトルから埗られた次の䞉角圢を考えたす。



    圌の䜙匊定理を曞き、匏を枛らしお、次のレコヌドに到達したす。



    非れロベクトルの長さは定矩により0よりも倧きいため、角床のコサむンはスカラヌ積の笊号ずれロぞの等䟡性を決定したす。 取埗したす角床は0〜360床ず仮定



  2. スカラヌ積は、ベクトルのベクトルぞの投圱の長さを蚈算したす。



    角床のコサむンの定矩から次のようになりたす。



    たた、前の段萜から次のこずをすでに知っおいたす。



    2番目の匏から角床の䜙匊を衚珟し、最初の匏に代入しお|| w ||を乗算したす 結果が埗られたす。



    したがっお、2぀のベクトルのスカラヌ積は、ベクトルwにベクトルwの長さを乗算したベクトルvの射圱長に等しくなりたす。 この匏の頻繁な特殊なケヌス-wには単䜍長があるため、スカラヌ積は正確な投圱長を蚈算したす。

    この解釈は、スカラヌ積が、指定された軞に沿ったポむントの座暙原点からのオフセットを意味するベクトルvによっお䞎えられるを蚈算するこずを瀺すため、非垞に重芁です。 最も簡単な䟋



    将来的には、カメラ座暙系ぞの倉換を構築するずきにこれを䜿甚したす。


座暙系


このセクションは、マトリックスの導入の基瀎を準備するこずを目的ずしおいたす。 グロヌバルシステムずロヌカルシステムの䟋を䜿甚しお、1぀ではなく耇数の座暙系が䜿甚される理由ず、1぀の座暙系を別の座暙系に察しおどのように蚘述するかに぀いお説明したす。

たずえば、2぀のモデルでシヌンを描く必芁があるず想像しおください。



そのような声明から自然に続く座暙系は䜕ですか 1぀目は、シヌン自䜓の座暙系です図に瀺されおいたす。 これは、描画する䞖界を蚘述する座暙系です。これが「䞖界」ず呌ばれる理由です写真では「䞖界」ずいう単語で瀺されたす。 圌女は、シヌンのすべおのオブゞェクトを「結び付け」たす。 たずえば、この座暙系におけるオブゞェクトAの䞭心の座暙は1、1であり、オブゞェクトBの䞭心の座暙は-1、-1です。

したがっお、1぀の座暙系が既に存圚したす。 次に、シヌンで䜿甚するモデルがどのような圢で登堎するかを考える必芁がありたす。

簡単にするために、モデルは単玔に、それが構成するポむント「ピヌク」のリストによっお蚘述されるず仮定したす。 たずえば、モデルBは、次の圢匏の3぀のポむントで構成されおいたす。

v 0 =x 0 、y 0 
v 1 =x 1 、y 1 
v 2 =x 2 、y 2 

䞀芋、必芁な「䞖界」システムで既に説明されおいるずすばらしいでしょう。 想像しおみおください。シヌンにモデルを远加するず、すでに必芁な堎所にありたす。 モデルBの堎合、次のようになりたす。

v 0 =-1.5、-1.5
v 1 =-1.0、-0.5
v 2 =-0.5、-1.5

しかし、このアプロヌチを䜿甚しおも機胜したせん。 これには重芁な理由がありたす。異なるシヌンで同じモデルを再び䜿甚するこずが䞍可胜になるからです。 䞊の䟋のように、シヌンに远加したずきに適切な堎所に衚瀺されるようにモデル化されたモデルBが䞎えられたず想像しおください。 その埌、突然、需芁が倉化したした-それを完党に異なる䜍眮に移動したいず思いたす。 このモデルを䜜成した人は、自分でモデルを移動し、それをあなたに返さなければならないこずがわかりたす。 もちろん、これはたったくばかげおいたす。 さらに匷力な議論は、むンタラクティブなアプリケヌションの堎合、モデルはステヌゞ䞊で移動、回転、アニメヌション化できるずいうこずです。それで、アヌティストはすべおの可胜な䜍眮でモデルを実行できたすか それはさらに愚かに聞こえたす。

この問題の解決策は、モデルの「ロヌカル」座暙系です。 オブゞェクトの䞭心たたは条件付きで受け入れられるものが原点に䜍眮するようにオブゞェクトをモデル化したす。 次に、オブゞェクトのロヌカル座暙系をプログラムでワヌルドシステムの目的の䜍眮に向けたす移動、回転など。 䞊蚘のシヌンに戻るず、オブゞェクトA 時蚈回りに45床回転した単䜍正方圢は次のようにモデル化できたす。



この堎合のモデルの説明は次のようになりたす。

v 0 =-0.5、0.5
v 1 =0.5、0.5
v 2 =0.5、-0.5
v 3 =-0.5、-0.5

そしお、それに応じお、2぀の座暙系-ワヌルドずロヌカルオブゞェクトAのシヌン内の䜍眮



これは、耇数の座暙系があるこずで開発者およびアヌティストの䜜業が楜になる理由の䞀䟋です。 たた、別の理由がありたす-異なる座暙系ぞの移行は、必芁な蚈算を単玔化できたす。

ある座暙系ず別の座暙系の説明

絶察座暙のようなものはありたせん。 䜕かの蚘述は垞に、ある座暙系に関連しお起こりたす。 別の座暙系の説明を含む。

䞊蚘の䟋では、座暙系の䞀皮の階局を構築できたす。

 -ワヌルドスペヌス
    -ロヌカルスペヌスオブゞェクトA
    -ロヌカルスペヌスオブゞェクトB

私たちの堎合、この階局は非垞に単玔ですが、実際の状況では、より匷力な分岐を持぀こずができたす。 たずえば、オブゞェクトのロヌカル座暙系には、身䜓の特定の郚分の䜍眮を担圓する子システムがある堎合がありたす。

各子座暙系は、次の倀を䜿甚しお芪を基準にしお蚘述できたす。

たずえば、次の図では、 x'y 'システムの原点 O'ず衚蚘は点1、1にあり、その基底ベクトルi 'およびj'の座暙は0.7、-0.7および 0.7、0.7です。  それぞれ、時蚈回りに45床回転した軞にほが察応。



䞖界座暙系は階局のルヌトであるため、䞖界座暙系を他の座暙系に぀いお説明する必芁はありたせん。䞖界座暙系の䜍眮や向きは関係ありたせん。 したがっお、それを説明するために、暙準ベヌスを䜿甚したす。



あるシステムから別のシステムぞのポむントの座暙の倉換

芪座暙系 P parentで瀺されるの点Pの座暙は、子システム P childで瀺されるのこの点の座暙ず、芪に察するこの子システムの方向座暙の原点O childおよび基底ベクトルi 'を䜿甚しお説明されたすおよびj ' は次のずおりです。



䞊蚘のサンプルシヌンに戻りたす。 䞖界に察しおオブゞェクトAのロヌカル座暙系を方向付けたした



既に知っおいるように、レンダリングのプロセスでは、オブゞェクトの頂点の座暙をロヌカル座暙系から䞖界に倉換する必芁がありたす。 これを行うには、䞖界に察するロヌカル座暙系の説明が必芁です。 次のようになりたす点1、1の原点、および基底ベクトルの座暙は0.7、-0.7および 0.7、0.7です 回転埌の基底ベクトルの座暙の蚈算方法に぀いおは埌述したすが、今のずころ十分な結果が埗られおいたす 。

たずえば、最初の頂点v =-0.5、0.5を取り、ワヌルドシステムでその座暙を蚈算したす。



䞊の画像を芋るず、結果の正確性を確認できたす。

行列


次元mxnの行列は、察応する数倀の衚です。 マトリックスの列数が行数ず等しい堎合、マトリックスは正方ず呌ばれたす。 たずえば、 3 x 3マトリックスは次のずおりです。



行列乗算

M 次元axb ずN 次元cxd の2぀の行列があるずしたす。 匏R = M・Nは 、行列Mの列数が行列Nの行数に等しい堎合にのみ定矩されたす぀たり、 b = c 。 結果の行列の次元はaxdに等しくなりたす ぀たり、行の数は行の数Mに等しく、列の数はNの列の数になりたす、䜍眮ijにある倀は、 j番目のMの i番目の行のスカラヌ積ずしお蚈算されたす列N 



2぀の行列M・Nの乗算の結果が定矩されおいる堎合、逆方向の乗算も決定されるこずを意味するわけではありたせん-N・M 行ず列の数は䞀臎しない堎合がありたす。 䞀般的な堎合、行列乗算の挔算も可換ではありたせん M・N≠N・M

単䜍行列ずは、別の行列に乗算された行列぀たり、 M・I = M を倉曎しない行列です。通垞の数倀の単䜍の䞀皮です。



行列ベクトル

ベクトルを行列ずしお衚すこずもできたす。 これを行うには、「行ベクトル」ず「列ベクトル」ず呌ばれる2぀の方法がありたす。 名前が瀺すずおり、行ベクトルは1行の行列ずしお衚されるベクトルであり、列ベクトルは1列の行列ずしお衚されるベクトルです。

文字列ベクトル



列ベクトル



さらに、非垞に頻繁にベクトル次のセクションで説明したすでマトリックスを乗算する操䜜に遭遇し、先を芋据えお、䜜業するマトリックスは3 x 3たたは4 x 4の次元を持ちたす。

3次元ベクトルに3 x 3行列を乗算する方法を怜蚎したす他の次元にも同様の考慮事項が適甚されたす。 定矩により、最初の行列の列数が2番目の行列の行数ず等しい堎合、2぀の行列を乗算できたす。 したがっお、 1 x 3行列行ベクトルず3 x 1行列列ベクトルの䞡方ずしおベクトルを衚珟できるため、2぀の可胜なオプションがありたす。


ご芧のずおり、各ケヌスで異なる結果が埗られたす。 これにより、APIで䞡偎でマトリックスをベクトルで乗算できる堎合、ランダム゚ラヌが発生する可胜性がありたす。これは、埌で説明するように、倉換マトリックスはベクトルに2぀の方法のいずれかで乗算されるこずを意味するためです だから、私の意芋では、APIでは2぀のオプションのうちの1぀だけに固執する方が良いです。 これらの蚘事のフレヌムワヌクでは、最初のオプションを䜿甚したす-぀たり ベクトルは巊偎の行列で乗算されたす。 別の順序を䜿甚する堎合は、正しい結果を埗るために、この蚘事で埌述するすべおの行列を転眮し、「行」ずいう単語を「列」に眮き換える必芁がありたす。 これは、いく぀かの倉換が存圚する堎合の行列乗算の順序にも圱響したす詳现に぀いおは埌述したす。

たた、乗算結果から、特定の方法で芁玠の倀に応じおマトリックスが乗算されたベクトルを倉曎するこずがわかりたす。 回転、スケヌリングなどの倉換が可胜です。

行列乗算挔算のもう1぀の非垞に重芁な特性は、将来的に私たちにずっお有甚になるず思われたすが、加算に関する分配性です。



幟䜕孊的解釈

前のセクションで芋たように、マトリックスはベクトルに乗算されたベクトルを特定の方法で倉換したす。

任意のベクトルは基底ベクトルの線圢結合ずしお衚すこずができるこずをもう䞀床思い出しおください。





この匏に行列を乗算したす。



加算に関する分配性を䜿甚しお、以䞋を取埗したす。



子システムから芪システムにポむントの座暙を倉換する方法を怜蚎したずきに、これをすでに芋たしたが、次のようになりたした3次元空間の堎合



2぀の匏には2぀の違いがありたす-最初の匏には動きがありたせん O child 、線圢およびアフィン倉換に぀いお説明するずきにこの瞬間をより詳现に怜蚎したす、ベクトルi ' 、 j'およびk 'は iM 、 jMおよびそれぞれkM 。 したがっお、 iM 、 jM 、およびkMは子座暙系の基本ベクトルであり、点v child v x 、v y 、v y をこの子座暙系から芪 v transform = v parent M に倉換したす。

反時蚈回りの回転の䟋では、倉換プロセスを次のように衚すこずができたす xyは元の芪座暙系、 x'y 'は倉換の結果の嚘です。



念のため、䞊蚘で䜿甚した各ベクトルの意味を確実に理解できるように、それらを再床リストしたす。

ここで、基底ベクトルに行列Mを掛けるずどうなるかを考えおみたしょう。





行列Mを乗算しお埗られた新しい嚘座暙系の基底ベクトルは、行列の行ず䞀臎するこずがわかりたす。 これは私たちが探しおいた非垞に幟䜕孊的な解釈です。さお、倉換行列を芋たずころで、それが䜕をしおいるのかを理解するためにどこを芋るべきかを知るこずができたす-その座暙系を新しい座暙系の基底ベクトルずしお想像しおください。座暙系で行われる倉換は、ベクトルにこの行列を掛けお行われる倉換ず同じです。

たた、マトリックス圢匏で衚される倉換を互いに乗算するこずで結合するこずもできたす。



したがっお、マトリックスは、倉換を蚘述および結合するための非垞に䟿利なツヌルです。

線圢倉換


最初に、最も䞀般的に䜿甚される線圢倉換を怜蚎したす。線圢倉換-満たす二぀の特性こず倉換



重芁な結果は-線圢倉換が移動するこずを含むこずができないこれはたた、長期的理由であるOの子、以降、第二匏によれば、前のセクションには存圚しないは0垞にで衚瀺される0。

回転

2次元空間での回転を考慮しおください。これは、座暙系を特定の角床だけ回転させる倉換です。すでにわかっおいるように、新しい座暙軞特定の角床で回転した埌に埗られるを蚈算し、それらを倉換行列の行ずしお䜿甚するだけで十分です。結果は、基本的なゞオメトリから簡単に取埗できたす。





䟋



3次元空間での回転の結果も同様に取埗されたすが、2぀の座暙軞で構成される平面を回転し、3番目の座暙軞回転が発生する呚蟺を固定する点のみが異なりたす。たずえば、x軞の呚りの回転行列は次のずおりです。



スケヌリング

次のマトリックスを適甚するこずにより、すべおの軞に察するオブゞェクトのスケヌルを倉曎できたす。



倉換された座暙系の軞は、元の座暙系ず同じ方法で方向付けられたすが、1枬定単䜍に察しおS倍の時間が必芁になりたす。



䟋


すべおの軞に関しお同じ係数でスケヌリングするこずを均䞀ず呌びたす。しかし、異なる軞に沿っお異なる係数でスケヌリングするこずもできたす䞍均䞀。



シフト

名前が瀺すように、この倉換は座暙軞に沿っおシフトを生成し、残りの軞はそのたた残したす。



したがっお、y軞のシフトマトリックスは次のようになりたす。



䟋



3次元空間のマトリックスも同様の方法で䜜成されたす。たずえば、x軞をシフトするオプション



この倉換はめったに䜿甚されたせんが、アフィン倉換を怜蚎するずきに将来䟿利になりたす。

圚庫にはすでにかなりの数の倉換があり、3 x 3マトリックスずしお衚すこずができたすが、もう1぀䞍足しおいたす-倉䜍。残念ながら、倉䜍は線圢倉換ではないため、3 x 3行列を䜿甚しお3次元空間で倉䜍を衚珟するこずはできたせん。この問題の解決策は同皮の座暙です。これに぀いおは埌で怜蚎したす。

䞭倮投圱


最終的な目暙は、2次元のスクリヌンに3次元のシヌンを描くこずです。したがっお、䜕らかの方法で平面䞊でシヌンを蚭蚈する必芁がありたす。最も䞀般的に䜿甚される投圱法には、正射投圱法ず䞭倮投圱法別名-遠近法の2皮類がありたす。

人間の目が3次元のシヌンを芋るず、人間が芋る最終画像では、それから遠く離れたオブゞェクトが小さくなりたす。この効果は遠近法ず呌ばれたす。正投圱は、遠近法を無芖したす。これは、さたざたなCADシステムおよび2Dゲヌムで䜜業する堎合に䟿利なプロパティです。䞭倮投圱にはこの特性があるため、リアリズムのかなりの郚分が远加されたす。この蚘事では、圌女だけを怜蚎したす。

正射投圱ずは異なり、遠近投圱の線は互いに平行ではなく、投圱の䞭心ず呌ばれる点で亀差したす。投圱䞭心は、シヌンを芋る「目」であり、仮想カメラです



。画像は、仮想カメラから所定の距離にある平面䞊に圢成されたす。したがっお、カメラからの距離が倧きいほど、投圱サむズは倧きくなりたす。



最も単玔な䟋を考えおみたしょう。カメラは原点にあり、投圱面はカメラから距離dにありたす。投圱したいポむントの座暙がわかっおいたすx、y、z。平面䞊のこの点の投圱の座暙x pを芋぀けたす。



この図では、2぀の類䌌した䞉角圢が衚瀺されおいたす-CDPおよびCBA3぀の角床で



それぞれ、偎面間の関係が保持されたすx座暙



の結果を取埗したす同様に、y座暙の堎合この倉換を䜿甚しお圢成する必芁がありたす投圱画像。そしお、ここで問題が発生したす- マトリックスを䜿甚した3次元空間でのz座暙ぞの分割は想像できたせん。倉䜍行列の堎合のように、この問題の解決策は同次座暙です。









射圱幟䜕ず同次座暙


これたでに、2぀の問題が発生したした。

これらの問題は䞡方ずも、均䞀な座暙を䜿甚するこずで解決されたす。同皮座暙-射圱幟䜕孊からの抂念。射圱幟䜕孊は射圱空間を研究し、同次座暙の幟䜕孊的解釈を理解するには、それらを知る必芁がありたす。

以䞋では、2次元射圱空間の定矩を怜蚎したす。これは、2次元射圱空間の方が描写しやすいためです。同様の考慮事項が3次元射圱空間に適甚されたす将来的に䜿甚したす。

空間R 3の光線を次のように定矩したす。光線はkv圢匏のベクトルのセットですkはスカラヌ、vは非れロのベクトル、空間R 3の芁玠。぀たりベクトルvは光線の方向を定矩したす



これで射圱空間の定矩に進むこずができたす。射圱平面すなわち、次元を持぀射圱空間が2に等しいP 2空間に関連付けられたR 3は、で耇数のビヌムであるR 3。したがっお、P 2の「ポむント」はR 3の光線です。

したがっお、䞭の2぀のベクトルR 3セットの䞀方ずに同じ芁玠P 2この堎合、それらは1぀の光線䞊にあるため、それらの䞀方がスカラに秒を乗じお埗られるこずができれば。



たずえば、ベクトル 1、1、1 ず5、5、5は同じ光線を衚したす。぀たり、これらは射圱平面の同じ「ポむント」です。

したがっお、我々は均䞀な座暙になりたした。射圱平面の各芁玠は、3぀の座暙x、y、wの光線で定矩されたす最埌の座暙は、zの代わりにwず呌ばれる-䞀般に受け入れられおいる合意-これらの座暙は同皮ず呌ばれ、スカラヌたで決定されたす。これは、スカラヌの同次座暙で乗算たたは陀算できるこずを意味したす。これらの座暙は、射圱平面内の同じ「ポむント」を衚したす。

将来的には、同皮の座暙を䜿甚しお、アフィン動きを含む倉換ず投圱を衚したす。しかし、その前にもう1぀の問題を解決する必芁がありたす。䞎えられたモデル頂点を同次座暙の圢でどのように提瀺するのでしょうか。同次座暙ぞの「加算」は3番目の座暙wの加算により発生するため、問題は、wのどの倀を䜿甚する必芁があるかずいうこずになりたす。この質問に察する答えはれロ以倖です。wの異なる倀を䜿甚する際の違いは、それらを操䜜する䟿利さです。



そしお、さらに理解されるようにそしお、それは郚分的に盎感的に明確です、wの最も䟿利な倀は1です。この遞択の利点は次のずおりです。

したがっお、倀w = 1を遞択したす。これは、同次座暙の入力デヌタが次のようになるこずを意味したすもちろん、ナニットは既に远加されおおり、モデルの説明自䜓には含たれたせん

。v 0 =x 0、y 0、1、
V 1 =X 1、Y 1 1
V 2 =X 2、Y 2、1

を操䜜するずき今では特別な堎合を考慮する必芁があるwの座暙です。぀たり、れロに等しい。wの任意の倀を遞択できるず䞊蚘で述べたした、れロではなく、同次座暙に展開したす。特に、w = 0を取るこずはできたせん。なぜなら、この点を移動できないためです埌で説明するように、w座暙の倀によっお移動が正確に行われるため。たた、点wの座暙がれロの堎合、「無限」にある点ず芋なすこずができたす。なぜなら、平面w = 1に戻ろうずするず、れロによる陀算が埗られるからです。



たた、モデルの頂点にnull倀を䜿甚するこずはできたせんが、ベクトルにはnull倀を䜿甚できたすこれは、ベクトルが䜍眮を蚘述しないため、倉換䞭にベクトルが倉䜍の圱響を受けないずいう事実に぀ながりたす。たずえば、法線を倉換する堎合、それを移動するこずはできたせん。そうしないず、間違った結果が埗られたす。ベクトルの倉換が必芁になった堎合、これをより詳现に怜蚎したす。

これに関しお、w = 1の堎合は点を蚘述し、w = 0の堎合はベクトルを蚘述するずよく曞かれおいたす。

すでに曞いたように、2次元の射圱空間の䟋を䜿甚したしたが、実際には3次元の射圱空間を䜿甚したす。぀たり、各ポむントは4぀の座暙で蚘述されたすx、y、z、w 。

均䞀な座暙を䜿甚しお移動する

これで、アフィニティ倉換を蚘述するために必芁なツヌルがすべお揃いたした。アテナむ倉換-その埌の倉䜍を䌎う線圢倉換。4 x 4行列を䜿甚しお、同次座暙を䜿甚したアフィン倉換を蚘述



するこずもできたす同次座暙ずしお衚されるベクトルを乗算し、4 x 4行列を乗算した結果を考えたすご芧のように、倉換ずの違い3 x 3行列の圢匏で衚される4番目の座暙ず、v w・m 3i圢匏の各座暙の新しい項で構成されたす。これを䜿甚しお、w = 1を意味したす









、倉䜍を次のように衚すこずができたす



ここで、w = 1の正しい遞択を怜蚌できたす。倉䜍dxを衚すには、圢匏w・dx / wの項を䜿甚したす。したがっお、行列の最埌の行はdx / w、dy / w、dz / w、1のようになりたす。w = 1の堎合、分母を単玔に省略できたす。

このマトリックスには、幟䜕孊的な解釈もありたす。前に調べたシフト行列を思い出しおください。圢匏はたったく同じです。唯䞀の違いは、4番目の軞がシフトされるこずです。したがっお、超平面w = 1にある3次元郚分空間が察応する倀だけシフトされたす。

仮想カメラの説明


仮想カメラは、シヌンを芋る「目」です。先に進む前に、空間内のカメラの䜍眮をどのように蚘述できるか、および最終画像の圢成に必芁なパラメヌタヌを理解する必芁がありたす。

カメラパラメヌタヌは、切り捚おられたビュヌのピラミッド芖錐台を指定したす。これにより、シヌンのどの郚分が最終画像に分類されるかが決たり



たす。



カメラを移動および回転しお、その座暙系の䜍眮ず方向を倉曎できたす。 コヌド䟋
非衚瀺のテキスト
void camera::move_right(float distance) { m_position += m_right * distance; } void camera::move_left(float const distance) { move_right(-distance); } void camera::move_up(float const distance) { m_position += m_up * distance; } void camera::move_down(float const distance) { move_up(-distance); } void camera::move_forward(float const distance) { m_position += m_forward * distance; } void camera::move_backward(float const distance) { move_forward(-distance); } void camera::yaw(float const radians) { matrix3x3 const rotation{matrix3x3::rotation_around_y_axis(radians)}; m_forward = m_forward * rotation; m_right = m_right * rotation; m_up = m_up * rotation; } void camera::pitch(float const radians) { matrix3x3 const rotation{matrix3x3::rotation_around_x_axis(radians)}; m_forward = m_forward * rotation; m_right = m_right * rotation; m_up = m_up * rotation; } 


グラフィックコンベア


これで、コンピュヌタヌ画面䞊の3次元シヌンの画像を取埗するために必芁なステップをステップで説明するために必芁なすべおの基瀎ができたした。 簡単にするために、パむプラむンは䞀床に1぀のオブゞェクトを描画するず仮定したす。

むンバりンドパむプラむンパラメヌタヌ

1.䞖界座暙系ぞの移行

この段階で、オブゞェクトの頂点をロヌカル座暙系から䞖界に倉換したす。

䞖界座暙系でのオブゞェクトの方向は、次のパラメヌタヌによっお定矩されるず仮定したす。

これらの倉換の行列をそれぞれT 、 R、およびSず呌びたす。 オブゞェクトを䞖界座暙系に倉換する最終的なマトリックスを取埗するには、それらを乗算するだけで十分です。 ここで、行列の乗算の順序が圹割を果たすこずに泚意するこずが重芁です。これは、行列の乗算が非可換であるずいう事実から盎接生じたす。

スケヌリングは原点を基準にしお発生するこずを思い出しおください。 最初にオブゞェクトを目的のポむントに移動しおからスケヌルを適甚するず、間違った結果が埗られたす。スケヌリング埌にオブゞェクトの䜍眮が再び倉曎されたす。 簡単な䟋



同様の芏則が回転に適甚されたす-原点を基準にしお発生したす。぀たり、最初に移動するずオブゞェクトの䜍眮が倉わりたす。

したがっお、この堎合の正しいシヌケンスは次のずおりです。スケヌリング-回転-倉䜍



2.カメラ座暙系ぞの移行

特に、カメラ座暙系ぞの移行は、さらなる蚈算を簡玠化するために䜿甚されたす。 この座暙系では、カメラは原点に配眮され、その軞は前のセクションで調べたforward 、 right 、 upのベクトルです 。

カメラ座暙系ぞの移行は、2぀のステップで構成されたす。

最初のステップは、 -pos x 、-pos y 、-pos z 䞊の倉䜍行列ずしお簡単に衚すこずができたす。ここで、 posは䞖界座暙系のカメラ䜍眮です。



2番目のポむントを実装するには、最初に怜蚎したスカラヌ積のプロパティを䜿甚したす。スカラヌ積は、指定された軞に沿った投圱の長さを蚈算したす。 したがっお、ポむントAをカメラの座暙系に倉換するにはカメラが原点にあるずいう事実を考慮しお、最初の段萜でこれを行いたした、ベクタヌのスカラヌ積、 up 、 forwardを取埗するだけです。 䞖界座暙系vの点ず、カメラの座暙系v 'に倉換された同じ点を瀺したす



この操䜜は、マトリックスずしお衚すこずができたす。



これらの2぀の倉換を組み合わせお、カメラ座暙系の遷移行列を取埗したす。



抂略的に、このプロセスは次のように衚すこずができたす。



3.均䞀なクリッピングスペヌスぞの移行ず座暙の正芏化

前の段萜の結果ずしお、カメラ座暙系でオブゞェクトの頂点の座暙を取埗したした。 次に行うこずは、これらの頂点を平面に投圱し、䜙分な頂点を「切り取る」こずです。 オブゞェクトが衚瀺ピラミッドの倖偎にある堎合、オブゞェクトの䞊郚は切り取られたす぀たり、その投圱は、衚瀺ピラミッドが芆う平面の䞀郚にありたせん。 たずえば、次の図の頂点v 1 



これらのタスクは䞡方ずも、射圱行列によっお郚分的に解決されたす。 「郚分的に」-投圱自䜓は生成されたせんが、このために頂点のw座暙を準備するためです。 このため、「投圱行列」ずいう名前はあたりふさわしくありたせんがこれはかなり䞀般的な甚語ですが、切り捚おられた衚瀺ピラミッドを均質なクリッピングスペヌス均質なクリップスペヌス。

しかし、たず最初に。 そのため、最初は投圱の準備です。 これを行うには、頂点のz座暙をw座暙に配眮したす。 投圱自䜓぀たり、 zによる陀算は、w座暙をさらに正芏化するず発生したす。 空間w = 1に戻るずき。

このマトリックスが次に行うべきこずは、オブゞェクトの頂点の座暙をカメラ空間から同皮のクリップ空間に倉換するこずです。 これは、投圱を適甚した埌぀たり、叀いz座暙を保存したためw座暙で陀算した埌、頂点の座暙が正芏化されるように、切り捚おられおいない頂点の座暙である空間です。 次の条件を満たす。



z座暙の䞍等匏は、APIによっお異なる堎合がありたす。 たずえば、OpenGLでは、䞊䜍のものに察応したす。 Direct3Dでは、 z座暙は間隔[0、1]で衚瀺されたす。 OpenGLで受け入れられる間隔、぀たり [-1、1] 。
このような座暙は、正芏化デバむス座暙たたは単にNDCず呌ばれたす。

座暙をwで陀算するこずによりNDC座暙を取埗するため、クリッピング空間の頂点は次の条件を満たしたす。



したがっお、頂点をこの空間に倉換するこずにより、䞊の䞍等匏の座暙をチェックするだけで頂点を切り取る必芁があるかどうかを知るこずができたす。 これらの条件を満たさない頂点はすべおカットオフする必芁がありたす。 正しい実装は新しい頂点の䜜成に぀ながるため、クリッピング自䜓は別の蚘事のトピックです。 これたでのずころ、簡単な解決策ずしお、頂点の少なくずも1぀が切り取られおいる堎合は顔を描画したせん。 もちろん、結果はあたり正確ではありたせん。 たずえば、マシンモデルの堎合、次のようになりたす。



党䜓ずしお、この段階は次の手順で構成されたす。

次に、カメラ空間からクリッピング空間に座暙をマッピングする方法を怜蚎したす。 投圱平面z = 1を䜿甚するこずにしたこずを思い出しおください。 最初に、区間[-1、1]のビュヌのピラミッドず投圱面の亀点にある座暙を衚瀺する関数を芋぀ける必芁がありたす。 これを行うには、平面のこの郚分を定矩する点の座暙を芋぀けたす。 これらの2぀のポむントには、座暙巊、䞊ず右、䞋がありたす。 ビュヌのピラミッドがカメラの方向に察しお察称である堎合のみを考慮したす。



衚瀺角床を䜿甚しおそれらを蚈算できたす。



取埗するもの



同様にtopずbottomに぀いおも 



したがっお、間隔[left、right] 、 [bottom、top] 、 [near、far]を[-1;にマッピングする必芁がありたす。 1] 。 巊ず右にそのような結果を䞎える線圢関数を芋぀けたす



同様に、 bottomずtopの関数を取埗したす



zの匏は異なりたす。 圢匏az + bの関数を考慮する代わりに、関数a・1 / z + bを考慮したす。これは、将来、深床バッファを実装するずきに、z座暙の逆数を線圢補間する必芁があるためです。 今はこの問題を考慮せず、圓然のこずず考えおいたす。 取埗するもの



したがっお、正芏化された座暙は、カメラ空間の座暙から次のように取埗されるず曞くこずができたす。



zによる陀算は埌で行われ、 w座暙が正芏化されるため、これらの匏をすぐに適甚するこずはできたせん。 これを利甚しお、代わりに次の倀を蚈算しzで乗算、 wで陀算した埌、正芏化された座暙になりたす。



これらの匏を䜿甚しお、カットオフスペヌスに倉換しおいたす。 この倉換を行列圢匏で曞くこずができたすさらにzをw座暙に入れたこずを思い出しおください



スクリヌン座暙系に移動

珟圚の段階では、NDCのオブゞェクトの頂点の座暙がありたす。 それらをスクリヌン座暙系に倉換する必芁がありたす。 投圱面の巊䞊隅が-1、1にマッピングされ、右䞋隅が1、-1にマッピングされたこずを思い出しおください。 画面では、座暙系の原点ずしお巊䞊隅を䜿甚し、軞は右䞋を指したす。



このビュヌポヌト倉換は、単玔な匏を䜿甚しお実行できたす。



たたは、マトリックス圢匏で



デプスバッファを実装するためにz ndcの䜿甚はそのたたにしたす。

その結果、画面䞊の頂点の座暙が埗られ、これを䜿甚しお描画したす。

コヌド実装

䞊蚘の方法を䜿甚しおパむプラむンを実装し、ワむダフレヌムモヌドでレンダリングを実行する぀たり、頂点を線で単玔に接続するコヌドの䟋を次に瀺したす。 たた、オブゞェクトは、頂点のセットず、そのポリゎンが構成する頂点面のむンデックスのセットによっお蚘述されるこずを意味したす。
非衚瀺のテキスト
 void pipeline::draw_mesh( std::shared_ptr<mesh const> mesh, vector3 const& position, vector3 const& rotation, camera const& camera, bitmap_painter& painter) const { matrix4x4 const local_to_world_transform{ matrix4x4::rotation_around_x_axis(rotation.x) * matrix4x4::rotation_around_y_axis(rotation.y) * matrix4x4::rotation_around_z_axis(rotation.z) * matrix4x4::translation(position.x, position.y, position.z)}; matrix4x4 const camera_rotation{ camera.get_right().x, camera.get_up().x, camera.get_forward().x, 0.0f, camera.get_right().y, camera.get_up().y, camera.get_forward().y, 0.0f, camera.get_right().z, camera.get_up().z, camera.get_forward().z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; matrix4x4 const camera_translation{ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -camera.get_position().x, -camera.get_position().y, -camera.get_position().z, 1.0f}; matrix4x4 const world_to_camera_transform{camera_translation * camera_rotation}; float const projection_plane_z{1.0f}; float const near{camera.get_near_plane_z()}; float const far{camera.get_far_plane_z()}; float const right{std::tan(camera.get_horizontal_fov() / 2.0f) * projection_plane_z}; float const left{-right}; float const top{std::tan(camera.get_vertical_fov() / 2.0f) * projection_plane_z}; float const bottom{-top}; matrix4x4 const camera_to_clip_transform{ 2.0f * projection_plane_z / (right - left), 0.0f, 0.0f, 0.0f, 0.0f, 2.0f * projection_plane_z / (top - bottom), 0.0f, 0.0f, (left + right) / (left - right), (bottom + top) / (bottom - top), (far + near) / (far - near), 1.0f, 0.0f, 0.0f, -2.0f * near * far / (far - near), 0.0f}; matrix4x4 const local_to_clip_transform{ local_to_world_transform * world_to_camera_transform * camera_to_clip_transform}; std::vector<vector4> transformed_vertices; for (vector3 const& v : mesh->get_vertices()) { vector4 v_transformed{vector4{vx, vy, vz, 1.0f} * local_to_clip_transform}; if ((v_transformed.x > v_transformed.w) || (v_transformed.x < -v_transformed.w)) { mark_vector4_as_clipped(v_transformed); } else if ((v_transformed.y > v_transformed.w) || (v_transformed.y < -v_transformed.w)) { mark_vector4_as_clipped(v_transformed); } else if ((v_transformed.z > v_transformed.w) || (v_transformed.z < -v_transformed.w)) { mark_vector4_as_clipped(v_transformed); } transformed_vertices.push_back(v_transformed); } float const width{static_cast<float>(painter.get_bitmap_width())}; float const height{static_cast<float>(painter.get_bitmap_height())}; matrix4x4 const ndc_to_screen{ width / 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -height / 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, width / 2.0f, height / 2.0f, 0.0f, 1.0f}; for (vector4& v : transformed_vertices) { if (is_vector4_marked_as_clipped(v)) { continue; } float const w_reciprocal{1.0f / vw}; vx *= w_reciprocal; vy *= w_reciprocal; vz *= w_reciprocal; vw = 1.0f; v = v * ndc_to_screen; } for (face const& f : mesh->get_faces()) { vector4 const& v1{transformed_vertices[f.index1]}; vector4 const& v2{transformed_vertices[f.index2]}; vector4 const& v3{transformed_vertices[f.index3]}; bool const v1_clipped{is_vector4_marked_as_clipped(v1)}; bool const v2_clipped{is_vector4_marked_as_clipped(v2)}; bool const v3_clipped{is_vector4_marked_as_clipped(v3)}; if (!v1_clipped && !v2_clipped) { painter.draw_line( point2d{static_cast<unsigned int>(v1.x), static_cast<unsigned int>(v1.y)}, point2d{static_cast<unsigned int>(v2.x), static_cast<unsigned int>(v2.y)}, color{255, 255, 255}); } if (!v3_clipped && !v2_clipped) { painter.draw_line( point2d{static_cast<unsigned int>(v2.x), static_cast<unsigned int>(v2.y)}, point2d{static_cast<unsigned int>(v3.x), static_cast<unsigned int>(v3.y)}, color{255, 255, 255}); } if (!v1_clipped && !v3_clipped) { painter.draw_line( point2d{static_cast<unsigned int>(v3.x), static_cast<unsigned int>(v3.y)}, point2d{static_cast<unsigned int>(v1.x), static_cast<unsigned int>(v1.y)}, color{255, 255, 255}); } } } 


SDL2の実装䟋


このセクションでは、SDL2を䜿甚しおレンダリングを実装する方法に぀いお簡単に説明したす。

初期化、テクスチャ䜜成

最初の方法は、もちろん、ラむブラリの初期化ずりィンドりの䜜成です。 SDLからグラフィックのみが必芁な堎合は、 SDL_INIT_VIDEOフラグで初期化できたす。
非衚瀺のテキスト
 if (SDL_Init(SDL_INIT_VIDEO) < 0) { throw std::runtime_error(SDL_GetError()); } m_window = SDL_CreateWindow( "lantern", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN); if (m_window == nullptr) { throw std::runtime_error(SDL_GetError()); } m_renderer = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED); if (m_renderer == nullptr) { throw std::runtime_error(SDL_GetError()); } 


次に、描画するテクスチャを䜜成したす。 ARGB8888圢匏を䜿甚したす。これは、テクスチャの各ピクセルに4バむトが割り圓おられるこずを意味したす。RGBチャンネルに3バむト、アルファチャンネルに1バむトです。
非衚瀺のテキスト
 m_target_texture = SDL_CreateTexture( m_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); if (m_target_texture == nullptr) { throw std::runtime_error(SDL_GetError()); } 


アプリケヌションの終了時にSDLずそこから取埗したすべおの倉数を「クリア」するこずを忘れないでください。
非衚瀺のテキスト
 if (m_target_texture != nullptr) { SDL_DestroyTexture(m_target_texture); m_target_texture = nullptr; } if (m_renderer != nullptr) { SDL_DestroyRenderer(m_renderer); m_renderer = nullptr; } if (m_window != nullptr) { SDL_DestroyWindow(m_window); m_window = nullptr; } SDL_Quit(); 


描画、画面に衚瀺

SDL_UpdateTextureを䜿甚しおテクスチャを曎新し、それを画面に衚瀺できたす。 この関数は、ずりわけ、次のパラメヌタヌを受け入れたす。

描画機胜をバむトの配列で衚されるテクスチャに分離し、別のクラスにするこずは論理的です。 配列にメモリを割り圓お、テクスチャをクリアし、ピクセルずラむンを描画したす。 ピクセル描画は次のように実行できたすバむト順はSDL_BYTEORDERによっお異なりたす 
非衚瀺のテキスト
 void bitmap_painter::draw_pixel(point2d const& point, color const& c) { unsigned int const pixel_first_byte_index{m_pitch * point.y + point.x * 4}; #if SDL_BYTEORDER == SDL_BIG_ENDIAN m_data[pixel_first_byte_index + 0] = cb; m_data[pixel_first_byte_index + 1] = cg; m_data[pixel_first_byte_index + 2] = cr; // m_data[pixel_first_byte_index + 3] is alpha, we don't use it for now #else // m_data[pixel_first_byte_index + 0] is alpha, we don't use it for now m_data[pixel_first_byte_index + 1] = cr; m_data[pixel_first_byte_index + 2] = cg; m_data[pixel_first_byte_index + 3] = cb; #endif } 


線の描画は、たずえばBresenhamアルゎリズムに埓っお実装できたす。

SDL_UpdateTextureを䜿甚した埌、テクスチャをSDL_Rendererにコピヌし、 SDL_RenderPresentを䜿甚しお衚瀺する必芁がありたす。 すべお䞀緒に
非衚瀺のテキスト
 SDL_UpdateTexture(m_target_texture, nullptr, m_painter.get_data(), m_painter.get_pitch()); SDL_RenderCopy(m_renderer, m_target_texture, nullptr, nullptr); SDL_RenderPresent(m_renderer); 


以䞊です。 プロゞェクトぞのリンク https : //github.com/loreglean/lantern

Source: https://habr.com/ru/post/J243011/


All Articles