Переменная 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, который обеспечивает построение сетки графика функции.
Управление светом в Direct3D8.
Direct 3D имеет довольно мощные средства для работы с освещением 3D-сцены. Поддерживается несколько различных типов источников света: параллельный (directional), точечный (point-source) и прожектор (spotlight). Параллельный свет не имеет источника – только направление. В качестве аналогии приведу солнце: все его лучи параллельны (простите, физики и астрономы!). Точечный свет и прожектор имеют вполне определенную точку, из которой исходят все лучи. Точечный свет испускается во все стороны, а прожектор имеет строго очерченный конус распространения лучей. Для получения более подробной информации можете обратиться к DirectX 8.0a SDK. Сейчас меня больше интересует другое. Есть одна проблема: все точки (vertexes), которые принимают участие в вычислении освещенности сцены должны включать вектор нормали. Определение нормали на самом деле очень просто, если Вы все еще помните курс школьной геометрии. Как это сделать? Есть, как минимум, 2 пути:
• Во-первых, мы можем найти аналитическое выражение, описывающее координаты вектора нормали. Это будет очень точный результат, но для каждого новой 3D-функции придется провести все выкладки заново.
• Во-вторых, мы можем рассчитать примерные координаты нормали исходя из координат точки, в которой определяется нормаль и координат соседних с ней точек. Пусть это лишь приближенное решение: его вполне достаточно для наших целей. К тому же оно зато абсолютно не зависит от самой функции. Именно этот подход реализован в демо-приложении и Вы можете найти его в функции C3DGraphic::CalcNormal(). Попробую прокомментировать сам алгоритм.