天行健 君子当自强而不息

D3D中的网格模型(5)

11.2.4 产生顶点法线

一个X文件不包含顶点法线数据,这是很有可能的。假如是这种情况,那么手动计算顶点法线以便我们能够使用灯光这是很有必要的。现在知道了ID3DXMesh接口和它的父接口ID3DXBaseMesh,我们能够使用下面的函数来产生任何mesh的顶点法线:

Computes unit normals for each vertex in a mesh. Provided to support legacy applications. Use D3DXComputeTangentFrameEx for better results.

HRESULT D3DXComputeNormals(
LPD3DXBASEMESH pMesh,
CONST DWORD * pAdjacency
);

Parameters

pMesh
[in, out] Pointer to an ID3DXBaseMesh interface, representing the normalized mesh object. This function may not take an ID3DXPMesh progressive mesh as input.
pAdjacency
[in] Pointer to an array of three DWORDs per face that specify the three neighbors for each face in the created progressive mesh. This parameter is optional and should be set to NULL if it is unused.

Return Values

If the function succeeds, the return value is S_OK. If the function fails, the return value can be one of the following: D3DERR_INVALIDCALL, D3DXERR_INVALIDDATA, E_OUTOFMEMORY.

Remarks

The input mesh must have the D3DFVF_NORMAL flag specified in its flexible vertex format (FVF).

A normal for a vertex is generated by averaging the normals of all faces that share that vertex.

If adjacency is provided, replicated vertices are ignored and "smoothed" over. If adjacency is not provided, replicated vertices will have normals averaged in from only the faces explicitly referencing them.

This function simply calls D3DXComputeTangentFrameEx with the following input parameters:

D3DXComputeTangentFrameEx( pMesh,
D3DX_DEFAULT,
0,
D3DX_DEFAULT,
0,
D3DX_DEFAULT,
0,
D3DDECLUSAGE_NORMAL,
0,
D3DXTANGENT_GENERATE_IN_PLACE | D3DXTANGENT_CALCULATE_NORMALS,
pAdjacency,
-1.01f,
-0.01f,
-1.01f,
NULL,
NULL);

这个函数通过使用平均法线的方法来产生顶点法线。假如有邻接信息,那么重复的顶点是被忽略的。假如没有邻接信息,那么重复的顶点也会被重复计算。了解这些是很重要的,我们检查pMash必须有一个包含D3DFVF_NORMAL标记的顶点格式。

注意假如X文件不包含顶点法线数据,那么通过D3DXLoadMeshFromX创建的ID3DXMesh对象在它的顶点格式中没有指定的D3DFVF_NORMAL标记。因此,在我们能够使用D3DXComputeNormals之前,我们必须克隆mesh并且为其指定包含D3DFVF_NORMAL的顶点格式。下面就是相应的代码:

// does the mesh have a D3DFVF_NORMAL in its vertex format?

if ( !(pMesh->GetFVF() & D3DFVF_NORMAL) )

{

       // no, so clone a new mesh and add D3DFVF_NORMAL to its format:

       ID3DXMesh* pTempMesh = 0;

       pMesh->CloneMeshFVF(

              D3DXMESH_MANAGED,

              pMesh->GetFVF() | D3DFVF_NORMAL, // add it here

              Device,

              &pTempMesh );

 

       // compute the normals:

       D3DXComputeNormals( pTempMesh, NULL );

 

       pMesh->Release(); // get rid of the old mesh

       pMesh = pTempMesh; // save the new mesh with normals

}

11.3渐进网格(Progressive Meshes)

渐进网格,它通过ID3DXPMesh接口来表现,允许我们通过边缩减转换(edge collapse transformations,ECT)来简化mesh。每执行一次ECT就移除一个顶点和一或2个面。因为每个ECT是可逆的(它的逆过程叫顶点分裂),我们能够逆转简化过程并且恢复mesh为它的原始状态。当然,我们不可能得到比原始情况还要精细的网格。我们仅仅只能简化然后恢复简化操作。图11.2显示了同一个mesh的三种不同精细级别(levels of detail,LOD):高,中,低。

渐进网格和mipmaps纹理非常相似。当使用纹理时,我们已经注意到在一个小或远的图元上使用高分辨率的纹理简直就是浪费。对于mesh也是同样的道理,一个小或远的mesh不需要太多三角形,多了也是浪费。因此,我们不会花费渲染高三角形模型的时间来渲染一个只需要表现小的低三角形模型。

我们可以使用渐进网格来根据模型距离摄象机的距离来调整模型的LOD。也就是说,当距离减少时,我们增加mesh的细节,当距离增加时我们减少mesh的细节。

11.3.1 产生一个渐进网格

我们能够使用下面的函数来创建一个ID3DXPMesh对象:

HRESULT D3DXGeneratePMesh(

       LPD3DXMESH pMesh,

       CONST DWORD *pAdjacency,

       CONST LPD3DXATTRIBUTEWEIGHTS pVertexAttributeWeights,

       CONST FLOAT *pVertexWeights,

       DWORD MinValue,

       DWORD Options,

       LPD3DXPMESH *ppPMesh

);

pMesh— 输入原始mesh,它包含了我们想要生成的渐进网格的mesh数据。

pAdjacency — 指向一个包含pMesh邻接信息的DWORD数组。

pVertexAttributeWeights — 指向一个D3DXATTRIBUTEWEIGHTS数组,它的大小是pMesh->GetNumVertices()。它的第i项与pMesh中的第i个顶点相对应并且指定的是它的品质权重。品质权重被用来确定一个顶点被删除的可能性大小。你能够将此参数设置为null,对于每个顶点一个默认的顶点品质权重将被设置。

pVertexWeights — 指向一个float数组,它的大小是pMesh->GetNumVertices(),它的第i项与pMesh中的第i个顶点相对应并且指定的是它的顶点权重。顶点权重越高被删除的可能性越小。你能够将此参数设置为null,对于每个顶点一个默认的顶点品质权重1.0将被设置。

MinValue — 我们想要简化到的最小顶点或面数。注意该值是必须的,而且与顶点/品质权重有关,最终可能达不到该值。

Options — 只能取D3DXMESHSIMP枚举类型中的一个值:

         D3DXMESHSIMP_VERTEX — 指定在上一个参数MinValue中提到的数为顶点数。

         D3DXMESHSIMP_FACE —指定在上一个参数MinValue中提到的数为面数。

ppPMesh — 返回生成好的渐进网格。

11.3.2 顶点品质权重

Specifies mesh weight attributes.

typedef struct D3DXATTRIBUTEWEIGHTS {
FLOAT Position;
FLOAT Boundary;
FLOAT Normal;
FLOAT Diffuse;
FLOAT Specular;
FLOAT Texcoord[8];
FLOAT Tangent;
FLOAT Binormal;
} D3DXATTRIBUTEWEIGHTS, *LPD3DXATTRIBUTEWEIGHTS;

Members

Position
Position.
Boundary
Blend weight.
Normal
Normal.
Diffuse
Diffuse lighting value.
Specular
Specular lighting value.
Texcoord
Eight texture coordinates.
Tangent
Tangent.
Binormal
Binormal.

Remarks

This structure describes how a simplification operation will consider vertex data when calculating relative costs between collapsing edges. For example, if the Normal field is 0.0, the simplification operation will ignore the vertex normal component when calculating the error for the collapse. However, if the Normal field is 1.0, the simplification operation will use the vertex normal component. If the Normal field is 2.0, double the amount of errors; if the Normal field is 4.0, then quadruple the number of errors, and so on.

The LPD3DXATTRIBUTEWEIGHTS type is defined as a pointer to the D3DXATTRIBUTEWEIGHTS structure.

    
typedef D3DXATTRIBUTEWEIGHTS* LPD3DXATTRIBUTEWEIGHTS;

