Загрузка...
 
Печать

Оптимизация 3D-приложений

C++, Win32

Содержание



Intro

Оптимизация трехмерного приложения может вестись по нескольким ключевым направлениям:1
  • Оптимизация рендеринга.
  • Оптимизация процессорной части приложения.
  • Оптимизация алгоритмов.

Упорядочивание (сортировка) по глубине прорисовки (Depth Sorting)

При разработке 3D-игры становится очевидно, что при рендеринге полигональных мешей на сцене наиболее удалённые от виртуальной камеры (=viewer) объекты заслоняются (перекрываются) теми, что расположены ближе.2 Именно на этом эффекте основана методика сортировки по глубине прорисовки. В свою очередь она состоит из двух методов:
  • Алгоритм художника (Painter's algorithm);
  • Z-буфер (Z-buffer).

Алгоритм художника (Painter's algorithm)


Image
Рис.1 Рендерятся только три пиксела, т.к. остальные два перекрыты ими


Здесь все объекты сцены делятся на полигоны, которые затем сортируются, начиная от самого дальнего (от наблюдателя) и заканчивая самым близким. В том же порядке они затем выводятся на экран. При этом, когда один пиксел перекрывает другой, сравниваются их Z-координаты, в результате чего рендерится только пиксел с меньшим значением по оси Z (См. Рис.1). Метод обеспечивает гарантированную прорисовку передних объектов/полигонов и отсечение заслонённых (перекрытых).

Метод Z-буфера (Z-buffer)

  • Поддерживается большинством современных видеокарт.
  • Работает на попиксельном (per-pixel) уровне.
Каждый пиксел сцены имеет Z-координату в пространстве объекта наблюдателя (viewer's object space). Прежде чем отрисовать пиксел, рендерер выясняет, есть ли у него "собраться" с одинаковыми X- и Y-координатами, но меньшими Z-координатами (т.е. расположенные блюже к наблюдателю). Если есть, то выводится пиксел с наименьшим значением Z-координаты. Остальные пикселы, участвующие в выборке, пропускаются (См. Рис.1).
Сегодня практически в любой современной видеокарте есть аппаратная поддержка Z-буфера. Поэтому данный метод однозначно надо применять при разработке в принципе любой игры.

Применение Z-buffer в DirectX Graphics (Win32, DirectX 8)

Самый простой способ применить Z-буфер в своём Direct3D-приложении - это инициализировать его при создании объекта устройства и установки метода презентации (presentation method). Сперва выбирается точность (precision; 16, 24 или 32 бита) путём назначения подходящих настроек структуры D3DFORMAT. 32-битный Z-буфер расходует куда больше памяти по сравнению с 16-битным. Но 32-битный - куда аккуратнее. Вот пример:
d3dp.EnableAutoDepthStencil = TRUE;
d3dp.AutoDepthStencilFormat = D3DFMT_D16; // Выбрали 16-битный формат Z-буфера.

Закрыть
noteОбрати внимание

Приведённые выше строки лучше прописывать в функции инициализации движка (пример названия - DoInit).

Сразу после этого можно переходить к процедурам инициализации. Как только будешь готов активировать Z-буфер (сам он не активируется), укажи соответствующий рендер-стейт:
// g_pD3DDevice - предварительно созданный и проинициализированный объект устройства Direct3D.
g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE); // Включаем Z-буфер
g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE); // Выключаем Z-буфер

Закрыть
noteОбрати внимание

Поначалу может показаться, что куда проще и логичнее включить Z-буфер один раз и не выключать до самого выхода из приложения. Это даже поможет выиграть в производительности рендеринга. Но, например, при прорисовке экранных меню (on-screen menu) Z-буфер лучше отключать, т.к. в этом случае необходим полный контроль того, что и где прорисовывается.

Если рендерить только с приведённым выше кодом, ты быстро заметишь, что экран перестанет обновляться уже спустя несколько кадров, либо слишком удалённые объекты будут удаляться полностью. А всё дело в том, что Z-буфер необходимо очищать в каждом кадре + пересчитывать матрицу проекции (projection matrix) для корректного расчёта расстояний до объектов.

Для очистки Z-буфера в каждом кадре вызываем функцию IDirect3DDevice8::Clear. Вот её прототип:
Прототип функции Clear (DirectX 8)
HRESULT Clear(
	DWORD Count,
	CONST D3DRECT* pRects,
	DWORD Flags,
	D3DCOLOR Color,
	float Z,
	DWORD Stencil
	);

Вот описание её параметров:3
Count - количество прямоугольников в массиве pRects.
pRects - указатель на массив структуры D3DRECT описывающий прямоугольную область очистки. Если Вам нужно очистить полностью экран укажите NULL.
Flags - этот флажок указывает, какая поверхность должна быть очищена. Здесь можно использовать несколько флажков, комбинируя с другими, но один должен присутствовать обязательно:
D3DCLEAR_STENCIL - очищает буфер трафарета значением Stencil. D3DCLEAR_TARGET - очищает отображаемую часть экрана цветом указанным в Color. D3DCLEAR_ZBUFFER - очищает буфер глубины значением указанным в Z.
Color - это значение имеет 32-битовый цвет, для очистки экрана приложения.
Z - это новая переменная, которая хранится в буфере глубины. Этот параметр может быть в диапазоне от 0.0 до 1.0 (для z-основы или w-основы буфера глубины). Значение 0.0 говорит о самом близком расстоянии к зрителю, 1.0 - наборот, о самом дальнем.
Stencil - это число хранится в каждом трафаретном буфере входа. Этот параметр может принимать значения от 0 до 2n-1, где n - это глубина буфера трафарета. Возвращаемое значение
Если функция завершена успешно, тогда возвращается значение D3D_OK. В случае неудачи возвращается значение D3DERR_INVALIDCALL.
Требуется подключение заголовка D3d8.h и библиотеки D3d8.lib. (Куда ж без них!)
В нашем примере вызов функции Clear выглядит так:
g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
	D3DCOLOR_RGBA(0.0f, 0.0f, 0.0f, 0.0f), 1.0f, 0);

