シェーダ(点光源の場合)
ファイル(F) -> 新規作成(N) -> プロジェクト(P)
Visual C++ -> Windowsユニバーサル -> DirectX11アプリ(ユニバーサルWindows)
の順に操作して VS2017 を開始すると、3Dアニメーションのサンプルアプリが自動作成されます。
このサンプルアプリを改変することで、DirectX11アプリ(ユニバーサルWindows) について、ほんの僅かではありますが 学んできました。
直近の3回( 《434》~《436》 )も、点光源をつかって立方体に光を照射する場合についての内容だったので、前回までに扱ったコードと重複してしまいますが、関連するプログラムコードを まとめて載せておきます。
下記のプログラムにより、次のような画像が表示されます。
以下は、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.9,1.5) の位置にあり、y軸に沿って上方向のポイント (0,-0.1,0) を見ています。
static const XMVECTORF32 eye = { 0.0f, 0.9f, 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 [] =
{
// DXGI_FORMAT_R32G32B32_FLOAT ・・・ 3成分_96ビット浮動小数点フォーマット
// (カラーチャネルあたり32ビット)
// D3D11_INPUT_PER_VERTEX_DATA ・・・ 入力データは頂点単位のデータ
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
/*
※ https://msdn.microsoft.com/ja-jp/library/ee416244(v=vs.85).aspx
typedef struct D3D11_INPUT_ELEMENT_DESC {
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D11_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D11_INPUT_ELEMENT_DESC;
SemanticName
シェーダー入力署名でこの要素に関連付けられている HLSL セマンティクスです。
SemanticIndex
要素のセマンティクス インデックスです。
セマンティクス インデックスは、整数のインデックス番号によってセマンティクスを修飾するものです。
セマンティクス インデックスは、同じセマンティクスの要素が複数ある場合にのみ必要です。
たとえば、4x4 のマトリクスには 4 個の構成要素があり、
それぞれの構成要素にはセマンティクス名として matrix が付けられますが、
4 個の構成要素にはそれぞれ異なるセマンティクス インデックス (0、1、2、3) が割り当てられます。
Format
要素データのデータ型です。「DXGI_FORMAT」を参照してください。
InputSlot
入力アセンブラーを識別する整数値です (「入力スロット」を参照してください)。
有効な値は 0 〜 15 であり、D3D11.h で定義されています。
AlignedByteOffset
(省略可能)各要素間のオフセット (バイト単位) です。
前の要素の直後で現在の要素を定義するには、D3D11_APPEND_ALIGNED_ELEMENT を使用すると便利です。
必要に応じてパッキング処理も指定できます。
InputSlotClass
単一の入力スロットの入力データ クラスを識別します (「D3D11_INPUT_CLASSIFICATION」を参照してください)。
InstanceDataStepRate
バッファーの中で要素の 1 つ分進む前に、インスタンス単位の同じデータを使用して描画するインスタンスの数です。
頂点単位のデータを持つ要素 (スロット クラスは 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].col = XMFLOAT3( 0.6f, 0.6f, 1.0f);
verticesPosNrm[ 1].col = XMFLOAT3( 0.6f, 0.6f, 1.0f);
verticesPosNrm[ 2].col = XMFLOAT3( 0.6f, 0.6f, 1.0f);
verticesPosNrm[ 3].col = XMFLOAT3( 0.6f, 0.6f, 1.0f);
verticesPosNrm[ 4].col = XMFLOAT3( 1.0f, 0.0f, 0.0f);
verticesPosNrm[ 5].col = XMFLOAT3( 1.0f, 0.0f, 0.0f);
verticesPosNrm[ 6].col = XMFLOAT3( 1.0f, 0.0f, 0.0f);
verticesPosNrm[ 7].col = XMFLOAT3( 1.0f, 0.0f, 0.0f);
verticesPosNrm[ 8].col = XMFLOAT3( 1.0f, 1.0f, 0.0f);
verticesPosNrm[ 9].col = XMFLOAT3( 0.0f, 1.0f, 0.0f);
verticesPosNrm[10].col = XMFLOAT3( 1.0f, 1.0f, 0.0f);
verticesPosNrm[11].col = XMFLOAT3( 0.0f, 1.0f, 0.0f);
verticesPosNrm[12].col = XMFLOAT3( 1.0f, 1.0f, 0.0f);
verticesPosNrm[13].col = XMFLOAT3( 0.0f, 1.0f, 0.0f);
verticesPosNrm[14].col = XMFLOAT3( 1.0f, 1.0f, 0.0f);
verticesPosNrm[15].col = XMFLOAT3( 0.0f, 1.0f, 0.0f);
verticesPosNrm[16].col = XMFLOAT3( 1.0f, 0.0f, 1.0f);
verticesPosNrm[17].col = XMFLOAT3( 1.0f, 0.0f, 1.0f);
verticesPosNrm[18].col = XMFLOAT3( 0.0f, 1.0f, 1.0f);
verticesPosNrm[19].col = XMFLOAT3( 0.0f, 1.0f, 1.0f);
verticesPosNrm[20].col = XMFLOAT3( 1.0f, 0.0f, 1.0f);
verticesPosNrm[21].col = XMFLOAT3( 1.0f, 0.0f, 1.0f);
verticesPosNrm[22].col = XMFLOAT3( 0.0f, 1.0f, 1.0f);
verticesPosNrm[23].col = XMFLOAT3( 0.0f, 1.0f, 1.0f);
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();
}
以下は、ShaderStructures.h です。
#pragma once
namespace App20
{
// MVPマトリックスを頂点シェーダに送信するために使用する定数バッファ
// MVP_Matrix
// Model_Matrix(モデル変換行列), View_Matrix(ビュー変換行列), Projection_Matrix(プロジェクション変換行列)
struct ModelViewProjectionConstantBuffer
{
DirectX::XMFLOAT4X4 model;
DirectX::XMFLOAT4X4 view;
DirectX::XMFLOAT4X4 projection;
};
// 頂点シェーダへの頂点ごとのデータの送信に使用
struct VertexPositionColor
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 col;
DirectX::XMFLOAT3 nrm;
};
}
以下は、SampleVertexShader.hlsl です。
// ジオメトリを作成するために 3つの基本的な列優先のマトリックスを保存する定数バッファ
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix model;
matrix view;
matrix projection;
};
// 頂点シェーダへの入力として使用する頂点ごとのデータ
struct VertexShaderInput
{
float3 pos : POSITION;
float3 col : COLOR;
float3 nrm : NORMAL;
};
// ピクセルシェーダを通じて渡されるピクセルごとのデータ
struct PixelShaderInput
{
float4 pos : SV_POSITION; // レンダリングパイプラインで加工済みの点座標
float3 pos_ : POSITION; // 未処理のままの点座標
float3 col : COLOR;
float3 nrm : NORMAL;
};
// GPU で頂点処理を行うための簡単なシェーダ
PixelShaderInput main(VertexShaderInput input)
{
PixelShaderInput output;
float4 pos_ = float4(input.pos, 1.0f);
pos_ = mul(pos_, model);
output.pos_ = pos_.xyz;
float4 pos = float4(input.pos, 1.0f);
pos = mul(pos, model);
pos = mul(pos, view);
pos = mul(pos, projection);
output.pos = pos;
output.col = input.col;
float4 nrm = float4(normalize(input.nrm), 0.0f);
nrm = mul(nrm, model);
output.nrm = normalize(nrm.xyz);
return output;
}
以下は、SamplePixelShader.hlsl です。
// ピクセルシェーダを通じて渡されるピクセルごとのデータ
struct PixelShaderInput
{
float4 pos : SV_POSITION;
float3 pos_ : POSITION;
float3 col : COLOR;
float3 nrm : NORMAL;
};
// (補間済み)色データのパススルー関数
float4 main(PixelShaderInput input) : SV_TARGET
{
// light処理をしたピクセルの色成分を返します。
float3 light = float3(0.0, 0.8, 1.1); // 点光源の位置
float3 lightDirection = input.pos_ - light; // 光の方向ベクトル
float len = length(lightDirection); // 光の方向ベクトルを正規化(大きさを 1 にします)
float4 col = float4(input.col, 1.0);
// 正規化した法線ベクトル と 正規化した光の方向ベクトル のなす角から、
// その点の明るさの程度(0 〜 1)を算出。
float lightMagnitude = saturate(dot(input.nrm, -normalize(lightDirection)));
// 物理的には、k は距離の2乗に反比例するべきですが、
// ここでは出力画像が自分のイメージに合うように 式を変形しています。
float k = saturate(1.0f / (0.2f + 1.0f * len + 1.0 * len * len));
// k と lightMagnitude の両方の効果を使用しています。
// 光が当たらない部分にも 0.2 の割合の光を与えて真っ黒にならないようにしています。
return col * (0.8 * k * lightMagnitude + 0.2f);
}