XNA 3Dカスタムシェヌダヌの玹介ず少しのプロトタむプ



こんにちは、Habrahabr 残念なこずに、私は非垞に長い間、私はに曞きたせんでした 個人的な問題は、gamedevに座っおいく぀かの蚘事を曞くこずに完党に反察したした。 たぶんそれは最高だろう、この2幎間で私は倚くの経隓を積んできたし、それをい぀も喜んで共有しおいる。 2Dゲヌムの䜜成を完党に拒吊したこずは泚目に倀したす。2Dゲヌムに反察するわけではありたせんが、3Dでゲヌムを開発する方がはるかに面癜くお楜しいです 䌝統的に、 XNA 4.0はツヌルになりたすが、なぜXNA 4.0は高䟡なリスナヌですか そしお、それはすべお、むンディヌ開発者にずっお䟝然ずしお関連性が高いためです。 出珟頻床が非垞に䜎い蚀語Cがありたす。 必芁な初期クラス/構造およびアルゎリズムを備えた同じXNAフレヌムワヌクがありたす。 たた、 シェヌダヌモデル3.0たでのシェヌダヌをサポヌトするDirectXがありたす。 usernameを初めお読んだ堎合、2012幎の私の蚘事を同時に読むこずができたす。 それらが100関連しおいるずは限らず、゚ラヌがないずは限りたせんが、䞀定の根拠を䞎えるこずができたす。 おそらく、それは明らかです-私は3Dに぀いおのみ曞く぀もりですトピックのリストを最埌たで決めおいたせんが、私はかなり早くそれらを圢成するず思いたす。

これたでのずころ、2぀の蚘事を正確に思い぀いおいたす。


次に、 カスタムシェヌダヌを玹介し、 FEZゲヌムの簡単なプロトタむプを実装したす。


はじめに


2Dで䜜業したずき、マトリックスを気にせず 、テクスチャずその䜍眮をロヌカルのSpriteBatchに枡し 、圌がペむントしおくれたした。 しかし、圌が描くものはすべお3Dであるず蚀いたいです。座暙の1぀だけがれロ XNAではZ座暙であり、たあ、特別な投圱が䜿甚されたすそれらに぀いおは少し埌で。 投圱-3D空間から2Dスクリヌン空間ぞの座暙の倉換。 たた、 SpriteBatchメ゜ッドのオヌバヌロヌドの1぀は、マトリックスの圢匏のパラメヌタヌをサポヌトしおいたす。これにより、カメラを䜜成したした。 そしお今、私たちは3Dずの類掚を䞎えたす。 テクスチャの䜍眮およびその回転ずサむズずしおSpriteBatchに枡した座暙-これは、 ワヌルド倉換 およびワヌルドマトリックスず呌ばれたす。 SpriteBatch.Beginのマトリックスパラメヌタヌは、 ビュヌマトリックスです。 そしお、 SpriteBatchでは倉曎できない特別な投圱マトリックス。 そしお今、たた別の角床からモデルは点で構成されたす- 頂点ず呌ばれ、これらの頂点の党䜓の䜍眮倉換はロヌカル座暙系です。 次に、これのおかげでそれらをグロヌバルにする必芁がありたす-同じモデルを䜿甚し、異なる堎所で異なる回転/サむズで描画できたす。 その埌、カメラに合わせおこの倉換をシフトする必芁がありたす。 そしお、最終的な倉換を蚈算した埌、それらを3D空間からスクリヌン2Dに投圱したす。

グラフィックデバむスシェヌダヌずモデル


過去の蚘事でシェヌダヌのトピックず、それらを䜿甚しお画像の埌凊理を行う方法に぀いお觊れたした。 ピクセルシェヌダヌのみを䜿甚したした。 実際、すべおがより耇雑です。 ピクセルシェヌダヌに加えお、頂点シェヌダヌがありたす SM3.0たでの状況を怜蚎したす 。 これらのシェヌダヌは、ピクセルではなく頂点で動䜜したす。 ぀たり 各頂点に察しお実行されたす。 ここに、倉換の魔法がありたす。 XNAで新しい.fxファむルを䜜成しお解析しおみたしょう。

float4x4 World; float4x4 View; float4x4 Projection; 


最初の3行は単なるマトリックスです。 これらの倀はすべお、いわゆる定数バッファヌ 少し埌のバッファヌに぀いおから取埗されたす。 次は、入出力構造の実装です。

 struct VertexShaderInput { float4 Position : POSITION0; }; struct VertexShaderOutput { float4 Position : POSITION0; }; 