Закрыть
noteОбрати внимание

Приведённый выше вызов функции Clear лучше прописывать в функции покадровой (per-frame) прорисовки движка (пример названия - DoFrame).

Во втором параметре стоит флаг D3DCLEAR_ZBUFFER, который указывает функции Clear очистить Z-буфер на глубину, указанную в пятом параметре (1.0f).
Здесь:
  • 0.0f - минимальная глубина Z-буфера;
  • 1.0f - максимальная глубина Z-буфера.
Указав значение 1.0f, мы указываем функции Clear установить все значения глубины (depth) на максимум. Для очистуи Z-буфера на половину от максимального диапазона, ставь значение 0,5f. Но что означает максимальное значение? Z-буфер измеряет расстояние от наблюдателя (viewpoint) до объекта. При рендеринге объекты, расположенные дальше установленной видимой дистанции (а также те, что размещены слишком близко) не отрисовываются (= не выводятся на экран). Так вот, именно путём вызова функции расчёта матрицы проекции (из библиотеки D3DX) мы устанавливаем минимальное и максимальное расстояние до видимых объектов. Вот пример:
// Устанавливаем минимальное значение в 1.0f и максимальное - в 1000.0f
D3DMatrixPerspectiveFovLH(&MatrixProj, D3DX_PI/4, 1.0f, 1.0f, 1000.0f);

Последние два параметра и есть устанавливаемые значения. На деле эти параметры подбирают "вручную", экспериментируя с разными значениями. Важно помнить, что, чем больше диапазон между минимумом и максимумом значений, тем менее эффективна работа Z-буфера. Типичные значения:
  • 0.0 для минимального значения;
  • 1000.0f, 2000.f или 5000.f для максимального значения.

Усечённая пирамида вида (View Frustum)

  • Представляет собой метод отсечения невидимых граней, применяемый в целях повышения производительности рендеринга.4
  • Определяет, какие объекты игровой сцены видимы в данном кадре, а какие нет.
Предполагается, что видимые в данный момент объекты расположены перед объективом объекта-камеры, причём не слишком далеко от него, но и не слишком близко к нему. В большинстве случаев усечённую пирамиду вида можно рассматривать как обычную пирамиду, центральная вершина которой расположена в середине "линзы" объектива виртуальной камеры. Все объекты внутри неё видимы. Остальные - нет.
С точки зрения математики усечённая пирамида вида состоит из 6 плоскостей, каждая из которых является её стороной. У усечённой пирамиды два основания:
  • сверху (в нашем случае на "линзе" объектива виртуальной камеры; т.н. ближняя плоскость отсечения, near clipping plane)
  • снизу (в нашем случае это воображаемая дальняя плоскость отсечения, far clipping plane). Другие плоскости (сверху, снизу, слева и справа) расположены между ними.
Плоскость представляет собой бесконечно большой "лист", толщиной которого можно пренебречь. Стандартной является проверка положения точки относительно данной плоскости (подробнее об этом здесь: Базовые понятия 3D-графики ). Конкретнее, точка проверяется на три возможных варианта:
  • точка расположена за плоскостью;
  • точка расположена на плоскости;
  • точка расположена перед плоскостью.
По аналогии положение точки сравнивается относительно ближней и дальней плоскостей усечённой пирамиды вида. Также сравнивается позиция точки относительно других её плоскостей. Если расширить такие тесты на 3 точки, то можно эффективно определять, как данный полигон входит в пространство усечённой пирамиды вида: частично, полностью или не входит совсем. Схожие проверки можно проделать и для 3D-мешей.
За основу возьмём статью Камера вида от первого лица (First Person Camera), где создаётся камера. Данную камеру оснастим усечённой пирамидой, дополнив класс камеры. Также добавим ряд методов, проверяющих положение точки, (ограничивающих) куба, сферы, а также меша относительно усечённой пирамиды вида.

Создаём усечённую пирамиду вида (Constructing the Frustum)

