Уроки, алгоритмы, программы, примеры

Вход на сайт

Материалы по разделам

Построения
на плоскости (2D)
Графика
в пространстве (3D)
Вычислительная
геометрия
Физическое
моделирование
Фрактальная
графика

Новые комментарии

Сейчас проверила нашла причину не запускания // Создание контекста воспроизведения OpenGL и привязка его к панели на форме OpenGLControl1:=TOpenGLControl.Create(Self); with OpenGLControl1 do begin Name:='OpenGLControl1'; //вот тут...
Ну..кажется что то пошло не так http://pp.usera...
Комментарии на английском переведите на русский. Дополните код комментариями, чтоб было понятно как работает алгоритм
Пример, к которому вы оставили комментарий строит именно то самое изображение на языке с#, которое вам необходимо. Отличается только цветовая палитра.

Счетчики и рейтинг

Рейтинг@Mail.ru
Среда программирования: 
Visual C

Растровые шрифты и ортогональная проекция

Общее назначение использования растровых шрифтов - это представление информации пользователю (не забываем, что они 2D). Например, мы хотим показать у запущенного приложения количество кадров в секунду. Эта информация должна оставаться в том же положении на экране, даже когда пользователь перемещает камеру по сцене. Кроме того, легче для вычисления позиции использовать 2D-ортогональную проекцию, а не перспективную проекцию, так как мы можем определить позицию в пикселях.

Принципиальная схема отрисовки, это нарисовать мир, так как мы привыкли это делать, с перспективной проекции, а затем перейти к ортогональной проекции и вывести текст. После этого, последним шагом будет восстановление оригинального взгляда так, что-бы каждый следующий кадр отображался правильно.

Представляем шаблон функции визуализации для достижения этого эффекта:

void renderScene() {
 
// делать все, что нужно, чтобы сделать мир, как обычно,
 
        setOrthographicProjection(); //новая функция 1
	glPushMatrix();
	glLoadIdentity();
	renderBitmapString(5,30,GLUT_BITMAP_HELVETICA_18,"Informatika.TNU");
	glPopMatrix();
	restorePerspectiveProjection(); //новая функция 1
 
	glutSwapBuffers();
}

Выше представлены две новые функции , setOrthograpicProjection и restorePerspectiveProjection. Первая функция начинается с изменения матрицы GL_PROJECTION, и это означает, что мы работаем на камере. Впоследствии мы сохраним предыдущие настройки, которые в этом случае могут обратиться к перспективной проекции, определенные в другом месте. Затем мы сбрасываем матрицу с glLoadIdentity(), и определяем ортогональную проекцию с использованием gluOrtho.

Аргументы в пользу этой функции - это указание области в осях "x" и "y" оси. После преобразования нужно перевернуть ось "y", то есть положительное направление будет ​смотреть ​вниз, и отсчитывать начало координат до верхнего левого угла. Это значительно облегчает написание текста в координатах экрана.

Переменные w и h мы вычисляем в другом месте (см. ChangeSize функции в исходном коде).

void setOrthographicProjection() {
	//переключения режима проецирования
	glMatrixMode(GL_PROJECTION);
	//Сохраняем предыдущую матрицу, которая содержит
        //параметры перспективной проекции
	glPushMatrix();
	//обнуляем матрицу
	glLoadIdentity();
	//устанавливаем 2D ортографическую проекцию
	gluOrtho2D(0, w, 0, h);
	//перевернём ось y, положительное направление вниз
	glScalef(1, -1, 1);
        // Движение происходит из левого нижнего угла
        // В верхний левый угол
	glTranslatef(0, -h, 0);
	// возврата в режим обзора модели
	glMatrixMode(GL_MODELVIEW);
}

Более быстрый способ выполнения ортогональной проекции заключается в следующем. Идея заключается в том, чтобы установить проекции таким образом, что масштабирования и переворачивания не требуется.

void setOrthographicProjection() {
	//переключения режима проецирования
	glMatrixMode(GL_PROJECTION);
	//Сохраняем предыдущую матрицу, которая содержит
        //параметры перспективной проекции
	glPushMatrix();
	//обнуляем матрицу
	glLoadIdentity();
	//устанавливаем 2D ортогональную проекцию
	gluOrtho2D(0, w, h, 0);
	// возврата в режим обзора модели
	glMatrixMode(GL_MODELVIEW);
}