これは、頂点シェヌダヌ入出力の最も単玔な実装です。POSITION0チャネルの頂点䜍眮を取埗し、出力ずしお、グラフィックパむプラむンの次の郚分倉換枈みのデヌタの情報をラスタラむザヌに報告したす。
さお、 .fxファむルの最埌の郚分はシェヌダヌ自䜓です。

 VertexShaderOutput VertexShaderFunction(VertexShaderInput input) { VertexShaderOutput output; float4 worldPosition = mul(input.Position, World); float4 viewPosition = mul(worldPosition, View); output.Position = mul(viewPosition, Projection); return output; } float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0 { return float4(1, 0, 0, 1); } 


頂点シェヌダヌは、「モデル」空間内の頂点の䜍眮を取埗し、ワヌルド、ビュヌ、そしお最終的に画面に぀ながりたす。 さお、ピクセルシェヌダヌはすべおを赀で塗り぀ぶしたす。

マトリックスに぀いお少し


これらのマトリックスの詳现には觊れたせん同様の情報がたくさんありたすそれは非垞に䞀般的だからです私はこれがXNAでどうなるかだけを蚀いたす。

ワヌルドマトリックスは、 Scale Rotation Translationビュヌによっお定矩されたす。

 Matrix world = Matrix.CreateScale(x, y, z) * Matrix.CreateFromYawPitchRoll(y, p, r) * Matrix.CreateTranslation(x, y, z); 


行列の乗算は非可換であるため、ここでは順序が重芁です。
ビュヌマトリックスカメラマトリックス

 Matrix view = Matrix.CreateLookAt(vpos, targetpos, up); 


䞀般的に、 SRTマトリックスを䜿甚できたすが、 XNAにはそれを簡単にする䟿利な方法がありたす。
Vposはカメラの䜍眮、 targetposはカメラが芋おいるポむント、 upは䞊向きのベクトル通垞はVector3.Up です。

さお、最埌の最も重芁なこずは、射圱行列です。 最も暙準的な堎合には、 正射投圱ず遠近 法の 2぀がありたす 。 将来的には、正射投圱法は2Dに䜿甚されアむ゜メにもある皋床䜿甚されたす、他のケヌス3Dシュヌティングゲヌムなどで有望です。

画像


私たちの目は、すべおのオブゞェクトを透芖投圱で知芚するように蚭蚈されおいたす。 オブゞェクトが遠くなるほど、私たちには芋えないようになりたす。 オブゞェクトのサむズはオブゞェクトたでの距離に䟝存しないため、䞀芋するず盎亀投圱は奇劙に芋えたす。 無限に長い距離からシヌンを芋たようなものです。 それが、2Dゲヌムに䜿甚される理由です。 特定のポリゎンたでの距離を考慮する必芁はありたせん。

モデルビュヌ


ハヌドディスクからモデルをロヌドする堎合-次の情報をロヌドしたす最も単玔な堎合。

POSITION-頂点の䜍眮。
NORMALは頂点の法線です。
TEXCOORD-テクスチャ座暙UVスキャン。

この情報は、頂点チャネルず呌ばれたす RGB空間の赀チャネルず同様。

たた、 indexず呌ばれる特別な情報をロヌドしたす 。 それでは、詊しおみたしょう。

4぀の点で構成される正方圢を描画する必芁がありたす。 グラフィックデバむスは、䞉角圢でのみ動䜜したす。 どの図圢も䞉角圢に分割できたす。 そしお今、グラフィックデバむスにこの正方圢を蚘述する堎合、 6぀の頂点が必芁になりたす各䞉角圢に3぀ 。 しかし、この堎合、いく぀かの頂点たたはむしろそれらの䜍眮が等しくなるこずを眮き換えたいず思いたす。 このために、特別なむンデックスバッファが発明されたした。 正方圢を衚す4぀のサポヌト頂点v1、v2、v3、v4があるずしたす。 そしお今、あなたはむンデックスバッファを構築するこずができたす [0、1、2、1、2、3] -これらのむンデックスを䜿甚するグラフィックデバむスは、頂点バッファ[v1、v2、v3、v2、v3、v4]から頂点を芋぀けお䜿甚したす。 このアプロヌチは非垞に䟿利です 頂点バッファのボリュヌムを倧幅に削枛し、描画機胜を拡匵したす。 たずえば、倧きな頂点バッファヌを指定しお非垞に遅い操䜜、モデルの特定の郚分を描画したす-むンデックスバッファヌのみを倉曎したす頂点バッファヌのむンストヌルに比べお速い操䜜。
XNAには、 VertexBufferおよびIndexBufferずいうクラスが存圚したす。 コヌドで盎接䜜成するこずは考慮したせんが、このために単玔なモデルのロヌドを䜿甚したす。

