新建网页 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;
}
运行截图:
下载源程序