Это несложная функция. Так как мы сохранили настройки перспективной проекции прежде, чем перешли к ортогональной проекции, все, что мы должны сделать, это изменить матрицу проекции на GL_PROJECTION, и потом установить её настройки, ну а напоследок изменить матрицу проекции снова на на режим GL_MODELVIEW.

void restorePerspectiveProjection() {
	glMatrixMode(GL_PROJECTION);
	//восстановить предыдущую матрицу проекции
	glPopMatrix();
	//вернуться в режим модели
	glMatrixMode(GL_MODELVIEW);
}

Функция renderBitmapString, из первого примера кода данного урока, будет генерировать символы непрерывно, без дополнительного интервала, за исключением случаев, когда пробел появляется в тексте. Для того, чтобы добавить дополнительное пространство мы должны отслеживать, где сейчас текущая позиция растра, при этом добавляя дополнительный интервал по оси "x". Существуют по меньшей мере два различных подхода к отслеживанию положения растра, первый вычисляет текущее положения после нанесения растровое изображение. Второй вариант предполагает спросить ядро OpenGL, где сейчас текущая позиция растра.

Первый подход требует, чтобы мы знали размеры символа. В то время как максимальная высота всегда постоянна для конкретного шрифта, ширина может варьироваться в некоторых шрифтах. К счастью GLUT предоставляет функцию, которая возвращает ширину символа. Функция glutBitmapWidth и её синтаксис выглядят следующим образом:

int glutBitmapWidth(void *font, int character);

Параметры:

*font – один из предварительно определены шрифтов в GLUT.
character – символ, ширину которого мы хотим знать.

Так, например, если мы хотим получить функцию, которая записывает строку с определенным количеством пикселей между символами можно записать так:

void renderSpacedBitmapString(
 
			float x,
			float y,
			int spacing,
			void *font,
			char *string) {
 
  char *c;
  int x1=x;
 
  for (c=string; *c != '\0'; c++) {
 
	glRasterPos2f(x1,y);
	glutBitmapCharacter(font, *c);
	x1 = x1 + glutBitmapWidth(font,*c) + spacing;
  }
}

Если мы хотим нарисовать вертикальный текст мы можем сделать следующим образом:

void renderVerticalBitmapString(
 
			float x,
			float y,
			int bitmapHeight,
			void *font,
			char *string) {
 
  char *c;
  int i;
 
  for (c=string,i=0; *c != '\0'; i++,c++) {
 
	glRasterPos2f(x, y+bitmapHeight*i);
	glutBitmapCharacter(font, *c);
  }
}

Переменную bitmapHeight можно легко вычислить, потому что мы знаем, что максимальная высота каждого шрифта, это последняя цифра в имени шрифта. Например, GLUT_BITMAP_TIMES_ROMAN_10 - 10 пикселей в высоту.

GLUT имеет еще одну функцию для растровых шрифтов, это glutBitMapLength и эта функция вычисляет длину строки в пикселях. Возвращаемое значение этой функции является сумма всех ширины всех символов в строке. Здесь идет синтаксис:

int glutBitmapLength(void *font, char *string);

*font - один из заранее определенных шрифтов в GLUT.
*string - имя строки, которую мы хотим знать длину в пикселях

Линейные шрифты

Линейный шрифт является шрифтом построенном по кривой. В отличие от растровых шрифтов, эти шрифты ведут себя как любой другой 3D объект, т.е. символы можно вращать, масштабировать, и перемещать.

В этом разделе мы представим функции GLUT поставить некоторые инсульта текст на экране. Нам для этого нужна одна функция glutStrokeCharacter. Синтаксис выглядит следующим образом:

void glutStrokeCharacter(void *font, int character);

Параметры:

*font - имя используемого шрифта.
character - то, что нужно создать, слово, символ, число, и т.д.

Шрифт опции:

- GLUT_STROKE_ROMAN;
- GLUT_STROKE_MONO_ROMAN (шрифт фиксированной ширины: 104,76 единиц в ширину).

