新建網頁 1
17.5.3輪廓勾勒
要完成卡通效果,我們還需要勾勒(outline)輪廓邊(silhouette edge),這比卡通著色稍微復雜一點。
17.5.3.1 邊的表示法
我們將一個網格的一條邊表示為一個四元組(構建自2個三角形)——參見圖17.5。

我們選擇四元組有兩個原因:我們可以通過調整四元組的尺寸容易地改變邊的厚度,并且我們可以渲染退化的四元組來隱藏某些邊,也即非輪廓邊。在Direct3D中,我們從兩個三角形來構建一個四元組。退化四元組(degenerate quad)是從兩個退化三角形構建而來的四元組。退化三角形(degenerate triangle)是一個面積為零的三角形,或者換句話說,是一個三點位于一線上的三角形。如果我們傳入一個退化三角形到渲染管線,則該三角形顯示為空。這是很有用的,因為如果我們希望隱藏特定三角形,我們可以簡單的退化它而不需要實際的從三角形列表(頂點緩沖)移除它。回想一下,我們只需要顯示輪廓邊——而不是網格的每一條邊。
當我們首先創建一條邊的時候,我們指定其四個頂點,并使其退化,這意味著邊將會被隱藏(渲染時不顯示)。

注意圖17.6中的兩個頂點v0和v1,我們設置其頂點法線向量為零向量。然后當我們將邊的頂點送入頂點著色器的時候,頂點著色器將會檢測頂點是否位于輪廓邊上;如果是,則頂點著色器將沿頂點法線的方向偏移頂點位置的標量。觀察法線向量為零的頂點,它不會被偏移。
因此,我們最終以一個非退化四元組(non-degenerate quad)來表示輪廓邊,如圖17.7所示。

備注:如果我們沒有設置頂點v0和v1的頂點法線為零向量,那么那些頂點就同樣會被偏移。但是如果偏移描述輪廓邊的所有四個頂點,那么我們僅是平移了該退化四元組。通過保持頂點v0和v1固定并僅僅偏移頂點v2和v3,我們重新生成了四元組。
17.5.3.2 輪廓邊測試
若兩個三角面face0和face1在視圖方向上與兩個不同方向的面共享同一條邊,則該邊為輪廓邊。也就是說,如果一個面是前面(front facing)而另一個面是后面(back facing),那么這條邊就是一條輪廓邊。圖17.8給出了一個輪廓邊和一個非輪廓邊的例子。

接下來,為了檢測一個頂點是否在輪廓邊上,我們必須以每個頂點為基礎了解face0和 face1的法線向量。我們的邊的頂點數據結構反映如下:
struct VS_INPUT
{
vector position : POSITION;
vector normal : NORMAL0;
vector faceNormal1 : NORMAL1;
vector faceNormal2 : NORMAL2;
};
|
前兩個分量很直接,但讓我們看看兩個額外的法線向量,它們是faceNormal1和faceNormal2。這些向量描述了兩個三角面的面法線,共享邊的頂點位于這兩個面的共享邊上,這兩個面是face0和face1。
實際檢測頂點是否在共享邊上的數學原理如下。假設我們在視圖空間中,令v為一原點指向檢測頂點的向量——圖17.8,令n0為face0的面法線且n1為face0的面法線,若下面的不等式為真,則頂點位于輪廓邊上:
(1)(v·n0)(v·n1)<0
若兩點積符號相異,則不等式為真,使得不等式左邊為負。回想一下點積的性質:兩個點積的符號相異,這意味著一個三角面是前面而另一個是后面。
現在,考慮一條邊只有一個三角形共享它的情況,如圖17.9,其法線將會被存儲在faceNormal1中。

