Войти
ПрограммированиеСтатьи

Вспомогательная визуализация при разработке игр.

Автор:

Олег Кузнецов в настоящее время ведущий программист компании "Meridian'93". Опыт профессиональной работы в гейм индустрии 4 года. На русском рынке представлены малобюджетные проекты "Бешеные буренки" и "Месть болотной курицы *". В данный момент работает над еще не анонсированным проектом.

Обоснование: зачем это надо и описание контекста применения.

Часто при написании серезной игры и при ее отладке возникает задача получить больше информации о том, что в данный момент происходит внутри движка. К тому же эту информацию хотелось бы видеть в реальном времени и в удобном для восприятия виде.

Например, если мы пишем бота для игры в Quake, то было бы полезно видеть, куда наше создание целится, также неплохо бы видеть направление на цель и вектор скорости цели. Или другой пример: в начале разработки движка полезно видеть систему мировых координат.

Вот тут и оказывается, что эту информацию, а так же большое количество другой удобно отображать с помощью рисования "обычных" линий.

Примеры:
Система координат. Отрезками представленны орты. X красный, Y зеленый, Z синий. Легко запоминается XYZ->RGB.

Изображение

Здесь мы видим конус проектора, для расчета и рисования тени.

Изображение

Здесь белой линией представленно направление объекта.

Изображение

О том, как рисовать линии, чтобы это помогало, а не мешало процессу отладки и пойдет речь дальше.

Отметим, что нам не обязательно рисовать линии с максимально возможной скоростью - главное, чтобы это не мешало остальному коду, который занимается отрисовкой игры.

Объяснение алгоритма.

Перейдем к более конкретной постановке задачи:

Наша задача создать набор функций (класс), с помощью которого можно рисовать линии заданного цвета в мировых координатах.

При этом мы не должны допустить изменение состояния графической системы.

Поясню, мы ведь не хотим, чтобы после того как мы нарисуем нашу красную линию, некоторые части уровня или персонажей тоже стали красными. Хотя, конечно, дело хозяйское... и, при желании, вы сможете изменить алгоритм с этой целью, но за результат ответственность будете нести только вы.

Алгоритм получается следующий:
Запомнить состояние.
Установить нужное для рисования линии с нужным цветом состояние.
Нарисовать линию.
Восстановить запомненное состояние.

Все просто? Так и должно быть, но это теория... Дальше рассмотрим практику.

Пример реализации.

Перейдем к реализации этого алгоритма. Замечу, что она будет медленней, чем могла бы быть, и она не будет такой надежной как могла бы быть, но она очень проста для понимания и ее очень легко встроить в любой движок. В конце статьи я покажу, что можно с ней сделать, чтобы она стала быстрее и надежнее.

Еще один плюс этой реализации в том, что у нас будет всего одна функция.

Еще замечу, что эта функция не из реального движка, а написана специально для статьи.

Не надо забывать, что эту функцию надо вызывать между вызовами BeginScene() и EndScene(). Также надо помнить про то, что функция Clear() очистит нарисованные линии - поэтому вызывать нашу функцию надо после нее.

// Функция рисует линию из точки (x1, y1, z1) в точку(x2, y2, z2) цветом color
void draw_line(IDirect3DDevice8* device,
                         float x1, float y1, float z1, 
                         float x2, float y2, float z2, 
                         D3DCOLOR color)
{
  struct
  {
    float x, y, z;
    D3DCOLOR color;
  }verticies[2] = {x1, y1, z1, color,
                            x2, y2, z2, color};

  // Запомнить все, что мы будем менять
  D3DMATRIX old_world_matrix;
  DWORD old_lighting;
  DWORD old_alpha_blend_enable;
  DWORD old_color_op;
  DWORD old_alpha_op;
  DWORD old_vertex_shader;
  IDirect3DVertexBuffer8* old_stream_source;
  UINT old_stream_stride;

  device->GetTransform(D3DTS_WORLD, &old_world_matrix);
  device->GetRenderState(D3DRS_LIGHTING, &old_lighting);
  device->GetRenderState(D3DRS_ALPHABLENDENABLE, &old_alpha_blend_enable);
  device->GetTextureStageState(0, D3DTSS_COLOROP, &old_color_op);
  device->GetTextureStageState(0, D3DTSS_ALPHAOP, &old_alpha_op);
  device->GetVertexShader(&old_vertex_shader);
  // меняется неявно при вызове функции DrawPrimitiveUP
  device->GetStreamSource(0, &old_stream_source, &old_stream_stride);
  
  // Установить нужные нам свойства
  D3DMATRIX identity_matrix;

  memset(&identity_matrix, 0, sizeof(identity_matrix));
  identity_matrix._11 = 
  identity_matrix._22 = 
  identity_matrix._33 = 
  identity_matrix._44 = 1.f;

  device->SetTransform(D3DTS_WORLD, &identity_matrix);
  device->SetRenderState(D3DRS_LIGHTING, FALSE);
  device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
  device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_DISABLE);
  device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
  device->SetVertexShader(D3DFVF_XYZ | D3DFVF_DIFFUSE);

  // Нарисовать линию
  device->DrawPrimitiveUP(D3DPT_LINELIST, 1, verticies, sizeof(verticies[0]));

  // Восстановить все что меняли
  device->SetTransform(D3DTS_WORLD, &old_world_matrix);
  device->SetRenderState(D3DRS_LIGHTING, old_lighting);
  device->SetRenderState(D3DRS_ALPHABLENDENABLE, old_alpha_blend_enable);
  device->SetTextureStageState(0, D3DTSS_COLOROP, old_color_op);
  device->SetTextureStageState(0, D3DTSS_ALPHAOP, old_alpha_op);
  device->SetVertexShader(old_vertex_shader);
  device->SetStreamSource(0, old_stream_source, old_stream_stride);
}

Как нам теперь нарисовать систему координат?

На самом деле, очень просто - всего три вызова нашей функции, по одному на каждую ось:

// Ось X рисуем красным цветом
draw_line(device, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, D3DCOLOR_RGBA(255, 0, 0, 0));
// Ось Y рисуем зеленым цветом
draw_line(device, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, D3DCOLOR_RGBA(0, 255, 0, 0));
// Ось Z рисуем синим цветом
draw_line(device, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, D3DCOLOR_RGBA(0, 0, 255, 0));

Что еще можно сделать.

Здесь собранны пункты, которые нужно сделать, если вы хотите улучшить реализацию, приведенную в этой статье.

Естественно заменить x1, y1, z1 и x2, y2, z2 на какую-нибудь структуру вашего движка, представляющую тройку координат, например D3DXVECTOR3
Запоминать и устанавливать состояние с помощью State Blocks.
Перевести рисование линий на Dynamic Vertex Buffers и на DrawPrimitive().
Рисовать не по одной линии, а сразу по несколько. В этом случае можно устанавливать и восстанавливать состояние только один.
Учитывать ошибки драйверов и восстанавливать большее количество состояний, чем мы меняли.
Состояния, влияющие на использования Z Buffer-а в этой реализации, не меняется, поэтому результат может зависеть от точки вызова. Естественно это нужно исправить.

6 июня 2002