Следующие строки текста пример вызова функции glutStrokeCharacter для вывода одного символа в текущей (локальной) системе координат:

glutStrokeCharacter(GLUT_STROKE_ROMAN,'3');

В отличие от растровых шрифтов, место для визуализации траектории кривой шрифта указано так же, как и для любого графического примитива, т.е. с использованием перемещения, поворотов и масштабирования.

Следующая функция создаёт текст, начиная с указанной позиции в локальных координатах пространства:

void renderStrokeFontString(
		float x,
		float y,
		float z,
		void *font,
		char *string) {
 
	char *c;
	glPushMatrix();
	glTranslatef(x, y,z);
 
	for (c=string; *c != '\0'; c++) {
		glutStrokeCharacter(font, *c);
	}
 
	glPopMatrix();
}

Примечание: GLUT использует линии для отрисовки линейных шрифтов, поэтому мы можем указать ширину линии с функцией glLineWidth. Эта функция принимает параметр, задающий ширину линии шрифта.

Что касается растровых шрифтов, GLUT предоставляет функцию, которая возвращает ширину символа. Функция glutStrokeWidth и её синтаксис выглядят следующим образом:

int glutStrokeWidth(void *font, int character);

Параметры:

*font - один из заранее определены шрифты в GLUT, см. выше.
character - символ, ширину которого мы хотим знать.

Как вывести количество кадров в секунду на экран?

Какая реальная скорость у вашего приложения? Иногда мы вносим небольшие изменения, и мы не можем быть уверены в том, что изменения не снизили производительность, т.е. как они повлияли на количество отображаемых кадров в секунду. Сейчас мы увидим, как мы можем использовать GLUT для подсчета количества кадров в секунду.

GLUT предоставляет функцию, которая позволяет запросить многие функции системы, одной из которых является количество миллисекунд от вызова glutInit. Функция glutGet и синтаксис выглядит следующим образом:

int glutGet(GLenum state);

Параметры:

state – определяет нужное нам значение.

Эта функция может быть использована для многих целей, например получения координат окна или получить глубину буфера в OpenGL. В этом разделе мы будем использовать его, чтобы получить число миллисекунд от вызова glutInit, использующий аргумент состояния GLUT_ELAPSED_TIME.

int time;
...
time = glutGet(GLUT_ELAPSED_TIME);

Хорошо, теперь мы собираемся использовать эту функцию для вычисления количество выводимых кадров в секунду нашего приложения. Частота кадров от кадра к кадру, то есть не все кадры, занимать то же время, чтобы сделать, потому что наше приложение не одинок.Операционная система берет свое, и камера может быть перемещение тем самым изменяя то, что оказывались. Поэтому мы собираемся, чтобы избежать вычисления частоты кадров в каждом кадре, а вместо этого мы собираемся вычислить его примерно раз в секунду. Это также обеспечивает более точный рисунок, так как его среднее значение.

Мы собираемся объявить три переменные: frame, time, and timebase, где timebase и frame инициализируются нулем.

int frame=0,time,timebase=0;

Смысл этих переменных:

frame – количество кадров в секунду
time – текущее число миллисекунд
timebase – время, когда мы в последний раз вычислили частоту кадров.

Следующий фрагмент кода, помещенные внутрь зарегистрированной функции, будет выполнять работу (см. ниже подробное описание):

        ...
	frame++;
	time=glutGet(GLUT_ELAPSED_TIME);
	if (time - timebase > 1000) {
		fps = frame*1000.0/(time-timebase));
	 	timebase = time;
		frame = 0;
	}
	...

Начнем с увеличением числа кадров, т.е. с увеличения переменной frame. После этого мы получим текущее время в переменную time. Затем мы сравним его с timebase, чтобы проверить, сколько времени прошло, то есть, если разница между time и timebase превышает 1000 миллисекунд. Если это не так, то мы пропускаем часть вычислений . Однако, когда разница больше чем одна секунда, мы сделаем все вычисления.

Разница между time и timebase дает нам число миллисекунд, прошедших с момента нашего последнего вычисления количества кадров в секунду. Разделив 1000 на количество миллисекунд даёт нам обратную величину количеству секунд, прошедших. Также надо умножить это значение на количество кадров, прошедших с момента последнего вычисления частоты кадров, и мы получаем количество кадров в секунду. Наконец мы сбрасываем timebase к текущему количеству миллисекунд, и frame к нулю.

Обратите внимание, что при запуске приложения timebase равна нулю, т.е. придется подождать одну секунду, чтобы получить первое значение. Это первые несколько значений, однако, могу вводить в заблуждение, потому что они включают в себя время, необходимое для инициализации окна. Если вы сделаете несколько тестов, то вы увидите, что эта величина значительно ниже, чем фактическая частота кадров.

Если вы хотите вывести количество кадров в секунду вы можете использовать следующий фрагмент кода:

        ...
	frame++;
	time=glutGet(GLUT_ELAPSED_TIME);
	if (time - timebase > 1000) {
		sprintf(s,"FPS:%4.2f",
			frame*1000.0/(time-timebase));
		timebase = time;
		frame = 0;
	}
	glColor3f(0.0f,1.0f,1.0f);
	glPushMatrix();
	glLoadIdentity();
	setOrthographicProjection();
	renderBitmapString(30,35,(void *)font,s);
	glPopMatrix();
	restorePerspectiveProjection();
 
	...

Итоговый код. Мы должны получить линейный текст над снеговиками и счётчик FPS на экране.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <glut.h>
 
// угол поворота камеры
float angle=0.0;
// координаты вектора направления движения камеры
float lx=0.0f, lz=-1.0f;
// XZ позиция камеры
float x=0.0f, z=5.0f;
//Ключи статуса камеры. Переменные инициализируются нулевыми значениями
//когда клавиши не нажаты
float deltaAngle = 0.0f;
float deltaMove = 0;
int xOrigin = -1;
 
// Определения констант для меню
#define RED 1
#define GREEN 2
#define BLUE 3
#define ORANGE 4
 
#define FILL 1
#define LINE 2
 
// идентификаторы меню
int fillMenu, fontMenu, mainMenu, colorMenu;
//цвет носа
float red = 1.0f, blue=0.5f, green=0.5f;
//размер снеговика
float scale = 1.0f;
// menu статус
int menuFlag = 0;
// шрифт по умолчанию
void *font = GLUT_STROKE_ROMAN;
// высота и ширина окна
int h,w;
// переменные для вычисления количества кадров в секунду
int frame;
long time, timebase;
char s[50];
 
void changeSize(int ww, int hh) {
	h = hh;
	w = ww;
	// предотвращение деления на ноль
	if (h == 0)
		h = 1;
	float ratio =  w * 1.0 / h;
	// используем матрицу проекции
	glMatrixMode(GL_PROJECTION);
	// обнуляем матрицу
	glLoadIdentity();
	// установить параметры вьюпорта
	glViewport(0, 0, w, h);
	// установить корректную перспективу
	gluPerspective(45.0f, ratio, 0.1f, 100.0f);
	// вернуться к матрице проекции
	glMatrixMode(GL_MODELVIEW);
}
 
void drawSnowMan() 
{
	glScalef(scale, scale, scale);
	glColor3f(1.0f, 1.0f, 1.0f);
	// тело снеговика
	glTranslatef(0.0f ,0.75f, 0.0f);
	glutSolidSphere(0.75f,20,20);
	// голова снеговика
	glTranslatef(0.0f, 1.0f, 0.0f);
	glutSolidSphere(0.25f,20,20);
	// глаза снеговика
	glPushMatrix();
	glColor3f(0.0f,0.0f,0.0f);
	glTranslatef(0.05f, 0.10f, 0.18f);
	glutSolidSphere(0.05f,10,10);
	glTranslatef(-0.1f, 0.0f, 0.0f);
	glutSolidSphere(0.05f,10,10);
	glPopMatrix();
	// нос снеговика
	glColor3f(red, green, blue);
	glRotatef(0.0f,1.0f, 0.0f, 0.0f);
	glutSolidCone(0.08f,0.5f,10,2);
	glColor3f(1.0f, 1.0f, 1.0f);
}
 
