Win10 OBJ Normal Model

Windows10(Store) DirectX3D で OBJ Normal Model を描画します。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プログラムの説明

  1. Windows10(Store) で Wavefront の法線ベクトルが設定された cube.obj を描画します。
    Win10 OBJ Model では頂点座標だけのモデルを描画しました。
    法線はプログラムで計算していたのですが、今回は頂点座標と法線ベクトルを設定した OBJ Model を描画します。
  2. 頂点座標と法線ベクトルを設定した Cube の OBJ Model です。
    vn が法線ベクトルの3次元座標です。
    f の行はポリゴンを構成する頂点のインデックスで「数値//数値」で指定されています。
    最初の数値が頂点座標のインデックスで、後の数値が法線ベクトルのインデックスです。
    # NormModel
    g cube
    v -1 -1 -1
    v 1 -1 -1
    v -1 1 -1
    v 1 1 -1
    v -1 -1 1
    v 1 -1 1
    v -1 1 1
    v 1 1 1
    vn 0 0 -1
    vn -1 0 0
    vn 1 0 0
    vn 0 -1 0
    vn 0 1 0
    vn 0 0 1
    f 1//1 3//1 4//1 2//1
    f 1//2 5//2 7//2 3//2
    f 2//3 4//3 8//3 6//3
    f 1//4 2//4 6//4 5//4
    f 3//5 7//5 8//5 4//5
    f 5//6 6//6 8//6 7//6
    
  3. Load_OBJ.h では、頂点座標と法線ベクトルの領域を定義します。
    m_pos が頂点座標(三次元)の領域で、m_norm が法線ベクトル(三次元)の領域です。
    m_idxP が頂点 Index の並びで、m_idxN が法線 Index の並びです。
    m_idxP3, m_idxN3 は、m_idxP, m_idxN を3角形ポリゴンに変換した vector 配列です。
            vector<float3>  m_pos;      // 頂点座標
            vector<float3>  m_norm;     // 法線ベクトル
    
            vector<uint16> m_idxP;      // 頂点 Index の並び(角付)
            vector<uint16> m_idxN;      // 法線 Index の並び(角付)
            vector<uint16> m_idxP3;     // 頂点 Index(3P) の並び
            vector<uint16> m_idxN3;     // 法線 Index(3P) の並び
    
  4. 前回に比べて「f 1//1 3//1 4//1 2//1」の解析方法が大きく変わります。
    VT, TK は前回と同様にトークンで切り分けた vector 配列です。
    STK は、例えば「1//2」をトークンに「/」を加えて切り分けた配列「"1", "", "2"」です。
            vector<string>  VT;         // 行(\n)で切り分け
            vector<string>  TK;         // VT[m_pt] の行をトークンで切り分け
            vector<string>  STK;        // TK[i] からトークンを切り分け
    
  5. f 形式(f 1 3 4 2 や f 1//1 3//1 4//1 2//1)のプログラム解析は次のようになります。
    行を空白で切り分けた TK[0] が "f" のときは Index の設定(f 形式)です。
    TK.size() - 1; がポリゴンの角数で、今回は4角形を意味する4です。
    TK[j] を「/」で切り分けて STK に格納します。
    STK.size()==3 のときは「4//6」のような形式で、法線 Index に 6 を push_back します。
    STK.size()==2 のときは「4/5」のような形式で、テクスチャ Index が設定されています。(今回は無視)
    STK.size()==1 のときは「4」のような形式で、頂点 Index に 4 を push_back します。
    case 文に break; が書かれていないので、上から下まで流れることに注意して下さい。
            else    if (TK[0] == "f")
            {
                uint32  nn;
                int     norm_s = (int)m_norm.size();
                int     pos_s = (int)m_pos.size();
                nn = TK.size() - 1;
                m_idxP.push_back(nn);
                m_idxN.push_back(nn);
                for (uint32 j = 1; j <= nn; j++)
                {
                    SToken(TK[j]);
                    switch (STK.size())
                    {
                    case 3:
                        num = (uint16)atoi((char *)STK[2].data());
                        if (num>0)  m_idxN.push_back(num);
                        else  if (num<0)    m_idxN.push_back(norm_s+num+1);
                    case 2:
                    case 1:
                        num = (uint16)atoi((char *)STK[0].data());
                        if (num>0)  m_idxP.push_back(num);
                        else  if (num<0)    m_idxP.push_back(pos_s+num+1);
                        break;
                    default:
                        Debug(L"f SToken Error", STK.size());
                        break;
                    }
                }
            }
    
  6. Load_OBJ.cpp のソースコードです。
    掲載されていない関数は Windows10 DirectX Library を参照して下さい。
    "vn" などが増えると、先頭1文字で識別できないので if 文を使っています。
    今回はテクスチャの設定は無視しています。
    f 形式で負の値が使われたときは、最後に登録された値からさかのぼります。
    下記のソースプログラムでは、これに対応しています。
    // OBJ Norm モデルをロード
    #include "pch.h"
    #include "..\Common\DirectXHelper.h"
    #include "Load_OBJ.h"
    #include "BasicReaderWriter.h"
    
    using namespace App1;
    using namespace DirectX;
    using namespace Windows::Foundation;
    
    Load_OBJ::Load_OBJ(const std::shared_ptr<DX::DeviceResources>& deviceResources) :
        m_deviceResources(deviceResources)
    {
    }
    
    // Load OBJ Model
    void Load_OBJ::Load(
        ID3D11Buffer **vertexBuffer,
        ID3D11Buffer **indexBuffer,
        uint32 *indexCount)
    {
        float3  wf3;
        int     num, pt, pw;
    
        m_pos.clear();
        m_idxP.clear();
        m_idxP3.clear();
    
        BasicReaderWriter^ reader = ref new BasicReaderWriter();
        Array<byte>^ buf = reader->ReadData("cube.txt");
    
        // String を行で切り分ける(buf⇒VT)
        str = (char *)&(buf[0]);
        VT.clear();
        num = str.size();
        for (pt = 0; pt < num;)
        {   pw = pt;
            for (pt++; pt < num && str[pt] != '\n'; pt++);
            if (pt >= num)  break;
            pt++;
            Word = Word.assign(str, pw, pt - pw);
            VT.push_back(Word);
            for(; str[pt] == '\r' || str[pt] == '\n' || str[pt] == ' '; pt++);
        }
        VT_size = VT.size();
    
    //for(m_pt=0; m_pt<VT_size; m_pt++) Debug((char *)VT[m_pt].data());
        for (m_pt = 0; m_pt < VT_size; m_pt++)
        {
            Token(VT[m_pt]);
            if (TK.size() < 1 || TK[0][0]=='#') continue;
            if (TK[0] == "g")
            {
                if (TK.size()>1)    m_group = TK[1];
            }
            else    if (TK[0] == "v")
            {
                wf3.x = (float)atof((char *)TK[1].data());
                wf3.y = (float)atof((char *)TK[2].data());
                wf3.z = (float)atof((char *)TK[3].data());
                m_pos.push_back(wf3);
            }
            else    if (TK[0] == "vn")
            {
                wf3.x = (float)atof((char *)TK[1].data());
                wf3.y = (float)atof((char *)TK[2].data());
                wf3.z = (float)atof((char *)TK[3].data());
                m_norm.push_back(wf3);
            }
            else    if (TK[0] == "f")
            {
                uint32  nn;
                int     norm_s = (int)m_norm.size();
                int     pos_s = (int)m_pos.size();
                nn = TK.size() - 1;
                m_idxP.push_back(nn);
                m_idxN.push_back(nn);
                for (uint32 j = 1; j <= nn; j++)
                {
                    SToken(TK[j]);
                    switch (STK.size())
                    {
                    case 3:
                        num = (int)atoi((char *)STK[2].data());
                        if (num>0)  m_idxN.push_back(num);
                        else  if (num<0)    m_idxN.push_back(norm_s+num+1);
                    case 2:
                    case 1:
                        num = (int)atoi((char *)STK[0].data());
                        if (num>0)  m_idxP.push_back(num);
                        else  if (num<0)    m_idxP.push_back(pos_s+num+1);
                        break;
                    default:
                        Debug(L"f SToken Error", STK.size());
                        break;
                    }
                }
            }
            else
            {
                Debug((char *)VT[m_pt].data());
            }
        }
    
        // 4角ポリゴン ⇒ 3角ポリゴン
        PX_P3(m_idxP, &m_idxP3);
        if (m_idxP.size() == m_idxN.size()) PX_P3(m_idxN, &m_idxN3);
        // 頂点データとインデックスデータを作成
        uint32 vtx_size = m_idxP3.size();
        VertexPosition *Vertices = new VertexPosition[vtx_size];
        uint16 *Indices = new uint16[vtx_size];
    
        for(uint32 i=0; i<vtx_size; i++)
        {
            uint32  idx = m_idxP3[i]-1;
            if (idx<m_pos.size())
            {   Vertices[i].pos.x = m_pos[idx].x;
                Vertices[i].pos.y = m_pos[idx].y;
                Vertices[i].pos.z = m_pos[idx].z;
            }
            else    Debug(L"m_pos Index Error", idx);
            if (m_idxP3.size() == m_idxN3.size())
            {
                idx = m_idxN3[i]-1;
                if (idx<m_norm.size())
                {   Vertices[i].norm.x = m_norm[idx].x;
                    Vertices[i].norm.y = m_norm[idx].y;
                    Vertices[i].norm.z = m_norm[idx].z;
                }
                else    Debug(L"m_norm Index Error", idx);
            }
            Indices[i] = i;
        }
    
        // 法線ベクトルの計算
        if (m_idxP3.size() > m_idxN3.size())    ComputeNorm(Vertices, vtx_size);
    
        *indexCount = m_idxP3.size();
        // メッシュを生成
        D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
        vertexBufferData.pSysMem = Vertices;
        vertexBufferData.SysMemPitch = 0;
        vertexBufferData.SysMemSlicePitch = 0;
        CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(VertexPosition)*(*indexCount), D3D11_BIND_VERTEX_BUFFER);
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateBuffer(
                &vertexBufferDesc, &vertexBufferData, vertexBuffer));
    
        // インデックスを生成
        D3D11_SUBRESOURCE_DATA indexBufferData = {0};
        indexBufferData.pSysMem = Indices;
        indexBufferData.SysMemPitch = 0;
        indexBufferData.SysMemSlicePitch = 0;
        CD3D11_BUFFER_DESC indexBufferDesc(sizeof(uint16)*(*indexCount), D3D11_BIND_INDEX_BUFFER);
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateBuffer(
                &indexBufferDesc, &indexBufferData, indexBuffer));
    
        // 領域の解放
        if (Vertices)   delete Vertices;
        if (Indices)    delete Indices;
    
        // カリング設定
        D3D11_RASTERIZER_DESC rasterizerDesc;
        ZeroMemory( &rasterizerDesc, sizeof( D3D11_RASTERIZER_DESC ) );
        //rasterizerDesc.CullMode = D3D11_CULL_BACK;
        rasterizerDesc.CullMode = D3D11_CULL_FRONT;
        rasterizerDesc.FillMode = D3D11_FILL_SOLID;
        rasterizerDesc.DepthClipEnable = FALSE;
        rasterizerDesc.MultisampleEnable = TRUE;
        rasterizerDesc.DepthBiasClamp = 0;
        rasterizerDesc.SlopeScaledDepthBias = 0;
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateRasterizerState(
                &rasterizerDesc, &m_RasterizerState));
    }
    
    // TK[i]⇒st をトークンで切り分けて STK に格納
    void Load_OBJ::SToken(string st)
    {
        uint32  pt, wk;
    
        STK.clear();
        for (pt=0; pt<st.length(); )
        {
            pt = st.find_first_not_of(" ,;", pt);
            if (st[pt]=='\r' || st[pt]=='\n')   return;
            if (st[pt]=='/' && st[pt+1]=='/')
            {   STK.push_back("");
                pt+= 2;
                continue;
            }
            wk = st.find_first_of(" /\r\n", pt);
            if (wk>pt)
            {   Word = Word.assign(st, pt, wk-pt);
                STK.push_back(Word);
                pt = wk;
            }
            else    pt++;
        }
    }
    
  7. 星形のモデルもテストしてみましょう。
    cube.txt を次のように書き換えて下さい。
    # Reference Mesh Object
    v 0.125 0.0 0.0
    v -0.125 0.0 0.0
    v 0.0 0.125 0.0
    v 0.0 -0.125 0.0
    v 0.0 0.0 0.125
    v 0.0 0.0 -0.125
    v 0.5 0.0 0.0
    v -0.5 0.0 0.0
    v 0.0 0.5 0.0
    v 0.0 -0.5 0.0
    v 0.0 0.0 0.5
    v 0.0 0.0 -0.5
    # +x
    f 7 3 5
    f 7 5 4
    f 7 4 6
    f 7 6 3
    f 3 6 4 5
    # -x
    f 8 5 3
    f 8 3 6
    f 8 6 4
    f 8 4 5
    f 5 4 6 3
    # +y
    f 9 5 1
    f 9 1 6
    f 9 6 2
    f 9 2 5
    f 1 5 2 6
    # -y
    f 10 1 5
    f 10 5 2
    f 10 2 6
    f 10 6 1
    f 6 2 5 1
    # +z
    f 11 1 3
    f 11 3 2
    f 11 2 4
    f 11 4 1
    f 1 4 2 3
    # -z
    f 12 3 1
    f 12 1 4
    f 12 4 2
    f 12 2 3
    f 3 2 4 1
    
  8. このプログラムで多くの OBJ Model を描画することが出来ます。
    但し、カラーやテクスチャは無視しているので、モノクロで描画されます。
    ここまでは比較的簡単ですが、この先は OBJ Model と言えども難しくなりそうです。

[Previous Chapter ↑] Win10 OBJ Model

超初心者のプログラム入門(DirectX Store)