Для того, чтобы наша камера могла действовать в качестве усечённой пирамид вида, добавим в класс камеры несколько новых членов. Среди первых - m_Planes6 (массив из шести плоскостей, образующих усечённую пирамиду вида). Каждая из этих плоскостей должна быть инициализирована в конструкторе класса камеры.
Также матрицу вида перемножим на матрицу проекции (projection matrix). Такая матрица-результат будет содержать координаты, необходимые для генерирования наших плоскостей с корректными позициями и ориентациями. Вот код инициализации плоскостей усечённой пирамиды вида:
D3DXMATRIX Matrix, ViewMatrix, ProjectionMatrix;

pDevice->GetTransform(D3DTS_VIEW, &ViewMatrix);
pDevice->GetTransform(D3DTS_PROJECTION, &ProjectionMatrix);
D3DXMatrixMultiply(&Matrix, &ViewMatrix, &ProjectionMatrix);

m_Planes[0].a = Matrix._14 + Matrix._13;
m_Planes[0].b = Matrix._24 + Matrix._23;
m_Planes[0].c = Matrix._34 + Matrix._33;
m_Planes[0].d = Matrix._44 + Matrix._43;
D3DXPlaneNormalize(&m_Planes[0], &m_Planes[0]);

m_Planes[1].a = Matrix._14 + Matrix._13;
m_Planes[1].b = Matrix._24 + Matrix._23;
m_Planes[1].c = Matrix._34 + Matrix._33;
m_Planes[1].d = Matrix._44 + Matrix._43;
D3DXPlaneNormalize(&m_Planes[1], &m_Planes[1]);

m_Planes[2].a = Matrix._14 + Matrix._13;
m_Planes[2].b = Matrix._24 + Matrix._23;
m_Planes[2].c = Matrix._34 + Matrix._33;
m_Planes[2].d = Matrix._44 + Matrix._43;
D3DXPlaneNormalize(&m_Planes[2], &m_Planes[2]);

m_Planes[3].a = Matrix._14 + Matrix._13;
m_Planes[3].b = Matrix._24 + Matrix._23;
m_Planes[3].c = Matrix._34 + Matrix._33;
m_Planes[3].d = Matrix._44 + Matrix._43;
D3DXPlaneNormalize(&m_Planes[3], &m_Planes[3]);

m_Planes[4].a = Matrix._14 + Matrix._13;
m_Planes[4].b = Matrix._24 + Matrix._23;
m_Planes[4].c = Matrix._34 + Matrix._33;
m_Planes[4].d = Matrix._44 + Matrix._43;
D3DXPlaneNormalize(&m_Planes[4], &m_Planes[4]);

m_Planes[5].a = Matrix._14 + Matrix._13;
m_Planes[5].b = Matrix._24 + Matrix._23;
m_Planes[5].c = Matrix._34 + Matrix._33;
m_Planes[5].d = Matrix._44 + Matrix._43;
D3DXPlaneNormalize(&m_Planes[5], &m_Planes[5]);

С этого момента усечённая пирамида вида создана и можно начинать проверки.

Проверяем, входит ли точка в пространство усеченной пирамиды вида

Расположена ли точка внутри усечённой пирамиды вида? Чтобы это выяснить применим функцию D3DXPlaneDotCoord:
bool CXCamera::TestPoint(FLOAT XPOS, FLOAT YPOS, FLOAT ZPOS)
{
	for(short Counter=0; Counter<6; Counter++)
	{
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XPOS, YPOS, ZPOS)) < 0.0f)
			return false;
	}
	return true;
}


Проверяем, входит ли куб в пространство усечённой пирамиды вида

Куб представляет собой примитив, определённый 8-ю точками, по одной на каждый его угол. Он может входить полностью, частично или вообще не входить.
bool CXCamera::TestCube(float XCenter, float YCenter, float ZCenter, float size)
{
	for(short Counter=0; Counter<6; Counter++)
	{
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XCenter - Size, YCenter - Size, ZCenter - Size)) >= 0.0f)
			continue;
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XCenter + Size, YCenter - Size, ZCenter - Size)) >= 0.0f)
			continue;
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XCenter - Size, YCenter + Size, ZCenter - Size)) >= 0.0f)
			continue;
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XCenter + Size, YCenter + Size, ZCenter - Size)) >= 0.0f)
			continue;
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XCenter - Size, YCenter - Size, ZCenter + Size)) >= 0.0f)
			continue;
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XCenter + Size, YCenter - Size, ZCenter + Size)) >= 0.0f)
			continue;
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XCenter - Size, YCenter + Size, ZCenter + Size)) >= 0.0f)
			continue;
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XCenter + Size, YCenter + Size, ZCenter + Size)) >= 0.0f)
			continue;
		return false;
	}
	return true;
}


Проверяем, входит ли сфера в пространство усечённой пирамиды вида

Сфера определяется своей центральной точкой и радиусом. Для проверки, входит ли сфера в усечённую пирамиду вида, проверяем, возвратит ли функция D3DXPlaneDotCoord значение больше, чем отрицательная величина радиуса:
bool CXCamera::TestSphere(float XCenter, float YCenter, float ZCenter, float Radius)
{
	for(short Counter=0; Counter<6; Counter++)
	{
		if(D3DXPlaneDotCoord(&m_Planes[Counter], &D3DXVECTOR3(XCenter, YCenter, ZCenter)) < -Radius)
			return false;
	}
	return true;
}


