28.Dx11の描画

今回は新しいサンプルはありません。
これまでDx12版の説明に記事数を費やしてきましたが、ここらでDx11版の描画の仕組みをまとめて紹介したいと思います。 コミット的には、最新のをダウンロードしていただければいのですが、解説はSimplSampleTestサンプルのDx11版を基本にDx11版の描画の仕組みを説明したいと思います。

この記事は、
コミットSimpleSample004追加。ライブラリ修正ありが最新の状態で記述しています。
GitHubサイト

https://github.com/WiZFramework/BaseCross

を参照して下さい。

Dx11の描画について

SimplSampleTestサンプルの実行結果は以下のようになります。
今回は、Dx11版の解説なのでBaseCrossDx11.slnを開いてください。

2016082102

Dx11を理解するのに、ぜひとも知っておいてほしいのはCOM(コンポーネントオブジェクトモデル)というオブジェクトです。
これは名前くらいは聞いたことがある方もいるかもしれないですが、ようはID3D11DeviceのようにIで始まる(これはインターフェイス、という意味)のオブジェクトです。
COMについて細かく解説するのはこの記事の目的ではないので、かいつまんで言うと、複数の環境からアクセスできるAPIという感じです。複数の環境というのはVisualC++とかC#VisualBasicとか、まあ、マイクロソフト社の言語ですね。
決してマックからとかLinuxからという意味にはなりません。ブラウザに限って言えばActiveXというCOMの親戚のようなオブジェクトがあり、マイクロソフト社のブラウザを中心に、ある程度互換性があります。
さて、DirectXは、11や12に限らず、これまでもCOMの技術によって提供されてきました。
その中でもDx11は、COMの使用幅が広がったわけです。たとえば、ID3D11Deviceは一番重要なCOMですが、このほかに
ID3D11DeviceContext
ID3D11ShaderResourceView
ID3D11Buffer
IDXGISwapChain
ID3D11SamplerState
ID3D11BlendState
ID3D11DepthStencilState
ID3D11RasterizerState
ID3D11RenderTargetView
などなど、さまざまなCOMが定義されています。ですのでDx11の理解はCOMの理解が必要というわけです。

では、どいういう理解をしておけばいいかというと、まずはCOMがポインタによって操作されるということと通常、COMは別のCOMによって構築されるということくらいでしょうか。
では最初のCOMはどのように構築されるかというと、DirectXの場合、いわゆる通常のAPIが用意されているのでそれを使います。
具体的には、BasrCrossではデバイスとスワップチェーンとデバイスコンテキストを作成するAPIがあるので、それを使ってます。


D3D11CreateDeviceAndSwapChain

というAPIがそうです。これによって作成したID3D11Device、ID3D11DeviceContext、IDXGISwapChainを使って、別のCOMを構築するわけです。
実際には、COMのバージョンを上げたり、ハードウェアデバイスを取得するように試したり、いろいろやってるわけですが、基本は上記のような形です。

さてCOMがポインタによって操作されるは、例えばD3D11CreateDeviceAndSwapChain関数は構築すると、例えば、渡したID3D11Device**に構築したポインタを代入してくれるわけです。
でもこの操作が結構厄介で、構築したCOMは自分で解放(Release()メンバ関数を使う)もやらないといけません。
慣れてればそれでもいいんですが、Release忘れも怖いのでBaseCrossでは


ComPtr temp_device;

のように、COMのshared_ptrを使っています。この型は内部で参照カウンタを管理してくれるので非常に便利です。

さてここまでの予備知識で、Dx11版の場合の処理の流れを考えてみましょう。


1、デバイスの初期化
2、各オブジェクトの初期化
3、ターン毎の全体的な描画準備
4、ターン毎各オブジェクトの描画
5、ターン毎の全体的な描画終了
6、ターン毎のプレゼント
7、ESCキーなど終了時は全体の終了処理

ざっと書けばこんな感じです。流れはDx12版と変わりません。

Dx11のデバイスの初期化