顶点权重结构允许我们为每个顶点属性指定一个权值。0.0表示该属性没有权重。顶点属性的权重越高在简化过程中被移除的可能性越小。默认的权值如下:

D3DXATTRIBUTEWEIGHTS AttributeWeights;

AttributeWeights.Position = 1.0;

AttributeWeights.Boundary = 1.0;

AttributeWeights.Normal = 1.0;

AttributeWeights.Diffuse = 0.0;

AttributeWeights.Specular = 0.0;

AttributeWeights.Tex[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

默认的权值是被推荐的,除非你的应用程序有一个重要的理由而不使用它。

11.3.3 ID3DXPMesh方法

ID3DXPMesh接口是继承自ID3DXBaseMesh接口,下面是一些方法:

DWORD GetMaxFaces(VOID)——返回渐进网格能够被设置的最大面数。

DWORD GetMaxVertices(VOID)——返回渐进网格能够被设置的最大顶点数。

DWORD GetMinFaces(VOID)——返回渐进网格能够被设置的最小面数。

DWORD GetMinVertices(VOID)——返回渐进网格能够被设置的最小顶点数。

HRESULT SetNumFaces(DWORD Faces)——这个方法允许我们设置面的个数,以便让mesh简化/复杂化。例如,假设mesh目前有50个面,我们现在想将它简化到30个面;我们将写成:

pmesh->SetNumFaces(30);

注意调整后的面数可能并不是我们设定的面数。假如面数小于了GetMinFaces(),那么面数将为GetMinFaces()。同样的,假如面数大于了GetMaxFaces(),那么面数将为GetMaxFaces()。

HRESULT SetNumVertices(DWORD Vertices)——这个方法允许我们设置顶点的个数,以便让mesh简化/复杂化。例如,假设mesh目前有20个顶点,我们现在想将它增加到40个;我们将写成:

pmesh->SetNumVertices(40);

注意调整后的顶点数可能并不是我们设定的数。假如顶点数小于了GetMinVertices(),那么顶点数将为GetMinVertices()。同样的,假如顶点数大于了GetMaxVertices(),那么顶点数将为GetMaxVertices()。

HRESULT TrimByFaces(

DWORD NewFacesMin,

DWORD NewFacesMax,

DWORD *rgiFaceRemap, // Face remap info.

DWORD *rgiVertRemap // Vertex remap info.

);

这个方法允许我们设置新的最小和最大面数,分别通过NewFacesMin和NewFacesMax指定。注意新的最小和最大值必须在现有最小和最大面数之间;也就是说,必须在[GetMinFaces(),GetMaxFaces()]之中。该函数也返回面和顶点的重影射信息。

HRESULT TrimByVertices(

DWORD NewVerticesMin,

DWORD NewVerticesMax,

DWORD *rgiFaceRemap, // Face remap info.

DWORD *rgiVertRemap // Vertex remap info.

);

这个方法允许我们设置新的最小和最大顶点数,分别通过NewVerticesMin和NewVerticesMax指定。注意新的最小和最大值必须在现有最小和最大顶点数之间;也就是说,必须在[GetMinVertices(),GetMaxVertices()]之中。

11.3.4实例程序:渐进网格

渐进网格例子与X文件例子很相似,除了实际上我们创建和渲染的是一个渐进网格,通过ID3DXPMesh接口来表现。我们允许用户通过键盘输入进行交互式地改变渐进网格。你能通过按A键来增加mesh的面数,按S键来减少mesh的面数。

运行截图:
主程序:

/**************************************************************************************
  Demonstrates how to use the progressive mesh interface (ID3DXPMesh).  
  Use the 'A' key to add triangles, use the 'S' key to remove triangles.  
  Note that we outline the triangles in yellow so that you can see them get
  removed and added. 
 *************************************************************************************
*/

#include 
<vector>
#include 
"d3dUtility.h"

#pragma warning(disable : 
4100)