Проверяем, входит ли меш в пространство усечённой пирамиды вида

Общепринятая практика проверять не сам меш, а его ограничивающий объём (bounding volume). Здесь придёт на помощь функция D3DXComputeBoundingSphere, вычисляющая ограничивающую сферу меша:
bool CXCamera::TestMesh(LPD3DXBASEMESH pMesh)
{
	if(pMesh)
	{
		DWORD NumVertices = pMesh->GetNumVertices();
		DWORD FVF = pMesh->GetFVF();
		UINT FVFSize = D3DXGetFVFVertexSize(FVF);
		LPVOID ppData = NULL;
		
		pMesh->LockVertexBuffer(D3DLOCK_READONLY, &ppData);
		if(ppData)
		{
			D3DXVECTOR3 Center(0, 0, 0);
			FLOAT Radius = 0;
			D3DXComputeBoundingSphere((D3DXVECTOR3*)ppData, NumVertices, FVFSize, &Center, &Radius);
			
			if(TestSphere(Center.x, Center.y, Center.z, Radius))
			{
				pMesh->UnlockVertexBuffer();
				return true;
			}
		}
		pMesh->UnlockVertexBuffer();
	}
	return false;
}


Пример приложения с применением Z-буфера (Win32, DirectX 8)

Перед началом проверь, что у тебя установлены следующие программные компоненты:
  • MS Visual C++ 2010;
  • DirectX SDK 8.1
  • Windows SDK (для программирования под Win7/8/10).
Все эти штуки:
    • + инструкции по их установке ты найдёшь в разделе "Софт" нашего сайта;
    • Бесплатны;
    • Без труда гуглятся.

Создаём Проект приложения

  • Создай пустой Проект с именем "ZBuffer01".
Проект автоматически разместится внутри Решения с таким же именем. Весь процесс подробно расписан в статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.

Добавляем в Проект WinMain.cpp

Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта ZBuffer01.
  • Во всплывающем меню Добавить->Создать элемент...
Image
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи WinMain.cpp. Жмём "Добавить".
Image
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле WinMain.cpp набираем следующий код:
WinMain.cpp
/**************************************************
WinMain.cpp
Chapter 6 ZBuffer Demo

Programming Role-Playing Games with DirectX
by Jim Adams (01 Jan 2002)

Required libraries:
  WINMM.LIB, D3D8.LIB and D3DX8.LIB
**************************************************/

// Include files
#include <windows.h>
#include <stdio.h>
#include "d3d8.h"
#include "d3dx8.h"

// Window handles, class and caption text
// Дескриптор окна, оконный класс и текст тулбара
HWND          g_hWnd;
HINSTANCE     g_hInst;
static char   g_szClass[]   = "ZBufferClass";
static char   g_szCaption[] = "ZBuffer Demo by Jim Adams";

// The Direct3D and Device object
IDirect3D8       *g_pD3D       = NULL;
IDirect3DDevice8 *g_pD3DDevice = NULL;

// The 3-D vertex format and descriptor
// Создаём кастомный FVF-формат и его дескриптор.
typedef struct {
  FLOAT x, y, z;     // 3-D coordinates
  D3DCOLOR Color;    // Diffuse color. Рассеянный свет
} sVertex;
#define VERTEXFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)

// Vertex buffer. Вершинный буфер
IDirect3DVertexBuffer8 *g_pVB = NULL;

// Function prototypes
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow);
long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam);

BOOL DoInit();
BOOL DoShutdown();
BOOL DoFrame();
BOOL SetupMeshes();

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow)
{
  WNDCLASSEX wcex;
  MSG        Msg;

  g_hInst = hInst;

  // Create the window class here and register it
  // Заполняем класс окна приложения и регистрируем его
  wcex.cbSize        = sizeof(wcex);
  wcex.style         = CS_CLASSDC;
  wcex.lpfnWndProc   = WindowProc;
  wcex.cbClsExtra    = 0;
  wcex.cbWndExtra    = 0;
  wcex.hInstance     = hInst;
  wcex.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = NULL;
  wcex.lpszMenuName  = NULL;
  wcex.lpszClassName = g_szClass;
  wcex.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
  if(!RegisterClassEx(&wcex))
    return FALSE;

  // Create the Main Window
  // Создаём главное окно
  g_hWnd = CreateWindow(g_szClass, g_szCaption,
        WS_CAPTION | WS_SYSMENU,
        0, 0, 400, 400,
        NULL, NULL,
        hInst, NULL );
  if(!g_hWnd)
    return FALSE;
  ShowWindow(g_hWnd, SW_NORMAL);
  UpdateWindow(g_hWnd);

  // Run init function and return on error
  if(DoInit() == FALSE)
    return FALSE;

  // Start message pump, waiting for signal to quit
  // Стартуем цикл выборки сообщений. Ждём сигнала для выхода
  ZeroMemory(&Msg, sizeof(MSG));
  while(Msg.message != WM_QUIT) {
    if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {
      TranslateMessage(&Msg);
      DispatchMessage(&Msg);
    }
    if(DoFrame() == FALSE)
      break;
  }

  // Run shutdown function
  DoShutdown();
  
  UnregisterClass(g_szClass, hInst);

  return Msg.wParam;
}

long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;

  }

  return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

BOOL DoInit()
{
  D3DPRESENT_PARAMETERS d3dpp;
  D3DDISPLAYMODE        d3ddm;
  D3DXMATRIX matProj, matView;
  BYTE *Ptr;
  sVertex Verts[10] = {
      { -100.0f, -100.0f, 0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      { -100.0f,  100.0f, 0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      {  100.0f, -100.0f, 0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      {  100.0f,  100.0f, 0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      { -100.0f, -100.0f, 0.0f, D3DCOLOR_RGBA(255,0,0,255) },
      { -100.0f,  100.0f, 0.0f, D3DCOLOR_RGBA(255,0,0,255) },

      { -100.0f, -100.0f, 0.0f, D3DCOLOR_RGBA(0,0,255,255) },
      { -100.0f,  100.0f, 0.0f, D3DCOLOR_RGBA(0,0,255,255) },
      {  100.0f, -100.0f, 0.0f, D3DCOLOR_RGBA(0,0,255,255) },
      {  100.0f,  100.0f, 0.0f, D3DCOLOR_RGBA(0,0,255,255) },
    };

  // Do a windowed mode initialization of Direct3D
  // Выполняем инициализацию Direct3D для оконного режима
  if((g_pD3D = Direct3DCreate8(D3D_SDK_VERSION)) == NULL)
    return FALSE;
  if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return FALSE;
  ZeroMemory(&d3dpp, sizeof(d3dpp));
  d3dpp.Windowed = TRUE;
  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  d3dpp.BackBufferFormat = d3ddm.Format;
  d3dpp.EnableAutoDepthStencil = TRUE;
  d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
  if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, &g_pD3DDevice)))
    return FALSE;

  // Set the rendering states
  g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
  g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);

  // Create and set the projection matrix
  // Создаём и назначем в качестве действующей матрицу проекции
  D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/4.0f, 1.33333f, 1.0f, 1000.0f);
  g_pD3DDevice->SetTransform(D3DTS_PROJECTION, &matProj);

  // Create and set the view matrix
  // Создаём и назначем в качестве действующей матрицу вида
  D3DXMatrixLookAtLH(&matView,                                \
                     &D3DXVECTOR3(0.0f, 0.0f, -500.0f),       \
                     &D3DXVECTOR3(0.0f, 0.0f, 0.0f),          \
                     &D3DXVECTOR3(0.0f, 1.0f, 0.0f));
  g_pD3DDevice->SetTransform(D3DTS_VIEW, &matView);

  // Create the vertex buffer and set data
  // Создаём вершинный буфер и заполняем его данными о вершинах
  g_pD3DDevice->CreateVertexBuffer(sizeof(sVertex)*10, 0,     \
                          VERTEXFVF, D3DPOOL_DEFAULT, &g_pVB);
  g_pVB->Lock(0,0, (BYTE**)&Ptr, 0);
  memcpy(Ptr, Verts, sizeof(Verts));
  g_pVB->Unlock();


  return TRUE;
}

BOOL DoShutdown()
{
  // Release vertex buffer
	// Освобождаем вершинный буфер
  if(g_pVB != NULL)
    g_pVB->Release();

  // Release device and 3D objects
  // Освобождаем программный объект устройства Direct3D и 3D-объекты
  if(g_pD3DDevice != NULL)
    g_pD3DDevice->Release();

  if(g_pD3D != NULL)
    g_pD3D->Release();

  return TRUE;
}

BOOL DoFrame()
{
  D3DXMATRIX matWorld;

  // Clear device backbuffer
  // Очищаем бэкбуфер объекта устройства
  g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 
                      D3DCOLOR_RGBA(0,0,0,255), 1.0f, 0);

  // Begin scene
  if(SUCCEEDED(g_pD3DDevice->BeginScene())) {

    // Set the vertex stream, shader, and texture
	// Назначаем вершинный поток и текстуру
    g_pD3DDevice->SetStreamSource(0, g_pVB, sizeof(sVertex));
    g_pD3DDevice->SetVertexShader(VERTEXFVF);

    // Create and set the world transformation matrix
    // for first four primitives. Rotate along Y-axis.
	// Создаём и назначаем мировую матрицу трансформации
	// для первых четырёх примитивов. Вращаем вокруг оси Y
    D3DXMatrixRotationY(&matWorld, (float)timeGetTime() / 1000.0f);
    g_pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld);

    // Draw first four polygons
	// Рисуем первые 4 полигона
    g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 4);

    // Draw next four polygons, but rotate on Z-axis
	// Рисуем следующие 4 полигона, но их вращаем по оси Z
    D3DXMatrixRotationZ(&matWorld, -(float)timeGetTime() / 1000.0f);
    g_pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld);
    g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 6, 2);

    // End the scene
    g_pD3DDevice->EndScene();
  }

  // Display the scene
  // Выводим сцену на экран
  g_pD3DDevice->Present(NULL, NULL, NULL, NULL);

  return TRUE;
}

  • Сохрани Решение (Файл -> Сохранить все).