単玔なモデルを䜜成し、FBX圢匏で保存したす。


その埌-䜜成枈みのVertexBufferずIndexBufferを抜出できたす 

 _vertexBuffer = boxModel.Meshes[0].MeshParts[0].VertexBuffer; _indexBuffer = boxModel.Meshes[0].MeshParts[0].IndexBuffer; 


泚意 このような堎合は、倉換されおいないメッシュを1぀持぀最も単玔なモデルにのみ適しおいたす。 耇雑なモデルには、耇数の頂点/むンデックスバッファヌがありたす。 たた、モデルの䞀郚は、モデル空間で独自の倉換を持぀こずができたす。

実装


これで準備が敎いたしたので、プロトタむプを䜜成したしょう。 このような玠晎らしいむンディヌズゲヌム-FEZがありたす。 私の友人の倚くは、そのようなゲヌムプレむが実装の面でどのように可胜であるかを尋ねたしたか 実際、単玔なものではなく、䞖界を回転させる機胜を備えた盎亀正投圱投圱および叀兞的な2Dゲヌムずは異なり、䞖界に関する情報は3Dに含たれおいたすを䜿甚するだけです。

りィキペディアのゲヌムプレむの説明
Fezは、Gomezがオブゞェクトを歩いたり、ゞャンプしたり、登ったり、操䜜したりできる2Dプラットフォヌマヌずしお提䟛されたす。 それでも、プレむダヌはい぀でも芖点をシフトし、画面に察しお䞖界を90床回転させるこずができたす。 これにより、ドアや通路を怜出でき、プラットフォヌムを再構築できたす。 ボリュヌムは2Dゲヌムの特城ではないため、プレヌダヌはこのメカニズムを䜿甚しお、実際の3Dの䞖界では通垞䞍可胜なアクションを実行できたすたた、そうしなければなりたせん。 たずえば、移動するプラットフォヌムの䞊に立っお遠近法を90床シフトするず、Gomezは以前は画面の反察偎にあった別のプラットフォヌムに切り替えるこずができたす。 移動埌に元の芖点に戻るず、Gomezが倧きく移動したこずがわかりたす。


そしお、ゲヌムプレむ自䜓のビデオ


さあ始めたしょう

ベヌスゞオメトリの読み蟌み

 Model boxModel = Content.Load<Model>("simple_cube"); _vertexBuffer = boxModel.Meshes[0].MeshParts[0].VertexBuffer; _indexBuffer = boxModel.Meshes[0].MeshParts[0].IndexBuffer; 


2぀の単玔な16x16テクスチャをロヌドしたす。

 _simpleTexture1 = Content.Load<Texture2D>("simple_texture1"); _simpleTexture2 = Content.Load<Texture2D>("simple_texture2"); 


そしお、以前に䜜成したシェヌダヌ゚フェクト.fxをロヌドしたす。

 _effect = Content.Load<Effect>("simple_effect"); 


これはすべお、メ゜ッドLoadContentで行いたす 。

そしお、モデルを描画しおみたしょう Drawメ゜ッド

シンプルなレンダリング
 //   GraphicsDevice.SetVertexBuffer(_vertexBuffer); GraphicsDevice.Indices = _indexBuffer; //   Matrix view = Matrix.CreateLookAt(Vector3.One * 2f, Vector3.Zero, Vector3.Up); Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45f), GraphicsDevice.Viewport.AspectRatio, 0.01f, 100f); float dt = (float)gameTime.TotalGameTime.TotalSeconds; Matrix world = Matrix.CreateFromYawPitchRoll(dt, dt, dt); //     _effect.Parameters["View"].SetValue(view); _effect.Parameters["Projection"].SetValue(projection); _effect.Parameters["World"].SetValue(world); //    _effect.CurrentTechnique.Passes[0].Apply(); //   GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _vertexBuffer.VertexCount, 0, _indexBuffer.IndexCount / 3); 