さて、まず全体的な初期化ですが、前述したD3D11CreateDeviceAndSwapChain呼び出しを中心として、レンダリングターゲットビュー、デプスステンシルビューなどを構築します。
Dx11LibプロジェクトのDeviceResources.h/cppに記述があります。
Implイディオムを使っているので、DeviceResources::Impl::CreateDeviceResources関数が初期化の実体です。
ここではまず


// Direct2D ファクトリを初期化します。
ThrowIfFailed(
    D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        __uuidof(ID2D1Factory2),
        &options,
        &m_d2dFactory
    ),
    L"Factory作成に失敗しました。",
    L"D2D1CreateFactory()",
    L"DeviceResources::Impl::CreateDeviceResources()"
);


// DirectWrite ファクトリを初期化します。
ThrowIfFailed(
    DWriteCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(IDWriteFactory2),
        &m_dwriteFactory
    ),
    L"DirectWrite ファクトリ作成に失敗しました。",
    L"DWriteCreateFactory()",
    L"DeviceResources::Impl::CreateDeviceResources()"
);

ThrowIfFailed(
    CoCreateInstance(
        CLSID_WICImagingFactory2,
        nullptr,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&m_wicFactory)
    ),
    L"(WIC) ファクトリ作成に失敗しました。",
    L"CoCreateInstance()",
    L"DeviceResources::Impl::CreateDeviceResources()"
);

ファクトリを作成してます。これはID3D11DeviceID3D11DeviceContextIDXGISwapChainとは直接は関係ないのですが、後ほど使用するので先に作成しておきます。
重要なのは、この後です。


ComPtr<ID3D11Device> temp_device;
ComPtr<ID3D11DeviceContext>  temp_context;
ComPtr<IDXGISwapChain>   temp_swapChain;

//デバイスとスワップチェーンの作成
for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++) {
    //ドライバタイプを配列から取得
    m_D3DDriverType = driverTypes[driverTypeIndex];
    //デバイスとスワップチェーンの作成
    hr = D3D11CreateDeviceAndSwapChain(
        nullptr,
        m_D3DDriverType,
        nullptr,
        createDeviceFlags,
        featureLevels,
        numFeatureLevels,
        D3D11_SDK_VERSION,
        &sd,
        &temp_swapChain,
        &temp_device,
        &m_D3DFeatureLevel,
        &temp_context
    );
    //成功したらそのドライバを使う
    if (SUCCEEDED(hr))
        break;
}
ThrowIfFailed(
    hr,
    L"DX11デバイスとスワップチェーンの作成に失敗しました。",
    L"D3D11CreateDeviceAndSwapChain()",
    L"DeviceResources::Impl::CreateDeviceResources()"
);


この処理により、ハードウェアデバイスラップデバイスリファレンスデバイスの作成を試みます。ここで失敗したら、Dx11には対応してないということで、例外が出ます。
2016年夏現在、Windows8.1以上であれば、ほぼDx11(ハードウェアデバイス)には対応されていると思います。このあたりはDx12版がまだハードウェア対応マチマチのと違って、安定した動作はしやすいと考えられます。

非常に個人的な意見なのですが、実際にゲーム制作は、2016年夏時点では、安定したDx11版で行ったほうが、動かないマシンを最小に抑えられるという点では望ましいとは思います。
とはいえ、いずれDx12の時代がやってくるのは確実ですし(各GPUメーカーもほぼDx12対応を表明しています)、そういう意味では研究や実装は進めていってもいいのかなと思います。BaseCrossはそういう意味でも実験と実装となフレームワークだと思っています。

このあとに、構築したtemp_device、temp_context、temp_swapChainをもとに、


ComPtr<ID3D11Device2> m_D3D11Device;     //デバイス
ComPtr<ID3D11DeviceContext2> m_D3D11Context;     //コンテキスト
ComPtr<IDXGISwapChain1> m_D3D11SwapChain;    //スワップチェーン

へのバージョンアップを試みます。これはWindows8.1から使えるCOMで、タイルリソース、シェーダーバージョン5などが使えます。BaseCrossではタイルリソースは使用してませんがシェーダーバージョン5は使用しています。(もちろん、タイルリソースを自分で作ることは可能です。BaseCrossのライブラリ部分では使ってないということです)。