Данный исходный код целиком взят из примера к книге Programming Role-Playing Games with DirectX by Jim Adams (01 Jan 2002). Автор прогал на MS Visual C++ 6.0 (вышла в свет в 1998 г.) c установленным DirecX SDK 8.0. И если в нашем случае в качестве Graphic API мы юзаем практически идентичный DirectX SDK 8.1, то IDE у нас совсем другая (MS Visual C++ 2010 Express Edition). Нет, не с целью усложнения задачи. Просто MS Visual C++ 6.0 в своё время стоила 500 USD. Сейчас её, в принципе, можно поискать в торрентах. (За всё, что ты там скачаешь, администрация Igrocoder.ru ответственности не несёт!) Но использование платного/пиратского софта не соответствует концепции сайта Igrocoder.ru . Поэтому наш выбор:
  • Бесплатная IDE MS Visual C++ 2010 Express edition;
  • Бесплатный DirectX SDK 8.1 .
MSVC++2010 Express компилироваться не будет, выдавая многочисленные ошибки. Даже с настроенными путями к DirectX SDK.
Но мы это исправим.

Готовим Проект ZBuffer01 к компиляции

Для успешной компиляции изменим настройки (=свойства) текущего Проекта ZBuffer01, созданного в MSVC++2010. При этом сам код из книги 2002 года останется нетронутым.
С момента выхода MS Visual C++ 6.0 прошло немало времени. С MSVC++2010 Express их разделяют аж 12 лет. Из-за этого вышеприведённый код (написанный в начале 2002 г.) на данном этапе в MSVC++2010 Express компилироваться не будет, выдавая многочисленные ошибки. Даже с настроенными путями к DirectX SDK.
Закрыть
noteОбрати внимание

Напомним, что такую настройку необходимо повторно проделывать при создании каждого нового Проекта. Ниже представлен алгоритм действий по настройке Проекта, созданного в MSVC++2010 Express с применением DirectX SDK. При создании приложений под платформы, отличные от Win32, либо применении более новых версий DirectX SDK, процесс конфигурирования Проекта может отличаться от приведённого ниже.


Указываем пути к DirectX SDK 8.1

Если попытаться скомпилировать Проект ZBuffer01 в таком виде, то ничего не выйдет.
В единственном файле исходного кода WinMain.cpp можно увидеть инклуды различных заголовочных файлов DirectX (весии 8):
Фрагмент WinMain.cpp
...
// Include files
#include <windows.h>
#include <stdio.h>
#include "d3d8.h"
#include "d3dx8.h"
...

На данном этапе MSVC++2010 ничего не знает об их местоположении. В статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK мы указывали пути к DirectX SDK (версии 9 и выше). Для DirectX SDK 8 это делается аналогично. Начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект ZBuffer01.
  • Убедись, что DirectX SDK 8.1 установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.
В Обозревателе решений видим: "Решение "ZBuffer01"", а строкой ниже жирным шрифтом название Проекта (тоже ZBuffer01).
  • Жмём правой кнопкой мыши по названию Проекта ZBuffer01. Во всплывающем меню выбираем пункт "Свойства".
Image
Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.
В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC+ + . В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересуют только 2 строки: Каталоги включения и Каталоги библиотек.

Указываем каталог включений (include) DirectX SDK 8.1
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
Image
В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным файлам DirectX SDK 8.1 (include). В нашем случае это C:\DXSDK8\include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Жмём "ОК".

Указываем каталог библиотек (lib) DirectX SDK 8.1
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к 32-разрядным версиям файлов библиотек DirectX SDK 8.1 (lib). В нашем случае это c:\DXSDK8\lib\. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Жмём "ОК".
  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All).
Готово.

Указываем пути к Windows SDK

Помимо указания путей к заголовкам (include) и библиотекам (lib) DirectX SDK, для любого DirectX-Проекта также необходимо указать пути к заголовкам (include) и библиотекам (lib) Windows SDK. Самое смешное, что в MS Visual C++ 2010 эти пути указываются по умолчанию для каждого создаваемого Проекта. В этом нетрудно убедиться, если ещё раз открыть Проект -> Свойства -> Свойства конфигурации - >Каталоги VC++ -> Каталоги включения -> Изменить. В окне "Каталоги включения" в нижней (недоступной для редактирования) части видим список "Унаследованные значения", где в третьей строке стоит значение:
$(WindowsSdkDir)include

В результате видим, что добавленные пути к DirectX SDK (в обоих окнах: include и lib) расположены вверху списка, а пути к Windows SDK - в недоступной области, на несколько строк ниже.
Но! Заголовочным файлам DirectX SDK "жизненно важно", чтобы они включались после включений заголовков Windows SDK.
Закрыть
noteВажно!

