Каталог решений - Основы компьютерной графики (Часть 2)

Основы компьютерной графики (Часть 2)

Основы компьютерной графики (Часть 2)

В наличии

Статья является продолжением публикации «Основы компьютерной графики».
Во второй части будут рассмотрены следующие темы:
1. Преобразования в трехмерном пространстве.
2. Ортографическая проекция трехмерного изображения на экран.
3. Определение, какой поверхностью (лицевой/задней) проецируется грань на экран.
4. Перспективная проекция.

Категория:

Описание

Основы компьютерной графики (часть 2)

Выводы из первой части

Во второй части мы будем использовать весь накопленный опыт первой части. Перечислим итоги первой части статьи:

  1. Выполнять операции преобразования можно с помощью умножения специально заполненных матриц 3х3.
  2. С помощью матриц 3х3 можно накапливать результаты операций преобразований.
  3. Используя несколько матриц, можно организовать вставку преобразования в любое место цепочки преобразований.

Переходим к 3D

На этом моменте многих ждет разочарование. Расчет координат в 3-м мерном пространстве не сильно отличается, от расчета координат в 2-мерном пространстве.
Матрица точки теперь выглядит так (x,y,z,1)
Матрица перемещения

Матрица масштабирования

где  X,Y,Z – коэффициенты масштабирования по соответствующим координатам

С вращением немного сложнее, ключевое слово «немного».  Для начала определим как расположены оси координат. Центр координат расположен в центре экрана (отображаемой области). Ось X – направлена вправо, ось Y – направлена вверх, ось Z – направлена перпендикулярно плоскости монитора в даль, в геометрии обычно обозначают «х» — хвост стрелы.
Надеюсь, многие поняли, что в первой части вы выполняли вращение в плоскости Y0X вокруг оси Z, т.е. у нас уже есть готовая матрица поворота вокруг оси Z.

Матрицы поворота вокруг осей X,Y выводятся подобным образом.
Матрица поворота вокруг оси X

Матрица поворота вокруг оси Y

Программный код для вычисления координат в 3-мерном пространстве не многим отличается от кода для 2-мерного пространства.

Ортографическая проекция

Координаты вычислили, но монитор плоский, маловероятно, но возможно, у кого-то и не плоский, но экран все равно плоский.  Как выводить 3-х мерное изображение на плоский экран?
Мы будем проецировать изображение на (плоскость Y0X). Для начала мы построим ортографическую проекцию – координаты x, y не будут изменятся в зависимости от координаты z. Такие проекции строят, как правило, САПР (системы автоматизированного проектирования), где важно сохранение пропорций и размеров, а не реалистичность.

Теперь у нас уже не квадрат а куб. Точек стало больше, отрезки превратились в ребра, и стало их соответственно тоже больше.

&НаКлиенте
Процедура Инициализация()
	Куб = Новый Структура("Матрицы,Ребра,Точки,Владелец");
	
	Куб.Владелец = Неопределено;
	
	Точки = Новый Массив();
	Точки.Добавить(ПолучитьТочку(-10, -10, -10));
	Точки.Добавить(ПолучитьТочку(-10, 10, -10));
	Точки.Добавить(ПолучитьТочку(10, 10, -10));
	Точки.Добавить(ПолучитьТочку(10, -10, -10));
	
	Точки.Добавить(ПолучитьТочку(-10, -10, 10));
	Точки.Добавить(ПолучитьТочку(-10, 10, 10));
	Точки.Добавить(ПолучитьТочку(10, 10, 10));
	Точки.Добавить(ПолучитьТочку(10, -10, 10));
	
	Куб.Точки = Точки;
	
	Ребра = Новый Массив();
	Ребра.Добавить(ПолучитьРебро(0,1));
	Ребра.Добавить(ПолучитьРебро(1,2));
	Ребра.Добавить(ПолучитьРебро(2,3));
	Ребра.Добавить(ПолучитьРебро(3,0));
	
	Ребра.Добавить(ПолучитьРебро(4,5));
	Ребра.Добавить(ПолучитьРебро(5,6));
	Ребра.Добавить(ПолучитьРебро(6,7));
	Ребра.Добавить(ПолучитьРебро(7,4));
	
	Ребра.Добавить(ПолучитьРебро(0,4));
	Ребра.Добавить(ПолучитьРебро(1,5));
	Ребра.Добавить(ПолучитьРебро(2,6));
	Ребра.Добавить(ПолучитьРебро(3,7));
	
	Куб.Ребра = Ребра;
	
	М = ПолучитьЕдиничнуюМатрицу4х4();
	М[0][0] = 3;
	М[1][1] = 3;
	М[2][2] = 3;
	
	М[3][2] = 20;	
	
	Куб.Матрицы = Новый Массив();
	Куб.Матрицы.Добавить(М);
	
	ВывестиОбъект(Куб);