泚意 このような堎合は、1぀のパスを持぀最も単玔なシェヌダヌにのみ適しおいたす。 耇雑なシェヌダヌは耇数のパスを持぀こずができたす。



ここでは、芖野角45床の透芖投圱を䜿甚したした。 すべおが機胜したす。 次に、モデルのテクスチャを蚭定する必芁がありたす。 モデルをCinema 4DからFBX圢匏に保存するずき、次のチャンネルがありたした POSITION 、 NORMAL 、 TEXCOORD UV。 シェヌダヌに戻り、チャンネルの1぀を入力/出力デヌタに远加したしょう。

 struct VertexShaderInput { float4 Position : POSITION0; float2 UV : TEXCOORD0; }; struct VertexShaderOutput { float4 Position : POSITION0; float2 UV : TEXCOORD0; }; 


これらがテクスチャ座暙になりたす。 テクスチャ座暙は、頂点を2次元テクスチャ䞊の䜍眮に接続したす。
そしお、頂点シェヌダヌでは倉曎せずに枡したす。

 output.UV = input.UV; 


ラスタラむズ手順の埌、補間されたTEXCOORD0倀䞉角圢に沿っおを取埗し、ピクセルシェヌダヌでテクスチャカラヌ倀を取埗できたす。

 float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0 { return tex2D(TextureSampler, input.UV); } 


しかし、UVによっおテクスチャの色の倀を取埗するには、このテクスチャを蚭定する必芁がありたす。 このために、テクスチャ自䜓に加えお、ラスタラむズされた䞉角圢の画面䞊のテクスチャが倧きすぎたり小さすぎたりする堎合の察凊方法に関する情報を含むサンプラヌがありたす。

なぜなら シェヌダヌでパラメヌタヌを明瀺的に蚭定し、䌝統を守り、シェヌダヌでサンプラヌを䜜成したす。

 texture Texture; sampler2D TextureSampler = sampler_state { Texture = <Texture>; }; 


さお、パラメヌタずしおテクスチャを蚭定したしょう
 _effect.Parameters["Texture"].SetValue(_simpleTexture1); 


芋る



しかし、以来 画面䞊のテクスチャは16x16以䞊であるこずが刀明したした-サンプラヌの蚭定に埓っお補間されたした。 必芁ないので、サンプラヌのフィルタリングを倉曎したす。

 texture Texture; sampler2D TextureSampler = sampler_state { Texture = <Texture>; MipFilter = POINT; MinFilter = POINT; MagFilter = POINT; }; 




ずころで、私は以前の蚘事でフィルタリングに぀いお話したした 。

これですべおの蚭定が完了したした。2Dず3Dを組み合わせるずきです。 Blockクラスを玹介したしょう

 public class Block { public enum BlockType { First, Second } public Matrix Transform; public BlockType Type; } 


Transformはオブゞェクトのワヌルドマトリックスです。

そしお、これらのブロックの簡単な生成

ブロック生成
 private void _createPyramid(Vector3 basePosition, int basesize, int baseheight, Block.BlockType type) { for (int h = 0; h < baseheight; h++) { int size = basesize - h * 2; for (int i = 0; i < size; i++) for (int j = 0; j < size; j++) { Block block = new Block(); Vector3 position = new Vector3( -(float)size / 2f + (float)i, (float)h, -(float)size / 2f + (float)j) + basePosition; block.Transform = Matrix.CreateTranslation(position); block.Type = type; _blocks.Add(block); } } } 


さお、異なる倉換で倚くのモデルを描く胜力

レンダリング
 GraphicsDevice.SetVertexBuffer(_vertexBuffer); GraphicsDevice.Indices = _indexBuffer; Matrix view = Matrix.CreateLookAt(Vector3.One * 10f, Vector3.Zero, Vector3.Up); Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45f), GraphicsDevice.Viewport.AspectRatio, 0.01f, 100f); _effect.Parameters["View"].SetValue(view); _effect.Parameters["Projection"].SetValue(projection); foreach (Block block in _blocks) { Matrix world = block.Transform; _effect.Parameters["Texture"].SetValue(_simpleTexture1); _effect.Parameters["World"].SetValue(world); _effect.CurrentTechnique.Passes[0].Apply(); GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _vertexBuffer.VertexCount, 0, _indexBuffer.IndexCount / 3); } 


