準備は終わったかと思いきや……まだでした。
実際に何かを描画するためには、デバイスコンテキスト自体の準備が必要。
今から以下の4つの項目を設定していく。
物体を画面のどこに表示するのかを決める機能。
Mayaで作ったデータも、動いたり、回転したりすれば表示位置が変わる。
もちろんカメラが動いた場合も変わる。
頂点シェーダーでは頂点ごとの表示位置を決める。
それが決まったらラスタライザーが、どのピクセルを光らせるかを求める。
ラスタライザーによって求められた表示ピクセルを何色にするかを求める。
ポリゴンの色やテクスチャの色に影やハイライトの要素を加えて、最終的な色を決定する。
「1頂点にどんな情報を持たせるか」のことを頂点インプットレイアウトという。
一言で言えば、頂点情報のフォーマット。
最低限その頂点の位置は必要。それ以外に色や法線、UV情報などを持たせることができる。
まずは『頂点シェーダー』と『ピクセルシェーダー』をまとめて準備する。
これらの処理はHLSLという言語を使って別ファイルで用意しておき、実行する時にコンパイルされる。
細かい話は後期の『シェーダープログラミング』という科目で教えるので、今はあまり深く考えずに進めていこう。
ではHLSLファイルを作成する。
ソースファイルを使いする時と同じ要領で「新しい項目」
左側で「HLSL」を選び、名前を「Simple3D」にする。
できたファイルを「右クリック」→「プロパティ」。
図のように上から順に3か所設定する。
既に書かれているコードは消して、次の内容をコピペする。
//───────────────────────────────────────
// コンスタントバッファ
// DirectX 側から送信されてくる、ポリゴン頂点以外の諸情報の定義
//───────────────────────────────────────
cbuffer global
{
float4x4 matWVP; // ワールド・ビュー・プロジェクションの合成行列
};
//───────────────────────────────────────
// 頂点シェーダー出力&ピクセルシェーダー入力データ構造体
//───────────────────────────────────────
struct VS_OUT
{
float4 pos : SV_POSITION; //位置
};
//───────────────────────────────────────
// 頂点シェーダ
//───────────────────────────────────────
VS_OUT VS(float4 pos : POSITION)
{
//ピクセルシェーダーへ渡す情報
VS_OUT outData;
//ローカル座標に、ワールド・ビュー・プロジェクション行列をかけて
//スクリーン座標に変換し、ピクセルシェーダーへ
outData.pos = mul(pos, matWVP);
//まとめて出力
return outData;
}
//───────────────────────────────────────
// ピクセルシェーダ
//───────────────────────────────────────
float4 PS(VS_OUT inData) : SV_Target
{
return float4(1,1,1,1);
}
別言語と言いながら、C言語とほとんど書き方が同じたということが分かるでしょう。
では、実行時にSImple3D.hlslをロードしてコンパイルするコードを書いていこう。
まずは、必要なインクルードとリンカが必要。
<Direct3D.h>
#pragma once
//インクルード
#include <d3d11.h>
//リンカ
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
<Direct3D.cpp>
#include <d3dcompiler.h>
#include "Direct3D.h"
次に、出来上がった二つのシェーダーを入れるための変数を用意する。
<DIrect3D.cpp>
namespace Direct3D
{
ID3D11Device* pDevice = nullptr; //デバイス
ID3D11DeviceContext* pContext = nullptr; //デバイスコンテキスト
IDXGISwapChain* pSwapChain = nullptr; //スワップチェイン
ID3D11RenderTargetView* pRenderTargetView = nullptr; //レンダーターゲットビュー
ID3D11VertexShader* pVertexShader = nullptr; //頂点シェーダー
ID3D11PixelShader* pPixelShader = nullptr; //ピクセルシェーダー
}
あとはDirect3D::Initializeの続きに処理をかくことになるが、関数が長くなってきたので処理を分けよう。
<Direct3D.h>
namespace Direct3D
{
:
:
//初期化
void Initialize(int winW, int winH, HWND hWnd);
//シェーダー準備
void InitShader();
//描画開始
void BeginDraw();
<Direct3D.cpp>
void Direct3D::Initialize(int winW, int winH, HWND hWnd)
{
:
:
:
//シェーダー準備
InitShader();
}
//シェーダー準備
void Direct3D::InitShader()
{
}
では、この関数の中に頂点シェーダーをコンパイルする処理を書く。
void Direct3D::InitShader()
{
// 頂点シェーダの作成(コンパイル)
ID3DBlob *pCompileVS = nullptr;
D3DCompileFromFile(L"Simple3D.hlsl", nullptr, nullptr, "VS", "vs_5_0", NULL, 0, &pCompileVS, NULL);
pDevice->CreateVertexShader(pCompileVS->GetBufferPointer(), pCompileVS->GetBufferSize(), NULL, &pVertexShader);
pCompileVS->Release();
}
専用の変数を用意し、「Simple3D.hlsl」というファイルの中の「VS」という関数を「バージョン5」でコンパイル。
それをつかって頂点シェーダーを作成する。
終わったらコンパイルしたデータはいらないので削除する……といった流れ。
バージョンに関しては、グラボごとに使える限界が決まっている。
授業では最新機能を教える予定は無い(=教えられない(=俺も知らない))ので、もっと低い値を設定しても構わない。
続いてピクセルシェーダー
void Direct3D::InitShader()
{
:
:
// ピクセルシェーダの作成(コンパイル)
ID3DBlob *pCompilePS = nullptr;
D3DCompileFromFile(L"Simple3D.hlsl", nullptr, nullptr, "PS", "ps_5_0", NULL, 0, &pCompilePS, NULL);
pDevice->CreatePixelShader(pCompilePS->GetBufferPointer(), pCompilePS->GetBufferSize(), NULL, &pPixelShader);
pCompilePS->Release();
}
頂点インプットレイアウトは「頂点シェーダーにどんな情報を渡すか」を決めるものなので、ここでやってしまおう。
まずは変数を用意。
namespace Direct3D
{
:
:
ID3D11VertexShader* pVertexShader = nullptr; //頂点シェーダー
ID3D11PixelShader* pPixelShader = nullptr; //ピクセルシェーダー
ID3D11InputLayout* pVertexLayout = nullptr; //頂点インプットレイアウト
}
頂点シェーダーが完成した後、コンパイルしたものを開放する前のところに次のように書く。
void Direct3D::InitShader()
{
// 頂点シェーダの作成(コンパイル)
ID3DBlob *pCompileVS = nullptr;
D3DCompileFromFile(L"Simple3D.hlsl", nullptr, nullptr, "VS", "vs_5_0", NULL, 0, &pCompileVS, NULL);
pDevice->CreateVertexShader(pCompileVS->GetBufferPointer(), pCompileVS->GetBufferSize(), NULL, &pVertexShader);
//頂点インプットレイアウト
D3D11_INPUT_ELEMENT_DESC layout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, //位置
};
pDevice->CreateInputLayout(layout, 1, pCompileVS->GetBufferPointer(), pCompileVS->GetBufferSize(), &pVertexLayout);
pCompileVS->Release();
// ピクセルシェーダの作成(コンパイル)
とりあえず、最もシンプルな形である「位置情報のみ」取り扱うようにする。
各設定項目の詳細を書くの面倒なので、授業で説明する。
ラスタライザーは、必要な項目を設定すればいいだけなので簡単。
まずは変数を用意。
namespace Direct3D
{
:
:
ID3D11VertexShader* pVertexShader = nullptr; //頂点シェーダー
ID3D11PixelShader* pPixelShader = nullptr; //ピクセルシェーダー
ID3D11RasterizerState* pRasterizerState = nullptr; //ラスタライザー
}
シェーダーをコンパイルした続きに、以下のように書く。
void Direct3D::InitShader()
{
:
:
:
//ラスタライザ作成
D3D11_RASTERIZER_DESC rdc = {};
rdc.CullMode = D3D11_CULL_BACK;
rdc.FillMode = D3D11_FILL_SOLID;
rdc.FrontCounterClockwise = FALSE;
pDevice->CreateRasterizerState(&rdc, &pRasterizerState);
}
3つの項目を設定している。
CullModeはポリゴンの表面だけ描画するか、両面描画するかなどを設定する。ふつうは表面だけ。
FillModeはポリゴンを面として描画するか、線だけ(ワイヤーフレーム)描画するかなど。ふつうは面。
FrontCounterClockwiseはポリゴンのどっちの面を「表側」とみなすか。ふつうは「頂点が時計回りに見える面」は表。
頂点シェーダー、ピクセルシェーダー、頂点インプットレイアウト、ラスタライザーの4つが用意できたので、
デバイスコンテキストに対して「この4つを使って描画してくれ」と指示する。
void Direct3D::InitShader()
{
:
:
:
//それぞれをデバイスコンテキストにセット
pContext->VSSetShader(pVertexShader, NULL, 0); //頂点シェーダー
pContext->PSSetShader(pPixelShader, NULL, 0); //ピクセルシェーダー
pContext->IASetInputLayout(pVertexLayout); //頂点インプットレイアウト
pContext->RSSetState(pRasterizerState); //ラスタライザー
}
毎度忘れがちになる解放処理。
作ったものは必ず開放する。
//解放処理
void Direct3D::Release()
{
pRasterizerState->Release();
pVertexLayout->Release();
pPixelShader->Release();
pVertexShader->Release();
pRenderTargetView->Release();
pSwapChain->Release();
pContext->Release();
pDevice->Release();
}