КонецПроцедуры

Все преобразования выполняются схожим образом.

&НаКлиенте
Функция ПолучитьМатрицуПоворотаZ(Угол)
	М = ПолучитьЕдиничнуюМатрицу4х4();
	
	Pi = 3.1415926535897932;
	УголРадианы = Угол / 180 * Pi;
	Косинус = Cos(УголРадианы);
	Синус = Sin(УголРадианы);
	
	М[0][0] = Косинус;
	М[0][1] = -Синус;
	М[1][0] = Синус;
	М[1][1] = Косинус;

	Возврат М;
	
КонецФункции


&НаКлиенте
Процедура КомандаПоворотZ(Команда)
	// Вставить содержимое обработчика.
	М = ПолучитьМатрицуПоворотаZ(УголПоворота);
	
	Если МестоВставки = 0 Тогда
		УмножитьМатрицы4х4(М, Куб.Матрицы[0]);
		Куб.Матрицы[0] = М;		
	Иначе
		УмножитьМатрицы4х4(Куб.Матрицы[0], М);
	КонецЕсли;
	
	ВывестиОбъект(Куб);
	
КонецПроцедуры

Получение координат после всех преобразований:

&НаКлиенте
Функция ПолучитьМатрицуПреобразований(Объект)
	М = ПолучитьЕдиничнуюМатрицу4х4();
	
	Для Каждого Матрица Из Объект.Матрицы Цикл
		УмножитьМатрицы4х4(М, Матрица);		
	КонецЦикла;
	
	Если Объект.Владелец <> Неопределено Тогда
		Для Каждого Матрица Из Объект.Владелец.Матрицы Цикл
			УмножитьМатрицы4х4(М, Матрица);		
		КонецЦикла;		
	КонецЕсли;
	
	Возврат М;
КонецФункции

&НаКлиенте
Функция ПолучитьКоордиантыТочек(Объект, МатрицаПреобразований)
	рТочки = Новый Массив();
	
	Для Каждого Точка Из Объект.Точки Цикл
		рТочка = Новый Массив(4);
		рТочка[0] = Точка[0] * МатрицаПреобразований[0][0] + Точка[1] * МатрицаПреобразований[1][0] + Точка[2] * МатрицаПреобразований[2][0] + Точка[3] * МатрицаПреобразований[3][0];
		рТочка[1] = Точка[0] * МатрицаПреобразований[0][1] + Точка[1] * МатрицаПреобразований[1][1] + Точка[2] * МатрицаПреобразований[2][1] + Точка[3] * МатрицаПреобразований[3][1];
		рТочка[2] = Точка[0] * МатрицаПреобразований[0][2] + Точка[1] * МатрицаПреобразований[1][2] + Точка[2] * МатрицаПреобразований[2][2] + Точка[3] * МатрицаПреобразований[3][2];
		рТочка[3] = Точка[0] * МатрицаПреобразований[0][2] + Точка[1] * МатрицаПреобразований[1][2] + Точка[2] * МатрицаПреобразований[2][2] + Точка[3] * МатрицаПреобразований[3][3];
			
		рТочка[0] = рТочка[0] + 80;
		рТочка[1] = -рТочка[1] + 80;
		
		рТочки.Добавить(рТочка);		
	КонецЦикла;
	
	Возврат рТочки;
	
