AlexeySpace.Ru
Программирование трехмерной графики. Часть 1.

1.3. Создание буфера рисования
1.4. Программа генерации точек в пространстве
Этой статьей я начинаю свой цикл уроков о программировании трехмерной графики. В этом уроке мы познакомимся с основами трехмерного мира, научимся строить трехмерные точки и проецировать их на экран нашего монитора.
Для программирования трехмерной графики мы будем использовать интегрированную среду программирования Borland Delphi 7.0 (все примеры и программы написаны именно на ней). А вывод графики будем осуществлять через стандартный графический класс TCanvas, который присутствует практически у всех визуальных компонентов Delphi. Я не буду здесь описывать принципы работы в среде Borland Delphi и основ программирования на языке Object Pascal. Это отдельная и большая тема. Предполагается, что у вас уже есть опыт работы в Delphi, а также навыки программирования графики с помощью класса TCanvas. Если нет, то придется наверстать. Тем более что в интернете достаточно информации на эту тему.
Сразу скажу, что используя данный класс для вывода графики, нам не удаться добиться высокой скорости отрисовки объектов, и уж тем более сделать на нем свой DOOM 3. Но нам это и не нужно. Для изучения основ трехмерности он вполне пригоден. К тому же этот класс скрывает от нас множество ненужных проблем, которые у нас возникли бы, если бы мы выводили графику через низкоуровневые API – функции. Я уже не говорю про низкоуровневую работу с видеоадаптером. Вот там настоящий АД!
Как я уже и сказал, сейчас скорость для нас не важна. Сначала нам нужно понять теорию, с помощью которой строятся трехмерные изображения. А именно: виды проекций, формулы проецирования, формулы трансформации трехмерных объектов в пространстве, нахождение нормалей, расчет освещения и т.д… Ведь освоив всю эту теория, ну или хотя бы ее основы, нам будет намного легче перейти на более высокоуровневые интерфейсы программирования графики, такие так OpenGL или DiretcX. Так было и со мной. Поразбиравшись немного во всех этих математических формулах и геометрических задачках, мне удалось понять и изучить OpenGL буквально за неделю!
Как известно, что бы построить прочный дом, под него сначала нужно заложить фундамент. Так вот, вся теория, изученная нами в данных статьях, и будет нашим фундаментом из знаний, на основе которых мы сможем создавать свои качественные графические приложения и трехмерные игры.
Итак, что бы описать точку в пространстве, достаточно создать вот такую структуру:
На рисунке (1.2.1) видно, что каждая координата точки задает плоскость параллельную противоположенной координатной плоскости. Например, координата X – задает плоскости, которая параллельна координатной плоскости YZ. Пересечение всех трех плоскостей и дает нам искомое расположение точки в пространстве. На рисунке – это точка A.
Итак, у нас имеются координаты точки в пространстве (X, Y, Z). Как же теперь нам отобразить эту точку на экране монитора? Очень просто! Для того что бы отобразить ее на мониторе, нам нужно спроецировать ее на плоскость экрана. Поэтому следующее, что нам понадобится – это формулы перевода трехмерных координат, в двухмерные, т.е. формулы проецирования.
Казалось бы, зачем нам какие-то формулы, когда можно просто взять (X, Y) – координаты точки, и по этим значениям нарисовать ее на экране? А координату Z – просто отбросить... В этом случае у нас получится параллельное (ортогональное) проецирование точки. Так конечно тоже можно сделать, и объекты, построенные на экране, таким образом, тоже будут казаться трехмерными. Но все-таки отображаться они буду не очень красиво. Дело в том, что построенное таким образом изображение, не будет учитывать эффекта перспективы . Такой вид проекции используется в основном в техническом черчении.
Поэтому в программировании трехмерной графики лучше использовать центральное (перспективное) проецирование.
Посмотрите на рисунки (1.2.2), (1.2.3), (1.2.3) и (1.2.5), на них видны основные отличия между параллельной и центральной проекциями.
- Красные линии на рисунках – лучи проецирования.
- Синие точки – точки проецируемого объекта c координатами: (X, Y, Z).
- Красные точки – проекции синих точек на плоскость экрана.
По рисункам 1.2.3. и 1.2.5. видно, что в первом случае противоположенные ребра у куба параллельны, а во втором, все ребра куба имеют точку схода где-то на горизонте.
Мы уже выяснили, что найти параллельную проекцию трехмерного объекта, не составляет большого труда, а вот с центральной проекцией будет немного посложнее. Ниже представлена формула нахождения центральной проекции точки (X, Y, Z).
Давайте теперь разбираться в этих формулах. Начнем с простого. oX и oY – координаты центра нашего экрана. Что бы его вычислить, нужно проделать вот такую нехитрую операцию:
ClientWidth, ClientHeight – это стандартные свойства формы, задающие ширину и высоту клиентской части окна, т.е. без учета системного меню, строки заголовка и т.д. Собственно в этой области мы и будем, выводит графику.
Следующее, это параметр OfsZ – задающий расстояние от центра локальной координатной оси проецируемой точки, до центра плоскости экрана. Что бы понять это взгляните на Рис 1.2.6.
На рисунке, параметр Ofs показывает длину отрезка зеленого цвета, соединяющий центр плоскости экрана и центр локальной координатной оси.
Следующий параметр D – влияющий на коэффициент угла обзора проекции, который вычисляется по формуле: k := D / (Z + Ofs);
Нам важно научится правильно подбирать параметры D и Ofs для того что бы наша проекция отображалась корректно. На Рис 1.2.7. – показана проекция человеческой головы с неправильно подобранными параметрами D и Ofs. На Рис 1.2.8. параметры подобраны, верно.
Т.е. по этим рисункам видно, к каким искажениям приводит неправильный подбор параметров. Дело в том, что наша проекция работает по принципу объектива видеокамеры. И в соответствии с законами оптики, для получения резкого изображения, проектируемый объект должен располагаться на определенном расстоянии от центра проекции (объектива видеокамеры). В наших формулах это расстояние задает параметр Ofs. С параметром D – немного посложнее. Как уже и было сказано, этот параметр задает коэффициент угла обзора проекции. Т.е. с помощью этого параметра можно производить масштабирование объектов.
Вы можете пока не заморачиваться с подбором этих параметров, и присваивать им следующие значения:
Вот и все! Использование такого присвоения приводит к более-менее нормальным результатам отображения объектов. Вы можете сами поэкспериментировать с подборами этих параметров с помощью моей программы: Projection. И увидеть, как меняется проекция объекта в зависимости от значений данных переменных.
Конечно, что бы получить максимально точную проекцию объекта, нужно провести более сложные расчеты. Но об этом мы поговорим чуть попозже.
1.3. Создание буфера рисования
Теперь поговорим о фундаментальной составляющей программирования компьютерной графики. А именно о буферизации изображения. Реализация данного метода заключается в том, что изображения сначала формируется буфере, который находится в памяти компьютера. Все операция с рисованием графических объектов происходят именно в нем. Как только изображение полностью сформируется в буфере, оно сразу же сбрасывается на экран. Данный метод позволяет избавиться неприятного мерцания экрана, которое возникает при многократной перерисовке объектов. А так как наше изображения будет сначала рисоваться в памяти компьютера, и только потом сбрасываться на экран, то всех этих нежелательных эффектов, мы просто не увидим.
Я назвал этот метод фундаментальным, потому что он применяется во всех программах активно работающих с компьютерной графикой, а в компьютерных играх и подавно…
Поэтому в данном уроке мы научимся создавать и работать с этим буфером, и потом будем использовать его во всех последующих наших уроках.
Создать данный буфер в Delphi – проще пареной репы! Для этого достаточно использовать стандартный графический класс Delphi – TBitMap.
Создание происходит следующим образом:
В первой строке собственно и происходит создание буфера рисования. А именно, создается экземпляр класса TBitMap, и под него выделяется память в компьютере. В следующих строчках указываем размеры нашего буфера, т.е. ширину и длину. Здесь мы указали свойства ClientWidth и ClientHeight т.е. наш буфер будет занимать всю область окна.
Удаление:
Тут комментировать надеюсь ничего не надо. Просто удостоверяемся, что буфер уже не был удален, и только после это освобождаем память занятую им в компьютере. Удаления буфера происходит в конце работы приложения, т.е. перед его закрытием.
Так же нам понадобится процедура очистки буфера. Она нужна для того что бы стереть старый кадр из памяти компьютера, и рисовать начать рисовать новый на чистом ”холсте”.
Очистка происходит следующим образом:
Т.е. в первых двух строчках мы выбираем желаемый цвет буфера, а потом закрашиваем этим цветом все наше окно с помощью стандартной функции Rectangle ”Прямоугольник”, которая имеется в классе Canvas.
DrawBuffer.Canvas.Pen.Color – цвет рамки нашего прямоугольника
DrawBuffer.Canvas.Brush.Color – цвет заливки прямоугольника
В нашем случае, цвет рамки и цвет заливки у нас одинаковы.
Если размеры окна нашего приложения можно будет менять, то нам потребуется функция изменения размеров буфера рисования. Чтобы его размер всегда был равен размеру окна.
Для этого в процедуру, которая вызывается при изменении размеров окна, добавляем следующий код:
Если размеры нашего окна будут статичны, т.е. свойства формы BorderStyle будет иметь значения bsSingle или bsDialog, то данную функцию можно опустить.
Вот в принципе и все! Но остался еще один нюанс. Проведите небольшой эксперимент. Киньте на форму компонент Timer, свойству Enabled присвоим значение True, а значение Interval сделаем равным единице.
Кликнете два раза на таймере, и добавьте следующий код:
Запустите приложение и посмотрите что будет. А будет очень некрасиво, окно будет страшно мерцать. Это происходит потому, что при перерисовки окна, ему сначала посылается сообщение ”WM_ERASEBKGND”, которая стирает фон, а только потом посылается сообщение "WM_PAINT". Поэтому, что бы убрать это неприятное мерцание, нам просто нужно перекрыть функцию, которая вызывается при получении окном сообщения ”WM_ERASEBKGND”. Сделать это, тоже очень легко.
Достаточно добавить в раздел private главной формы следующую функцию:
А само описание функции оставить пустым:
1.4. Программа генерации точек в пространстве
Теперь настала пора применить полученные знания на практике. Мы уже научились проецировать трехмерную точку на экран, а также создавать буфер рисования.
Мы напишем программу, которая генерирует заданное количество точек со случайными координатами (X, Y, Z). Что бы показать, что эти точки действительно находятся в пространстве, мы будем вращать их вокруг локальной системы координат, к которой они принадлежат, и проецировать на экран (на окно приложения).
Процедура генерации точек выглядит следующим образом:
Из этой процедуры видно, что точки генерируются так, что бы их координаты не выходили из поля обзора нашей проекции. Points3D – массив структур TPoint3D. PointsCount – количество элементов в массиве (т.е. количество генерируемых точек).
Теперь напишем главную процедуру рисования точек. Мы будем вызывать ее тогда, когда наше окно нужно будет перерисовать. Например, когда выполняется событие формы OnPaint, OnResize или событие таймера OnTimer.
Блок-схема этой процедуры будет выглядеть следующим образом:
Как видно, в принципе ничего сложного тут нет... У нас остались не разобраны только формулы поворота точек вокруг координатной оси. В программе поворот точек у нас происходит следующим образом:
Переменная Direct задает направление поворота. Если Direct = True, то точки вертятся сверху вниз, если Direct = False, то слева направо. Подробно эти формулы мы сейчас разбирать не будем, а познакомимся с ними в следующих наших уроках. Но уже сейчас видно, что ничего сложного в них тоже нет…

Комментарии (12):
Спасибо большое!
С наступающим!
Поможет мне в написании универсального визуализатора музыки
и де?
Потестил проц Intel Core i7.
Нормально вытягивает 15000 - 17000 точек в один пиксель(с увеличением размера точки fps падает).
В диспетчере максимальная нагрузка на проц(какой нить один) начинается где то с 6000 - 6500 точек.
1. Как вы импортировали меш башки в свой проект?
2. Рисовали ее с помощью GDI?
3. Я вот думаю повысится ли производительность, если заюзать ассемблерные вставки, в проекте на VC++ 2008 при пересчете и отрисовке меша? Понимаю, что под винду лучше юзать d3d, ogl, чисто спортивный интерес.
А можете выложить исходники программы Projection?
очень интересно)
А будет ли продолжение в виде второй части?
Добавить комментарий: