Шрифт:
Интервал:
Закладка:
pDirect3DObject = Direct3DCreate8(D3D_SDK_VERSION);
if (!pDirect3DObject) {
// Do something!!! Error occured!!!
}
После создания объекта IDirect3D8 мы должны создать еще что-то, на чем мы будем рисовать. Это "что-то" называется IDirect3DDevice8 и мы можем его создать с помощью метода IDirect3D8::CreateDevice(). В демо-проекте эта функция вызывается из C3DGraphic::Create() следующим образом:
D3DDISPLAYMODE theDisplayMode;
hr = m_p3DApplication->m_pDirect3DObject->GetAdapterDisplayMode(3DADAPTER_DEFAULT, &theDisplayMode);
if (FAILED(hr)) {
return hr;
}
D3DPRESENT_PARAMETERS thePresentParams;
ZeroMemory(&thePresentParams, sizeof(thePresentParams));
thePresentParams.Windowed = TRUE;
thePresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
thePresentParams.BackBufferFormat = theDisplayMode.Format;
thePresentParams.EnableAutoDepthStencil = TRUE;
thePresentParams.AutoDepthStencilFormat = D3DFMT_D16;
hr = m_p3DApplication->m_pDirect3DObject->CreateDevice(
D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hwndRenderTarget,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&thePresentParams, &m_p3DDevice);
if (FAILED(hr)) {
return hr;
}
Только что мы создали 3D устройство, задав при этом кучу параметров. Несколько комментариев и разъяснений, что есть что:
• 3D устройство было создано для видеоадаптера, используемого по умолчанию. Об этом говорит параметр D3DADAPTER_DEFAULT. Средний житель России имеет от 0 до 1 видеоадаптеров, так что особых проблем с этим параметром возникнуть не должно.
• Созданное устройство имеет один back-буфер, цветовой формат буфера такой же как у экрана.
• Устройство создано для использования в оконном режиме. Весь рендеринг будет направлен на окно m_hwndRenderTarget. Эта переменная соотвествует окну нашего вида C3DGraphFrame. Антиподом оконному режиму служит полноэкранный режим. Для работы с ним надо при создании устройства переменную Windowed установить в FALSE, а не в TRUE.
• Устройство имеет автоматически созданный depth-буфер, который иногда еще называют z-буфером. Формат z-буфера – 16 бит на точку. В наше время трудно (но, правда, можно) найти видеоадаптер, который не поддерживает такой формат z-буфера. Z-буфер используется вычислительным ядром Direct3D для хранения информации о текущей z-координате каждой точки картинной плоскости. В конечном счете это используется для удаления невидимых линий и поверхностей.
• 3D устройство должно использовать все возможности аппаратуры. Если какое-либо требуемое свойство не поддерживается, Direct3D попытается эмулировать его на программном уровне. Вы можете поменять тип устройства с D3DDEVTYPE_HAL на D3DDEVTYPE_REF. В этом случае все вычисления будут проводиться на программном уровне. Такая эмуляция является очень медленной, и не все на свете можно эмулировать.
• Обрабатываться вершины (vertexes) должны на программном уровне. Мы сделали этот выбор, указав значение D3DCREATE_SOFTWARE_VERTEXPROCESSING. Можно указать вместо этого D3DCREATE_MIXED_VERTEXPROCESSING или D3DCREATE_HARDWARE_VERTEXPROCESSING, но эти режимы поддерживаются далеко не всеми видеокартами.
Стоит заметить, что реальное приложение (игра, например) должны вначале анализировать возможности установленного оборудования, а затем уже принимать решение о возможности или, наоборот, невозможности продолжать работу. Для упрощения такая проверка в демо-приложении не делается.
От f(x,y)=sin(x+y) до 2D картинкиЖизнь всегда была сложнее, чем хотелось бы программисту. Например, все было бы гораздо проще, если бы существовала некая функция ХочуЧтобыНарисовалсяГрафикФункции(). Но во-первых, по-русски функции называть нельзя, во-вторых, такой функции просто нет. Все, что умеет IDirect3DDevice8, это нарисовать некоторые примитивы, да и для этого надо проделать кучу подготовительной работы. Как минимум, вы должны разместить координаты примитивов в вертекс-буфер ("vertex buffer"). Полное обсуждение вертекс-буферов находится за пределами этой статьи и могу лишь сказать, что это некая абстракция простого блока памяти, предназначенного для хранения координат и свойств точек трехмерного пространства. Работа с буфером осуществляется через интерфейс IDirect3DVertexBuffer8. Вы может заблокировать вертекс-буфер, получив при этом указатель на область памяти. Разыменовав указатель, можно записать что-нибудь в буфер, не забыв потом его разблокировать. В демо-приложении вы можете увидеть как это делается.
Обычно перед рисованием чего-либо мы хотим очистить кадр, чтобы новый кадр не накладывался на предыдущий. Это делается вызовом метода IDirect3DDevice8::Clear(). Вы найдете это в функции C3DGraphic::ReRender():
hr = m_p3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, m_dwBackColor, f, 0);
if (FAILED(hr)) {
return hr;
}
Мы здесь очищаем весь back-буфер, заполняя его цветом m_dwBackColor. Кроме этого, очищается z-буфер – он заполняется значением 1.0. 1.0 соответствует максимально дальней от наблюдателя плоскости (горизонт, грубо говоря), 0.0 – максимально ближней. Непосредственно рисование на 3D устройстве начинается с
m_p3DDevice->BeginScene();
а заканчивается строкой
m_p3DDevice->EndScene();
Все, что находится между этими вызовами символизирует анимацию очередного кадра. Обратите внимание на то, что мы постоянно употребляем термин "back-буфер". Совершенно верно! Все, что мы сейчас нарисовали, не видно на экране, оно существует пока только в памяти компьютера (не будем уточнять, в какой именно: системной или видео – очень тонкий вопрос). Для того, чтобы эти изменения перенеслись на экран монитора необходимо скопировать изображение из back-буфера на основную поверхность. Это делается вызовом функции IDirect3DDevice8::Present().
Следующий код обеспечивает непосредственно отрисовку изображения трехмерной функции:
hr = m_p3DDevice->SetStreamSource(0, m_pDataVB, sizeof(GRAPH3DVERTEXSTRUCT));
if (FAILED(hr)) {
return hr;
}
hr = m_p3DDevice->SetVertexShader(D3DFVF_GRAPH3DVERTEX);
if (FAILED(hr)) {
return hr;
}
hr = m_p3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, m_dwElementsInVB - 2);
if (FAILED(hr)) {
return hr;
}
Переменная m_pDataVB является членом класса C3DGraphic, она содержит указатель на интерфейс IDirect3DVertexBuffer8. В нашем случае содержимое вертекс-буфера представляет из себя массив структур GRAPH3DVERTEXSTRUCT:
typedef struct {
FLOAT x, y, z;
FLOAT nx, ny, nz;
} GRAPH3DVERTEXSTRUCT;
Здесь x, y и z – координаты точки, nx, ny, nz – компоненты нормали. Вектор нормали используется подсистемой Direct3D, отвечающей за освещение сцены. Чуть позже мы рассмотрим, как можно рассчитать эти компоненты. Перед вызовом функции DrawPrimitive() мы должны указать vertex shader и используемый поток данных. Выбор и установка потока данных производится с помощью функции SetStreamSorce(). Мы передаем ей номер устанавливаемого потока (0, если оспользуется только один поток), указатель на вертекс-буфер, из которого будут браться данные, и размер каждого элемента в потоке (то есть, sizeof(GRAPH3DVERTEXSTRUCT). Vertex shaders – новая возможность Direct3D. Это эдакая абстракция, символизирующая обработку вершин (вертексов). Грубо говоря, vertex shader обеспечивает перевод точки из трехмерного пространства модели в двумерную картинную плоскость. Вы можете написать на особом языке скрипт, обеспечивающий этот перевод, а можете использовать один из поддерживаемых стандартных механизмов, что мы и сделаем. Наш vertex shader D3DFVF_GRAPH3DVERTEX определен как D3DFVF_XYZ | D3DFVF_NORMAL. Это означает, что каждая точка характеризуется 6-ю числами. Первые 3 из них трактуются как координаты вершины, остальные – как компоненты нормали.
После установки необходимых потока данных и вертекс-шейдера мы можем вызвать метод DrawPrimitive(), который отображает данные из вертекс-буфера на плоскость back-буфера. Способ рендеринга данных выбирается первым параметром этого метода – он может быть одним из значений перечисляемого типа D3DPRIMITIVETYPE: D3DPT_POINTLIST, D3DPT_LINELIST, D3DPT_LINESTRIP, D3DPT_TRIANGLELIST, D3DPT_TRIANGLESTRIP или D3DPT_TRIANGLEFAN. Я решил, что наиболее подходящим способом для построения графика функции является использование D3DPT_TRIANGLESTRIP, поскольку этот способ требует относительно немного памяти для хранения данных о поверхности. Триангуляции области построения графика осуществляется как показано на следующем рисунке.
Рисунок: триангуляции
Все это работает следующим образом: вертекс-буфер содержит точки P1, P2, P3, P4 и так далее. Когда я вызываю функцию DrawPrimitive() с параметром D3DPT_TRIANGLESTRIP, Direct3D начинает отображать треугольники 1, 2, 3 и так далее. Треугольник 1 определяется точками P1, P2, P3, треугольник 2 – P2, P3, P4. Таким образом, N точек, находящихся в вертекс-буфере, соответствуют (N-2) треугольникам. Все очень хорошо, но есть и недостатки: все треугольники получаются связаны друг с другом. Вот почему четные ряды триангулируются слева направо, а нечетные – наоборот. Думаю, нет необходимости напоминать, что всякого рода нумерации у меня начинаются с нуля.
Реализация всего этого находится в методе C3DGraphic::RecalculateData(). Эта функция использует вспомогательный класс CGraphGrid, который обеспечивает построение сетки графика функции.
- Программирование - Ирина Козлова - Программирование
- Программирование — вторая грамотность - Андрей Ершов - Программирование
- 97 этюдов для архитекторов программных систем - Нил Форд - Программирование
- Платформа J2Me - Автор неизвестен - Программирование