КонецФункции

&НаКлиенте
Процедура ОбновитьКоординатыРебер(Объект, Точки)
	Для Каждого Ребро Из Объект.Ребра Цикл
		Линия = Ребро[0];
		Точка1 = Точки[Ребро[1]];
		Точка2 = Точки[Ребро[2]];
		
		Линия.Лево = Точка1[0];
		Линия.Верх = Точка1[1];
		Линия.Ширина = Точка2[0] - Точка1[0];
		Линия.Высота = Точка2[1] - Точка1[1];		
	КонецЦикла;
КонецПроцедуры

Результат выглядит так.

Выделение невидимых граней куба

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

Для определения какой поверхностью грань повернута к экрану, воспользуемся следующим свойством:
Векторное произведение двух векторов
 в трехмерном евклидовом пространстве — вектор, перпендикулярный обоим исходным векторам. В нашем случае перпендикулярный грани. Координата Z как раз показывает в экран направлен вектор, либо наоборот.

Произведение векторов находится следующим образом:

Нас интересует только составляющая вектора z, которая у нас получилась равна ax* by – ay* bx .

Теперь у нашего куба появились грани, которые состоят из точек и ребер. Для грани важен порядок точек, я указал по порядку, в направлении хода часовой стрелки.

&НаКлиенте
Процедура Инициализация()
	Куб = Новый Структура("Матрицы,Ребра,Точки,Владелец,Грани");
	
	Куб.Грани = Новый Массив();
	Куб.Грани.Добавить(ПолучитьГрань(0,1,2,3,0,1,2,3));
	Куб.Грани.Добавить(ПолучитьГрань(1,5,6,2,9,5,10,1));
	Куб.Грани.Добавить(ПолучитьГрань(3,2,6,7,2,10,6,11));
	Куб.Грани.Добавить(ПолучитьГрань(0,3,7,4,3,11,2,8));
	Куб.Грани.Добавить(ПолучитьГрань(0,4,5,1,8,4,9,0));
	Куб.Грани.Добавить(ПолучитьГрань(4,7,6,5,7,6,5,1));	
	
КонецПроцедуры

&НаКлиенте
Функция ПолучитьГрань(Т0,Т1,Т2,Т3,Р0,Р1,Р2,Р3)
	Грань = Новый Структура("Точки,Ребра");
	Грань.Точки = Новый Массив(4);
	Грань.Точки[0] = Т0;
	Грань.Точки[1] = Т1;
	Грань.Точки[2] = Т2;
	Грань.Точки[3] = Т3;
	
	Грань.Ребра = Новый Массив(4);
	Грань.Ребра[0] = Р0;
	Грань.Ребра[1] = Р1;
	Грань.Ребра[2] = Р2;
	Грань.Ребра[3] = Р3;
	
	Возврат Грань;
КонецФункции

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

Возможно кому-то кажется, что было бы изящнее вместо куба взять более интересный объект, например, сделать объемную запись 1С. Во-первых на кубе — объяснять проще, а во-вторых — я уже даже на кубе запутался сам с ребрами, где-то в грани указал неправильный индекс ребра.

Изначально я хотел невидимые ребра выводить пунктирной линией, а видимые сплошной, но реализовать это оказалось не просто, т.к. свойство «тип линии», оказалось, доступным только для чтения. Остановился на том что видимые ребра – синие, невидимые – зеленные.

Добавляем определение видимости граней в код.

&НаКлиенте
Процедура ВывестиОбъект(Объект)
	МатрицаПреобразований = ПолучитьМатрицуПреобразований(Объект);
	Точки = ПолучитьКоордиантыТочек(Объект, МатрицаПреобразований);
	ОбновитьКоординатыРебер(Объект, Точки);
	
	ОпределитьВидимостьГраней(Объект, Точки);
	