using namespace std;

const int WIDTH  = 640;
const int HEIGHT = 480;

IDirect3DDevice9
*            g_device;
ID3DXMesh
*                    g_source_mesh;
ID3DXPMesh
*                    g_pmesh;
vector
<D3DMATERIAL9>        g_materials;
vector
<IDirect3DTexture9*>    g_textures;

////////////////////////////////////////////////////////////////////////////////////////////////////

bool setup()
{    
    
// load the XFile data

    ID3DXBuffer
*    adjacency_buffer = NULL;
    ID3DXBuffer
*    material_buffer  = NULL;
    DWORD            num_material     
= 0;

    HRESULT hr 
= D3DXLoadMeshFromX("bigship1.x", D3DXMESH_MANAGED, g_device, &adjacency_buffer, &material_buffer,
                                   NULL, 
&num_material, &g_source_mesh);

    
if(FAILED(hr))
    {
        MessageBox(NULL, 
"D3DXLoadMeshFromX() - FAILED""ERROR", MB_OK);
        
return false;
    }

    
// extract the materials, and load textures.
    if(material_buffer != NULL && num_material != 0)
    {
        D3DXMATERIAL
* materials = (D3DXMATERIAL*) material_buffer->GetBufferPointer();

        
for(DWORD i = 0; i < num_material; i++)
        {
            
// the MatD3D property doesn't have an ambient value set when it load, so set it now.
            materials[i].MatD3D.Ambient = materials[i].MatD3D.Diffuse;

            
// save the ith material
            g_materials.push_back(materials[i].MatD3D);

            
// check if the ith material has an associative texture
            if(materials[i].pTextureFilename != NULL)
            {
                
// yes, load the texture for the ith subset.
                IDirect3DTexture9* texture;
                D3DXCreateTextureFromFile(g_device, materials[i].pTextureFilename, 
&texture);

                
// save the loaded texture
                g_textures.push_back(texture);
            }
            
else
            {
                
// no texture for the ith subset
                g_textures.push_back(NULL);
            }
        }
    }

    safe_release
<ID3DXBuffer*>(material_buffer);
    
    
// optimize the mesh    
    hr = g_source_mesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE,
        (DWORD
*) adjacency_buffer->GetBufferPointer(), 
        (DWORD
*) adjacency_buffer->GetBufferPointer(), // new adjacency info
        NULL, NULL);    

    
if(FAILED(hr))
    {
        MessageBox(NULL, 
"OptimizeInplace() - FAILED""ERROR", MB_OK);
        safe_release
<ID3DXBuffer*>(adjacency_buffer);
        
return false;
    }

    
// generate the progressive mesh
    hr = D3DXGeneratePMesh(
        g_source_mesh,
        (DWORD
*) adjacency_buffer->GetBufferPointer(),
        NULL,                
// default vertex attribute weights
        NULL,                // default vertex weights
        1,                    // simply as low as possible
        D3DXMESHSIMP_FACE,    // simplify by face count
        &g_pmesh);

    safe_release
<ID3DXMesh*>(g_source_mesh);
    safe_release
<ID3DXBuffer*>(adjacency_buffer);

    
if(FAILED(hr))
    {
        MessageBox(NULL, 
"D3DXGeneratePMesh() - FAILED""ERROR", MB_OK);
        
return false;
    }

    
// set to original detail
    DWORD max_faces = g_pmesh->GetMaxFaces();
    g_pmesh
->SetNumFaces(max_faces);

    
// set texture filters
    g_device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    g_device
->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
    g_device
->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

    
// set lights

    D3DXVECTOR3 dir(
1.0f-1.0f1.0f);
    D3DXCOLOR color(
1.0f1.0f1.0f1.0f);
    D3DLIGHT9 light 
= init_directional_light(&dir, &color);

    g_device
->SetLight(0&light);
    g_device
->LightEnable(0, TRUE);
    g_device
->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
    g_device
->SetRenderState(D3DRS_SPECULARENABLE, TRUE);

    
// set camera

    D3DXVECTOR3 pos(
-8.0f4.0f-12.0f);
    D3DXVECTOR3 target(
0.0f0.0f0.0f);
    D3DXVECTOR3 up(
0.0f1.0f0.0f);

    D3DXMATRIX view_matrix;
    D3DXMatrixLookAtLH(
&view_matrix, &pos, &target, &up);
    g_device
->SetTransform(D3DTS_VIEW, &view_matrix);

    
// set the projection matrix
    D3DXMATRIX proj;
    D3DXMatrixPerspectiveFovLH(
&proj, D3DX_PI * 0.5f, (float)WIDTH/HEIGHT, 1.0f1000.0f);
    g_device
->SetTransform(D3DTS_PROJECTION, &proj);
    
    
return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////

void cleanup()
{    
    safe_release
<ID3DXPMesh*>(g_pmesh);
    
    
for(DWORD i = 0; i < g_textures.size(); i++)
        safe_release
<IDirect3DTexture9*>(g_textures[i]);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////

bool display(float time_delta)
{
    
// update: rotate the mesh

    
// get the current number of faces the pmesh has
    DWORD num_faces = g_pmesh->GetNumFaces();

    
// Add a face, note the SetNumFaces() will automatically clamp the specified value
    
// if it goes out of bounds.
    if(GetAsyncKeyState('A'& 0x8000f)
    {
        
// Sometimes we must add more than one face to invert an edge collaps transformation
        
// because of the internal implementation details of the ID3DXPMesh interface.
        
// In other words, adding one face may possibly result in a mesh with the same number
        
// of faces as before. Thus to increase the face count we may some times have to add
        
// two faces at once.

        g_pmesh
->SetNumFaces(num_faces + 1);

        
if(g_pmesh->GetNumFaces() == num_faces)
            g_pmesh
->SetNumFaces(num_faces + 2);
    }

     
// Remove a face, note the SetNumFaces() will automatically clamp the specified value
    
// if it goes out of bounds.
    if(GetAsyncKeyState('S'& 0x8000f)
        g_pmesh
->SetNumFaces(num_faces - 1);

    
// render now

    g_device
->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x000000001.0f0);

    g_device
->BeginScene();

    
for(DWORD i = 0; i < g_materials.size(); i++)
    {
        
// draw pmesh
        g_device->SetMaterial(&g_materials[i]);
        g_device
->SetTexture(0, g_textures[i]);
        g_pmesh
->DrawSubset(i);

        
// draw wireframe outline
        g_device->SetMaterial(&YELLOW_MATERIAL);
        g_device
->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
        g_pmesh
->DrawSubset(i);

        
// restore solid mode
        g_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
    }

    g_device
->EndScene();

    g_device
->Present(NULL, NULL, NULL, NULL);

    
return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////

LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM word_param, LPARAM long_param)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
break;

    
case WM_KEYDOWN:
        
if(word_param == VK_ESCAPE)
            DestroyWindow(hwnd);
        
break;
    }

    
return DefWindowProc(hwnd, msg, word_param, long_param);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////

int WINAPI WinMain(HINSTANCE inst, HINSTANCE, PSTR cmd_line, int cmd_show)
{
    
if(! init_d3d(inst, WIDTH, HEIGHT, true, D3DDEVTYPE_HAL, &g_device))
    {
        MessageBox(NULL, 
"init_d3d() - failed."0, MB_OK);
        
return 0;
    }

    
if(! setup())
    {
        MessageBox(NULL, 
"Steup() - failed."0, MB_OK);
        
return 0;
    }

    enter_msg_loop(display);

    cleanup();
    g_device
->Release();

    
return 0;
}

下载源程序


posted on 2008-03-29 17:05 lovedday 阅读(1934) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论