VisualStudio2017(Windows10 DirectX OpenGL)で始める3Dプログラミング

今(2017年5月)Windows&C++で3DプログラミングするならDirectXかOpenGLだと思いますが、最新バージョン(DirextX12など)は、性能重視のため手軽にプログラミングができなくなっています。
なので古いバージョンで使えるものを調べて、プログラミングができる環境を作ってみました。
作成した環境は、OpenGL1.1、OpenGL4.5、DirectX9、DirectX11、DirectX12で、初期化と画面クリア、簡単なポリゴンの描画まで行っています。
VSプロジェクト、ソースコードを公開していますので、3Dプログラミングの参考にしてください。
記事最後にあるダウンロードボタンから取得できます。


Visual Studio 2017インストール

プログラムの開発環境として、Visual Studio Community 2017が無料で利用できます。30日間試用期間があり、それ以降も使う場合は登録が必要になります。

ダウンロード

https://www.visualstudio.com/ja/downloads/
からインストーラーをダウンロード。

インストール

ダウンロードしたファイルを実行するとVisual Studio Installerが起動し、インストールしたい開発環境を選択します。3Dプログラム(DirectX、OpenGL)をするために「C++によるデスクトップ開発」をインストールします。

右側にはインストールする機能を細かく指定できるようになっています。今回はデフォルトのままでインストールします。右下に「Windows 10 SDK (10.xxxxxxx)」とありますが選択する必要はありません。古いバージョンのWindows SDKをインストールするときに使用します。

Windows 10 SDK

DirectXとOpenGLはこのSDKに含まれています。
Visual Studioをインストールすると最新版のSDKがインストールされます(のはず・・・、いつの間にかインストールされいてVisual Studio InstallerのWindowsSDKのリストにありませんでした)。


環境構築

ここで説明するものはすべてWindows10SDKに入っているためVisualStudioのインストールのみです。利用するヘッダやライブラリファイル、初期化の方法について説明していきます。


OpenGL1.1

簡単な3Dプログラムならこれで十分だと思います。Windows環境以外でも多くの環境で対応していて、本、ネットなどから簡単に情報を入手できます。古いAPIなのでシェーダや複雑な処理はできません。

ヘッダーとライブラリファイル
#include <gl/gl.h>
#pragma comment(lib,"OpenGL32.lib")
初期化と終了処理
簡単に初期化を行える外部ライブラリがありますが使用せずに、Windows用のOpenGL関数(wgl~)のみを使った方法を紹介します。

  //デバイスコンテキスト取得
  g_hDC = GetDC(hwnd);//hwnd:ウインドウハンドル

  //OpenGLが使用可能なピクセルフォーマットを取得
  PIXELFORMATDESCRIPTOR pfdesc;
  ZeroMemory(&pfdesc, sizeof(pfdesc));
  pfdesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
  pfdesc.nVersion = 1;
  pfdesc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
  pfdesc.iPixelType = PFD_TYPE_RGBA;
  pfdesc.cColorBits = 32;
  pfdesc.cDepthBits = 24;
  pfdesc.cStencilBits = 8;
  int format = ChoosePixelFormat(hDC, &pfdesc);
  //format==0 失敗 対応したフォーマットがない
  
  // 取得したピクセルフォーマットを設定
  // これで指定したウインドウがOpenGLで使用可能に
  SetPixelFormat(hDC, format, &pfdesc));

  //OpenGLコンテキスト作成
  hGL = wglCreateContext(hDC);
  wglMakeCurrent(hDC, hGL)) {

  //ここからOpenGL関数(gl~)が使用可能になる	

  //削除
 //OpenGLコンテキストの削除
  wglDeleteContext(hGL);
  //デバイスコンテキスト解放
  ReleaseDC(hwnd, hDC);
簡単な描画処理例
ポリゴンを描画するには、頂点や色などを1つずつ関数を呼び出して行います。簡単ですが効率は悪いです。ライティングは決められた計算方法のみで、シェーダーはありません。

  //画面クリア
  glClearColor(0.0f, 0.125f, 0.3f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  //ポリゴン描画
  glBegin(GL_TRIANGLES);
  glColor3f(0.0f, 0.0f, 1.0f);
  glVertex3f(0.0f, 0.5f, 0.0f);
  glColor3f(0.0f, 1.0f, 0.0f);
  glVertex3f(0.5f, -0.5f, 0.0f);
  glColor3f(1.0f, 0.0f, 0.0f);
  glVertex3f(-0.5f, -0.5f, 0.0f);
  glEnd();

  //描画処理を実行
  glFlush();

  //ダブルバッファ(描画バッファと表示バッファ)入れ替え
  //Windows用関数 g_hDC:デバイスコンテキスト
  SwapBuffers(g_hDC);

DirectX9

高度な処理や計算性能の必要としない3Dプログラムやゲームであれば十分使えます。古いAPIですが、それなりに複雑な処理が可能で、シェーダーも使えます。固定のライティング処理が用意されているためシェーダーを書かなくてもポリゴンなどを描画できます。最新の機能がなく、シェーダーの制約も多いですが、その代わり高性能PCでなくても動作します。
他のバージョンと比べて、簡単に色々な情報が入手できます。

ヘッダーとライブラリファイル
#include <d3d9.h>
#pragma comment(lib,"d3d9.lib")
初期化と終了処理
今回紹介する中で一番簡単で短いソースコードです。構造体に必要な情報を入力しデバイスを作成します。

 
  HWND hwnd = ウインドウハンドル;
  LPDIRECT3D9 pD3D = nullptr;
  LPDIRECT3DDEVICE9 pDevice = nullptr;

  pD3D = Direct3DCreate9(D3D_SDK_VERSION);

  // デバイス作成
  D3DPRESENT_PARAMETERS d3dpp;
  ZeroMemory(&d3dpp, sizeof(d3dpp));
  d3dpp.Windowed = TRUE;
  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
  d3dpp.EnableAutoDepthStencil = TRUE;
  d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
  pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
              D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &pDevice);
  
  //pDeviceを使って描画処理


  //削除
  pDevice->Release();
  pD3D->Release();
簡単な描画処理例
頂点データを作成し、固定のライティングかシェーダーかどちらかを選んで描画処理を行います。

//データ構築
  LPDIRECT3DVERTEXBUFFER9 pVBuffer = nullptr;
  // 頂点データ作成 
  truct Vertex
  {
    FLOAT x,y,z;
    D3DCOLOR color;
  };
  Vertex VertexData[3] = {
    { 0.0f,  0.5f, 0.0f, D3DCOLOR_ARGB(255, 0,0,255) },
    { 0.5f, -0.5f, 0.0f,  D3DCOLOR_ARGB(255,0,255,0) },
    { -0.5f, -0.5f, 0.0f, D3DCOLOR_ARGB(255, 255,0,0) },
  };
  pDevice->CreateVertexBuffer(sizeof(VertexData), 0,
                  D3DFVF_XYZ | D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &pVBuffer, NULL);

  void* vb;
  if (SUCCEEDED(pVBuffer->Lock(0, sizeof(VertexData), &vb, 0))) {
    memcpy(vb, &VertexData, sizeof(VertexData));
    pVBuffer->Unlock();
  }
//描画処理

  //画面クリア
  pDevice->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 50, 100), 1.0f, 0);

  pDevice->BeginScene();

  pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

  pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
  pDevice->SetStreamSource(0, pVBuffer, 0, sizeof(Vertex));
  pDevice->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
  pDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

  pDevice->EndScene();

  //画面更新
  pDevice->Present(NULL, NULL, NULL, NULL);

OpenGL4

シェーダーなど新しい機能に対応したバージョンです。描画処理もDirectXのような頂点バッファとシェーダーを使った方法に変更され、古い描画処理と固定のライティング機能は廃止されています。また使用するにはグラフィックボードのドライバーが対応している必要があり、利用できる環境が制限されます。
古いバージョンの情報が多く、新しいバージョンの情報を探すのに少し苦労します。

ヘッダーとライブラリファイル
glext.hというヘッダファイルが必要になります。
https://khronos.org/registry/OpenGL/index_gl.phpから入手できます。
新しいバージョンのlibファイルはなく、実行時に動的なリンク処理(関数ポインタの取得)が必要になります。

#include <gl/gl.h>
#include "exgl.h"
#pragma comment(lib,"OpenGL32.lib")
初期化と終了処理
ウインドウの初期化などはOpenGL1.1と同じです。新しい機能を使うために実行時に動的なリンク処理を行います。ここでは使用していませんが、この処理を行う外部ライブラリ(glewなど)があります。

  // 関数ポインタ取得
  // 未対応の場合NULLポインタ
  PFNGLBINDVERTEXBUFFERPROC glBindVertexBuffer;
  glBindVertexBuffer = (glBindVertexBuffer)wglGetProcAddress("glBindVertexBuffer");

  //関数呼び出し
  glBindVertexBuffer(GL_ARRAY_BUFFER, id)
簡単な描画処理例
頂点バッファやシェーダーの作成は長いため、描画処理の部分のみです。

  //画面クリア
  glClearColor(0.0f, 0.125f, 0.3f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  //シェーダー
  glUseProgram(ProgID);

  //属性0に頂点の位置データを割り当て
  glEnableVertexAttribArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, VBuffID);
  glVertexAttribPointer(0, 3, GL_FLOAT,	GL_FALSE, 0, (void*)0);

  // 三角形描画
  glDrawArrays(GL_TRIANGLES, 0, 3);

  glDisableVertexAttribArray(0);

  //描画処理を実行
  glFlush();

  //ダブルバッファ(描画バッファと表示バッファ)入れ替え
  SwapBuffers(hDC);

DirectX11

ジオメトリシェーダーなどの機能追加され、より高度で効率の良いプログラムが可能です。支援機能(フォント、画像からテクスチャ生成、シェーダーエフェクトなど)が削られているため、自分で作成するか外部のライブラリ(DirectXTKなど)を使用します。
Windows7以降、新機能に対応したGPUを搭載したPCが必要になります。
あと、情報がほとんどありません(英語ならあるかも)。中上級者向け。

ヘッダーとライブラリファイル
#include <d3d11.h>
#pragma comment(lib,"d3d11.lib")
初期化と終了処理
処理が複雑で長いため一部のみ、サンプルのソースコードを見てください。

  D3D_DRIVER_TYPE g_DriverType = D3D_DRIVER_TYPE_NULL;
  D3D_FEATURE_LEVEL g_FeatureLevel = D3D_FEATURE_LEVEL_11_0;
  ID3D11Device* g_pd3dDevice = nullptr;
  ID3D11DeviceContext* g_pImmediateContext = nullptr;
  IDXGISwapChain* g_pSwapChain = nullptr;
  
  // DirectX11&ハードウェア対応
  D3D_FEATURE_LEVEL feature_level = D3D_FEATURE_LEVEL_11_0;
  g_DriverType = D3D_DRIVER_TYPE_HARDWARE;
  
  // スワップチェイン設定
  DXGI_SWAP_CHAIN_DESC sd;
  ZeroMemory(&sd, sizeof(sd));
  sd.BufferCount = 1;
  sd.BufferDesc.Width = width;
  sd.BufferDesc.Height = height;
  sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  sd.BufferDesc.RefreshRate.Numerator = 60;
  sd.BufferDesc.RefreshRate.Denominator = 1;	//1/60 = 60fps
  sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  sd.OutputWindow = g_hWndDX11;
  sd.SampleDesc.Count = 1;
  sd.SampleDesc.Quality = 0;
  sd.Windowed = TRUE;
  
  // DirectX11デバイスとスワップチェイン作成
  D3D11CreateDeviceAndSwapChain(NULL, g_DriverType, NULL,
  			cdev_flags, &feature_level, 1, D3D11_SDK_VERSION, &sd,
  			&g_pSwapChain, &g_pd3dDevice, &g_FeatureLevel, &g_pImmediateContext);
