3D座標変換描画

この章ではポリゴンの描画方法について解説します。
このページの内容は、3D描画時の初期設定まで終わっていることが前提です。

実行結果

Shader設定

定数バッファー(コンスタントバッファー)の設定

頂点シェーダーとピクセルシェーダーの2つ以外にもコンスタントバッファーの生成と設定を行います。

  1. 先に必要な変数を定義します。

    /// シェーダー用の定数バッファー(コンスタント変数の定義)
    SharpDX.Direct3D11.Buffer _ContantBuffer = null;
    
  2. 頂点バッファの設定に使うコンスタントバッファーの生成をSharpDX.Direct3D11.Bufferで行います。

    そしてそれぞれのBufferで「使用するデバイス、バッファのサイズ、バッファの使用法、描画パイプラインへのバインド方法、CPUからリソースへのアクセス方法、その他オプション設定、構造化バッファのサイズ」の順に設定しています。

  3. 先ほど生成したコンスタントバッファーを頂点シェーダーに設定します。

    context.VertexShader.SetConstantBuffer(0, _ContantBuffer);
    
  4. 頂点シェーダーとピクセルシェーダーの2つを設定します。
    // 頂点シェーダーを設定する
    var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0", ShaderFlags.None, EffectFlags.None);
    var vertexShader = new VertexShader(_device, vertexShaderByteCode);
    // ピクセルシェーダーを設定する
    var pixelShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "PS", "ps_4_0", ShaderFlags.None, EffectFlags.None);
    var pixelShader = new PixelShader(_device, pixelShaderByteCode);
    
  5. 「MiniCube.fx」ファイルを実行ファイルであるexeと同じディレクトリ(フォルダ)内に入れます。

    ファイルの中身はページ下記に全文が載っていますのでテキストエディタでコピペして「MiniCube.fx」で保存してください。

    ポリゴン描画で使った「MiniTri.fx」とは別なfxファイルな点にご注意ください。

    シェーダーの細かい解説は省略しますが、おおまかに言えば「位置データを3D座標変換する」というコードに変わっています。

  6. DirectX用のデバイスやシェーダーの設定など描画関連の設定を行います。
    内容はポリゴン描画などと変わらないため省略します。

ポリゴンの生成

頂点データの定義

後で生成する頂点データの定義を事前に行います。

  1. シェーダー(HLSL)に渡すためのデータとして設定します。
    頂点データとして「何のデータを持たせてるか、何を意味しているのか」を定義します。

    頂点に位置と色の情報を持たせるため、InputElementを使って2つ定義しています。 そしてそれぞれのInputElementで「セマンティスク名、セマンティクスのインデックス番号値、データの型、オフセット値、スロット値」の順に設定しています。

    // バッファの構造であるLayoutを定義・生成
    var layout = new InputLayout(
        _device,
        ShaderSignature.GetInputSignature(vertexShaderByteCode),
        new[]
            {
                new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0),
                new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0)
            });
    

    セマンティクス名

    シェーダーに渡す値をどう使うか用途の応じた種類があります。「POSITION(位置)」「COLOR(色)」「NORMAL(法線)」「TEXCOORD0(1番目UV値)」等です。

    セマンティスクのインデックス番号

    セマンティクスの何番目のものとして定義しているか区別・指定するものです。例えば幾つかのセマンティクスをひとまとめにしたデータを渡した場合に何番目部分を使うのか指定したりします。

    データの型

    値として使う型・大きさ・順番などを指定するものです。どちらも32bit=4byteでRGBAの4種類定義しています。よってサイズは、4byte×RGBA(4種類)=16byteとなります。

    オフセット値

    現在のInputElement要素の定義位置を指定します。データの型に合わせたサイズ分、開始位置をズラしていく(オフセット値を指定する)ことになります。1つ目に定義しているPOSITIONのサイズは16byteなので、2つ目に定義しているCOLORの開始位置は16となります。もし3番目の要素を定義した場合、COLORのサイズ分さらにズレるため、開始位置は32になります。

    スロット値

    入力アセンブラーを識別する値(0~15)です。今回は使用しません。

頂点データの宣言