КонецПроцедуры

Реализация

&НаКлиенте
Процедура ОпределитьВидимостьГраней(Объект, Точки)
	Для Каждого Ребро Из Объект.Ребра Цикл
		Линия = Ребро[0];
		Линия.ЦветЛинии = Новый Цвет(0,150,0);
	КонецЦикла;
	
	Для Каждого Грань Из Объект.Грани Цикл
		Точка0 = Точки[Грань.Точки[0]];
		Точка1 = Точки[Грань.Точки[1]];
		Точка2 = Точки[Грань.Точки[2]];
		
		Ах = Точка1[0] - Точка0[0];
		Ау = Точка1[1] - Точка0[1];
		
		Вх = Точка2[0] - Точка1[0];
		Ву = Точка2[1] - Точка1[1];
		
		Z = Ах * Ву - Ау * Вх;
		
		//Сообщить(Z);
		
		Если Z < 0 Тогда
			Для Каждого ИндексРебра Из Грань.Ребра Цикл
				Линия = Объект.Ребра[ИндексРебра][0];	
				Линия.ЦветЛинии = Новый Цвет(0,0,150);
			КонецЦикла;			
		КонецЕсли;
		
	КонецЦикла
	
КонецПроцедуры

Результат.

Не сразу понятно, что куб наклонен вниз.

Перспективная проекция

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

Перспективу можно описать следующим образом – чем дальше объект, тем он меньше. Насколько объект удален от нас, зависит от координаты Z. Соответственно при удалении координаты x,y будут смещаться в центр, т.е. уменьшаться.
Вычислять новые координаты будем по следующие формуле:
x’ = x * F / (F + z)
где F – фокусное расстояние.

&НаКлиенте
Функция ПолучитьКоордиантыТочек(Объект, МатрицаПреобразований)
	рТочки = Новый Массив();
	
	Для Каждого Точка Из Объект.Точки Цикл
		рТочка = Новый Массив(4);
		рТочка[0] = Точка[0] * МатрицаПреобразований[0][0] + Точка[1] * МатрицаПреобразований[1][0] + Точка[2] * МатрицаПреобразований[2][0] + Точка[3] * МатрицаПреобразований[3][0];
		рТочка[1] = Точка[0] * МатрицаПреобразований[0][1] + Точка[1] * МатрицаПреобразований[1][1] + Точка[2] * МатрицаПреобразований[2][1] + Точка[3] * МатрицаПреобразований[3][1];
		рТочка[2] = Точка[0] * МатрицаПреобразований[0][2] + Точка[1] * МатрицаПреобразований[1][2] + Точка[2] * МатрицаПреобразований[2][2] + Точка[3] * МатрицаПреобразований[3][2];
		рТочка[3] = Точка[0] * МатрицаПреобразований[0][2] + Точка[1] * МатрицаПреобразований[1][2] + Точка[2] * МатрицаПреобразований[2][2] + Точка[3] * МатрицаПреобразований[3][3];
			
		рТочка[2] = рТочка[2] + 40;		
		
		//УголОбзора = 90;
		//рТочка[0] = рТочка[0] * УголОбзора/рТочка[2] + 80;
		//рТочка[1] = рТочка[1] * УголОбзора/рТочка[2] + 80;
		
		ФокусноеРасстояние = 80;
		рТочка[0] = рТочка[0] * ФокусноеРасстояние / (ФокусноеРасстояние + рТочка[2]) + 80;
		рТочка[1] = рТочка[1] * ФокусноеРасстояние / (ФокусноеРасстояние + рТочка[2]) + 80;
		
				
		рТочки.Добавить(рТочка);		
	КонецЦикла;
	
	Возврат рТочки;
	
КонецФункции

Куб в начальной позиции.

Добавим преобразования поворот вокруг осей

Обработка тестировалась на программных файлах 1С:Предприятие 8.3 (8.3.12.1529), подключение — тонкий клиент.

 

has been added to your cart:
Оформление заказа