На прошлом уроке мы научились инициализировать OpenGL. На этом мы рассмотрим, какие действия нужно выполнить для рендеринга сцены в окно созданного нами приложения.
Чтобы нарисовать сцену с помощью OpenGL нужно выполнить следующие действия.
- Установить порт просмотра
- Выбрать матрицу проекции
- Сбросить матрицу проекции
- Установить проекцию
- Выбрать видовую матрицу
- Сбросить видовую матрицу
- Очистить буфер цвета (а также другие буферы, если используются)
- Выполнить видовые преобразования
- Нарисовать сцену и завершить рисование
А сейчас разберем всё более подробно.
Порт просмотра
Первое, что нам нужно – задать порт просмотра. Что это такое? Порт просмотра – это прямоугольная область, которая определяет область вывода изображения в окне. Задается порт просмотра командой glViewport.
procedure glViewport (x, y: GLint; width, height: GLsizei); |
Первые два параметра x и y задают координаты левого нижнего угла прямоугольной области вывода изображения, Параметры width и height задают ширину и высоту области вывода (все параметры измеряются в пикселях).
Матричные операции
После установки порта просмотра нужно выбрать матрицу проекции. Делается это с помощью команды glMatrixMode:
procedure glMatrixMode (mode: GLenum); |
Где параметр mode задает матрицу, которую нам нужно выбрать. Например:
glMatrixMode(GL_PROJECTION); |
Делает текущей матрицу проекции. Следующая команда:
glMatrixMode(GL_MODELVIEW); |
Выбирает видовую матрицу.
После выбора видовой и или проекционной матрицы её, нужно установить в единичную матрицу (сбросить). Для установки текущей (выбранной матрицы) в единичную матрицу существует команда glLoadIdentity. Данная команда не содержит параметров.
Задание проекции
В OpenGL существует два вида проекции: ортографическая (прямоугольная) и перспективная. Более подробно читаем, перейдя по вышеуказанным ссылкам.
Очистка буферов
Очистку выполняем с помощью команды glClear.
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); |
Данный пример очищает буфер цвета и буфер глубины.
Видовые преобразования
Перед тем, как начать рисовать сцену нужно выполнить видовые преобразования. Читаем, перейдя по ссылке и, возвращаемся, чтобы рассмотреть пример приложения.
Пример приложения, рисующего сцену.
Ну вот, мы и добрались до самого главного – практической реализации пройденного материала. Итак, рассмотрим наш пример.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, OpenGL, GL_Utils, AppEvnts, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Panel1: TPanel;
ApplicationEvents1: TApplicationEvents;
ComboBox1: TComboBox;
ComboBox2: TComboBox;
CheckBox1: TCheckBox;
Label1: TLabel;
Label2: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
procedure ComboBox2Change(Sender: TObject);
procedure ApplicationEvents1ShortCut(var Msg: TWMKey;
var Handled: Boolean);
procedure CheckBox1Click(Sender: TObject);
private
procedure Points;
procedure Lines;
procedure Polygon;
procedure Triangle;
procedure Quad;
procedure Axis;
procedure SendOrtho;
procedure SendPersp;
procedure SendView;
procedure RendScene;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const
PR_POINT = 0;
PR_LINE = 1;
PR_POLYGON = 2;
PR_TRIANGLE = 3;
PR_QUAD = 4;
PROJ_ORTHO = 0;
PROJ_PERSP = 1;
// Цвета
CL_RED: Integer = $0000FF;
CL_GREEN: Integer = $00FF00;
CL_BLUE: Integer = $FF0000;
CL_YELLOW: Integer = $00FFFF;
type
TCamera = record // Запись сохраняет параметры камеры
AX, // Рыскание
AY, // Тангаж
L: GLfloat; // Расстояние от точки наблюдения
end;
var
Cnt: TCntData; // Дескриптор окна, контекст рендеринга и контекст окна
Camera: TCamera; // Камера
procedure TForm1.FormCreate(Sender: TObject);
begin
ZeroMemory(@Cnt, SizeOf(Cnt));
// Создаем контекст рендеринга OpenGL
// и получаем контекст окна
CreateContext(Handle, Cnt);
// Устанавливаем цвет очистки буфера цвета
glClearColor(0.0, 0.0, 0.0, 1);
glClearDepth(1); // Устанавливаем значение очистки буфера глубины
glDepthFunc(GL_LESS); // Устанавливаем функцию теста глубины
glEnable(GL_DEPTH_TEST); // Разрешаем тест глубины
// Инициализируем камеру
with Camera do
begin
AX := 0;
AY := 0;
L := 5;
end;
// Устанавливаем параметры просмотра
SendView;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// Удаляем контекст рендеринга OpenGL
// и освобождаем контекст окна
DestroyContext(Cnt);
end;
// Процедура рисует линии
procedure TForm1.Lines;
begin
glBegin(GL_LINES);
glColor3ubv(@CL_RED);
glVertex2f(-1, -1);
glVertex2f(1, -1);
glColor3ubv(@CL_GREEN);
glVertex2f(1, -1);
glVertex2f(1, 1);
glColor3ubv(@CL_BLUE);
glVertex2f(1, 1);
glVertex2f(-1, 1);
glColor3ubv(@CL_YELLOW);
glVertex2f(-1, 1);
glVertex2f(-1, -1);
glEnd;
end;
// Процедура рисует полигон
procedure TForm1.Polygon;
begin
glBegin(GL_POLYGON);
glColor3ubv(@CL_RED);
glVertex2f(0.586, -0.81);
glColor3ubv(@CL_GREEN);
glVertex2f(0.948, 0.31);
glColor3ubv(@CL_BLUE);
glVertex2f(0.0, 1.0);
glColor3ubv(@CL_YELLOW);
glVertex2f(-0.948, 0.31);
glColor3ubv(@CL_GREEN);
glVertex2f(-0.58, -0.81);
glEnd;
end;
// Процедура рисует точки
procedure TForm1.Points;
begin
glBegin(GL_POINTS);
glColor3ubv(@CL_RED);
glVertex2f(-1, -1);
glColor3ubv(@CL_GREEN);
glVertex2f(1, -1);
glColor3ubv(@CL_BLUE);
glVertex2f(1, 1);
glColor3ubv(@CL_YELLOW);
glVertex2f(-1, 1);
glEnd;
end;
// Процедура рисует квадрат
procedure TForm1.Quad;
begin
glBegin(GL_QUADS);
glColor3ubv(@CL_RED);
glVertex2f(-1, -1);
glColor3ubv(@CL_GREEN);
glVertex2f(1, -1);
glColor3ubv(@CL_BLUE);
glVertex2f(1, 1);
glColor3ubv(@CL_YELLOW);
glVertex2f(-1, 1);
glEnd;
end;
// Процедура рисует треугольник
procedure TForm1.Triangle;
begin
glBegin(GL_TRIANGLES);
glColor3ubv(@CL_RED);
glVertex2f(-1, -1);
glColor3ubv(@CL_GREEN);
glVertex2f(1, -1);
glColor3ubv(@CL_BLUE);
glVertex2f(0, 1);
glEnd;
end;
// Процедура рисует координатные оси
procedure TForm1.Axis;
begin
glBegin(GL_LINES);
glColor3ubv(@CL_RED);
glVertex2f(-10, 0);
glVertex2f(10, 0);
glColor3ubv(@CL_BLUE);
glVertex2f(0, -10);
glVertex2f(0, 10);
glColor3ubv(@CL_GREEN);
glVertex3f(0, 0, -10);
glVertex3f(0, 0, 10);
glEnd;
end;
// Процедура устанавливает порт просмотра и ортографическую проэкцию
procedure TForm1.SendOrtho;
var
W, H: Integer;
begin
W := ClientWidth;
H := ClientHeight;
// Устанавливаем порт просмотра
glViewport(0, 0, W, H);
// Выбираем матрицу проекции
glMatrixMode(GL_PROJECTION);
// Сбрасываем матрицу проекции
glLoadIdentity;
// Устанавливаем ортографическую проекцию
if W <= H then
glOrtho(-3, 3, -3 * H / W, 3 * H / W, -500, 500)
else
glOrtho(-3 * W / H, 3 * W / H, -3, 3, -500, 500);
// Выбираем видовую матрицу
glMatrixMode(GL_MODELVIEW);
end;
// Процедура устанавливает порт просмотра и перспективную проэкцию
procedure TForm1.SendPersp;
var
W, H: Integer;
begin
W := ClientWidth;
H := ClientHeight;
// Устанавливаем порт просмотра
glViewport(0, 0, W, H);
// Выбираем матрицу проекции
glMatrixMode(GL_PROJECTION);
// Сбрасываем матрицу проекции
glLoadIdentity;
// Устанавливаем перспективную проекцию
gluPerspective(45.0, W / H, 1.0, 500.0);
// Выбираем видовую матрицу
glMatrixMode(GL_MODELVIEW);
end;
// Процедура рисует изображение в окне
procedure TForm1.RendScene;
begin
// Сбрасываем текущую матрицу
glLoadIdentity;
// Очищаем буфер цвета и буфер глубины
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
// Передаём параметры камеры
with Camera do
begin
glTranslatef(0, 0, -L);
glRotatef(AY, 1, 0, 0);
glRotatef(AX, 0, 1, 0);
end;
// Рисуем выбранную фигуру
case ComboBox1.ItemIndex of
PR_POINT : Points;
PR_LINE : Lines;
PR_POLYGON : Polygon;
PR_TRIANGLE : Triangle;
PR_QUAD : Quad;
end;
// Рисуем координатные оси
if CheckBox1.Checked then
begin
glDisable(GL_DEPTH_TEST);
Axis;
glEnable(GL_DEPTH_TEST);
end;
// Завершаем рисование
SwapBuffers(Cnt.DC);
end;
// Процедура устанавливает порт просмотра и проекцию
procedure TForm1.SendView;
begin
case ComboBox2.ItemIndex of
PROJ_ORTHO : SendOrtho;
PROJ_PERSP : SendPersp;
end;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
// Рисуем сцену
RendScene;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
// Устанавливаем порт просмотра и проекцию
SendView;
// Рисуем сцену
RendScene;
end;
// Событие возникающее при выборе фигуры
procedure TForm1.ComboBox1Change(Sender: TObject);
begin
// Рисуем сцену
RendScene;
end;
// Событие возникающее при выборе проекции
procedure TForm1.ComboBox2Change(Sender: TObject);
begin
// Устанавливаем порт просмотра и проекцию,
SendView;
// Рисуем сцену
RendScene;
end;
// Обработка нажатий клавиш
procedure TForm1.ApplicationEvents1ShortCut(var Msg: TWMKey;
var Handled: Boolean);
begin
case Msg.CharCode of
// Отдалить камеру
VK_NUMPAD9 : with Camera do L := L + 0.1;
// Приблизить камеру
VK_NUMPAD3 : with Camera do L := L - 0.1;
// Поворот камеры вправо
VK_NUMPAD4 : with Camera do begin
AX := AX + 0.5;
if AX >= 360 then AX := AX - 360;
end;
// Поворот камеры влево
VK_NUMPAD6 : with Camera do begin
AX := AX - 0.5;
if AX < 0 then AX := AX + 360;
end;
// Поворот камеры вниз
VK_NUMPAD2 : with Camera do begin
AY := AY - 0.5;
if AY >= 360 then AY := AY - 360;
end;
// Поворот камеры вверх
VK_NUMPAD8 : with Camera do begin
AY := AY + 0.5;
if AY >= 360 then AY := AY - 360;
end;
// Спрятать / показать панель интерфейса
VK_F3 : Panel1.Visible := not Panel1.Visible;
// Сброс камеры
VK_NUMPAD5 : with Camera do begin
AX := 0;
AY := 0;
L := 5;
end;
end;
// Рисуем сцену
RendScene;
end;
// Показать / спрятать координатные оси
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
// Рисуем сцену
RendScene;
end;
end.
|
В программе, для инициализации OpenGL мы используем модуль GL_Utils, который мы рассматривали на прошлом уроке. Для создания программы, которая осуществляет рендеринг, нам нужно установить некоторые начальные параметры, при её старте (см. листинг).
procedure TForm1.FormCreate(Sender: TObject);
begin
ZeroMemory(@Cnt, SizeOf(Cnt));
// Создаем контекст рендеринга OpenGL
// и получаем контекст окна
CreateContext(Handle, Cnt);
// Устанавливаем цвет очистки буфера цвета
glClearColor(0.0, 0.0, 0.0, 1);
glClearDepth(1); // Устанавливаем значение очистки буфера глубины
glDepthFunc(GL_LESS); // Устанавливаем функцию теста глубины
glEnable(GL_DEPTH_TEST); // Разрешаем тест глубины
// Инициализируем камеру
with Camera do
begin
AX := 0;
AY := 0;
L := 5;
end;
// Устанавливаем параметры просмотра
SendView;
end;
|
В обработчике события FormCreate, после вызова процедуры CreateContext, вызываем команду glClearColor, которая задает цвет очистки:
procedure glClearColor (red, green, blue, alpha: GLclampf); |
Параметры red, green, blue, alpha задают уровни красного, зеленого, синего и альфа каналов, в диапазоне 0 - 1. Далее устанавливаем значение очистки буфера глубины с помощью команды glClearDepth:
procedure glClearDepth (depth: GLclampd); |
Параметр depth задаёт число, которое будет записано в буфер глубины при его очистке. Затем устанавливаем функцию теста глубины с помощью команды glDepthFunc:
procedure glDepthFunc (func: GLenum); |
Параметр func задает флаг функцию, которая будет использоваться для теста глубины. И наконец, включаем тест глубины с помощью команды glEnable:
procedure glEnable (cap: GLenum); |
Параметр cap – это флаг, который сообщает OpenGL об активации нужного нам механизма, в нашем случае это GL_DEPTH_TEST. Далее устанавливаем параметры камеры:
with Camera do
begin
AX := 0;
AY := 0;
L := 5;
end;
|
Затем, с помощью процедуры SendView устанавливаем порт просмотра и проекционную матрицу:
procedure TForm1.SendView;
begin
case ComboBox2.ItemIndex of
PROJ_ORTHO : SendOrtho;
PROJ_PERSP : SendPersp;
end;
end;
|
Здесь процедура осуществляет выбор между двумя процедурами SendOrtho и SendPersp, в зависимости от проекции выбранной пользователем. Рассмотрим каждую из этих процедур по отдельности.
Процедура SendOrtho задаёт порт просмотра и ортографическую проекцию (см. листинг).
procedure TForm1.SendOrtho;
var
W, H: Integer;
begin
W := ClientWidth;
H := ClientHeight;
// Устанавливаем порт просмотра
glViewport(0, 0, W, H);
// Выбираем матрицу проекции
glMatrixMode(GL_PROJECTION);
// Сбрасываем матрицу проекции
glLoadIdentity;
// Устанавливаем ортографическую проекцию
if W <= H then
glOrtho(-3, 3, -3 * H / W, 3 * H / W, -500, 500)
else
glOrtho(-3 * W / H, 3 * W / H, -3, 3, -500, 500);
// Выбираем видовую матрицу
glMatrixMode(GL_MODELVIEW);
end;
|
С начала, устанавливаем порт просмотра.
Затем выбираем матрицу проекции.
glMatrixMode(GL_PROJECTION); |
Далее сбрасываем проекционную матрицу с помощью команды glLoadIdentity. Теперь все готово для установки проекционной матрицы. Так, как нам нужно задать ортографическую (или прямоугольную) проекцию, мы используем команду glOrtho (подробнее см. Ортографическая проекция)
if W <= H then
glOrtho(-3, 3, -3 * H / W, 3 * H / W, -500, 500)
else
glOrtho(-3 * W / H, 3 * W / H, -3, 3, -500, 500);
|
Из листинга видно, что параметры left, right, bottom, top рассчитываются исходя из ширины W и высоты H порта просмотра. В результате получаем правильную (не искаженную) проекцию. Это значит, что при изменении размеров окна, от которых в, нашем случае, напрямую зависят размеры порта просмотра, изменяется и сама проекция, таким образом, финальное изображение остается неизменным. Дальше выбираем видовую матрицу:
glMatrixMode(GL_MODELVIEW); |
Рассмотрим процедуру SendPersp:
procedure TForm1.SendPersp;
var
W, H: Integer;
begin
W := ClientWidth;
H := ClientHeight;
// Устанавливаем порт просмотра
glViewport(0, 0, W, H);
// Выбираем матрицу проекции
glMatrixMode(GL_PROJECTION);
// Сбрасываем матрицу проекции
glLoadIdentity;
// Устанавливаем перспективную проекцию
gluPerspective(45.0, W / H, 1.0, 500.0);
// Выбираем видовую матрицу
glMatrixMode(GL_MODELVIEW);
end;
|
Как видно из листинга в процедуре используются те же команды, что и в процедуре SendOrtho, за исключением команды gluPerspective. Поэтому опустим рассмотрение этих команд и перейдем к команде gluPerspective.
gluPerspective(45.0, W / H, 1.0, 500.0); |
Параметр fovy устанавливаем в 45 градусов, что является оптимальным углом обзора человеческого глаза. Параметр aspect представляем как соотношение ширины и высоты порта просмотра. Параметр zNear устанавливаем в единицу. Параметр zFar устанавливаем в 500. Важно: параметр zNear не должен быть меньше единицы, параметр zFar всегда должен быть больше параметра zNear. Следует заметить, что в данной процедуре мы используем команду gluPerspective вместо команды glFrustum, что позволяет значительно упростить расчет всех нужных параметров (подробней о команде gluPerspective см. Перспективная проекция).
Для того, чтобы программа автоматически корректировала проекцию при изменении размеров окна, добавляем процедуру SendView в обработчик соответствующего события окна нашего приложения:
procedure TForm1.FormResize(Sender: TObject);
begin
// Устанавливаем порт просмотра и проекцию
SendView;
// Рисуем сцену
RendScene;
end;
|
Так же поступаем и с обработчиком события компонента ComboBox2, который отвечает за проекцию, выбранную пользователем. И в первом и во втором обработчиках, после процедуры SendView, вызываем процедуру RendScene, о которой сейчас и пойдет речь:
procedure TForm1.RendScene;
begin
// Сбрасываем текущую матрицу
glLoadIdentity;
// Очищаем буфер цвета и буфер глубины
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
// Передаём параметры камеры
with Camera do
begin
glTranslatef(0, 0, -L);
glRotatef(AY, 1, 0, 0);
glRotatef(AX, 0, 1, 0);
end;
// Рисуем выбранную фигуру
case ComboBox1.ItemIndex of
PR_POINT : Points;
PR_LINE : Lines;
PR_POLYGON : Polygon;
PR_TRIANGLE : Triangle;
PR_QUAD : Quad;
end;
// Рисуем координатные оси
if CheckBox1.Checked then
begin
glDisable(GL_DEPTH_TEST);
Axis;
glEnable(GL_DEPTH_TEST);
end;
// Завершаем рисование
SwapBuffers(Cnt.DC);
end;
|
В процедуре мы выполняем следующие команды. Сначала нам нужно обнулить видовую матрицу, которая была выбрана в процедуре SendOrtho или SendPersp (в зависимости от проекции, выбранной пользователем). Обнуляем матрицу с помощью команды glLoadIdentity. Далее очищаем буфер цвета и буфер глубины:
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); |
Для удобства просмотра изображения выполняем видовые преобразования:
with Camera do
begin
glTranslatef(0, 0, -L);
glRotatef(AY, 1, 0, 0);
glRotatef(AX, 0, 1, 0);
end;
|
Для того, чтобы создать эффект камеры мы вызываем команду glTranslatef (создает эффект перемещения камеры) и два раза команду glRotatef (), которая обеспечивает тангаж и рыскание камеры соответственно.
Рисуем выбранную фигуру, затем рисуем координатные оси и завершаем рисование с помощью команды SwapBuffers.
На данном уроке мы научились выводить сцену на экран. Осталось откомпилировать рассмотренный нами код или скачать его, а затем поиграть с получившимся примером.
Скачать исходники
|