簡単な描画処理例
頂点バッファやシェーダーの作成は長いため、描画処理の部分のみです。

  // 指定色で画面クリア
  float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha
  g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView, ClearColor);

  // 入力レイアウト
  g_pImmediateContext->IASetInputLayout(g_pVertexLayout);

  // 頂点バッファ
  UINT stride = sizeof(VertexData);
  UINT offset = 0;
  g_pImmediateContext->IASetVertexBuffers(0, 1, &g_pVertexBuffer, &stride, &offset);

  // プリミティブ形状
  g_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

  // シェーダ
  g_pImmediateContext->VSSetShader(g_pVertexShader, NULL, 0);
  g_pImmediateContext->PSSetShader(g_pPixelShader, NULL, 0);

  //ポリゴン描画
  g_pImmediateContext->Draw(3, 0);

  //結果をウインドウに反映
  g_pSwapChain->Present(0, 0);


DirectX12

機能的には11とほぼ同じですが、メモリなどのリソース管理、CPUとGPUの並列実行などすべて自分で行う必要があります。その代わり、かなり効率の良いプログラムが可能です。
グラフィックライブラリやゲームエンジンの開発者用です。通常の3Dプログラムで使用することはないと思います。DirectX11以上に情報の取得が困難です。

ヘッダーとライブラリファイル
#include <dxgi1_4.h>
#include <d3d12.h>
#pragma comment(lib,"dxgi.lib")
#pragma comment(lib,"d3d12.lib")
初期化と終了処理

処理が複雑で長いため省略。サンプルのソースコードを見てください。

簡単な描画処理例
画面クリアのみを行うソースコードです。

  pCmdAllocator->Reset();
  pCmdList->Reset(pCmdAllocator.Get(), pPLineState.Get());
  
  pCmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(apRenTarget[uFrameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
  
  CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(pRTVHeap->GetCPUDescriptorHandleForHeapStart(), uFrameIndex, uRTVDescSize);
  
  const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
  pCmdList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
  pCmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(apRenTarget[uFrameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
  
  hr = pCmdList->Close();
  
  ID3D12CommandList* ppCommandLists[] = { pCmdList.Get() };
  pCmdQ->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
  
  hr = pSwapChain->Present(1, 0);
  
  {//GPU実行待ち GPU命令実行後すぐに終了待ち
    const UINT64 fence = uFenceValue;
    pCmdQ->Signal(pFence.Get(), fence);
    uFenceValue++;
    
    if (pFence->GetCompletedValue() < fence)
    {
      hr = pFence->SetEventOnCompletion(fence, hFenceEvent);
      WaitForSingleObject(hFenceEvent, INFINITE);
    }
    
    uFrameIndex = pSwapChain->GetCurrentBackBufferIndex();
  }

サンプルソースコード

DirectX9、DirectX11、DirectX12、OpenGL1.1、OpenGL4.5各環境で初期化と画面クリア、簡単なポリゴンの描画まで行うプログラムです。WindowsSDK以外の外部ライブラリなどを使用していないため、VisualStudioのインストールだけでコンパイルできます。

コンパイル時の注意点

新しいバージョンのWindowsSDKでコンパイルする場合、「ソリューションの再ターゲット」を実行してください。

“Start 3DProgram VS2017” をダウンロード Start3DProgVS2017-1.zip – 724 回のダウンロード – 171 KB

「VisualStudio2017(Windows10 DirectX OpenGL)で始める3Dプログラミング」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

認証:数字を入力してください(必須) * Time limit is exhausted. Please reload CAPTCHA.