void renderBitmapString(
		float x,
		float y,
		float z,
		void *font,
		char *string) {
 
	char *c;
	glRasterPos3f(x, y,z);
	for (c=string; *c != '\0'; c++) {
		glutBitmapCharacter(font, *c);
	}
}
 
void renderStrokeFontString(
		float x,
		float y,
		float z,
		void *font,
		char *string) {  
 
	char *c;
	glPushMatrix();
	glTranslatef(x, y,z);
	glScalef(0.002f, 0.002f, 0.002f);
	for (c=string; *c != '\0'; c++) {
		glutStrokeCharacter(font, *c);
	}
	glPopMatrix();
}
 
void restorePerspectiveProjection() {
	glMatrixMode(GL_PROJECTION);
	//восстановить предыдущую матрицу проекции
	glPopMatrix();
	//вернуться в режим модели
	glMatrixMode(GL_MODELVIEW);
}
 
void setOrthographicProjection() {
	//выбрать режим проекции
	glMatrixMode(GL_PROJECTION);
	//Сохраняем предыдущую матрицу, которая содерж
	//параметры перспективной проекции
	glPushMatrix();
	//обнуляем матрицу
	glLoadIdentity();
	//устанавливаем 2D ортогональную проекцию
	gluOrtho2D(0, w, h, 0);
	//выбираем режим обзора модели
	glMatrixMode(GL_MODELVIEW);
}
 
void computePos(float deltaMove) {
	x += deltaMove * lx * 0.1f;
	z += deltaMove * lz * 0.1f;
}
 
void renderScene(void) {
	if (deltaMove)
		computePos(deltaMove);
 
	//очистить буфер цвета и глубины
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
	// обнулить трансформацию
	glLoadIdentity();
	// установить камеру
	gluLookAt(	x, 1.0f, z,
			x+lx, 1.0f,  z+lz,
			0.0f, 1.0f,  0.0f);
 
	// нарисуем "землю"
	glColor3f(0.9f, 0.9f, 0.9f);
	glBegin(GL_QUADS);
		glVertex3f(-100.0f, 0.0f, -100.0f);
		glVertex3f(-100.0f, 0.0f,  100.0f);
		glVertex3f( 100.0f, 0.0f,  100.0f);
		glVertex3f( 100.0f, 0.0f, -100.0f);
	glEnd();
 
	// Нарисуем 64 снеговика
	char number[4];
	for(int i = -4; i < 4; i++)
		for(int j=-4; j < 4; j++) {
			glPushMatrix();
			glTranslatef(i*10.0f, 0.0f, j * 10.0f);
			drawSnowMan();
			sprintf(number,"%d",(i+3)*8+(j+4));
			renderStrokeFontString(0.0f, 0.5f, 0.0f, (void *)font ,number);
			glPopMatrix();
		}
	// Код для вычисления кадров в секунду
	frame++;
 
	time=glutGet(GLUT_ELAPSED_TIME);
	if (time - timebase > 1000) {
		sprintf(s,"Informatika - FPS:%4.2f",
			frame*1000.0/(time-timebase));
		timebase = time;
		frame = 0;
	}
	//Код для отображения строки (кадров в секунду) с растровых шрифтов
	setOrthographicProjection();
	glPushMatrix();
	glLoadIdentity();
	renderBitmapString(5,30,0,GLUT_BITMAP_HELVETICA_18,s);
	glPopMatrix();
 
	restorePerspectiveProjection();
 
	glutSwapBuffers();
}
 
// -----------------------------------	//
//            клавиатура				//
// -----------------------------------	//
 
void processNormalKeys(unsigned char key, int xx, int yy) {
 
	switch (key) {
		case 27:
			glutDestroyMenu(mainMenu);
			glutDestroyMenu(fillMenu);
			glutDestroyMenu(colorMenu);
			glutDestroyMenu(fontMenu);
			exit(0);
			break;
	}
}
 
void pressKey(int key, int xx, int yy) {
 
	switch (key) {
		case GLUT_KEY_UP : deltaMove = 0.5f; break;
		case GLUT_KEY_DOWN : deltaMove = -0.5f; break;
	}
}
 