頂点データの定義に沿ってデータを生成します。

  1. 頂点データを宣言と同時に生成して、頂点バッファのインスタンスを生成します。
    データの順番はInputLayoutで宣言した順となります。今回だと「位置、色」の順となります。
    // 頂点データから頂点バッファをインスタンス化
    var vertices = Buffer.Create(_device, BindFlags.VertexBuffer, new[]
                          {
                              // Front:赤
                              new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
                              new Vector4(-1.0f,  1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
                              new Vector4( 1.0f,  1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
                              new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
                              new Vector4( 1.0f,  1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
                              new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
    
                              // Back:緑
                              new Vector4(-1.0f, -1.0f,  1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4( 1.0f,  1.0f,  1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4(-1.0f,  1.0f,  1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4(-1.0f, -1.0f,  1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4( 1.0f, -1.0f,  1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4( 1.0f,  1.0f,  1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
    
                              // Top:青
                              new Vector4(-1.0f, 1.0f, -1.0f,  1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4(-1.0f, 1.0f,  1.0f,  1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4( 1.0f, 1.0f,  1.0f,  1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4(-1.0f, 1.0f, -1.0f,  1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4( 1.0f, 1.0f,  1.0f,  1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4( 1.0f, 1.0f, -1.0f,  1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
    
                              // Bottom:黄色
                              new Vector4(-1.0f,-1.0f, -1.0f,  1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4( 1.0f,-1.0f,  1.0f,  1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4(-1.0f,-1.0f,  1.0f,  1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4(-1.0f,-1.0f, -1.0f,  1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4( 1.0f,-1.0f, -1.0f,  1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
                              new Vector4( 1.0f,-1.0f,  1.0f,  1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
    
                              // Left:紫
                              new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4(-1.0f, -1.0f,  1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4(-1.0f,  1.0f,  1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4(-1.0f,  1.0f,  1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
                              new Vector4(-1.0f,  1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
    
                              // Right:水色
                              new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
                              new Vector4( 1.0f,  1.0f,  1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
                              new Vector4( 1.0f, -1.0f,  1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
                              new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
                              new Vector4( 1.0f,  1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
                              new Vector4( 1.0f,  1.0f,  1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
                    });
    
  2. 先ほどの頂点バッファインスタンスを描画対象に設定します。

リソースのバッファ生成

フォーム自体をリサイズされても、正しく描画するためにリサイズされても大丈夫なバッファ生成を行います。

  1. リサイズされた場合に行う必要のある設定を別関数化して作ります。
  2. 初期設定を再度行うか判定する変数を定義します。

    /// リサイズによる初期設定を行うか判定する
    /// ※それにより描画用のテクスチャを宣言する
    bool userResized = true;
    
  3. 別関数化した初期化処理を呼びます。リサイズされている場合、再初期化を行う必要があるためMainLoop()内で行ってください。
    // リサイズした場合の処理
    if (userResized)
    {
        CreateBuffer();
    
        // サイズ変更処理が完了したのでフラグ初期化
        userResized = false;
    }
    

ポリゴンの描画

  1. 3D座標変換を行うために使う変数を定義します。描画時に、3次元の座標から画面上の座標に変換する計算で使います。

    /// ビュー変換行列
    Matrix View { get; set; } = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY);
    /// 射影変換行列
    Matrix Projection { get; set; } = Matrix.Identity;
    
  2. 3D描画を開始します。描画は毎フレーム行うためMainLoop()内で行ってください。
    アクセスしやすいように一時変数で定義します。
    var context = _device.ImmediateContext;
    
  3. 六面体を後々回転させるための変数を定義・生成します。
    var time = 0.0f;
    
  4. 画面を初期化します。
    今回は背景を黒で初期化しています。

    また今回から3D座標変換を行う=奥行きを伴った描画をするため、描画前に奥行き判定に使っているステンシルを毎回初期化します。

    // 奥行判定につかうステンシルを初期化する
    context.ClearDepthStencilView(_DepthView, DepthStencilClearFlags.Depth, 1.0f, 0);
    // 描画対象を指定色で初期化する
    context.ClearRenderTargetView(_RenderTarget3D, SharpDX.Color.Black);
    
  5. 3D座標変換を行います。

    // WorldViewProjの変換行列更新
    var viewProj = Matrix.Multiply(View, Projection);
    var worldViewProj = Matrix.RotationX(time) * Matrix.RotationY(time * 2.0F) * Matrix.RotationZ(time * 0.7F) * viewProj;
    worldViewProj.Transpose();
    // シェーダーに値を設定
    context.UpdateSubresource(ref worldViewProj, _ContantBuffer);
    
  6. 3D描画でポリゴンを描画します。
    Draw()では「頂点データの宣言」で作った頂点数と開始する頂点番号の2つを指定してください。
    今回は、サイコロのような6面体×1面に2ポリゴン(6頂点)=36頂点を描画に使うためDraw(36, 0)と指定しています。
    // 指定した頂点バッファを描画する
    context.Draw(36, 0);
    
  7. 描画したポリゴン(バックバッファにキューされたバッファ)を画面に反映します。
    // 描画を反映する
    _SwapChain.Present(0, PresentFlags.None);
    

おまけ1:ポリゴンの回転

この1つ前の段階でも六面体の描画と座標変換が出来ているのですが少し分かりづらいです。
よって、3次元描画になっていることを分かりやすくするため、六面体を回転させてみましょう。

  1. 回転とは「時間によって角度が変わる」ことなため、まずは時間が測定できるようStopwatchを作ります。
    using System.Diagnostics;
    
    /// 時間測定用のストップウォッチ
    Stopwatch clock = new Stopwatch();
    
  2. 時間の測定を開始するためExec()内で行ってください。
    // 時間計測開始
    clock?.Start();
    
  3. MainLoop()内で定義していたtimeを、「時間とともに値が変化する」ように修正してください。
    var time = clock.ElapsedMilliseconds / 1000.0f;
    

おまけ2:リサイズ時の初期化呼び出し

フォームのリサイズができた場合、初期化呼び出しを行うように修正します。
※リサイズできないフォームアプリの場合は不要です。

  1. GameForm()でフォームのリサイズが行われた場合にフラグが立つイベント用関数を登録しておきます。
    今回は、関数を用意する手間暇を考えてラムダ式形式で記述しています。
    // リサイズした際の処理を登録する
    this.Resize += (object sender, EventArgs e) =>
    {
        userResized = true;
    };
    

コード

Program.cs

GameForm.cs

MiniCube.fx