1. 程式人生 > >Directx11教程(56) 建立一個skydome

Directx11教程(56) 建立一個skydome

.com wol 投影 indexlist 教程 pos htm form ref

原文:Directx11教程(56) 建立一個skydome

      本章建立一個skydome(天空穹),主要學習如何使用cube mapping。

     cube map就是把六張紋理當作一個cube的六個面,而cube的中心,則是坐標軸,而六個面則是垂直於坐標軸某個軸,如下圖所示,在cube mapping中,我們不在使用二維紋理坐標,而是用(u,v,w)三維紋理坐標,用這個坐標產生一個查詢向量,這個向量和cube 紋理的交點,即為該頂點對應的紋理texel。

技術分享圖片

    

       可以通過微軟的Directx Texture tool制作cube map紋理,前提是要準備好6張無縫過度的圖片,這可能需要專業的工具。如下圖,就是用6張圖片生成的cube map紋理圖,每章圖片對應cube的一個面。

技術分享圖片

     我們建立的skydome是一個球形體,並不是skybox,但使用cube map的方法是一樣的。

     首先是建立一個SkyDomeModelClass類,這個類中的頂點結構比較簡單,只有一個參數:position,至於skydome貼圖用的紋理坐標,我們則是在vs中生成。

struct VertexType
    {
    D3DXVECTOR3 position;

    };

    SkyDome模型是通過SkyDomeModelClass中的函數BuildGeoSphere得到,它通過細分一個20面體,得到一個近似的球形模型,具體的細分算法是把一個三角形細分成四個三角形(取每條邊的中點

),如下圖所示:

技術分享圖片

生成SkyDome模型的代碼如下:

//通過一個20面體細分,近似得到一個球體
void SkyDomeModelClass::BuildGeoSphere(    int numSubdivisions,    float radius,    VertexList& vertices, IndexList& indices)
    {
    // 最小的細分數量.
    numSubdivisions = min(numSubdivisions, 5);

    const float X = 0.525731f;
    const float Z = 0.850651f;

    D3DXVECTOR3 pos[12] =
        {
        D3DXVECTOR3(-X, 0.0f, Z),  D3DXVECTOR3(X, 0.0f, Z), 
        D3DXVECTOR3(-X, 0.0f, -Z), D3DXVECTOR3(X, 0.0f, -Z),   
        D3DXVECTOR3(0.0f, Z, X),   D3DXVECTOR3(0.0f, Z, -X),
        D3DXVECTOR3(0.0f, -Z, X),  D3DXVECTOR3(0.0f, -Z, -X),   
        D3DXVECTOR3(Z, X, 0.0f),   D3DXVECTOR3(-Z, X, 0.0f),
        D3DXVECTOR3(Z, -X, 0.0f),  D3DXVECTOR3(-Z, -X, 0.0f)
        };

    DWORD k[60] =
        {
        1,4,0,  4,9,0,  4,5,9,  8,5,4,  1,8,4,   
        1,10,8, 10,3,8, 8,3,5,  3,2,5,  3,7,2,   
        3,10,7, 10,6,7, 6,11,7, 6,0,11, 6,1,0,
        10,1,6, 11,0,9, 2,11,9, 5,2,9,  11,2,7
        };

    vertices.resize(12);
    indices.resize(60);

    for(int i = 0; i < 12; ++i)
        vertices[i] = pos[i];

    for(int i = 0; i < 60; ++i)
        indices[i] = k[i];

    for(int i = 0; i < numSubdivisions; ++i)
        Subdivide(vertices, indices);

    //投影頂點到球面上,然後縮放頂點到球心的距離
    for(int i = 0; i < vertices.size(); ++i)
        {
        D3DXVec3Normalize(&vertices[i], &vertices[i]);
        vertices[i] *= radius;
        }
    }
