15 января 2010 в 13:31
Уроки Direct3D - 1.5. 3D, перспектива, матрицы
Очевидно, что способ, которым мы в предыдущей главе рисовали треугольники, неудобен и неполноценен для 3D игр. Неудобен потому, что мы, построив сложную сцену, лишены возможности взглянуть на нее с другой точки. Камера всегда направлена вдоль оси Z так, что ось X направлена вправо. Чтобы посмотреть, например, вдоль оси X, необходимо пересчитать позиции всех вертексов сцены! То же и в случае масштабирования, придется пересчитывать позиции вертексов, и при смене разрешения экрана или размеров формы – опять предстоит перерасчет, ведь шкала жестко привязана к размеру пикселя.
Неполноценность заключается в отсутствии перспективы – видимый размер наших треугольников не зависит от расстояния до них (координаты Z). Чтобы избавиться от этих недостатков, нужно отказаться от «приведенного» формата вертекса. Но сначала немного теории.
В математике существует понятие «матрица» – таблица чисел, построенная по определенным правилам. Из всех типов матриц нас будет интересовать только один – квадратная (4 × 4) таблица чисел типа Single. Фактически это таблица коэффициентов в системе уравнений, которая описывает почти любое изменение (трансформацию) пространства. Это прежде всего перемещение и масштабирование – наиболее простые трансформации, это также поворот вокруг любой из осей координат или вокруг произвольной оси. Матрицей можно описать перспективу – изменение видимого размера объекта в зависимости от расстояния до него. Кроме того, одной единственной матрицей можно задать любое сочетание всех перечисленных трансформаций. Правда есть некоторые ограничения – после любого преобразования прямая останется прямой (либо выродится в точку), а плоскость – плоскостью (либо выродится в прямую или точку).
Каким образом произвести расчет нужной матрицы? Ответ на этот вопрос может дать курс аналитической геометрии, но мы не будем углубляться в дебри науки, так как, к нашей радости, в составе DirectX уже имеется набор готовых функций.
Каким образом применять матрицы? d3dDevice содержит несколько трансформаций, которые задаются с помощью метода SetTransform, рассмотрим три основные:
Первая – трансформация мира, обозначаемая D3DTS_WORLD. Записывая в неё матрицу, например, перемещения, мы вызываем соответствующее перемещение всей выводимой в дальнейшем геометрии.
Вторая – трансформация камеры или обзора D3DTS_VIEW. Её действие противоположно трансформации мира. Записывая в D3DTS_VIEW матрицу вращения вокруг оси Y на 30 градусов, мы получим вращение мира на -30 градусов, что соответствует повороту камеры (иначе говоря, глаз наблюдателя) на 30 градусов. То есть с помощью этой трансформации мы задаем точку, из которой смотрим на наш 3D мир, и направление взгляда.
Третья – трансформация проекции D3DTS_PROJECTION. Она задает проекцию изображения на экран монитора (точнее на BackBuffer). Это может быть ортогональная проекция или перспектива, левосторонняя или правосторонняя.
Создадим новый проект, взяв за основу предыдущий.
Добавим глобальную переменную типа D3DMATRIX:
Dim Mtrx As D3DMATRIX Из формата вертекса исключим поле RHW: Private Type vFormat Pos As D3DVECTOR Color As Long End Type
Поле Pos соответствует трем старым полям PosX, PosY, и PosZ.
Соответственно изменим флаговое описание вертекса:
Private Const vFlag = D3DFVF_XYZ Or D3DFVF_DIFFUSE
Добавим функцию для быстрого создания векторов:
Private Function vec3(X As Single, Y As Single, Z As Single) As D3DVECTOR vec3.X = X vec3.Y = Y vec3.Z = Z End Function
Использование «неприведенного» формата вертекса подразумевает использование света по умолчанию. Поскольку свет мы еще не изучили – отключаем:
d3dDevice.SetRenderState D3DRS_LIGHTING, 0
И процедура, задающая начальные значения трансформаций:
Private Sub InitMatrix() D3DXMatrixIdentity Mtrx d3dDevice.SetTransform D3DTS_WORLD, Mtrx D3DXMatrixPerspectiveFovLH Mtrx, 3.141593 / 4, Me.ScaleHeight / Me.ScaleWidth, 0.1, 10 d3dDevice.SetTransform D3DTS_PROJECTION, Mtrx D3DXMatrixLookAtLH Mtrx, vec3(0, 0, -2), vec3(0, 0, 0), vec3(0, 1, 0) d3dDevice.SetTransform D3DTS_VIEW, Mtrx End Sub
Рассмотрим ее подробнее. С помощью D3DXMatrixIdentity рассчитывается матрица «нулевого» преобразования (или идентичности) и в следующей строке записывается в трансформацию мира. Это означает, что координаты, указанные в вертексе, будут использоваться без каких-либо изменений, как есть.
С помощью D3DXMatrixPerspectiveFovLH рассчитывается матрица левосторонней перспективы, описывающая камеру с углом зрения 45º по вертикали (значение 3.141593 / 4 указано в радианах), и с отношением размера по вертикали к размеру по горизонтали, соответствующим размеру формы. Последние две величины – ограничение ближнего и дальнего планов видимости, зачем это нужно – рассмотрим чуть позже. Эта матрица записывается в трансформацию проекции.
И остается трансформация обзора, D3DXMatrixLookAtLH позволяет задать позицию наблюдателя в пространстве (первый вектор), направление его взгляда (второй вектор) и направление «вверх» (третий вектор). В нашем примере наблюдатель из точки «0, 0, -2» смотрит в начало координат. Направление «вверх» соответствует направлению оси Y.
Теперь зададим геометрию:
Private Sub InitGeometry() Set vBuffer = d3dDevice.CreateVertexBuffer(2 * 3 * vSize, 0, vFlag, D3DPOOL_DEFAULT) Vert(0).Pos = vec3(-0.5, -0.5, 0) Vert(0).Color = &HFF Vert(1).Pos = vec3(0.5, -0.5, 0) Vert(1).Color = &HFF Vert(2).Pos = vec3(0, 0.5, 0) Vert(2).Color = &HFF D3DVertexBuffer8SetData vBuffer, 0, vSize * 3, 0, Vert(0) Vert(0).Pos = vec3(0, -0.5, -0.5) Vert(0).Color = &HFF0000 Vert(1).Pos = vec3(0, -0.5, 0.5) Vert(1).Color = &HFF0000 Vert(2).Pos = vec3(0, 0.5, 0) Vert(2).Color = &HFF0000 D3DVertexBuffer8SetData vBuffer, vSize * 3, vSize * 3, 0, Vert(0) End Sub
Два треугольника, синий и красный, расположены в начале координат и взаимно пересекаются, создавая фигуру, наподобие наконечника стрелы, направленной вверх. Жмем <F5> и видим синий треугольник. Почему нет красного? Правильно, красный треугольник расположен к нам боком и его не видно. Чтобы его увидеть – переместим наблюдателя:
D3DXMatrixLookAtLH Mtrx, vec3(1, 0, -2), vec3(0, 0, 0), vec3(0, 1, 0)
С этой позиции видно оба треугольника. Вернем наблюдателя на место и попробуем по-другому. Добавим перед вызовом Render такие строки:
D3DXMatrixRotationY Mtrx, Timer d3dDevice.SetTransform D3DTS_WORLD, Mtrx
Теперь, не меняя позиции наблюдателя, мы видим сцену с разных сторон. Мы вращаем саму сцену.
Вернемся к рассмотрению D3DTS_PROJECTION, почему бы не расширить зону видимости по оси Z от 0 до ∞? Дело в использовании ZBuffer, расстояние до рисуемого пикселя преобразуется перед записью в ZBuffer в значение от 0 до 1, что мы видели при использовании приведенного формата. Это значение имеет конечную точность, чем шире мы будем раздвигать границы, тем больше вероятность ошибки при Z-отсечении. Попробуем поменять значения zn и zf:
D3DXMatrixPerspectiveFovLH Mtrx, 3.141593 / 4, Me.ScaleHeight / Me.ScaleWidth, 0.0001, 1000
Запускаем программу и видим результат неточности.
Восстановим прежнее значение трансформации проекции и попробуем осуществить более сложное движение. Допустим, нам нужно так же вращать нашу «стрелу», но отодвинув ее на некоторое расстояние назад. Можно, конечно, отодвинуть наблюдателя, но что, если в сцене много разных объектов? Они отодвинутся все, а нам нужно, чтобы отодвинулась «стрела».
Второй вариант – перезаписать в вертексы новые значения, но этот вариант тоже неудобен, вдруг нам нужно не разово отодвинуть «стрелу», а передвигать ее постоянно, как мы постоянно ее вращаем. То есть нужно перемещение задавать матрицей, но как записать в одну трансформацию сразу две матрицы, вращения и перемещения? В математике для матриц определено действие умножения, при этом в результате получается матрица, объединяющая обе трансформации пространства. Добавим в основной цикл две строки:
Do While Running DoEvents D3DXMatrixRotationY Mtrx, Timer d3dDevice.SetTransform D3DTS_WORLD, Mtrx D3DXMatrixTranslation Mtrx, 0, 0, 1 d3dDevice.MultiplyTransform D3DTS_WORLD, Mtrx Render Loop
Здесь мы рассчитали матрицу вращения, записали ее в D3DTS_WORLD, потом рассчитали матрицу перемещения и домножили D3DTS_WORLD на эту матрицу.
Запускаем программу – результат не совсем правильный, «стрела» переместилась на некоторое расстояние, но продолжает вращаться вокруг начала координат, а не вокруг своей оси, которая с началом координат уже не совпадает. Дело в том, что в умножении матриц, в отличие от умножения чисел, важен порядок множителей, и этот порядок обратный по отношению к порядку трансформаций. Поменяем местами матрицы таким образом:
D3DXMatrixTranslation Mtrx, 0, 0, 1 d3dDevice.SetTransform D3DTS_WORLD, Mtrx D3DXMatrixRotationY Mtrx, Timer d3dDevice.MultiplyTransform D3DTS_WORLD, Mtrx
Теперь цель достигнута, «стрела» отодвинулась на 1 вдоль оси Z и вращается вокруг своей оси. Того же результата можно достичь по-другому – сначала рассчитать матрицу необходимой трансформации, перемножив матрицы вращения и перемещения, а потом записать в D3DTS_WORLD полученную матрицу. Задайте еще одну матрицу:
Dim Mtrx2 As D3DMATRIX
И поменяйте строки в основном цикле так:
D3DXMatrixTranslation Mtrx, 0, 0, 1 D3DXMatrixRotationY Mtrx2, Timer D3DXMatrixMultiply Mtrx, Mtrx2, Mtrx d3dDevice.SetTransform D3DTS_WORLD, Mtrx
Как обычно, код данного проекта можно найти в папке Pr04. Скачайте архив в 1 уроке.
комментарии отсутствуют
авторизуйтесь