Каталоги, пути к которым указаны в окнах "Каталоги включения" и "Каталоги библиотек" при компиляции считываются один за другим по списку сверху вниз. Поэтому для корректного указания путей надо разместить пути к Windows SDK выше, а к DirectX SDK - ниже по списку (чтобы они считывались последними).

В данной ситуации мы не можем поднять пути к Windows SDK, прописанные по умолчанию при создании Проекта, т.к. они расположены в специальной нередактируемой области (ограничение бесплатной версии MSVC++2010 Express). Но можем схитрить и добавить ещё раз пути к тем же самым каталогам Windows SDK, подняв эти строки выше строк DirectX SDK.
Выше мы указывали пути к DirectX SDK. Для путей к Windows SDK это делается аналогично. Начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь в данный момент.
  • Убедись, что Windows SDK (в нашем случае версия 7) установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.
В Обозревателе решений видим: "Решение "..."", а строкой ниже жирным шрифтом название Проекта с тем же названием (если не менял вручную).
  • Жмём правой кнопкой мыши по названию Проекта. Во всплывающем меню выбираем пункт "Свойства".
Image
Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.
В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++ . В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас, как и в прошлый раз, интересуют только 2 строки: Каталоги включения и Каталоги библиотек.

Указываем каталог включений (include) Windows SDK
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
Image
В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным (include) файлам Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Меняй порядок считывания каталогов включений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".
Каталог включений DirectX SDK должен всегда стоять в самом конце списка, как на этом скриншоте:
Image
  • Жмём "ОК".

Указываем каталог библиотек (lib) Windows SDK
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к папке с файлами библиотек (lib) Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\MicrosoftSDKs\Windows\v7.0A\Lib. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Меняй порядок считывания каталогов вложений с помощью кнопок с чёрными стрелками в верхней части окна.
Каталог библиотек DirectX SDK должен всегда стоять в самом конце списка, как на этом скриншоте:
Image
  • Жмём "ОК".
  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All). Готово.

Выбираем многобайтовую кодировку

Закрыть
noteПримечание

В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 - напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, данный Проект мы настроим под многобайтовую кодировку.

Чтобы сильно не переделывать исходные коды под UNICODE, все наши игровые Проекты мы настроим под многобайтовую кодировку. Для этого...
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект ZBuffer01.
В Обозревателе решений видим: "Решение "ZBuffer01"", а строкой ниже жирным шрифтом название Проекта (тоже ZBuffer01).
  • В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта ZBuffer01.
  • Во всплывающем контекстном меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации->Общие, в правой части в строке "Набор символов" выставляем значение "Использовать многобайтовую кодировку".
Image
  • Жмём ОК.
  • Сохрани Решение (Файл->Сохранить все).

Отключаем инкрементную компоновку (incremental linking)

Инкрементная компоновка призвана сократить время компилирования. Но на деле её присутствие часто вызывает ошибки вроде этой:
Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден


Закрыть
warningОшибка LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt

В MS Visual C++ 2010 даже компиляция консольных приложений нередко завершается неудачей, а вместо исполняемого файла программист видит сообщение:
LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt.

ПРИЧИНА: MS Visual С++ 2010 "не нравится" версия .NET Framework(external link), установленная в операционной системе. В общих чертах, MS Visual С++ 2010 спрограммирована для работы под управлением .NET Framework 4.0 и сильно к нему привязана. Если точнее, к нему сильно привязана система инкрементной компоновки приложений (incremental linking), которая по умолчанию включена для всех создаваемых проектов.
Во время установки MS Visual C++ 2010 пытается установить свой "родной" .NET Framework 4.0, проверяя версию этой программной платформы, установленную в ОС на данный момент. Если версия .NET Framework ниже 4.0, то она обновляется до 4.0 и всё прекрасно компилируется. Если версия .NET Framework выше 4.0, то всё оставляется как есть: IDE успешно завершает установку, но при компиляции ВСЕХ приложений выскакивает данная ошибка. Более того, ошибка была замечена даже при наличии в системе .NET Framework версии 4.0, но отличающейся от "родной" припиской вроде "Beta" или "Release Candidate".

ВАРИАНТЫ РЕШЕНИЙ:
1. Отключить инкрементную линковку в опциях Проекта.
В главном меню MSVC++2010 выбираем: Проект->Свойства->Свойства конфигурации->Компоновщик(Linker)->Включить инкрементное построение (Incremental Linking). Данный пункт по умолчанию включен для всех новых проектов. Выставляем его в Нет (No). И жмём OK. Инкрементное построение заметно сокращает время компилирования больших проектов. Но в нашем случае его отсутствие некритично.
2. Удалить (переместить в другое место) утилиту cvtres.exe из каталога bin установленной MS Visual C++ 2010.
В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe . Официальное название данного приложения - Microsoft® Resource File To COFF Object Conversion Utility (утилита конвертации файлов двоичных ресурсов в Component Object File Format). Опытным путём установлено, что при линковке IDE "спотыкается" именно об него.
3. Удалить из системы все версии .NET Framework (включая языковые пакеты и всякие профайлеры, если есть) и саму MS Visual C++ 2010.
Всё вышеперечисленное можно без труда найти в меню "Программы и компоненты" (MS Windows Vista/7/8). Затем заново установить MS Visual C++ 2010. При этом автоматом установится .NET Framework 4.0, идущий с ней в наборе.