我們定義這種邊總為輪廓邊。要確保頂點著色器將這種邊作為輪廓邊處理,我們要讓faceNormal2 = -faceNormal1。因此,反向的面法線和不等式(1)為真,表示該邊為一輪廓邊。
17.5.3.3 邊的生成
生成網格的邊是微不足道的;我們簡單的遍歷網格的每個三角面并為三角面上每條邊計算一個四元組(退化的,如圖17.6所示)。注意:每個三角形有三條邊。
對于每條邊上的頂點,我們同樣需要知道共享邊的兩個三角面。一個面是邊所在的三角形。例如,如果要計算第1個面的一條邊,那么第1個面共享該邊。共享該邊的另一個面可以使用網格的鄰接信息找到。
輪廓邊的實現代碼:
OutlineEdges.h:
/***************************************************************************************
Generates the outline geometry of a mesh and renders it.
Note that we assume mesh vertex formats as described in sMeshVertex.
***************************************************************************************/
#ifndef OUTLINE_EDGES_H
#define OUTLINE_EDGES_H
#include "d3dUtility.h"
struct sEdgeVertex
{
D3DXVECTOR3 position;
D3DXVECTOR3 normal;
D3DXVECTOR3 face_normal_1;
D3DXVECTOR3 face_normal_2;
};
struct sMeshVertex
{
D3DXVECTOR3 position;
D3DXVECTOR3 normal;
};
const DWORD MESH_VERTEX_FVF = D3DFVF_XYZ | D3DFVF_NORMAL;
////////////////////////////////////////////////////////////////////////////////////
class cOutlineEdges
{
private:
IDirect3DDevice9* m_device;
IDirect3DVertexBuffer9* m_vertex_buffer;
IDirect3DIndexBuffer9* m_index_buffer;
IDirect3DVertexDeclaration9* m_vertex_decl;
UINT m_num_verts;
UINT m_num_faces;
public:
cOutlineEdges(IDirect3DDevice9* device, ID3DXMesh* mesh, ID3DXBuffer* adj_buffer);
~cOutlineEdges();
void render();
private:
bool create_vertex_declaration();
void get_face_normal(ID3DXMesh* mesh, DWORD face_index, D3DXVECTOR3* face_normal);
void get_adj_faces_normal(
ID3DXMesh* mesh,
ID3DXBuffer* adj_buffer,
DWORD current_face_index,
D3DXVECTOR3* current_face_normal,
D3DXVECTOR3 adj_face_normals[3]);
void generate_edge_vertices(ID3DXMesh* mesh, ID3DXBuffer* adj_buffer);
void generate_edge_indices(ID3DXMesh* mesh);
};
#endif
OutlineEdges.cpp:
/***************************************************************************************
Generates the outline geometry of a mesh and renders it.
Note that we assume mesh vertex formats as described in sMeshVertex.
***************************************************************************************/
#include "OutlineEdges.h"
cOutlineEdges::cOutlineEdges(IDirect3DDevice9* device, ID3DXMesh* mesh, ID3DXBuffer* adj_buffer)
{
m_device = device;
m_vertex_buffer = NULL;
m_index_buffer = NULL;
m_vertex_decl = NULL;
m_num_verts = 0;
m_num_faces = 0;
generate_edge_vertices(mesh, adj_buffer);
generate_edge_indices(mesh);
create_vertex_declaration();
}
cOutlineEdges::~cOutlineEdges()
{
safe_release<IDirect3DVertexBuffer9*>(m_vertex_buffer);
safe_release<IDirect3DIndexBuffer9*>(m_index_buffer);
safe_release<IDirect3DVertexDeclaration9*>(m_vertex_decl);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
bool cOutlineEdges::create_vertex_declaration()
{
// typedef struct _D3DVERTEXELEMENT9
// {
// WORD Stream; // Stream index
// WORD Offset; // Offset in the stream in bytes
// BYTE Type; // Data type
// BYTE Method; // Processing method
// BYTE Usage; // Semantics
// BYTE UsageIndex; // Semantic index
// } D3DVERTEXELEMENT9, *LPD3DVERTEXELEMENT9;
D3DVERTEXELEMENT9 decl[] =
{
{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 1},
{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 2},
D3DDECL_END()
};
HRESULT hr = m_device->CreateVertexDeclaration(decl, &m_vertex_decl);
if(FAILED(hr))
{
MessageBox(NULL, "CreateVertexDeclaration() - FAILED", "ERROR", MB_OK);
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
void cOutlineEdges::get_face_normal(ID3DXMesh* mesh, DWORD face_index, D3DXVECTOR3* face_normal)
{
sMeshVertex* vertices;
mesh->LockVertexBuffer(0, (void**)&vertices);
WORD* indices;
mesh->LockIndexBuffer(0, (void**)&indices);
// get the triangle's indices
WORD vert_index_0 = indices[face_index * 3];
WORD vert_index_1 = indices[face_index * 3 + 1];
WORD vert_index_2 = indices[face_index * 3 + 2];
// now extract the triangles vertices position
D3DXVECTOR3 v0 = vertices[vert_index_0].position;
D3DXVECTOR3 v1 = vertices[vert_index_1].position;
D3DXVECTOR3 v2 = vertices[vert_index_2].position;
// compute face normal
D3DXVECTOR3 edge0 = v1 - v0;
D3DXVECTOR3 edge1 = v2 - v0;
D3DXVec3Cross(face_normal, &edge0, &edge1);
D3DXVec3Normalize(face_normal, face_normal);
mesh->UnlockVertexBuffer();
mesh->UnlockIndexBuffer();
}
void cOutlineEdges::get_adj_faces_normal(ID3DXMesh* mesh,
ID3DXBuffer* adj_buffer,
DWORD current_face_index,
D3DXVECTOR3* current_face_normal,
D3DXVECTOR3 adj_face_normals[3])
{
sMeshVertex* vertices;
mesh->LockVertexBuffer(0, (void**)&vertices);
WORD* indices;
mesh->LockIndexBuffer(0, (void**)&indices);
get_face_normal(mesh, current_face_index, current_face_normal);
DWORD* adj = (DWORD*) adj_buffer->GetBufferPointer();
// get adjacent face indices
DWORD adj_face_index_0 = adj[current_face_index * 3];
DWORD adj_face_index_1 = adj[current_face_index * 3 + 1];
DWORD adj_face_index_2 = adj[current_face_index * 3 + 2];
// Get adjacent face normals, if there is no adjacent face, then set the adjacent face normal
// to the opposite of the "current_face_normal". Recall we do this because edges that don't
// have an adjacent triangle are automatically considered outline edges. And in order to
// make that happen, we need the current face normal and adjacent face normal to point in the
// opposite direction. Also, recall that an entry in the adjacency buffer equal to -1 denotes
// that the edge doesn't have an adjacent triangle.
D3DXVECTOR3 adj_face_normal_0, adj_face_normal_1, adj_face_normal_2;
if(adj_face_index_0 != USHRT_MAX) // is there an adjacent triangle?
{
WORD adj_vert_index_0 = indices[adj_face_index_0 * 3];
WORD adj_vert_index_1 = indices[adj_face_index_0 * 3 + 1];
WORD adj_vert_index_2 = indices[adj_face_index_0 * 3 + 2];
D3DXVECTOR3 v0 = vertices[adj_vert_index_0].position;
D3DXVECTOR3 v1 = vertices[adj_vert_index_1].position;
D3DXVECTOR3 v2 = vertices[adj_vert_index_2].position;
D3DXVECTOR3 edge0 = v1 - v0;
D3DXVECTOR3 edge1 = v2 - v0;
D3DXVec3Cross(&adj_face_normal_0, &edge0, &edge1);
D3DXVec3Normalize(&adj_face_normal_0, &adj_face_normal_0);
}
else
{
adj_face_normal_0 = -(*current_face_normal);
}
if( adj_face_index_1 != USHRT_MAX ) // is there an adjacent triangle?
{
WORD adj_vert_index_0 = indices[adj_face_index_1 * 3];
WORD adj_vert_index_1 = indices[adj_face_index_1 * 3 + 1];
WORD adj_vert_index_2 = indices[adj_face_index_1 * 3 + 2];
D3DXVECTOR3 v0 = vertices[adj_vert_index_0].position;
D3DXVECTOR3 v1 = vertices[adj_vert_index_1].position;
D3DXVECTOR3 v2 = vertices[adj_vert_index_2].position;
D3DXVECTOR3 edge0 = v1 - v0;
D3DXVECTOR3 edge1 = v2 - v0;
D3DXVec3Cross(&adj_face_normal_1, &edge0, &edge1);
D3DXVec3Normalize(&adj_face_normal_1, &adj_face_normal_1);
}
else
{
adj_face_normal_1 = -(*current_face_normal);
}
if( adj_face_index_2 != USHRT_MAX ) // is there an adjacent triangle?
{
WORD adj_vert_index_0 = indices[adj_face_index_2 * 3];
WORD adj_vert_index_1 = indices[adj_face_index_2 * 3 + 1];
WORD adj_vert_index_2 = indices[adj_face_index_2 * 3 + 2];
D3DXVECTOR3 v0 = vertices[adj_vert_index_0].position;
D3DXVECTOR3 v1 = vertices[adj_vert_index_1].position;
D3DXVECTOR3 v2 = vertices[adj_vert_index_2].position;
D3DXVECTOR3 edge0 = v1 - v0;
D3DXVECTOR3 edge1 = v2 - v0;
D3DXVec3Cross(&adj_face_normal_2, &edge0, &edge1);
D3DXVec3Normalize(&adj_face_normal_2, &adj_face_normal_2);
}
else
{
adj_face_normal_2 = -(*current_face_normal);
}
// save adjacent face normals
adj_face_normals[0] = adj_face_normal_0;
adj_face_normals[1] = adj_face_normal_1;
adj_face_normals[2] = adj_face_normal_2;
mesh->UnlockVertexBuffer();
mesh->UnlockIndexBuffer();
}
//////////////////////////////////////////////////////////////////////////////////////////////////
void cOutlineEdges::generate_edge_vertices(ID3DXMesh* mesh, ID3DXBuffer* adj_buffer)
{
// 3 edges per face and 4 vertices per edge
m_num_verts = mesh->GetNumFaces() * 3 * 4;
m_device->CreateVertexBuffer(
m_num_verts * sizeof(sEdgeVertex),
D3DUSAGE_WRITEONLY,
0, // using vertex declaration
D3DPOOL_MANAGED,
&m_vertex_buffer,
NULL);
sMeshVertex* vertices;
mesh->LockVertexBuffer(0, (void**) &vertices);
WORD* indices;
mesh->LockIndexBuffer(0, (void**) &indices);
sEdgeVertex* edge_vertices;
m_vertex_buffer->Lock(0, 0, (void**) &edge_vertices, 0);
for(unsigned int i = 0; i < mesh->GetNumFaces(); i++)
{
D3DXVECTOR3 current_face_normal;
D3DXVECTOR3 adj_face_normals[3];
get_adj_faces_normal(mesh, adj_buffer, i, ¤t_face_normal, adj_face_normals);
// get the indices for this face
WORD vert_index_0 = indices[i * 3];
WORD vert_index_1 = indices[i * 3 + 1];
WORD vert_index_2 = indices[i * 3 + 2];
// get the vertices for this face
sMeshVertex v0 = vertices[vert_index_0];
sMeshVertex v1 = vertices[vert_index_1];
sMeshVertex v2 = vertices[vert_index_2];
// A B
// *--------*
// | edge |
// *--------*
// C D
//
// Note, C and D are duplicates of A and B respectively, such that the quad is degenerate.
// The vertex shader will un-degenerate the quad if it is a outline edge.
// compute edge0 v0->v1, note adjacent face normal is adj_face_normals[0]
sEdgeVertex A0, B0, C0, D0;
A0.position = v0.position;
A0.normal = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
A0.face_normal_1 = current_face_normal;
A0.face_normal_2 = adj_face_normals[0];
B0.position = v1.position;
B0.normal = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
B0.face_normal_1 = current_face_normal;
B0.face_normal_2 = adj_face_normals[0];
C0 = A0;
C0.normal = v0.normal;
D0 = B0;
D0.normal = v1.normal;
*edge_vertices = A0; edge_vertices++;
*edge_vertices = B0; edge_vertices++;
*edge_vertices = C0; edge_vertices++;
*edge_vertices = D0; edge_vertices++;
// compute edge0 v1->v2, note adjacent face normal is adj_face_normals[1]
sEdgeVertex A1, B1, C1, D1;
A1.position = v1.position;
A1.normal = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
A1.face_normal_1 = current_face_normal;
A1.face_normal_2 = adj_face_normals[1];
B1.position = v2.position;
B1.normal = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
B1.face_normal_1 = current_face_normal;
B1.face_normal_2 = adj_face_normals[1];
C1 = A1;
C1.normal = v1.normal;
D1 = B1;
D1.normal = v2.normal;
*edge_vertices = A1; ++edge_vertices;
*edge_vertices = B1; ++edge_vertices;
*edge_vertices = C1; ++edge_vertices;
*edge_vertices = D1; ++edge_vertices;
// compute edge0 v0->v2, note adjacent face normal is adj_face_normals[2]
sEdgeVertex A2, B2, C2, D2;
A2.position = v0.position;
A2.normal = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
A2.face_normal_1 = current_face_normal;
A2.face_normal_2 = adj_face_normals[2];
B2.position = v2.position;
B2.normal = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
B2.face_normal_1 = current_face_normal;
B2.face_normal_2 = adj_face_normals[2];
C2 = A2;
C2.normal = v0.normal;
D2 = B2;
D2.normal = v2.normal;
*edge_vertices = A2; ++edge_vertices;
*edge_vertices = B2; ++edge_vertices;
*edge_vertices = C2; ++edge_vertices;
*edge_vertices = D2; ++edge_vertices;
}
m_vertex_buffer->Unlock();
mesh->UnlockVertexBuffer();
mesh->UnlockIndexBuffer();
}
void cOutlineEdges::generate_edge_indices(ID3DXMesh* mesh)
{
DWORD num_edges = mesh->GetNumFaces() * 3;
m_num_faces = num_edges * 2;
m_device->CreateIndexBuffer(
num_edges * 6 * sizeof(WORD), // 2 triangles per edge
D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &m_index_buffer, NULL);
WORD* indices;
m_index_buffer->Lock(0, 0, (void**)&indices, 0);
// 0 1
// *--------*
// | edge |
// *--------*
// 2 3
for(WORD i = 0; i < num_edges; i++)
{
// Six indices to define the triangles of the edge, so every edge we skip six entries in the index buffer.
// Four vertices to define the edge, so every edge we skip four entries in the vertex buffer.
indices[i * 6] = i * 4 + 0;
indices[i * 6 + 1] = i * 4 + 1;
indices[i * 6 + 2] = i * 4 + 2;
indices[i * 6 + 3] = i * 4 + 1;
indices[i * 6 + 4] = i * 4 + 3;
indices[i * 6 + 5] = i * 4 + 2;
}
m_index_buffer->Unlock();
}
//////////////////////////////////////////////////////////////////////////////////////////////////
void cOutlineEdges::render()
{
m_device->SetVertexDeclaration(m_vertex_decl);
m_device->SetStreamSource(0, m_vertex_buffer, 0, sizeof(sEdgeVertex));
m_device->SetIndices(m_index_buffer);
m_device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, m_num_verts, 0, m_num_faces);
}
17.5.4 輪廓邊頂點著色器代碼
我們現在呈現渲染輪廓邊的頂點著色器代碼。這個著色器的主要任務就是確定傳入的頂點是否在輪廓邊上。如果是,頂點著色器就以一定的值,沿頂點法線的方向偏移頂點。
/*************************************************************************************
Vertex shader that draws the outline edges of a mesh.
*************************************************************************************/
matrix g_world_view;
matrix g_proj;
static vector BLACK = {0.0f, 0.0f, 0.0f, 0.0f};
struct sVertexInput
{
vector position : POSITION;
vector normal : NORMAL0;
vector face_normal_1 : NORMAL1;
vector face_normal_2 : NORMAL2;
};
struct sVertexOutput
{
vector position : POSITION;
vector diffuse : COLOR;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
sVertexOutput main(sVertexInput input)
{
sVertexOutput output = (sVertexOutput) 0;
// transform position to view space
input.position = mul(input.position, g_world_view);
// Compute a vector in the direction of the vertex from the eye.
// Recall the eye is at the origin in view space - eye is just camera position.
vector eye_to_vertex = input.position;
// Transform normals to view space.
// !! Important, set w components to zero since we're transforming vectors.
// Assume there are no scalings in the world matrix as well.
input.normal.w = 0.0f;
input.face_normal_1.w = 0.0f;
input.face_normal_2.w = 0.0f;
input.normal = mul(input.normal, g_world_view);
input.face_normal_1 = mul(input.face_normal_1, g_world_view);
input.face_normal_2 = mul(input.face_normal_2, g_world_view);
// compute the cosine of the angles between the eye_to_vertex vector and the face normals
float dot0 = dot(eye_to_vertex, input.face_normal_1);
float dot1 = dot(eye_to_vertex, input.face_normal_2);
// If cosines are different signs (positive/negative) than we are on a outline edge.
// Do the signs differ?
if((dot0 * dot1) < 0.0f)
{
// Yes, then this vertex is on a outline edge, offset the vertex position by some scalar
// in the direction of the vertex normal, which scalar value designate outline's tickness.
input.position += 0.1f * input.normal;
}
// transform to homogeneous clip space
output.position = mul(input.position, g_proj);
output.diffuse = BLACK; // set outline color
return output;
}
執行程序:
/**************************************************************************************************
Demonstrates cartoon rendering with outline edges using a vertex shader.
Note that you will have to switch to the REF device to view this sample if your
graphics card does not support vertex shaders.
Or you can use software vertex processing: D3DCREATE_SOFTWARE_VERTEXPROCESSING.
**************************************************************************************************/
#include "d3dUtility.h"
#include "OutlineEdges.h"
#pragma warning(disable : 4100)
#define MESH_TEAPOT 0
#define MESH_SPHERE 1
#define MESH_TORUS 2
#define MESH_CYLINDER 3
#define NUM_MESH 4
const int WIDTH = 640;
const int HEIGHT = 480;
IDirect3DDevice9* g_device;
IDirect3DVertexShader9* g_vertex_shader;
ID3DXConstantTable* g_constant_table;
IDirect3DTexture9* g_shade_texture;
ID3DXMesh* g_meshes[NUM_MESH];
D3DXMATRIX g_world_matrices[NUM_MESH];
D3DXVECTOR4 g_mesh_colors[NUM_MESH];
D3DXMATRIX g_proj_matrix;
D3DXHANDLE g_world_view_handle;
D3DXHANDLE g_world_view_proj_handle;
D3DXHANDLE g_color_handle;
D3DXHANDLE g_dir_to_light_handle;
cOutlineEdges* g_mesh_outlines[NUM_MESH];
IDirect3DVertexShader9* g_outline_shader;
ID3DXConstantTable* g_outline_constant_table;
D3DXHANDLE g_outline_world_view_handle;
D3DXHANDLE g_outline_proj_handle;
////////////////////////////////////////////////////////////////////////////////////////////////////
bool setup()
{
// create geometry and compute corresponding world matrix and color for each mesh
ID3DXBuffer* adj_buffer[NUM_MESH];
D3DXCreateTeapot(g_device, &g_meshes[MESH_TEAPOT], &adj_buffer[MESH_TEAPOT]);
D3DXCreateSphere(g_device, 1.0f, 20, 20, &g_meshes[MESH_SPHERE], &adj_buffer[MESH_SPHERE]);
D3DXCreateTorus(g_device, 0.5f, 1.0f, 20, 20, &g_meshes[MESH_TORUS], &adj_buffer[MESH_TORUS]);
D3DXCreateCylinder(g_device, 0.5f, 0.5f, 2.0f, 20, 20, &g_meshes[MESH_CYLINDER], &adj_buffer[MESH_CYLINDER]);
D3DXMatrixTranslation(&g_world_matrices[MESH_TEAPOT], 0.0f, 2.0f, 0.0f);
D3DXMatrixTranslation(&g_world_matrices[MESH_SPHERE], 0.0f, -2.0f, 0.0f);
D3DXMatrixTranslation(&g_world_matrices[MESH_TORUS], -3.0f, 0.0f, 0.0f);
D3DXMatrixTranslation(&g_world_matrices[MESH_CYLINDER], 3.0f, 0.0f, 0.0f);
g_mesh_colors[MESH_TEAPOT] = D3DXVECTOR4(1.0f, 0.0f, 0.0f, 1.0f);
g_mesh_colors[MESH_SPHERE] = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);
g_mesh_colors[MESH_TORUS] = D3DXVECTOR4(0.0f, 0.0f, 1.0f, 1.0f);
g_mesh_colors[MESH_CYLINDER] = D3DXVECTOR4(1.0f, 1.0f, 0.0f, 1.0f);
// allocate mesh outlines
g_mesh_outlines[MESH_TEAPOT] = new cOutlineEdges(g_device, g_meshes[MESH_TEAPOT], adj_buffer[MESH_TEAPOT]);
g_mesh_outlines[MESH_SPHERE] = new cOutlineEdges(g_device, g_meshes[MESH_SPHERE], adj_buffer[MESH_SPHERE]);
g_mesh_outlines[MESH_TORUS] = new cOutlineEdges(g_device, g_meshes[MESH_TORUS], adj_buffer[MESH_TORUS]);
g_mesh_outlines[MESH_CYLINDER] = new cOutlineEdges(g_device, g_meshes[MESH_CYLINDER], adj_buffer[MESH_CYLINDER]);
safe_release<ID3DXBuffer*>(adj_buffer[MESH_TEAPOT]);
safe_release<ID3DXBuffer*>(adj_buffer[MESH_SPHERE]);
safe_release<ID3DXBuffer*>(adj_buffer[MESH_TORUS]);
safe_release<ID3DXBuffer*>(adj_buffer[MESH_CYLINDER]);
// compile cartoon shader
ID3DXBuffer* shader_buffer;
ID3DXBuffer* error_buffer;
HRESULT hr = D3DXCompileShaderFromFile("ToonShader.cxx", NULL, NULL, "main", "vs_1_1",
D3DXSHADER_ENABLE_BACKWARDS_COMPATIBILITY,
&shader_buffer, &error_buffer, &g_constant_table);
// output any error messages
if(error_buffer)
{
MessageBox(NULL, (char*)error_buffer->GetBufferPointer(), "ERROR", MB_OK);
safe_release<ID3DXBuffer*>(error_buffer);
}
if(FAILED(hr))
{
MessageBox(NULL, "D3DXCreateEffectFromFile() - FAILED", "ERROR", MB_OK);
return false;
}
hr = g_device->CreateVertexShader((DWORD*) shader_buffer->GetBufferPointer(), &g_vertex_shader);
if(FAILED(hr))
{
MessageBox(NULL, "CreateVertexShader - FAILED", "ERROR", MB_OK);
return false;
}
safe_release<ID3DXBuffer*>(shader_buffer);
// compile outline shader
ID3DXBuffer* outline_shader_buffer;
ID3DXBuffer* outline_error_buffer;
hr = D3DXCompileShaderFromFile("OutlineShader.cxx", NULL, NULL, "main", "vs_1_1",
D3DXSHADER_DEBUG, &outline_shader_buffer, &outline_error_buffer, &g_outline_constant_table);
// output any error messages
if(outline_error_buffer)
{
MessageBox(NULL, (char*) outline_error_buffer->GetBufferPointer(), "ERROR", MB_OK);
safe_release<ID3DXBuffer*>(outline_error_buffer);
}
if(FAILED(hr))
{
MessageBox(NULL, "D3DXCompileShaderFromFile() - FAILED", "ERROR", MB_OK);
return false;
}
hr = g_device->CreateVertexShader((DWORD*) outline_shader_buffer->GetBufferPointer(), &g_outline_shader);
if(FAILED(hr))
{
MessageBox(NULL, "CreateVertexShader - FAILED", "ERROR", MB_OK);
return false;
}
safe_release<ID3DXBuffer*>(outline_shader_buffer);
// load textures
D3DXCreateTextureFromFile(g_device, "toonshade.bmp", &g_shade_texture);
g_device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
g_device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
g_device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE); // disable mipmap
// get handles
g_world_view_handle = g_constant_table->GetConstantByName(NULL, "g_world_view");
g_world_view_proj_handle = g_constant_table->GetConstantByName(NULL, "g_world_view_proj");
g_color_handle = g_constant_table->GetConstantByName(NULL, "g_color");
g_dir_to_light_handle = g_constant_table->GetConstantByName(NULL, "g_dir_to_light");
g_outline_world_view_handle = g_outline_constant_table->GetConstantByName(NULL, "g_world_view");
g_outline_proj_handle = g_outline_constant_table->GetConstantByName(NULL, "g_proj");
// set shader constants
D3DXVECTOR4 dir_to_light(-0.57f, 0.57f, -0.57f, 0.0f);
g_constant_table->SetVector(g_device, g_dir_to_light_handle, &dir_to_light);
g_constant_table->SetDefaults(g_device);
g_outline_constant_table->SetDefaults(g_device);
// set the projection matrix
D3DXMatrixPerspectiveFovLH(&g_proj_matrix, D3DX_PI/4.0f, (float)WIDTH/HEIGHT, 1.0f, 1000.0f);
//g_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
void cleanup()
{
for(int i = 0; i < NUM_MESH; i++)
{
safe_release<ID3DXMesh*>(g_meshes[i]);
safe_delete<cOutlineEdges*>(g_mesh_outlines[i]);
}
safe_release<IDirect3DTexture9*>(g_shade_texture);
safe_release<IDirect3DVertexShader9*>(g_vertex_shader);
safe_release<ID3DXConstantTable*>(g_constant_table);
safe_release<IDirect3DVertexShader9*>(g_outline_shader);
safe_release<ID3DXConstantTable*>(g_outline_constant_table);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
bool display(float time_delta)
{
static float angle = (3.0f * D3DX_PI) / 2.0f;
static float height = 5.0f;
if(GetAsyncKeyState(VK_LEFT) & 0x8000f)
angle -= 0.5f * time_delta;
if(GetAsyncKeyState(VK_RIGHT) & 0x8000f)
angle += 0.5f * time_delta;
if(GetAsyncKeyState(VK_UP) & 0x8000f)
height += 5.0f * time_delta;
if(GetAsyncKeyState(VK_DOWN) & 0x8000f)
height -= 5.0f * time_delta;
D3DXVECTOR3 position(cosf(angle) * 7.0f, height, sinf(angle) * 7.0f);
D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMATRIX view_matrix;
D3DXMatrixLookAtLH(&view_matrix, &position, &target, &up);
// render now
g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xFFFFFFFF, 1.0f, 0);
g_device->BeginScene();
// draw cartoon
g_device->SetVertexShader(g_vertex_shader);
g_device->SetTexture(0, g_shade_texture);
D3DXMATRIX world_view, world_view_proj;
for(int i = 0; i < NUM_MESH; i++)
{
world_view = g_world_matrices[i] * view_matrix;
world_view_proj = g_world_matrices[i] * view_matrix * g_proj_matrix;
g_constant_table->SetMatrix(g_device, g_world_view_handle, &world_view);
g_constant_table->SetMatrix(g_device, g_world_view_proj_handle, &world_view_proj);
g_constant_table->SetVector(g_device, g_color_handle, &g_mesh_colors[i]);
g_meshes[i]->DrawSubset(0);
}
// draw outlines
g_device->SetVertexShader(g_outline_shader);
g_device->SetTexture(0, NULL);
// !! Important, do not cull back faces.
g_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
for(int i = 0; i < NUM_MESH; i++)
{
world_view = g_world_matrices[i] * view_matrix;
g_outline_constant_table->SetMatrix(g_device, g_outline_world_view_handle, &world_view);
g_outline_constant_table->SetMatrix(g_device, g_outline_proj_handle, &g_proj_matrix);
g_mesh_outlines[i]->render();
}
// restore to cull back faces with counterclockwise vertices
g_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
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;
}
運行截圖:

下載源程序