//リソースをバージョンアップする
ThrowIfFailed(
    temp_device.As(&m_D3D11Device),
    L"DX11デバイスのバージョンアップに失敗しました。",
    L"temp_device.As(&m_D3D11Device)",
    L"DeviceResources::Impl::CreateDeviceResources()"
);
ThrowIfFailed(
    temp_context.As(&m_D3D11Context),
    L"DX11コンテクストのバージョンアップに失敗しました。",
    L"temp_context.As(&m_D3D11Context)",
    L"DeviceResources::Impl::CreateDeviceResources()"
);
ThrowIfFailed(
    temp_swapChain.As(&m_D3D11SwapChain),
    L"DX11スワップチェーンのバージョンアップに失敗しました。",
    L"temp_swapChain.As(&m_D3D11SwapChain)",
    L"DeviceResources::Impl::CreateDeviceResources()"
);

このあと2D用のデバイス等を作成してますが、これはデバッグ文字列のものです。実際にDirect2Dをグラフィック処理には使用してません。

初期化処理ではこのあと、レンダーターゲットビューデプスステンシルビューを作成しています。
この処理はDeviceResources::GetDefaultRenderTarget関数で行います。


shared_ptr<DefaultRenderTarget> DeviceResources::GetDefaultRenderTarget() {
    if (!pImpl->m_DefaultRenderTarget) {
        //デフォルトのレンダリングターゲットを作成
        pImpl->m_DefaultRenderTarget = make_shared<DefaultRenderTarget>();
    }
    return pImpl->m_DefaultRenderTarget;
}

この処理を見てあれと思った方も多いと思います。
つまりpImpl->m_DefaultRenderTargetが初期化されてなければ初期化して返す、という関数です。
なぜこのようにしたかというと、もう一つのレンダーターゲットであるシャドウマップのレンダーターゲットの構築もみてください。


shared_ptr<ShadowMapRenderTarget> DeviceResources::GetShadowMapRenderTarget(float ShadowMapDimension ) {
    if (!pImpl->m_ShadowMapRenderTarget) {
        //シャドウマップのレンダリングターゲットを作成
        pImpl->m_ShadowMapRenderTarget = make_shared<ShadowMapRenderTarget>(ShadowMapDimension);
    }
    return pImpl->m_ShadowMapRenderTarget;
}

こちらもなければ構築というロジックになっています。これはシャドウマップを使わないときは使用しなくてもよい。という処理になっています。例えば2Dのみのゲームを作成する場合、影は必要ないでしょうから。
そういうわけで、通常のレンダーターゲットも、こちらは使わないことはないでしょうが、一応、シャドウマップと合わせているわけです。

上のDeviceResources::GetDefaultRenderTarget関数は最初にどこで呼び出されているかというと、DeviceResources::ClearDefultViews関数から呼ばれます。そしてこの関数はシーン(BaseCrossDx11のScene.cpp内)のScene::OnDraw関数で呼ばれます。つまり1回目の描画時にレンダーターゲットビューは作成されるわけです。
ではレンダーターゲットビューの作成はどのような処理になっているでしょうか。DefaultRenderTargetクラスのコンストラクタです。
ポイントとなるのは以下の処理です


auto Dev = App::GetApp()->GetDeviceResources();
auto pD3D11Device = Dev->GetD3DDevice();
auto pSwapChain = Dev->GetSwapChain();
auto pD3D11DeviceContext = Dev->GetD3DDeviceContext();
auto pD2D11DeviceContext = Dev->GetD2DDeviceContext();


//レンダリングターゲットビューの作成
ComPtr<ID3D11Texture2D> pBackBuffer;
//まずバックバッファのポインタを得る
ThrowIfFailed(
    pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer),
    L"スワップチェーンからバックバッファの取得に失敗しました。",
    L"pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer)",
    L"DefaultRenderTarget::DefaultRenderTarget()"
);
//バックバッファからレンダリングターゲットのビューを作成する
ThrowIfFailed(
    pD3D11Device->CreateRenderTargetView(pBackBuffer.Get(), nullptr, &pImpl->m_D3D11RenderTargetView),
    L"DX11バックバッファからのレンダリングターゲットビューを作成に失敗しました。",
    L"pD3D11Device->CreateRenderTargetView(pBackBuffer.Get(), nullptr, &m_D3D11RenderTargetView)",
    L"DefaultRenderTarget::DefaultRenderTarget()"
);