void releaseKey(int key, int x, int y) {
 
	switch (key) {
		case GLUT_KEY_UP :
		case GLUT_KEY_DOWN : deltaMove = 0;break;
	}
}
// -----------------------------------	//
//            функции мыши				//
// -----------------------------------	//
 
void mouseMove(int x, int y) 
{
	// только когда левая кнопка не активна
	if (xOrigin >= 0) 
	{
		// обновить deltaAngle
		deltaAngle = (x - xOrigin) * 0.001f;
		// обновить направление камеры
		lx = sin(angle + deltaAngle);
		lz = -cos(angle + deltaAngle);
	}
}
 
void mouseButton(int button, int state, int x, int y) 
{
	// только начало движение, если левая кнопка мыши нажата
	if (button == GLUT_LEFT_BUTTON) 
	{
		// когда кнопка отпущена
		if (state == GLUT_UP) 
		{
			angle += deltaAngle;
			xOrigin = -1;
		}
		else  
		{// state = GLUT_DOWN
			xOrigin = x;
		}
	}
}
 
// ------------------------------------//
//            меню  					//
// ------------------------------------//
 
void processMenuStatus(int status, int x, int y) {
 
	if (status == GLUT_MENU_IN_USE)
		menuFlag = 1;
	else
		menuFlag = 0;
}
 
void processMainMenu(int option) 
{
	//ничего здесь не делаем
	//все действия для подменю
}
 
void processFillMenu(int option) {
	switch (option) 
	{
		case FILL: glPolygonMode(GL_FRONT, GL_FILL); break;
		case LINE: glPolygonMode(GL_FRONT, GL_LINE); break;
	}
}
 
void processFontMenu(int option) {
 
	switch (option) {
		case 1: font = GLUT_STROKE_ROMAN;
			break;
		case 2: font = GLUT_STROKE_MONO_ROMAN;
			break;
	}
}
 
void processColorMenu(int option) {
 
	switch (option) {
		case RED :
			red = 1.0f;
			green = 0.0f;
			blue = 0.0f; break;
		case GREEN :
			red = 0.0f;
			green = 1.0f;
			blue = 0.0f; break;
		case BLUE :
			red = 0.0f;
			green = 0.0f;
			blue = 1.0f; break;
		case ORANGE :
			red = 1.0f;
			green = 0.5f;
			blue = 0.5f; break;
	}
}
 
void createPopupMenus() {
 
	fontMenu = glutCreateMenu(processFontMenu);
 
	glutAddMenuEntry("STROKE_ROMAN",1 );
	glutAddMenuEntry("STROKE_MONO_ROMAN",2 );
	fillMenu = glutCreateMenu(processFillMenu);
	glutAddMenuEntry("Fill",FILL);
	glutAddMenuEntry("Line",LINE);
	colorMenu = glutCreateMenu(processColorMenu);
	glutAddMenuEntry("Red",RED);
	glutAddMenuEntry("Blue",BLUE);
	glutAddMenuEntry("Green",GREEN);
	glutAddMenuEntry("Orange",ORANGE);
	mainMenu = glutCreateMenu(processMainMenu);
	glutAddSubMenu("Polygon Mode", fillMenu);
	glutAddSubMenu("Color", colorMenu);
	glutAddSubMenu("Font",fontMenu);
	// прикрепить меню к правой кнопке
	glutAttachMenu(GLUT_RIGHT_BUTTON);
	//статус активности меню
	glutMenuStatusFunc(processMenuStatus);
}
 
// ------------------------------------	//
//             main()					//
// -----------------------------------	//
 
int main(int argc, char **argv) {
 
	// инициализация Glut и создание окна
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(480,480);
	glutCreateWindow("Урок - 12");
	//регистрация
	glutDisplayFunc(renderScene);
	glutReshapeFunc(changeSize);
	glutIdleFunc(renderScene);
	glutIgnoreKeyRepeat(1);
	glutKeyboardFunc(processNormalKeys);
	glutSpecialFunc(pressKey);
	glutSpecialUpFunc(releaseKey);
	glutMouseFunc(mouseButton);
	glutMotionFunc(mouseMove);
	//OpenGL инициализация
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	//инициализация меню
	createPopupMenus();
	//главный цикл
	glutMainLoop();
 
	return 1;
}