//細分輸入三角形,為四個面積相等的三角形
void SkyDomeModelClass::Subdivide(VertexList& vertices, IndexList& indices)
    {
    VertexList vin = vertices;
    IndexList  iin = indices;

    vertices.resize(0);
    indices.resize(0);

    int numTris = (int)iin.size()/3;
    for(int i = 0; i < numTris; ++i)
        {
        D3DXVECTOR3 v0 = vin[ iin[i*3+0] ];
        D3DXVECTOR3 v1 = vin[ iin[i*3+1] ];
        D3DXVECTOR3 v2 = vin[ iin[i*3+2] ];

        D3DXVECTOR3 m0 = 0.5f*(v0 + v1);
        D3DXVECTOR3 m1 = 0.5f*(v1 + v2);
        D3DXVECTOR3 m2 = 0.5f*(v0 + v2);

        vertices.push_back(v0); // 0
        vertices.push_back(v1); // 1
        vertices.push_back(v2); // 2
        vertices.push_back(m0); // 3
        vertices.push_back(m1); // 4
        vertices.push_back(m2); // 5

        //索引出四個三角形
        indices.push_back(i*6+0);

        indices.push_back(i*6+4);
        }
    }

     另外我們新建一個SkyDomeShaderClass, 用來渲染SkyDome,它調用的vs,ps shader文件為cubetex.vs, cubetex.ps

     在cubeTex.vs中,我們要註意兩點:

1、設置skydome頂點的世界坐標系z=w,這樣,skydome總會在遠裁剪平面上。

2、用頂點local坐標做為cubemap的紋理坐標。

// set z = w so that z/w = 1 (i.e., skydome always on far plane).
//設置z=w
output.position =  output.position.xyww;

//用local坐標做為cubemap查詢向量.
output.tex = input.position;

     cubeTex.ps中,我們通過cube sample函數得到cube紋理:

TextureCube gCubeMap;

float4 CubePixelShader(PixelInputType input) : SV_TARGET
{

     return gCubeMap.Sample(SampleType, input.tex);
}

     在GraphicsClass類中渲染SkyDome時,我們要關掉cull,再就是建立一個skydome專用depthstencil狀態,在該狀態中深度比較函數是: D3D11_COMPARISON_LESS_EQUAL

//skyome 頂點和索引數據放入緩沖區,準備渲染
m_SkydomeModel->Render(m_D3D->GetDeviceContext());

m_D3D->EnableCubeDepthStencil();
m_D3D->ChangeNoCullMode(true);

result = m_SkydomeShader->Render(m_D3D->GetDeviceContext(), m_SkydomeModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
    m_TexManager->createCubeTex(m_D3D->GetDevice(),string("grassenvmap1024.dds")));

if(!result)
    {
    return false;
    }
m_D3D->ChangeNoCullMode(false);
m_D3D->EnableDefaultDepthStencil();

     另外,在textureManagerClass類中,我們增加了函數createCubeTex,專門用來讀入cube texture

ID3D11ShaderResourceView* TexManagerClass::createCubeTex(ID3D11Device* device,string filename)
    {

   // 如果紋理資源已經存在,則返回,否則創建
    for(int i = 0; i < m_TextureRVs.size(); ++i)
        if(! m_TextureNames[i].compare(filename) )
            return m_TextureRVs[i];

     HRESULT result;
     D3DX11_IMAGE_LOAD_INFO loadInfo;
     loadInfo.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE;

     ID3D11Texture2D* tex = 0;
     result = D3DX11CreateTextureFromFile(device, stringToLPCWSTR(filename), &loadInfo, 0, (ID3D11Resource**)&tex, 0) ;

     D3D11_TEXTURE2D_DESC texDesc;
    tex->GetDesc(&texDesc);

    D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc;
    viewDesc.Format = texDesc.Format;
    viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
    viewDesc.TextureCube.MipLevels = texDesc.MipLevels;
    viewDesc.TextureCube.MostDetailedMip = 0;

    ID3D11ShaderResourceView* rv = 0;
    result = device->CreateShaderResourceView(tex, &viewDesc, &rv);
    if(FAILED(result))
        {
        HR(result);
        return false;
        }

    m_TextureNames.push_back(filename);
    m_TextureRVs.push_back(rv);

    return rv;
    }

程序執行後界面如下,你可以旋轉攝像機,看看能不能超過skydome的包圍:
技術分享圖片

完整的代碼請參考:

工程文件myTutorialD3D11_51

代碼下載:

http://files.cnblogs.com/mikewolf2002/d3d1150-58.zip

http://files.cnblogs.com/mikewolf2002/pictures.zip

Directx11教程(56) 建立一個skydome