//深度テクスチャの作成
D3D11_TEXTURE2D_DESC descDepth;
ZeroMemory(&descDepth, sizeof(descDepth));
descDepth.Width = App::GetApp()->GetGameWidth();
descDepth.Height = App::GetApp()->GetGameHeight();
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDepth.SampleDesc.Count = 1;
descDepth.SampleDesc.Quality = 0;
descDepth.Usage = D3D11_USAGE_DEFAULT;
descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
descDepth.CPUAccessFlags = 0;
descDepth.MiscFlags = 0;

ThrowIfFailed(
    pD3D11Device->CreateTexture2D(&descDepth, nullptr, &pImpl->m_DepthStencil),
    L"DX11深度テクスチャの作成失敗の作成に失敗しました。",
    L"pD3D11Device->CreateTexture2D(&descDepth, nullptr, &m_DepthStencil)",
    L"DefaultRenderTarget::DefaultRenderTarget()"
);

//深度ステンシルビューの作成
D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
ZeroMemory(&descDSV, sizeof(descDSV));
descDSV.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
descDSV.Texture2D.MipSlice = 0;

ThrowIfFailed(
    pD3D11Device->CreateDepthStencilView(pImpl->m_DepthStencil.Get(), &descDSV, &pImpl->m_DepthStencilView),
    L"DX11深度ステンシルビューの作成に失敗しました。",
    L"pD3D11Device->CreateDepthStencilView(m_DepthStencil.Get(), &descDSV, &m_DepthStencilView)",
    L"DefaultRenderTarget::DefaultRenderTarget()"
);

ComPtr<IDXGISurface2> dxgiBackBuffer;
ThrowIfFailed(
    pSwapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer)),
    L"2dデバイスコンテキスト作成に失敗しました。",
    L"m_d2dDevice->CreateDeviceContext()",
    L"DeviceResources::Impl::CreateDeviceResources()"
);

スワップチェーンからバックバッファを取り出し、そこからレンダーターゲットビューを作成2Dテクスチャを作成してそこから、デプスステンシルビューを作成と2つです。その後、デバッグ文字列用の2Dのビューを作成していますが、大きいのは上の2つです。
そんなながれで、1回目の描画時に、レンダーターゲットビューデプスステンシルビューが作成されるわけです。

各オブジェクトの初期化

ここで配置されているのは、NormalTextureBoxというクラスです。回転するテクスチャ付きボックスです。
BaseCrossDx11プロジェクトScene.h/cppに記述があります。ここで話題にするのはNormalTextureBox::OnCreate関数です。 ここではMeshResource::CreateCube関数呼び出しにより、立方体のメッシュを作成してるわけですが、その呼び出し方はDx12版とかわりません。
変わるのは内部的に呼び出している、MeshResource::CreatePrimitiveBuffer関数によりMeshResourceクラスのメンバ


ComPtr<ID3D11Buffer> m_VertexBuffer; //頂点バッファ
ComPtr<ID3D11Buffer> m_IndexBuffer;  //インデックスバッファ

頂点バッファ、インデックスバッファを作成します。
同様に、テクスチャもTextureResource::CreateTextureResource関数呼び出しにより、TextureResourceクラスのイディオムにある


ComPtr<ID3D11ShaderResourceView> m_ShaderResView;    //リソースビュー

シェーダーリソースビューを作成します。この作成にはDirectXTexプロジェクトを使用します。

このように、Dx11版は非常にシンプルです。

ターン毎の全体的な描画準備

この処理は、Scene::OnDraw関数に記述があります。以下にその部分を紹介します。


void Scene::OnDraw() {
    //描画デバイスの取得
    auto Dev = App::GetApp()->GetDeviceResources();
    Dev->ClearDefultViews(Color4(0, 0, 0, 1.0f));
    //デフォルト描画の開始
    Dev->StartDefultDraw();
    m_NormalTextureBox->OnDraw();
    //デフォルト描画の終了
    Dev->EndDefultDraw();
}

