新建網頁 1
作為第二個頂點著色器的例子,讓我們編寫兩個頂點著色器,它們以卡通風格的繪畫方式對網格著色(shade)和輪廓勾勒(outline)。圖17.2展示了這一點:

注意:卡通渲染是一種特定類型的非寫實渲染(non-photorealistic rendering),有時被稱作風格化渲染(stylistic rendering)。
雖然卡通渲染不適用于所有游戲,例如激烈的第一人稱射擊游戲,但是它仍然可以增強一些希望表現卡通感覺類型游戲的氣氛。此外,卡通渲染是漂亮的,并易于實現。讓我們好好的演示一個頂點著色器。
我們將卡通渲染分為兩步:
1. 卡通繪畫的特點是:在一個頂點到下一個頂點的強烈轉換時,有少量的陰影強度級別;我們看一下這個卡通陰影(cartoon shading)。在圖17.2(a)中,我們看到網絡著色使用了三種陰影強度(亮、中、暗),而且其間的過渡是不平滑的——不像圖17.2(c),其明暗過渡是平滑的。
2. 卡通繪圖的主要特點是:在其外框上勾畫輪廓,如圖17.2(b)所示。
這兩個步驟都需要其各自的頂點著色器。
17.5.1 卡通著色
要實現卡通著色,我們采用Lander在2000年3月發表在Game Developer Magazine的文章“Shades of Disney: Opaquing a 3D World”中所描述的方法。我們創建一個帶強度級別的灰度紋理,它包含我們需要的不同的著色強度。圖17.3顯示了我們在樣例程序中使用的這個紋理。

然后在頂點著色器中,我們執行標準漫反射光運算(standard diffuse calculation dot product)來確定頂點法線N和光線向量L之間角度的余弦,用以確定頂點接收到多少光線:s=L·N
如果s<0,就表示光線向量和頂點法線之間的角度大于90度,即該表面接收不到光線。因此,如果s<0,我們就讓s=0。所以s∈ [0, 1]。
現在,在通常的散射光照模型中,我們使用s來標記顏色向量。這樣,頂點顏色的明暗取決于接收到的光照的數量:diffuseColor = s(r, g, b, a)
但是,這將會導致從亮到暗之間平滑的著色。這是與我們期望的卡通著色相反的。我們想要一種在幾個不同著色器間突然轉換顏色的效果(對卡通渲染來說,在2至4種著色器工作起來還是挺不錯的)。
不使用s來標記顏色向量,我們將使用s作為早先提到的強度紋理的u紋理坐標——如圖17.3。
注意:標量(scalar)s必定是一個有效的紋理坐標,因為s∈ [0, 1],這是通常的紋理坐標區間。
按這種方式,頂點不會被平滑著色,而是間斷的。例如,強度紋理可能被分成3種著色,如圖17.4所示:

由圖17.4可以看出:若s∈ [0, 0.33]則使用shader0著色,s∈ [ 0.33,0.66]則使用shader1著色,s∈ [0.66,1]則使用shader2著色。當然,從這些著色的一種到另一種的過渡是不平滑的,這就賦予了我們期望的效果。
注意:我們還為卡通著色關閉了紋理過濾,因為這種過濾會試圖使著色過渡變平滑。這對于我們要求的不連續過渡是多余的。
17.5.2 卡通著色的頂點著色器代碼
我們現在介紹卡通著色的頂點著色器。這個著色器的主要任務只是根據s=L·N計算并設置紋理坐標。注意觀察輸出結構,我們已經增加了一個數據成員來存儲已被計算過的紋理坐標。同時還需注意,我們仍然輸出頂點顏色,雖然我們不修改它,不過當顏色與強度紋理組合起來的時候,便呈現出著色的效果。
/*****************************************************************************************
Vertex shader that lights geometry such it appears to be drawn in a cartoon style.
*****************************************************************************************/
matrix g_world_view;
matrix g_world_view_proj;
vector g_color;
vector g_dir_to_light;
struct sVertexInput
{
vector position : POSITION;
vector normal : NORMAL;
};
struct sVertexOutput
{
vector position : POSITION;
float2 uv : TEXCOORD;
vector diffuse : COLOR;
};
////////////////////////////////////////////////////////////////////////////////////////////////
sVertexOutput main(sVertexInput input)
{
sVertexOutput output = (sVertexOutput) 0;
// transforma vertex position to homogenous clip space
output.position = mul(input.position, g_world_view_proj);
// Transform lights and normals to view space, set w components to zero since we're transforming vectors.
// Assume there are no scalings in the world matrix as well.
g_dir_to_light.w = 0.0f;
input.normal.w = 0.0f;
g_dir_to_light = mul(g_dir_to_light, g_world_view);
input.normal = mul(input.normal, g_world_view);
// compute the 1D texture coordinate for catoon rendering
float u = dot(g_dir_to_light, input.normal);
// Clamp to zero if u is negative because u negative implies the angle between the light and normal
// is greater than 90 degrees. And if that is true then the surface receives no light.
if(u < 0.0f)
u = 0.0f;
float v = 0.5f; // set other texture coordinate to middle
output.uv.x = u;
output.uv.y = v;
output.diffuse = g_color; // save color
return output;
}
兩點注解:
我們假設世界矩陣沒有執行任何縮放。因為如果它執行,它就會弄亂乘以它的頂點的長度和方向。
我們總是設置v紋理坐標為紋理的中點。這意味著我們僅使用紋理中一條單一的線,那就是說我們可以使用1D強度紋理來代替2D的那個紋理。不管怎樣,1D和2D紋理都能工作。本例中,我們使用了2D紋理而不是1D紋理,這是沒有什么特別的原因的。
執行程序:
/**************************************************************************************************
Demonstrates cartoon rendering using a vertex shader. You will have to switch to
the REF device to run this sample if your hardware does not support shaders.
Or you can use software vertex processing: D3DCREATE_SOFTWARE_VERTEXPROCESSING.
**************************************************************************************************/
#include "d3dUtility.h"
#pragma warning(disable : 4100)
#define MESH_TEAPOT 0
#define MESH_SPHERE 1
#define MESH_TORUS 2
#define MESH_CYLINDER 3
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[4];
D3DXMATRIX g_world_matrices[4];
D3DXVECTOR4 g_mesh_colors[4];
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;
////////////////////////////////////////////////////////////////////////////////////////////////////
bool setup()
{
D3DXCreateTeapot(g_device, &g_meshes[MESH_TEAPOT], NULL);
D3DXCreateSphere(g_device, 1.0f, 20, 20, &g_meshes[MESH_SPHERE], NULL);
D3DXCreateTorus(g_device, 0.5f, 1.0f, 20, 20, &g_meshes[MESH_TORUS], NULL);
D3DXCreateCylinder(g_device, 0.5f, 0.5f, 2.0f, 20, 20, &g_meshes[MESH_CYLINDER], NULL);
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);
// compile 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);
// 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");
// 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);
// 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 < 4; i++)
safe_release<ID3DXMesh*>(g_meshes[i]);
safe_release<IDirect3DTexture9*>(g_shade_texture);
safe_release<IDirect3DVertexShader9*>(g_vertex_shader);
safe_release<ID3DXConstantTable*>(g_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, 0xFF333333, 1.0f, 0);
g_device->BeginScene();
g_device->SetVertexShader(g_vertex_shader);
g_device->SetTexture(0, g_shade_texture);
D3DXMATRIX world_view, world_view_proj;
for(int i = 0; i < 4; 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);
}
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;
}
運行截圖:

下載源程序