ずっと前に、私はHabrに手紙を書きませんでした:勉強、セッションが来ています、あなたは理解しています。 今日は、リーチプロファイルとシェーダーモデル2.0を使用しながら、3つの光源への通常のマッピングを使用して、XNAで遅延照明 (遅延照明)を実装する方法を説明します 。
シェーダーのトピックについて説明する前に、ここで思い出させてください。 残りは、カット、ビデオ、デモの下にあります。
このパートでは:
- 遅延照明とは
- 法線マッピングとは何ですか?
- 実装、シェーダーの接続
- ピクセルシェーダーの実装
理論
なぜちょうど3つの光源が同時にあるのですか? シェーダーには制限があります。 シェーダーモデル2.0では 、1つのシェーダーパスに64を超える算術演算を含めることはできません。 1パスで3つ以上の光源を使用するには、 シェーダーモデル3.0が必要です。512個の算術演算をサポートします。パスごとに約30光源です。 ただし、モデル3.0の場合、 hidefプロファイルが必要であり、 DirectX10に適したグラフィックカードが必要です。 したがって、3つのソースに制限します(さらに、複数のパスでソースを描画できないのはなぜですか?したがって、3つのソースはシェーダーのみの制限です。この記事では1つのパスのみを考慮します)。
ところで、シェーダーモデルの比較:
詳細はこちらをご覧ください 。
2番目のモデルのピクセルシェーダーの制限は整理されているようです。
ここで、もう1つの奇妙な言葉を見てみましょう。「 遅延照明 ( 遅延照明 )」です。
据え置き照明
遅延ライティングと標準のライティングメソッドからのシェーディングの主な違いは、これらのメソッドがシェーダーの結果を直ちにカラーフレームバッファーに書き込むことです。
説明が非常に簡単な場合は、光源からのすべてのピクセルをカウントし、すぐにフレームバッファーに書き込みます。この場合、フレームバッファーに4つの合計があります(周囲+ 3つのソースの結果)
非常に簡単に言えば、我々が見つけた遅延照明とは何か、どのような法線マッピングがどのようなものであるかを理解することです。
法線マッピング
普通の人々には、テクスチャ法線のマップも知られています。これは、まだ法線は表面に垂直であると考える人向けです。
あなたがそれで勝つことができるものを見てみましょう。 通常のマップを使用した場合と使用しない場合の照明の例を示します。 次に、その仕組みを説明します。
これは、照明を使用しない場合のシーンの外観です。
これは、ライティングを使用して(法線マッピングを使用せずに)シーンを表示する方法です。
これは、ライティングを使用したシーンの外観です(法線マッピングを使用):
ご覧のとおり、結果は顔にあります。 法線マッピングを使用したシーンは「ボリューム感」があり、特にダイナミクス(光源の動き)でよりリアルに見えます。
テクスチャ自体を見てみましょう。
テクスチャ自体( カラーマップ ):
法線テクスチャ( 法線マップ ):
2番目のテクスチャでは、何がすぐにわかるのではなく、ディスプレイスメントシェーダーに関する記事を覚えていますか? そこで、ピクセルを曲げる方法に関する情報をR、Gチャネルを通じて送信しました。ここでは、R、G、Bを使用して法線に関する情報を送信します。 R = X、G = Y、B =Z。そしてすでにシェーダーで、ライティングの計算でX、Y、Z座標を操作します、簡単ですか?
読者が再び何も理解しなかった場合、彼が初めて「普通」という言葉を見て説明を受け取った後でも、法線マップを使用して、テクスチャは照明用の3D表現を取得します。
そして、はい、法線マップの作成については、それらを描画することは事実上不可能です 。それらはハイポリゴンモデルから削除されるか、テクスチャ自体から生成されます(たとえば、Photoshop プラグインを使用)。
少し理論を理解しました。これをすべてコードで実装してみましょう。
練習する
ここで、すでにコメント付きのコードを提供します。
光源ビュー、 LightEmmiterクラスを作成します。
public class LightEmmiter { // : X, Y, Z public Vector3 position; // public Vector3 color; // ( — ) public float corrector; // public float radius; // internal void UpdateEffect(EffectParameter effectParameter) { effectParameter.StructureMembers["position"].SetValue(position); effectParameter.StructureMembers["color"].SetValue(color * corrector); effectParameter.StructureMembers["invRadius"].SetValue(1f / radius); } }
ここで、 Game1 (メインクラス)を使用します。
変数を作成します。
Texture2D texture; // Color ( ) Texture2D textureNormal; // Normal private Effect deferred; // SpriteFont spriteFont; // , Debug- EffectParameter lightParameter; // private float lightRadius; // private float lightZ; // Z- private float lightC; // LightEmmiter[] lights = new LightEmmiter[2]; //
これは、 LoadContentメソッドですべて初期化されます 。
texture = Content.Load<Texture2D>("test1"); // textureNormal = Content.Load<Texture2D>("test1_map"); // deferred = Content.Load<Effect>("deferred"); // spriteFont = Content.Load<SpriteFont>("default"); // lightRadius = 320f; // lightZ = 50f; // Z- lightC = 1f; // lights[0] = new LightEmmiter(); lights[0].position = new Vector3(20, 30, 0); lights[0].radius = lightRadius; lights[0].corrector = lightC; lights[0].color = new Vector3(1f, 0f, 0f); lights[1] = new LightEmmiter(); lights[1].position = new Vector3(20, 30, 0); lights[1].radius = lightRadius; lights[1].corrector = lightC; lights[1].color = new Vector3(1f, 1f, 1f);
Draw自体を更新します。
// RenderTarget — GraphicsDevice.Clear(Color.LightSkyBlue); GraphicsDevice.SetRenderTarget(null); // lights[0].position = new Vector3(Mouse.GetState().X, Mouse.GetState().Y, lightZ); lights[0].radius = lightRadius; lights[0].corrector = lightC; lights[1].position = new Vector3(800 - Mouse.GetState().X, 480 - Mouse.GetState().Y, lightZ); lights[1].radius = lightRadius; lights[1].corrector = lightC; // deferred.CurrentTechnique = deferred.Techniques["Deferred"]; // deferred.Parameters["screenWidth"].SetValue(GraphicsDevice.Viewport.Width); deferred.Parameters["screenHeight"].SetValue(GraphicsDevice.Viewport.Height); deferred.Parameters["ambientColor"].SetValue(new Vector3(1, 1, 1) * 0.1f); deferred.Parameters["numberOfLights"].SetValue(2); deferred.Parameters["normaltexture"].SetValue(textureNormal); // lights- lightParameter = deferred.Parameters["lights"]; // lights- for (int i = 0; i < lights.Length; i++) { LightEmmiter l = lights[i]; l.UpdateEffect(lightParameter.Elements[i]); } // ( ) foreach (EffectPass pass in deferred.CurrentTechnique.Passes) { spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise); pass.Apply(); spriteBatch.Draw(texture, new Rectangle(0, 0, 800, 480), Color.White); spriteBatch.End(); } // Debug- spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise, null); //800 480 spriteBatch.DrawString(spriteFont, "lightRadius: " + lightRadius + "\nlightCorrector: " + lightC + "\nligthZ: " + lightZ, new Vector2(10, 10), Color.LightYellow); spriteBatch.End();
残っている最も重要なことはすべて、 deferred.fxシェーダーを作成して、次のリストを作成することです。
// , struct Light { float3 position; float3 color; float invRadius; }; // texture normaltexture; // - 3- int numberOfLights; Light lights[3]; // float3 ambientColor; // , float screenWidth; float screenHeight; // Color- () sampler ColorMap : register(s0); // Normal- sampler NormalMap : samplerState { Texture = normaltexture; MinFilter = Linear; MagFilter = Linear; AddressU = Clamp; AddressV = Clamp; }; // float3 CalculateLight(Light light, float3 normal, float3 pixelPosition) { // float3 direction = light.position - pixelPosition; float atten = length(direction); direction /= atten; // float amount = max(dot(normal, direction), 0); atten *= light.invRadius; // , modifer , float modifer = max((1 - atten), 0); // return light.color * modifer * amount; } float4 DeferredNormalPS(float2 texCoords : TEXCOORD0) : COLOR { float4 base = tex2D(ColorMap, texCoords); // color- texCoords float3 normal = normalize(tex2D(NormalMap, texCoords) * 2.0f - 1.0f); // X,Y,Z - texCoords : -1, 1. // float3 pixelPosition = float3(screenWidth * texCoords.x, screenHeight * texCoords.y,0); // - float3 finalColor = 0; for (int i=0;i<numberOfLights;i++) { // finalColor += CalculateLight(lights[i], normal, pixelPosition); } // , * , multiply return float4((ambientColor + finalColor) * base.rgb, base.a); } technique Deferred { pass Pass0 { PixelShader = compile ps_2_0 DeferredNormalPS(); } }
この記事ではそれだけです-複数のシェーダーパスを介して無限の数の光源を実装する方法については説明しません。これは自分で行うことができます;)
照明デモンストレーションビデオ:
デモ(exe)へのリンク: こちら 。
ソースへのリンク(プロジェクト、VS2010): こちら
また、材料を分析し、きれいな処女の道を指すのを助けるために、 lazychaserに特別な感謝を表明したいと思います。
頑張ってください;)