この中のDev->StartDefultDraw関数呼び出しです。この関数呼び出しはDx12版は空関数になっていますが、Dx11版は実装されています。追いかけていくと以下の呼び出しになります。


void DefaultRenderTarget::StartRenderTarget() {
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pD3D11Device = Dev->GetD3DDevice();
    auto pD3D11DeviceContext = Dev->GetD3DDeviceContext();

    ID3D11RenderTargetView* pV = pImpl->m_D3D11RenderTargetView.Get();
    //レンダリングターゲットとステンシルを設定
    pD3D11DeviceContext->OMSetRenderTargets(1, &pV, pImpl->m_DepthStencilView.Get());
    //ビューポートの設定
    pD3D11DeviceContext->RSSetViewports(1, &GetViewport());

    //中略
}

ここで重要なのはpD3D11DeviceContext->OMSetRenderTargets関数です。ここでレンダーターゲットビューとデプスステンシルビューを設定してます。

ターン毎のオブジェクトの描画

オブジェクトの描画はCharacter.cppにあるNormalTextureBox::OnDraw関数です。以下に紹介します。


void NormalTextureBox::OnDraw() {
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pD3D11DeviceContext = Dev->GetD3DDeviceContext();
    auto RenderState = Dev->GetRenderState();

    //行列の定義
    Matrix4X4 World, View, Proj;
    //ワールド行列の決定
    World.AffineTransformation(
        m_Scale,            //スケーリング
        Vector3(0, 0, 0),       //回転の中心(重心)
        m_Qt,               //回転角度
        m_Pos               //位置
    );
    //転置する
    World.Transpose();
    //ビュー行列の決定
    View.LookAtLH(Vector3(0, 2.0, -5.0f), Vector3(0, 0, 0), Vector3(0, 1.0f, 0));
    //転置する
    View.Transpose();
    //射影行列の決定
    float w = static_cast<float>(App::GetApp()->GetGameWidth());
    float h = static_cast<float>(App::GetApp()->GetGameHeight());
    Proj.PerspectiveFovLH(XM_PIDIV4, w / h, 1.0f, 100.0f);
    //転置する
    Proj.Transpose();
    //コンスタントバッファの準備
    PNTStaticConstantBuffer sb;
    sb.World = World;
    sb.View = View;
    sb.Projection = Proj;
    //ライティング
    Vector4 LightDir(0.5f, -1.0f, 0.5f, 0.0f);
    LightDir.Normalize();
    sb.LightDir = LightDir;
    //ディフューズ
    sb.Diffuse = Color4(1.0f, 1.0f, 1.0f, 1.0f);
    //エミッシブ加算は行わない。
    sb.Emissive = Color4(0, 0, 0, 0);
    //コンスタントバッファの更新
    pD3D11DeviceContext->UpdateSubresource(CBPNTStatic::GetPtr()->GetBuffer(), 0, nullptr, &sb, 0, 0);

    //ストライドとオフセット
    UINT stride = sizeof(VertexPositionNormalTexture);
    UINT offset = 0;
    //頂点バッファのセット
    pD3D11DeviceContext->IASetVertexBuffers(0, 1, m_CubeMesh->GetVertexBuffer().GetAddressOf(), &stride, &offset);
    //インデックスバッファのセット
    pD3D11DeviceContext->IASetIndexBuffer(m_CubeMesh->GetIndexBuffer().Get(), DXGI_FORMAT_R16_UINT, 0);

    //描画方法(3角形)
    pD3D11DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    //コンスタントバッファの設定
    ID3D11Buffer* pConstantBuffer = CBPNTStatic::GetPtr()->GetBuffer();
    ID3D11Buffer* pNullConstantBuffer = nullptr;
    //頂点シェーダに渡す
    pD3D11DeviceContext->VSSetConstantBuffers(0, 1, &pConstantBuffer);
    //ピクセルシェーダに渡す
    pD3D11DeviceContext->PSSetConstantBuffers(0, 1, &pConstantBuffer);
    //シェーダの設定
    pD3D11DeviceContext->VSSetShader(VSPNTStatic::GetPtr()->GetShader(), nullptr, 0);
    pD3D11DeviceContext->PSSetShader(PSPNTStatic::GetPtr()->GetShader(), nullptr, 0);
    //インプットレイアウトの設定
    pD3D11DeviceContext->IASetInputLayout(VSPNTStatic::GetPtr()->GetInputLayout());

    //ブレンドステート
    //透明処理しない
    pD3D11DeviceContext->OMSetBlendState(RenderState->GetOpaque(), nullptr, 0xffffffff);

    //デプスステンシルステート
    pD3D11DeviceContext->OMSetDepthStencilState(RenderState->GetDepthDefault(), 0);

    //テクスチャとサンプラーの設定
    ID3D11ShaderResourceView* pNull[1] = { 0 };
    pD3D11DeviceContext->PSSetShaderResources(0, 1, m_WallTex->GetShaderResourceView().GetAddressOf());
    ID3D11SamplerState* pSampler = RenderState->GetLinearClamp();
    pD3D11DeviceContext->PSSetSamplers(0, 1, &pSampler);

    //ラスタライザステート(表面描画)
    pD3D11DeviceContext->RSSetState(RenderState->GetCullBack());
    //描画
    pD3D11DeviceContext->DrawIndexed(m_CubeMesh->GetNumIndicis(), 0, 0);
    //後始末
    Dev->InitializeStates();

}