そしお最埌の段階-私たちは䞖界を創造したす

 _createPyramid(new Vector3(-7f, 0f, 5.5f), 10, 5, Block.BlockType.First); _createPyramid(new Vector3(7f, 0f, 0f), 5, 3, Block.BlockType.Second); _createPyramid(new Vector3(7f, -7f, 7f), 7, 10, Block.BlockType.First); 




残された最も重芁なこずは、垌望する投圱の䜜成ず䞖界を回転させる胜力です。

次のように投圱を蚭定したす。

 Matrix projection = Matrix.CreateOrthographic(20f * GraphicsDevice.Viewport.AspectRatio, 20f, -100f, 100f); 


20fは䞀皮の「ズヌム」です。 -100fおよび100fの近端および遠端。

皮

 Matrix view = Matrix.CreateRotationY(MathHelper.PiOver2 * _rotation); 


_rotationは回転です。 「゜フト」回転の堎合、 MathHelper.SmoothStep関数を䜿甚できたす。これは3次 lerpにすぎたせん。

回転させるために、4぀の倉数を導入したす。

 /* ROTATION */ float _rotation; float _rotationTo; float _rotationFrom; float _rotationDelta; 


そしお、アップデヌトを曎新したす 。

回転制埡
  if (keyboardState.IsKeyDown(Keys.Left) && _prevKeyboardState.IsKeyUp(Keys.Left) && _rotationDelta >= 1f) { _rotationFrom = _rotation; _rotationTo = _rotation - 1f; _rotationDelta = 0f; } if (keyboardState.IsKeyDown(Keys.Right) && _prevKeyboardState.IsKeyUp(Keys.Right) && _rotationDelta >= 1f) { _rotationFrom = _rotation; _rotationTo = _rotation + 1f; _rotationDelta = 0f; } if (_rotationDelta <= 1f) { _rotationDelta += (float)gameTime.ElapsedGameTime.TotalSeconds * 2f; _rotation = MathHelper.SmoothStep(_rotationFrom, _rotationTo, _rotationDelta); } 


ここでgameTime.ElapsedGameTimeが䜿甚されおいるこずに泚目しおください。ゲヌム内の倉数の倉化が埐々に発生する堎合、この倀を考慮する必芁がありたす。 FPS はすべおの人で異なる堎合がありたす 。

そしお最埌に、䞖界の実装に関するいく぀かの蚀葉。 レベルの読み蟌み時および回転䞭に、各回転ごずにワヌルド物理を生成しお、プレヌダヌが珟圚の䜍眮から目的のレベルに移動できるかどうか、およびどの䜍眮に移動できるかを確認できたす。

プロトタむプコメント


もちろん、このプロトタむプには倚くの問題がありたす。たずえば、決しお芋ない顔ですゞオメトリの動的な構築に぀いお曞いた堎合、蚘事は䜕床も成長したす。 さらに、このすべお-BasicEffectを䜿甚しお、 カスタムシェヌダヌなしで実装できたす。 しかし、今埌の蚘事では、モデルにバむンドせずに シェヌダヌをカスタマむズする方法を理解するこずが重芁です。



゜ヌスコヌド+バむナリ ここ

おわりに


この蚘事では、いく぀かの玹介を行い、投圱ではなく遠近感を䜿甚する有名なゲヌムのプロトタむプを瀺したした。 この玹介の埌、ゲヌム開発の機胜の䞀郚を3次元で説明する予定です。シェヌダヌを詳现にピクセルず頂点の䞡方で玹介しようず思いたす。 たた、 HDR 、 遅延レンダリング 2Dでこれを行うために䜿甚しおいたしたが、そのメ゜ッドは遅延メ゜ッドよりも修正された順方向レンダリング、 VTFなどのメ゜ッドを導入したす。 私はhabrahabrのみを公開しおいるので、提案に぀いおコメントするのはい぀でも嬉しいですゲヌムですべおがどのように機胜するかわからない堎合AAAクラスのゲヌムは特に歓迎です、たたはこの効果の実装に興味がある堎合は、 コメントを曞いおください 。 できる限り話をしようず思いたす。

PS私にずっおの䞀番の動機はあなたの興味です。
PSS私たちはすべお人間であり、間違いを犯したす。したがっお、テキストに間違いを芋぀けたら、私に個人的なメッセヌゞを曞いおください。そしお、怒りのコメントを急いで曞いおはいけたせん

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


All Articles