Третий пункт - самый долгий. На деле почти всегда хватает выполнения первых двух. Данным вопросом озадачивались ребята здесь: https://www.cyberforum.ru/cpp-beginners/thread637174.html?ysclid=l3akpmanrq(external link). После этого линковка (=компоновка) проходит идеально. Подобные "костыли" в IDE от Майков - не редкость. Можно предположить, что команда с головой ударилась в тестирование .NET-возможностей MSVC++2010, совсем забыв о Win32-направлении (либо признав его бесперспективным).


Отключим инкрементную компоновку в свойствах открытого Проекта. Для MS Visual C++ 2010 порядок следующий:
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь.
В Обозревателе решений видим: "Решение "..."", а строкой ниже жирным шрифтом название Проекта. Обычно с тем же названием (если не менял вручную).
  • В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта.
  • Во всплывающем контекстном меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации -> Компоновщик -> Общие (Configuration Properties -> Linker -> General), в правой части в строке "Включить инкрементную компоновку" ставим значение Нет (/INCREMENTAL:NO).
  • Жмём ОК.

Удаляем файл cvtres.exe

В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe
В Win32-кодинге он особо ни на что не влияет. Зато без него линковка идёт как по маслу.

Прописываем библиотеки d3dx8.lib, d3d8.lib и Winmm.lib в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)

Данный этап проходил даже автор кода Jim Adams в 2002 году, прогая на MSVC++6.0. В начальных комментариях листинга WinMain.cpp он намекнул, что библиотеки D3D8.LIB и D3DX8.LIB необходимо явно указывать в списке дополнительных зависимостей линкера ( = компоновщика):
Фрагмент WinMain.cpp
...
Required libraries:
  WINMM.LIB, D3D8.LIB and D3DX8.LIB
...

Библиотеки D3D8.LIB и D3DX8.LIB расположены в папке с установленным DirectX SDK 8 (в нашем случае по пути C:\DXSDK8\lib\), пути к которой мы прописали выше.
Т.к. мы создаём исполняемое приложение (исполняемый .exe-файл), а не библиотеку, то после компиляции полученный объектный модуль сразу линкуется путём вызова компоновщика (=linker). Так вот, этот самый компоновщик по ранее прописанным каталогам данные библиотеки не ищет. Поэтому их необходимо указывать отдельно в окне настроек Проекта, в разделе "Компоновщик". ОК, начинаем.
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Ввод (Configuration Properties -> Linker -> Input).
  • В правой части, напротив строки "Дополнительные зависимости" жмём кнопку с чёрным треугольником.
  • В всплывающем списке жмём "Изменить".
  • В появившемся окне "Дополнительные зависимости" в верхнем поле ввода прописываем в столбик (один под другим) имена файлов трёх библиотек:
d3dx8.lib
d3d8.lib
Winmm.lib
Image
Библиотека Winmm.lib отвечает за мультимедиа-возможности приложения и расположена в каталоге с установленным Windows SDK.
  • Жмём ОК, ОК.
  • Сохрани Решение (Файл->Сохранить все)

Отключаем использование компоновщиком библиотеки libci.dll

Да, даже на данном этапе компиляция Проекта выдаст ошибку. Библиотека libci.dll использовалась в VisualStudio когда-то очень давно, и в современных версях IDE её нет. Тем не менее компоновщик почти всегда вызывает её при компиляции, ругаясь на её отсутствие. Самый простой способ это исправить - запретить использовать libci.dll по умолчанию.
ОК, начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь в данный момент.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Командная строка (Configuration Properties -> Linker -> Command Promt).
В правой части, внизу, видим поле ввода "Дополнительные параметры".
  • Пишем в него строку: /NODEFAULTLIB:libci
Image
  • Жмём ОК.
  • Сохрани Решение (Файл->Сохранить все).

Компилируем Проект ZBuffer01

Наконец, наш тестовый Проект готов к компиляции.
  • Жми кнопку с зелёным треугольником на панели инструментов главного окна MSVC++2010 или F5 на клавиатуре.
Image
Скомпилированное .exe-приложение в нашем случае (Win7 x64) расположено по пути C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\ZBuffer01\Debug .
После компиляции приложение ZBuffer01 автоматически запустится и покажет окно с вращающимися квадратами. Синий вращается по оси Z, а красный по оси Y. При перекрытии части одного квадрата другим перекрытые части не рендерятся, т.к. работает Z-буфер.

Источники


2. Adams J. Programming Role Playing Games with DirectX 8.0. - Premier Press, 2002
4. Thorn A. DirectX 9 graphics: the definitive guide to Direct3D. - Wordware Publishing, 2005


Последние изменения страницы Понедельник 27 / Июнь, 2022 08:27:42 MSK

Последние комментарии wiki

No records to display

Search Wiki Page

Точное совпадение

Категории

|--> C#
|--> C++