長いソースですが、順を追って考えていくと、わかると思います。


1、コンスタントバッファに必要情報のセット
2、頂点シェーダ、ピクセルシェーダの設定
3、ブレンドステートの設定
4、デプスステンシルステートの設定
5、サンプラーステートの設定
6、ラスタライザステートの設定

を行って、頂点関連として


1、頂点バッファの設定
2、インデックスバッファの設定
3、インプットレイアウトの設定
4、インデックス描画

で描画します。
Dx11版はこのようにDx11のAPIがわかりやすいので、Dx12のような描画コンテキストクラスのようなものはありません。

ターン毎の全体的な描画終了

この処理はScene::OnDraw関数内のDev->EndDefultDraw関数呼び出しです。追いかけていくとDefaultRenderTarget::EndRenderTarget関数にたどり着きます。ここではパイプライン設定を初期化しています。

ターン毎のプレゼント

この処理はDeviceResources::Present関数です。
ここでは、スワップチェーンのPresent関数を呼び出しています。

終了処理

終了処理に関しては、COMがすべてComPtrを使用しているので、自動的にスマートポインタがリリースするので、特別な後処理は必要ありません。

Dx11描画まとめ

このように、この記事は少し長めになってしまいましたが、Dx12版の説明に費やした記事数を考えると、非常にシンプルなのがわかります。
Dx9やDx10に比べ、Dx11は、多少はプログラマの負担はかかっていると思いますが、それはシェーダを記述しなければならないとか頂点や行列操作用のライブラリ(D3DXのようなもの)がないくらいで、多くの部分では、GPUとのやり取りはDx11が内部で処理している部分が多く存在します。
しかしDx12になると、DirectX側で行うのは、GPUのインターフェイスの提供くらいで、プログラマの負担もDx11とは比べ物にならないくらい多くなります。
しかし、逆に考えると、Dx12を勉強する、ということはGPUを勉強する、に近い部分があり、たとえば学生の皆さんが将来、ゲーム専用機の開発にかかわることになれば、きっと役に立つでしょう。
そういった、Dx11とDx12という、大きく違うアプローチ方法を同時に学ぶ、という部分でもBaseCrossが多少のお役に泣てればいいなと考えています。

それではこの項はこの辺で。
次回もまたよろしくお願いします。

カテゴリー

ピックアップ記事

  1. 2016092201
    今回は前回のサンプルを少し機能を追加しまして、いろんなオブジェクトを追加しています。FullTuto…
  2. 2016092001
    前回更新から時間がたってしまいましたが、今回はフルバージョンチュートリアル003をアップしました。内…
  3. eyecatch
    前回更新から時間がたってしまいましたが、今回はフルバージョンチュートリアル002で懸案となっていまし…
PAGE TOP