文章目录
图片渲染,在前文【DirectX3D - 2】渲染YUV图片(离屏表面方式)已经介绍过使用离屏表面的方式渲染图片了。本文介绍使用纹理内存的方式来渲染图片
纹理
纹理资源格式
- 图 - 1 [1]
Direct3D中的纹理
表示物体表面细节的一幅或几幅二维图形,也称纹理贴图(texture mapping)当把纹理按照特定的方式映射到物体表面上的时候能使物体看上去更加真实。纹理映射是一种允许我们为三角形赋予图象数据的技术;这让我们能够更细腻更真实地表现我们的场景。[2]
我们在这里使用纹理来渲染图片,简单的我们既可以将纹理视为一张图片,我们的目标就是要将这一张图片,贴在我们预先设置好的区域内。这个区域可以是屏幕窗口的区域,也可以是窗口中某个三维物体的一个表面。在本文中,我们的目的是将图片显示在窗口中。
坐标系
四大变换
世界变换、取景变换、投影变换、视口变换
- 为了在世界空间中的指定位置绘制图形,需要进行世界变换
- 为了以不同的视角观察图形,需要进行取景变换
- 为了将相对较远的图形投影到同一个平面上并体现出“近大远小”的真实视觉效果,需要进行投影变换
- 为了控制显示图形的窗口大小、比例以及深度,需要用到视口变换
视口
typedef struct D3DVIEWPORT9 {
DWORD X;
DWORD Y;
DWORD Width;
DWORD Height;
float MinZ;
float MaxZ;
} D3DVIEWPORT9, *LPD3DVIEWPORT9;
用DirectX实现多视图有几种方法,可以使用多个Viewport,也可以使用多个Swap Chain,后者实现起来比较复杂,以后再做介绍,先看如何使用多个viewport来实现。那么到底什么是viewport呢?举个现实中的例子,假设你站在一个密封的房子里,这个房子只有一个很小的窗口,你现在就站在窗口前面,通过这个窗口你可以观察到外面的世界,那么这个窗口就相当于一个视口,而外面的世界就是3D中的场景。视口有以下几个属性,长度和宽度,为了确定窗口的位置,我们还需要一个左上角坐标。为了支持Z-Buffer,还需要两个深度值,分别是zMin, zMax,表示最小深度和最大深度。这就是视口的定义。
在D3D中,视口用如上的结构体来表示,X和Y表示视口的左上角坐标,Width和Height表示窗口的宽度和高度,MinZ和MaxZ表示Z-buffer的最小值和最大值。[3]
在本例子中,图片的不同视口,区别不大,我们可以通过改变参数来改变视口的大小, 从而看见不同大小的图片。而在三维渲染中,我们通过不同的视口,能够从不同的角度观察物体, 例如三视图就可以看做是不同视口的观察结果。
关于D3D中的坐标系可以参看以下资料:
代码
/***************************************************************************************
创建纹理内存,渲染图片
***************************************************************************************/
#include <windows.h>
#include <stdio.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <tchar.h>
#include <time.h>
#include "d3dx9math.h"
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib,"dxguid.lib")
#pragma comment(lib, "winmm.lib")
#define WINDOW_WIDTH 1600
#define WINDOW_HEIGHT 900
#define Safe_Release(p) if((p)) (p)->Release();
HWND g_hwnd;
HINSTANCE g_inst;
static wchar_t g_class_name[] = L"D3D TEST";
static wchar_t g_caption[] = L"D3D Texture";
IDirect3D9Ex* g_d3d = NULL;
IDirect3DDevice9* g_d3d_device = NULL;
IDirect3DVertexBuffer9* g_vertex_buffer = NULL; // 顶点坐标缓存
IDirect3DTexture9* g_texture = NULL; // 纹理
// 3D顶点格式
typedef struct
{
float x, y, z; // 3D 坐标, z = 0, 表示2维平面
float u, v; // 纹理坐标
} VERTEX;
#define VERTEX_FVF (D3DFVF_XYZ | D3DFVF_TEX1)
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return (long)DefWindowProc(hwnd, msg, wParam, lParam);
}
BOOL d3d_init()
{
D3DPRESENT_PARAMETERS present_param;
D3DDISPLAYMODE display_mode;
D3DXMATRIX mat_proj, mat_view;
BYTE* vertex_ptr;
// 纹理坐标(u,v)的范围是[0, 1],在这里的设置,(0,0)(1,0)(0,1)(1,1),表示一个矩形
VERTEX verts[] = {
{ -200.0f, 200.0f, 0.0f, 0.0f, 0.0f },
{ 200.0f, 200.0f, 0.0f, 1.0f, 0.0f },
{ -200.0f, -200.0f, 0.0f, 0.0f, 1.0f },
{ 200.0f, -200.0f, 0.0f, 1.0f, 1.0f }
};
Direct3DCreate9Ex(D3D_SDK_VERSION, &g_d3d);
if (FAILED(g_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
return FALSE;
ZeroMemory(&present_param, sizeof(present_param));
// 初始化参数
present_param.Windowed = TRUE;
present_param.SwapEffect = D3DSWAPEFFECT_DISCARD;
present_param.BackBufferFormat = display_mode.Format;
present_param.EnableAutoDepthStencil = TRUE;
present_param.AutoDepthStencilFormat = D3DFMT_D16;
// 创建D3D设备
if (FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &present_param, &g_d3d_device))) {
return FALSE;
}
// 设置渲染状态
// FALSE:关闭渲染灯光
g_d3d_device->SetRenderState(D3DRS_LIGHTING, FALSE);
// 使能z坐标,这里不需要使用纵坐标注释掉
// g_d3d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
// 创建设置投影矩阵
// builds a left-handed perspective projection matrix based on a field of view
D3DXMatrixPerspectiveFovLH(&mat_proj, D3DX_PI / 4.0, 1.33333, 1.0, 1000.0);
// sets a single device transformation-related state
g_d3d_device->SetTransform(D3DTS_PROJECTION, &mat_proj);
// create and set the view matrix
D3DXMatrixLookAtLH(&mat_view,
&D3DXVECTOR3(0.0, 0.0, -1000.0),
&D3DXVECTOR3(0.0f, 0.0f, 0.0f),
&D3DXVECTOR3(0.0f, 1.0f, 0.0f));
g_d3d_device->SetTransform(D3DTS_VIEW, &mat_view);
// create the vertex buffer and set data
g_d3d_device->CreateVertexBuffer(sizeof(VERTEX) * 4, 0, VERTEX_FVF, D3DPOOL_DEFAULT, &g_vertex_buffer, NULL);
// locks a range of vertex data and obtains a pointer to the vertex buffer memory
g_vertex_buffer->Lock(0, 0, (void**)& vertex_ptr, 0);
memcpy(vertex_ptr, verts, sizeof(verts));
// unlocks vertex data
g_vertex_buffer->Unlock();
// load the texture map
D3DXCreateTextureFromFile(g_d3d_device, L"test.png", &g_texture);
return TRUE;
}
BOOL d3d_release()
{
Safe_Release(g_vertex_buffer);
Safe_Release(g_texture);
Safe_Release(g_d3d_device);
Safe_Release(g_d3d);
return TRUE;
}
BOOL d3d_render()
{
D3DXMATRIX mat_world;
// 清理设备缓存
g_d3d_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0, 0, 0, 0), 1.0f, 0);
// Begin scene
if (SUCCEEDED(g_d3d_device->BeginScene()))
{
// 创建和设置世界转换矩阵,沿着 z 轴旋转
// D3DXMatrixRotationZ(&mat_world, (float)(timeGetTime() / 1000.0));
// 创建和设置世界转换矩阵,不旋转
D3DXMatrixRotationZ(&mat_world, 0.0);
// 平移矩阵, 向左平移200,向下平移100个单位
/*D3DXMATRIX mTrans;
D3DXMatrixTranslation(&mTrans, -200, -100, 0);
mat_world *= mTrans;
D3DXMatrixMultiply(&mat_world, &mat_world, &mTrans);*/
// 设置视口位置
D3DVIEWPORT9 vp = {0, 320, WINDOW_WIDTH, WINDOW_HEIGHT, 0, 1};
g_d3d_device->SetViewport(&vp);
// 认定矩阵 mat_world 就是世界矩阵
g_d3d_device->SetTransform(D3DTS_WORLD, &mat_world);
// 设置顶点流、着色器和纹理。
// 将顶点缓冲区设置到设备数据流
g_d3d_device->SetStreamSource(0, g_vertex_buffer, 0, sizeof(VERTEX));
// 设置当前顶点流声明
g_d3d_device->SetFVF(VERTEX_FVF);
// 设置设备纹理
g_d3d_device->SetTexture(0, g_texture);
// 从当前数据输入流集中呈现指定类型的无索引几何原语序列。
g_d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
// 清空纹理
g_d3d_device->SetTexture(0, NULL);
g_d3d_device->EndScene();
}
return TRUE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
WNDCLASSEX win_class;
MSG msg;
g_inst = hInstance;
win_class.cbSize = sizeof(win_class);
win_class.style = CS_CLASSDC;
win_class.lpfnWndProc = Window_Proc;
win_class.cbClsExtra = 0;
win_class.cbWndExtra = 0;
win_class.hInstance = hInstance;
win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
win_class.hbrBackground = NULL;
win_class.lpszMenuName = NULL;
win_class.lpszClassName = g_class_name;
win_class.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&win_class))
return FALSE;
g_hwnd = CreateWindow(g_class_name, g_caption,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);
if (g_hwnd == NULL)
return FALSE;
ShowWindow(g_hwnd, SW_NORMAL);
UpdateWindow(g_hwnd);
if (d3d_init() == FALSE)
return FALSE;
ZeroMemory(&msg, sizeof(MSG));
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
d3d_render();
g_d3d_device->Present(NULL, NULL, NULL, NULL);
}
d3d_release();
UnregisterClass(g_class_name, hInstance);
return (int)msg.wParam;
}
离屏表面和纹理内存的比较
离屏表面的方式,一般只用来显示数据,因为不能定义坐标来丰富显示的方式,所以这种方式使用简单;而使用纹理内存的方式,可以自定义坐标,能够使数据以更加灵活的方式呈现出来,不过用法相较表面(Surface)就复杂一些。
另外,使用纹理内存的方式,可以与CUDA结合操作GPU显存,有更广泛的应用场景。至于D3D与CDUA的互操作性,在后面的文章会陆续讲到。
参考资料
[1] 《Introduction to 3D Game Programming with Directx 11》, 第4.1.3章节
[2] baike.baidu.com/item/%E7%BA…
[3] www.cnblogs.com/graphics/ar…