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

Вход на сайт

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

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

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

Всем у кого не работает. файл wizard.script Ещё одно упоминание Glut32 в строке "if (!VerifyLibFile(dir_nomacro_lib, _T("glut32"), _T("GLUT's"))) return false;" меняем на "if (!VerifyLibFile(dir_nomacro_lib, _T("freeglut"), _T("GLUT's"))) return...
Не получается, емаё
огромное спасибо за подробное объяснение про 3д графику на питоне, в интернете очень мало подобной информации
dobryj den, popytalas otkryt prikreplionnyj fail ctoby posmotret kak rabotaet, no mne ego ne pokazyvaet vydajet osibku. Pochemu?
Очень интересно! ии сайт крутой жалко что умирает(

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

Рейтинг@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;
}