Введение в Direct3D8
Шрифт:
От f(x,y)=sin(x+y) до 2D картинки
Жизнь всегда была сложнее, чем хотелось бы программисту. Например, все было бы гораздо проще, если бы существовала некая функция ХочуЧтобыНарисовалсяГрафикФункции. Но во-первых, по-русски функции называть нельзя, во-вторых, такой функции просто нет. Все, что умеет IDirect3DDevice8, это нарисовать некоторые примитивы, да и для этого надо проделать кучу подготовительной работы. Как минимум, вы должны разместить координаты примитивов в вертекс-буфер ("vertex buffer"). Полное обсуждение вертекс-буферов находится за пределами этой статьи и могу лишь сказать, что это некая абстракция простого блока памяти, предназначенного для хранения координат и свойств точек трехмерного пространства. Работа с буфером осуществляется через интерфейс IDirect3DVertexBuffer8. Вы может
Обычно перед рисованием чего-либо мы хотим очистить кадр, чтобы новый кадр не накладывался на предыдущий. Это делается вызовом метода IDirect3DDevice8::Clear. Вы найдете это в функции C3DGraphic::ReRender:
Мы здесь очищаем весь back-буфер, заполняя его цветом m_dwBackColor. Кроме этого, очищается z-буфер – он заполняется значением 1.0. 1.0 соответствует максимально дальней от наблюдателя плоскости (горизонт, грубо говоря), 0.0 – максимально ближней. Непосредственно рисование на 3D устройстве начинается с
а заканчивается строкой
Все, что находится между этими вызовами символизирует анимацию очередного кадра. Обратите внимание на то, что мы постоянно употребляем термин "back-буфер". Совершенно верно! Все, что мы сейчас нарисовали, не видно на экране, оно существует пока только в памяти компьютера (не будем уточнять, в какой именно: системной или видео – очень тонкий вопрос). Для того, чтобы эти изменения перенеслись на экран монитора необходимо скопировать изображение из back-буфера на основную поверхность. Это делается вызовом функции IDirect3DDevice8::Present.
Следующий код обеспечивает непосредственно отрисовку изображения трехмерной функции:
Переменная m_pDataVB является членом класса C3DGraphic, она содержит указатель на интерфейс IDirect3DVertexBuffer8. В нашем случае содержимое вертекс-буфера представляет из себя массив структур GRAPH3DVERTEXSTRUCT:
Здесь x, y и z – координаты точки, nx, ny, nz – компоненты нормали. Вектор нормали используется подсистемой Direct3D, отвечающей за освещение сцены. Чуть позже мы рассмотрим, как можно рассчитать эти компоненты. Перед вызовом функции DrawPrimitive мы должны указать vertex shader и используемый поток данных. Выбор и установка потока данных производится с помощью функции SetStreamSorce. Мы передаем ей номер устанавливаемого потока (0, если оспользуется только один поток), указатель на вертекс-буфер, из которого будут браться данные, и размер каждого элемента в потоке (то есть, sizeof(GRAPH3DVERTEXSTRUCT). Vertex shaders – новая возможность Direct3D. Это эдакая абстракция, символизирующая обработку вершин (вертексов). Грубо говоря, vertex shader обеспечивает перевод точки из трехмерного пространства модели в двумерную картинную плоскость. Вы можете написать на особом языке скрипт, обеспечивающий этот перевод, а можете использовать один из поддерживаемых стандартных механизмов,
После установки необходимых потока данных и вертекс-шейдера мы можем вызвать метод 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, который обеспечивает построение сетки графика функции.
Управление светом в Direct3D8.
Direct 3D имеет довольно мощные средства для работы с освещением 3D-сцены. Поддерживается несколько различных типов источников света: параллельный (directional), точечный (point-source) и прожектор (spotlight). Параллельный свет не имеет источника – только направление. В качестве аналогии приведу солнце: все его лучи параллельны (простите, физики и астрономы!). Точечный свет и прожектор имеют вполне определенную точку, из которой исходят все лучи. Точечный свет испускается во все стороны, а прожектор имеет строго очерченный конус распространения лучей. Для получения более подробной информации можете обратиться к DirectX 8.0a SDK. Сейчас меня больше интересует другое. Есть одна проблема: все точки (vertexes), которые принимают участие в вычислении освещенности сцены должны включать вектор нормали. Определение нормали на самом деле очень просто, если Вы все еще помните курс школьной геометрии. Как это сделать? Есть, как минимум, 2 пути:
• Во-первых, мы можем найти аналитическое выражение, описывающее координаты вектора нормали. Это будет очень точный результат, но для каждого новой 3D-функции придется провести все выкладки заново.
• Во-вторых, мы можем рассчитать примерные координаты нормали исходя из координат точки, в которой определяется нормаль и координат соседних с ней точек. Пусть это лишь приближенное решение: его вполне достаточно для наших целей. К тому же оно зато абсолютно не зависит от самой функции. Именно этот подход реализован в демо-приложении и Вы можете найти его в функции C3DGraphic::CalcNormal. Попробую прокомментировать сам алгоритм.
– Находим 4 вектора к соседним точкам:
V01 = P1 – P0;
V02 = P2 – P0;
V03 = P3 – P0;
V04 = P4 – P0;
– Находим 4 нормали к каждой из треугольных граней. Нормали находятся как векторное произведение соответствующих векторов.
N1 = [V02, V01];
N2 = [V03, V02];