シェーダ(平行光線の場合)
ウィキペディア によると、「シェーダー(英: shader)とは、3次元コンピュータグラフィックスにおいて、シェーディング(陰影処理)を行うコンピュータプログラムのこと」だそうです。
本ブログの《427》~《429》では立方体を描画しましたが、今回は立方体に 平行光線による陰影処理をしてみたいと思います。
次の画像は、陰影処理した立方体です。
回転角度が 0度のとき
回転角度が 30度のとき
回転角度が 45度のとき
回転角度が 60度のとき
下記はプログラムですが、分量が多いので今回と次回は Sample3DSceneRenderer.cppのコードについて のみです。
※次の3つのプログラムに関しては、《433》以降になります。
ShaderStructures.h
SamplePixelShader.hlsl
SampleVertexShader.hlsl
以下は、Sample3DSceneRenderer.cpp のコードです。
※コードは長いですが、陰影処理に関する箇所以外はデフォルトのサンプルアプリのままです。
(枠から右にはみだした部分のコードを見る際は、最下部のスクロールバーをご利用ください。)
#include "pch.h"
#include "Sample3DSceneRenderer.h"
#include "..\Common\DirectXHelper.h"
using namespace App20;
using namespace DirectX;
using namespace Windows::Foundation;
// ファイルから頂点とピクセルシェーダを読み込み、キューブのジオメトリをインスタンス化します。
Sample3DSceneRenderer::Sample3DSceneRenderer(const std::shared_ptr<DX::DeviceResources>& deviceResources) :
m_loadingComplete(false),
m_degreesPerSecond(45),
m_indexCount(0),
m_tracking(false),
m_deviceResources(deviceResources)
{
CreateDeviceDependentResources();
CreateWindowSizeDependentResources();
}
// ウィンドウのサイズが変更されたときに、ビューのパラメータを初期化します。
void Sample3DSceneRenderer::CreateWindowSizeDependentResources()
{
Size outputSize = m_deviceResources->GetOutputSize();
float aspectRatio = outputSize.Width / outputSize.Height;
float fovAngleY = 70.0f * XM_PI / 180.0f;
// これは、アプリケーションが縦向きビューまたはスナップビュー内にあるときに行うことのできる変更の簡単な例です。
if (aspectRatio < 1.0f)
{
fovAngleY *= 2.0f;
}
// OrientationTransform3D マトリックスは、シーンの方向を表示方向と正しく一致させるため、ここで事後乗算されます。
// この事後乗算ステップは、スワップチェーンのターゲットビットマップに対して行われるすべての描画呼び出しで実行する必要があります。
// 他のターゲットに対する呼び出しでは、適用する必要はありません。
// このサンプルでは、行優先のマトリックスを使用した右辺座標系を使用しています。
XMMATRIX perspectiveMatrix = XMMatrixPerspectiveFovRH(
fovAngleY,
aspectRatio,
0.01f,
100.0f
);
XMFLOAT4X4 orientation = m_deviceResources->GetOrientationTransform3D();
XMMATRIX orientationMatrix = XMLoadFloat4x4(&orientation);
XMStoreFloat4x4(
&m_constantBufferData.projection,
XMMatrixTranspose(perspectiveMatrix * orientationMatrix)
);
// 視点は (0,0.7,1.5) の位置にあり、y軸に沿って上方向のポイント (0,-0.1,0) を見ています。
static const XMVECTORF32 eye = { 0.0f, 0.7f, 1.5f, 0.0f };
static const XMVECTORF32 at = { 0.0f, -0.1f, 0.0f, 0.0f };
static const XMVECTORF32 up = { 0.0f, 1.0f, 0.0f, 0.0f };
XMStoreFloat4x4(&m_constantBufferData.view, XMMatrixTranspose(XMMatrixLookAtRH(eye, at, up)));
}
// フレームごとに 1回呼び出し、キューブを回転させてから、モデルおよびビューのマトリックスを計算します。
void Sample3DSceneRenderer::Update(DX::StepTimer const& timer)
{
if (!m_tracking)
{
// 度をラジアンに変換し、秒を回転角度に変換します。
float radiansPerSecond = XMConvertToRadians(m_degreesPerSecond);
double totalRotation = timer.GetTotalSeconds() * radiansPerSecond;
float radians = static_cast<float>(fmod(totalRotation, XM_2PI));
Rotate(radians);
}
}
// 3Dキューブモデルを、ラジアン単位で設定された大きさだけ回転させます。
void Sample3DSceneRenderer::Rotate(float radians)
{
// 更新されたモデルマトリックスをシェーダに渡す準備をします。
XMStoreFloat4x4(&m_constantBufferData.model, XMMatrixTranspose(XMMatrixRotationY(radians)));
}
void Sample3DSceneRenderer::StartTracking()
{
m_tracking = true;
}
// 追跡時に、出力画面の幅方向を基準としてポインタの位置を追跡することにより、3Dキューブを Y軸に沿って回転させることができます。
void Sample3DSceneRenderer::TrackingUpdate(float positionX)
{
if (m_tracking)
{
float radians = XM_2PI * 2.0f * positionX / m_deviceResources->GetOutputSize().Width;
Rotate(radians);
}
}
void Sample3DSceneRenderer::StopTracking()
{
m_tracking = false;
}
// 頂点とピクセルシェーダを使用して、1つのフレームを描画します。
void Sample3DSceneRenderer::Render()
{
// 読み込みは非同期です。読み込みが完了した後にのみ描画してください。
if (!m_loadingComplete)
{
return;
}
auto context = m_deviceResources->GetD3DDeviceContext();
// 定数バッファを準備して、グラフィックスデバイスに送信します。
context->UpdateSubresource1(
m_constantBuffer.Get(),
0,
NULL,
&m_constantBufferData,
0,
0,
0
);
// 各頂点は、VertexPositionColor構造体の 1つのインスタンスです。
UINT stride = sizeof(VertexPositionColor);
UINT offset = 0;
context->IASetVertexBuffers(
0,
1,
m_vertexBuffer.GetAddressOf(),
&stride,
&offset
);
context->IASetIndexBuffer(
m_indexBuffer.Get(),
DXGI_FORMAT_R16_UINT, // 各インデックスは、1つの 16ビット符号なし整数(short) です。
0
);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->IASetInputLayout(m_inputLayout.Get());
// 頂点シェーダをアタッチします。
context->VSSetShader(
m_vertexShader.Get(),
nullptr,
0
);
// 定数バッファをグラフィックスデバイスに送信します。
context->VSSetConstantBuffers1(
0,
1,
m_constantBuffer.GetAddressOf(),
nullptr,
nullptr
);
// ピクセルシェーダをアタッチします。
context->PSSetShader(
m_pixelShader.Get(),
nullptr,
0
);
// オブジェクトを描画します。
context->DrawIndexed(
m_indexCount,
0,
0
);
}
void Sample3DSceneRenderer::CreateDeviceDependentResources()
{
// シェーダを非同期で読み込みます。
auto loadVSTask = DX::ReadDataAsync(L"SampleVertexShader.cso");
auto loadPSTask = DX::ReadDataAsync(L"SamplePixelShader.cso");
// 頂点シェーダファイルを読み込んだ後、シェーダと入力レイアウトを作成します。
auto createVSTask = loadVSTask.then([this](const std::vector<byte>& fileData) {
DX::ThrowIfFailed(
m_deviceResources->GetD3DDevice()->CreateVertexShader(
&fileData[0],
fileData.size(),
nullptr,
&m_vertexShader
)
);
static const D3D11_INPUT_ELEMENT_DESC vertexDesc [] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
DX::ThrowIfFailed(
m_deviceResources->GetD3DDevice()->CreateInputLayout(
vertexDesc,
ARRAYSIZE(vertexDesc),
&fileData[0],
fileData.size(),
&m_inputLayout
)
);
});
// ピクセルシェーダファイルを読み込んだ後、シェーダと定数バッファを作成します。
auto createPSTask = loadPSTask.then([this](const std::vector<byte>& fileData) {
DX::ThrowIfFailed(
m_deviceResources->GetD3DDevice()->CreatePixelShader(
&fileData[0],
fileData.size(),
nullptr,
&m_pixelShader
)
);
CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer) , D3D11_BIND_CONSTANT_BUFFER);
DX::ThrowIfFailed(
m_deviceResources->GetD3DDevice()->CreateBuffer(
&constantBufferDesc,
nullptr,
&m_constantBuffer
)
);
});
// 両方のシェーダの読み込みが完了したら、メッシュを作成します。
auto createCubeTask = (createPSTask && createVSTask).then([this] () {
// メッシュの頂点座標と頂点法線ベクトルを読み込みます。
static VertexPositionColor verticesPosNrm[24];
verticesPosNrm[ 0].pos = XMFLOAT3(-0.5f, -0.5f, -0.5f);
verticesPosNrm[ 1].pos = XMFLOAT3(-0.5f, -0.5f, 0.5f);
verticesPosNrm[ 2].pos = XMFLOAT3(-0.5f, 0.5f, -0.5f);
verticesPosNrm[ 3].pos = XMFLOAT3(-0.5f, 0.5f, 0.5f);
verticesPosNrm[ 4].pos = XMFLOAT3( 0.5f, -0.5f, -0.5f);
verticesPosNrm[ 5].pos = XMFLOAT3( 0.5f, -0.5f, 0.5f);
verticesPosNrm[ 6].pos = XMFLOAT3( 0.5f, 0.5f, -0.5f);
verticesPosNrm[ 7].pos = XMFLOAT3( 0.5f, 0.5f, 0.5f);
verticesPosNrm[ 8].pos = XMFLOAT3(-0.5f, -0.5f, -0.5f);
verticesPosNrm[ 9].pos = XMFLOAT3(-0.5f, -0.5f, 0.5f);
verticesPosNrm[10].pos = XMFLOAT3(-0.5f, 0.5f, -0.5f);
verticesPosNrm[11].pos = XMFLOAT3(-0.5f, 0.5f, 0.5f);
verticesPosNrm[12].pos = XMFLOAT3( 0.5f, -0.5f, -0.5f);
verticesPosNrm[13].pos = XMFLOAT3( 0.5f, -0.5f, 0.5f);
verticesPosNrm[14].pos = XMFLOAT3( 0.5f, 0.5f, -0.5f);
verticesPosNrm[15].pos = XMFLOAT3( 0.5f, 0.5f, 0.5f);
verticesPosNrm[16].pos = XMFLOAT3(-0.5f, -0.5f, -0.5f);
verticesPosNrm[17].pos = XMFLOAT3(-0.5f, -0.5f, 0.5f);
verticesPosNrm[18].pos = XMFLOAT3(-0.5f, 0.5f, -0.5f);
verticesPosNrm[19].pos = XMFLOAT3(-0.5f, 0.5f, 0.5f);
verticesPosNrm[20].pos = XMFLOAT3( 0.5f, -0.5f, -0.5f);
verticesPosNrm[21].pos = XMFLOAT3( 0.5f, -0.5f, 0.5f);
verticesPosNrm[22].pos = XMFLOAT3( 0.5f, 0.5f, -0.5f);
verticesPosNrm[23].pos = XMFLOAT3( 0.5f, 0.5f, 0.5f);
verticesPosNrm[ 0].nrm = XMFLOAT3(-1.0f, 0.0f, 0.0f);
verticesPosNrm[ 1].nrm = XMFLOAT3(-1.0f, 0.0f, 0.0f);
verticesPosNrm[ 2].nrm = XMFLOAT3(-1.0f, 0.0f, 0.0f);
verticesPosNrm[ 3].nrm = XMFLOAT3(-1.0f, 0.0f, 0.0f);
verticesPosNrm[ 4].nrm = XMFLOAT3( 1.0f, 0.0f, 0.0f);
verticesPosNrm[ 5].nrm = XMFLOAT3( 1.0f, 0.0f, 0.0f);
verticesPosNrm[ 6].nrm = XMFLOAT3( 1.0f, 0.0f, 0.0f);
verticesPosNrm[ 7].nrm = XMFLOAT3( 1.0f, 0.0f, 0.0f);
verticesPosNrm[ 8].nrm = XMFLOAT3( 0.0f, 0.0f, -1.0f);
verticesPosNrm[ 9].nrm = XMFLOAT3( 0.0f, 0.0f, 1.0f);
verticesPosNrm[10].nrm = XMFLOAT3( 0.0f, 0.0f, -1.0f);
verticesPosNrm[11].nrm = XMFLOAT3( 0.0f, 0.0f, 1.0f);
verticesPosNrm[12].nrm = XMFLOAT3( 0.0f, 0.0f, -1.0f);
verticesPosNrm[13].nrm = XMFLOAT3( 0.0f, 0.0f, 1.0f);
verticesPosNrm[14].nrm = XMFLOAT3( 0.0f, 0.0f, -1.0f);
verticesPosNrm[15].nrm = XMFLOAT3( 0.0f, 0.0f, 1.0f);
verticesPosNrm[16].nrm = XMFLOAT3( 0.0f, -1.0f, 0.0f);
verticesPosNrm[17].nrm = XMFLOAT3( 0.0f, -1.0f, 0.0f);
verticesPosNrm[18].nrm = XMFLOAT3( 0.0f, 1.0f, 0.0f);
verticesPosNrm[19].nrm = XMFLOAT3( 0.0f, 1.0f, 0.0f);
verticesPosNrm[20].nrm = XMFLOAT3( 0.0f, -1.0f, 0.0f);
verticesPosNrm[21].nrm = XMFLOAT3( 0.0f, -1.0f, 0.0f);
verticesPosNrm[22].nrm = XMFLOAT3( 0.0f, 1.0f, 0.0f);
verticesPosNrm[23].nrm = XMFLOAT3( 0.0f, 1.0f, 0.0f);
D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
vertexBufferData.pSysMem = verticesPosNrm;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(verticesPosNrm), D3D11_BIND_VERTEX_BUFFER);
DX::ThrowIfFailed(
m_deviceResources->GetD3DDevice()->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&m_vertexBuffer
)
);
// メッシュのインデックスを読み込みます。インデックスの 3つ 1組の値のそれぞれは、画面上に描画される三角形を表します。
// たとえば、0,2,1 とは、頂点バッファのインデックス 0, 2, 1 にある頂点が、このメッシュの最初の三角形を構成することを意味します。
static const unsigned short cubeIndices [] =
{
0,2,1, // -x
1,2,3,
4,5,6, // +x
5,7,6,
16,17,21, // -y
16,21,20,
18,22,23, // +y
18,23,19,
8,12,10, // -z
10,12,14,
9,11,15, // +z
9,15,13,
};
m_indexCount = ARRAYSIZE(cubeIndices);
D3D11_SUBRESOURCE_DATA indexBufferData = {0};
indexBufferData.pSysMem = cubeIndices;
indexBufferData.SysMemPitch = 0;
indexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC indexBufferDesc(sizeof(cubeIndices), D3D11_BIND_INDEX_BUFFER);
DX::ThrowIfFailed(
m_deviceResources->GetD3DDevice()->CreateBuffer(
&indexBufferDesc,
&indexBufferData,
&m_indexBuffer
)
);
});
// キューブが読み込まれたら、オブジェクトを描画する準備が完了します。
createCubeTask.then([this] () {
m_loadingComplete = true;
});
}
void Sample3DSceneRenderer::ReleaseDeviceDependentResources()
{
m_loadingComplete = false;
m_vertexShader.Reset();
m_inputLayout.Reset();
m_pixelShader.Reset();
m_constantBuffer.Reset();
m_vertexBuffer.Reset();
m_indexBuffer.Reset();
}