SkyBoxを実装した
SkyBoxを実装した。
といっても、リフレクションの前哨戦みたいなものだが。
リフレクションは基本的には環境マップで実装される。理想的には、そのオブジェクトを中心とする環境マップを一個一個作ることだが、動的な環境マップとなるとあまりにも現実的ではない。そのため、環境マップを入れ子構造にし、リフレクションの精度を高めたいところに小さな環境マップを作成するという方針が、近年のゲームエンジンの基本戦略である。
さて、そんな入れ子構造の最も外側の環境マップ、それがスカイボックスだ。そして、環境マップとしてだけではなく、シーンの背景としても活用される。
実装としてはいたって単純で、キューブテクスチャ―と、頂点法線と面の向きを反転した内側向きのボックスを用意する。
それをシーンを包むように巨大にスケールさせ、ボックスにキューブテクスチャをマッピングすれば完成だ。とても簡単である。今回の実装にあたって一番苦労したのはボックスモデルの用意だ。手元にちょうど良い.xモデルを吐き出せる3Dモデリングソフトがなかったので、手打ちで作った。
にしても、書くことがないので、仕方なしに文字数を稼ぐために、DirectXのキューブテクスチャの初期化処理でも張ることにする。こんな手続きじみたもの、なんの技術的な価値もないが....。適当に抜粋する。
// device: ID3D11Device width: テクスチャ一枚の横幅 height: 縦幅 textures: 6枚のテクスチャ param: テクスチャ ComPtr<ID3D11Texture2D> mTexture; ComPtr<ID3D11ShaderResourceView> mView; ComPtr<ID3D11SamplerState> mSampler; std::vector<D3D11_SUBRESOURCE_DATA> initData; param.width = textures[0].Width(); param.height = textures[0].Height(); param.arraySize = textures.size(); initData.resize(6); for (int i = 0; i < param.arraySize; i++) { initData[i].pSysMem = textures[i].get(); initData[i].SysMemPitch = textures[i].Stride(); } D3D11_TEXTURE2D_DESC desc; desc.Width = width; desc.Height = height; desc.MipLevels = 1; desc.ArraySize = 6; desc.Format = CastToD3D11Format<DXGI_FORMAT>(param.format); desc.SampleDesc.Count = 1; desc.SampleDesc.Quality = 0; desc.Usage = CastToD3D11Format<D3D11_USAGE>(param.usage); desc.BindFlags = CastToD3D11Format<UINT>(param.bindFlag); desc.CPUAccessFlags = CastToD3D11Format<UINT>(param.accessFlag); desc.MiscFlags |= D3D11_RESOURCE_MISC_FLAG::D3D11_RESOURCE_MISC_TEXTURECUBE; auto hr = device->CreateTexture2D(&desc, &initData[0], mTexture.ToCreator()); if (FAILED(hr)) { return false; } D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc = {}; SRVDesc.Format = GetShaderResourceFormat(desc.Format); SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; SRVDesc.TextureCube.MostDetailedMip = 0; SRVDesc.TextureCube.MipLevels = 1; hr = device->CreateShaderResourceView(mTexture.Get(), &SRVDesc, mView.ToCreator()); if (FAILED(hr)) { return false; } D3D11_SAMPLER_DESC samplerDesc; samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.MipLODBias = 0.0f; samplerDesc.MaxAnisotropy = 1; samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; samplerDesc.BorderColor[0] = 0; samplerDesc.BorderColor[1] = 0; samplerDesc.BorderColor[2] = 0; samplerDesc.BorderColor[3] = 0; samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; // Create the texture sampler state. hr = device->CreateSamplerState(&samplerDesc, mSampler.ToCreator()); if (FAILED(hr)) { return false; }
ああ...パラメータをDirectXの列挙体やフラグに変換する関数の説明が面倒くさい...(開発中のエンジンからコピペってきたので)
....ので省略する。大事なのはサブリソースの配列にそれぞれのテクスチャの先頭アドレスを突っ込み、それでテクスチャを初期化することだ。
また、テクスチャのMiscFlag、サブリソースビューのViewDimension、それぞれにCubeTextureの設定をする必要がある。そのほかは普通のテクスチャの初期化と一緒だろうか?
ちなみにHLSLでは前回も説明した通り、TextureCubeには位置ベクトルでアクセスする。
TextureCube TextureMap : register(t10); SamplerState samLinear : register(s10); float4 main(pixcelIn IN) : SV_Target { return TextureMap.Sample(samLinear, IN.posw.xyz); }
スカイボックスにはライティングもなにもないので、これだけになる。poswはワールド変換後の頂点座標だ。スカイボックスはワールドの原点に配置するので、ワールド座標がそのまま位置ベクトルとして使える。
レンダリングするとこうなる。
360度どこを向いても背景がついた。
現在ライティングがまだまだPBRのレベルに達していないので、実際の写真を使っているスカイボックスを使うと違和感がある....まあ仕方なし。
宙に浮いている謎の球体は、次回スカイボックスのリフレクションに使う。この球体にスカイボックスを映していこうと思う。