WebGL - методическое пособие

Введение

WebGL — стандарт, спецификация которого разрабатывается в настоящее время, предназначенная для отображения 3D графики в web браузерах. Данная технология позволяет внедрять аппаратно-ускоренную 3D графику в веб-страницы без необходимости использовать специальные плагины веб-браузера на любой платформе, поддерживающей OpenGL или OpenGL ES. Технически, это будет представлять собой привязку JavaScript-скриптов к функциям, определенным в библиотеках OpenGL ES 2.0, реализованную на уровне браузера.

EnergizeGL - JavaScript-библиотека, позволяющая работать с WebGL без знаний OpenGL и матричных преобразований.

В связи с тем, что на данный момент (16 ноября 2010 года) спецификация WebGL все еще является черновой, ведущие производители браузеров Google Chrome и Mozilla Firefox не включают поддержку этой технологии в стабильные ревизии. Для работы с этой технологией следует скачать последнюю бета-версию указанных браузеров: Chromium или Minefield.

Содержание


1. Размещение простейших примитивов на сцене
2. Создание сложных объектов: полигональная сетка
3. Текстуирование, UV-маппинг
4. Создание кривых поверхностей
5. Анимация

1. Размещение простейших примитивов на сцене

Прежде всего создадим html-файл, подключим к нему библиотеку EnergizeGL, создадим тег canvas, запустим EnergizeGL.

example0.html

Подпрограмма StartEnergizeGL принимает три параметра:

  • id тега canvas к которому нужно подключить WebGL,
  • режим работы (пустая строка или 'fillscreen'),
  • id тега div, внутрь которого будет скидываться лог работы библиотеки.

  • Таким образом запуск EnergizeGL в режиме разработки: StartEnergizeGL('appcanvas', '', 'applog');, в то время как запуск в полноэкранном режиме: StartEnergizeGL('appcanvas', 'fillscreen', ''); Подпрограмма запуска инициализирует все необходимое для работы с WebGL, а так же ищет в javascript коде функции setup() и draw(), после чего исполняет первую - однократно, а вторую - 60 раз в секунду.

    Теперь мы готовы рисовать простейшие примитивы, используя функцию draw(), однако, перед этим нам нужно сориентироваться в пространстве. Дело в том, что в WebGL используется левосторонняя система координат (смотри рисунок), в то время как камера строго зафиксированна в точке (0,0,0) и смотрит по направлению оси Z в отрицательную сторону. Таким образом, если нам нужно переместиться правее камеры - мы должны увеличивать координату X, левее - уменьшать X, выше - увеличивать Y, ниже - уменьшать Y, глубже (дальше) - уменьшать Z, а если нам вдруг понадобится размещать объекты за точкой обзора (за нашей спиной) - то увеличивать координату Z.

    Таким образом, чтобы нарисовать видимый примитив, сперва нам следует переместиться немного вперед, вдоль оси Z в отрицательную сторону. Добавим внутрь функции draw() код:

    example1/example1a.html
    function draw(){
    	translate(0, 0, -20);
    	cube();
    }

    В результате мы увидим куб со сторонами 1-1-1 сверху. Функция translate(x, y, z); перемещает координаты центра сцены на указанный вектор. Это значит, что если мы были в позиции (0, 0, -20) и использовали функцию translate(5, 0, 0); - мы переместили центр рисуемой сцены на вектор {+5, +0, +0} в координаты (5, 0, -20). Функция cube(); без параметров рисует простейший примитив - куб в центре сцены со сторонами 1-1-1. Функция cube(x, y, z, dx, dy, dz); рисует куб с центром в векторе {+x,+y,+z} относительно центра сцены и с параметрами: длинна - dx, ширина - dy, высота - dz. Оставим первый куб без изменений, переместим центр сцены правее и нарисуем куб с другими параметрами:

    example1/example1b.html
    function draw() {
    	translate(0, 0, -20);
    	cube();
    	translate(5, 0, 0);
    	cube(0, 0, 0, 1, 3, 2);
    }

    В результате получилось два объекта на сцене. Возникает желание посмотреть на них с другого ракурса, для этого у нас есть функция rotate(x, y, z);, которая изменяет ориентацию нашей сцены на указаные углы. Углы (x, y, z) указываются в радианах.

    example1/example1c.html
    function draw() {
    	translate(0, 0, -20);
    	rotate(1, 1, 0);
    	cube();
    	translate(5, 0, 0);
    	cube(0, 0, 0, 1, 3, 2);
    }

    Обратите внимание, что после поворота сцены на какой-либо угол, функция translate перестает перемещать вас на координаты, соответствующие левосторонней системе координат относительно камеры (например, второй объект поднялся выше). Дело в том, что вместе с поворотом сцены, поворачиваются и ее несущие XYZ-оси, по которым в дальнейшем и работает функция translate. Для того чтобы лучше понять происходящее, поперемещайте функцию rotate по коду, сравните разницу между результатами выполнения.

    Добавим на сцену еще парочку простейших примитивов: пирамиду и сферу.

    example1/example1d.html
    function draw() {
    	translate(0, 0, -20);
    	rotate(1, 1, 0);
    	cube();
    	translate(5, 0, 0);
    	cube(0, 0, 0, 1, 3, 2);
    	translate(-10, 0, 0);
    	sphere();
    	translate(5, 0, -5);
    	pyramid();
    }

    Функция sphere(); без параметров рисует сферу в центре текущей сцены с радиусом 1. Функция sphere(x, y, z, rx, ry, rz); рисует сферу, смещенную на вектор {+x,+y,+z} от центра сцены с радиусами по X, Y и Z - соответсвенно - rx, ry, rz. Функция pyramid(); без параметров рисует пирамиду в центре текущей сцены с высотой и шириной 1. Функция pyramid(x, y, z, длинна, высота, ширина); рисует пирамиду, смещенную на вектор {+x,+y,+z} от центра сцены с указанными длинной, высотой и шириной.

    Немного изменим параметры сферы и пирамиды.

    example1/example1e.html
    function draw() {
    	translate(0, 0, -20);
    	rotate(1, 1, 0);
    	cube();
    	translate(5, 0, 0);
    	cube(0, 0, 0, 1, 3, 2);
    	translate(-10, 0, 0);
    	sphere(0, 0, 0, 2, 2, 2);
    	translate(5, 0, -5);
    	pyramid(0, 0, 0, 2, 3, 2);
    }

    Окрашивать примитивы в разные цвета позволяет функция setColor(r, g, b, a);, задающая цвет для всех последующих после нее примитивов. Параметры r, g и b - RGB-составляющие цвета, параметр a - прозрачность. Параметры - нецелые числа в интервале [0..1]. Например, если вам нужно задать непрозрачный красный цвет, используйте setColor(1, 0, 0, 1);

    example1/example1f.html
    function draw() {
    	translate(0, 0, -20);
    	rotate(1, 1, 0);
    	
    	translate(0, 0, 0);
    	setColor(1, 0, 0, 1);
    	cube();
    
    	translate(5, 0, 0);
    	setColor(0, 1, 0, 1);
    	cube(0, 0, 0, 1, 3, 2);
    	
    	translate(-10, 0, 0);
    	setColor(0, 0, 1, 1);
    	sphere(0, 0, 0, 2, 2, 2);
    	
    	translate(5, 0, -5);
    	setColor(1, 0, 1, 1);
    	pyramid(0, 0, 0, 2, 3, 2);
    }

    Возникает желание осмотреть нашу сцену с разных сторон. Чтобы это сделать, можно менять параметры первых двух операторов - translate и rotate вручную, но очень хочется делать это динамически. Для этого нужно организовать управление положением камеры, например, с помощью мыши. На помощь приходят функции mouseDrag() и mouseWheel(), вызываемые библиотекой каждый раз, когда пользователь двигает мышкой с зажатой левой кнопкой по области тега canvas или крутит колесико мыши. Создадим глобальные переменные rotX (вращение по оси X), rotY (вращение по оси Y), zoom (удаленность), подставим их как параметры в первые две функции, а в функциях mouseDrag() и mouseWheel() будем их изменять с помощью глобальных переменных mouseX, lastMouseX, mouseY, lastMouseY, wheelSpeed, предоставляемых библиотекой EnergizeGL.

    example1/example1g.html
    rotX = 1;
    rotY = 1;
    zoom = -20;
    function draw() {
    	translate(0, 0, zoom);
    	rotate(rotX, rotY, 0);
    	
    	translate(0, 0, 0);
    	setColor(1, 0, 0, 1);
    	cube();
    	grid();
    
    	translate(5, 0, 0);
    	setColor(0, 1, 0, 1);
    	cube(0, 0, 0, 1, 3, 2);
    	
    	translate(-10, 0, 0);
    	setColor(0, 0, 1, 1);
    	sphere(0, 0, 0, 2, 2, 2);
    	
    	translate(5, 0, -5);
    	setColor(1, 0, 1, 1);
    	pyramid(0, 0, 0, 2, 3, 2);
    }
    function mouseDrag() {
    	rotY += (mouseX-lastMouseX)/100;
    	rotX += (mouseY-lastMouseY)/100;
    }
    function mouseWheel() {
    	zoom += wheelSpeed;
    }

    Обратите внимание, после рисования первого куба добавлено рисование мировой сетки с помощью функции grid();. Так как сетка рисуется сразу же после куба, она рисуется с тем же цветом и с теми же координатами, что и куб.

    Продолжаем работать с полученной сценой. Поступила задача повернуть второй примитив по осям X и Y на один радиан. Для выполнения этой задачи можно пойти двумя путями. Первый и не самый удачный, это: после перемещения в позицию второго примитива повернуть сцену на {+1; +1; +0}, нарисовать примитив, повернуть сцену обратно на {-1; -1; 0}, продолжить рисование остальных примитивов. Этот способ плох тем, что если понадобится поворот на нецелый градус, точный поворот в обратную сторону будет затруднен. На помощь приходят функции pushMatrix() и popMatrix(). Функция pushMatrix(); сохраняет матрицу текущей сцены (координаты, поворот, масштабирование) в стеке матриц. Функция popMatrix(); восстанавливает последнюю сохраненную матрицу сцены из стека матриц. Используя эти две функции можно решить задачу следующим образом: сохранить матрицу сцены, переместиться и повернуть сцену, нарисовать примитив, восстановить последнюю сохраненную матрицу сцены.

    example1/example1h.html
    rotX = 1;
    rotY = 1;
    zoom = -20;
    function draw() {
    	translate(0, 0, zoom);
    	rotate(rotX, rotY, 0);
    	
    	translate(0, 0, 0);
    	setColor(1, 0, 0, 1);
    	cube();
    	grid();
    	
    	pushMatrix();
    		translate(5, 0, 0);
    		rotate(1, 1, 0);
    		setColor(0, 1, 0, 1);
    		cube(0, 0, 0, 1, 3, 2);
    	popMatrix();
    	
    	translate(-5, 0, 0);
    	setColor(0, 0, 1, 1);
    	sphere(0, 0, 0, 2, 2, 2);
    	
    	translate(5, 0, -5);
    	setColor(1, 0, 1, 1);
    	pyramid(0, 0, 0, 2, 3, 2);
    }
    function mouseDrag() {
    	rotY += (mouseX-lastMouseX)/100;
    	rotX += (mouseY-lastMouseY)/100;
    }
    function mouseWheel() {
    	zoom += wheelSpeed;
    }

    Сохранять матрицу сцены можно неограниченное количество раз.

    Варианты заданий

    1. Нарисуйте снеговика, состоящего из пяти сфер: три на туловище, две на руки, и из пирамиды, символизирующей шапку на голове. Пирамида должна быть наклонена.
    2. Нарисуйте человечка из пяти параллелипипедов и сферы: торс, две ноги, две руки, голова. Руки должны быть наклонены вниз.
    3. Нарисуйте коттеджный поселок, состоящий из шести домиков: по три напротив дороги. Домик состоит из параллелипипеда (основа) и пирамиды (крыша). Дорога изображается тонким параллелепипедом.

    2. Создание сложных объектов: полигональная сетка

    Практически все трехмерные объекты в WebGL описываются набором точек-вершин, которые в свою очередь описывают плоские треугольники, из которых и состоит объект. Точки называются вертексами (vertex), а треугольники - сторонами (face). Линии между точками, разделяющие стороны, называются гранями (edge); если несколько сторон лежат в одной плоскости и смежны, они логически объединяются в полигон (polygon); и, наконец, смежные полигоны объединяются в поверхности (surface).

    В большинстве случаев в конечном счете визуализируются лишь стороны-треугольники (faces), логически объедененные в полигоны (polygons), в случае отладки или разработки иногда визуализируются так же вершины и грани. В этой главе рассказывается как создать полигональную сетку (mesh), описывая ее координатами вершин треугольников, и отобразить ее на сцене. На этот раз нам понадобится так же функция setup(), вызываемая один раз в процессе запуска библиотеки EnergizeGL. В ней мы опишем нашу полигональную сетку, загрузим ее в память и будем ее в дальшейшем использовать в функции рисования draw().

    Функция startMesh(name); создает полигональную сетку (mesh) в памяти, обращаться в дальнейшем к которой можно будет по указанному имени. Все последующие вызовы функции addVertex будут относиться к указанной новой полигональной сетке (mesh).
    Функция addVertex(x, y, z); добавляет к полигональной сетке (mesh) вершину (vertex) с указанными координатами.
    Функция endMesh(); завершает создание полигональной сетки.

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

    Функция mesh(name, method); распологает созданную ранее полигональную сетку (mesh) на текущей сцене (позиция, поворот, масштабирование). Как правило эта функция вызывается внутри функции draw(). Параметры: name - имя полигональной сетки, которую требуется разместить на сцене; method - метод отрисовки полигональной сетки. В следующей главе будут подробней рассмотрены методы отрисовки, в этой главе мы будем использовать метод TRIANGLES, это означает что полигональная сетка формируется из отдельных треугольников, вершины для которых сгруппированы тройками в списке вершин (vertex list) полигональной сетки (mesh).

    example2/example2a.html
    function setup() {
    	startMesh('mesh1');
    		addVertex( 0, 0, 0); 
    		addVertex( 1, 0, 0); 
    		addVertex( 0, 0, 1); 
    	endMesh();
    }
     
    var zoom = -5;
    var rotX = 23, rotY = 100;
    function draw() {
    	translate(0, 0, zoom);
    	rotateY(rotY/100);
    	rotateX(rotX/100);
    
    	mesh('mesh1', TRIANGLES);
    	
    	setColor(0.2); grid(); noColor();
    }
    

    В примере 2а я создал полигональную сетку из трех вершин и, соответсвенно, одной стороны, автоматически сформированной из этой тройки вершин, а затем разместил ее на сцене. Вершины расположены в координатах ( 0, 0, 0), ( 1, 0, 0), ( 0, 0, 1). Все три вершины Y=0, в результате получается плоский треугольник, расположенный в плоскости XZ перпендикулярно оси Y. Попробуйте поизменять координаты в примере, понаблюдайте за перемещением вершин.

    Дорисуем треугольник до квадрата, добавив еще один треугольник в нашу полигональную сетку. Для этого добавим описание еще трех вершин.

    example2/example2b.html
    function setup() {
    	startMesh('mesh1');
    		// face 1
    		addVertex( 0, 0, 0); 
    		addVertex( 1, 0, 0); 
    		addVertex( 0, 0, 1); 
    		// face 2
    		addVertex( 1, 0, 1); 
    		addVertex( 1, 0, 0); 
    		addVertex( 0, 0, 1); 
    	endMesh();
    }
    

    Обратите внимание на то, что в описании вершин второго треугольника вторая и третья вершины совпадают с предыдущим треугольником, и лишь первая вершина отлична. В результате получились две стороны (два треугольника) в одной плоскости со смежной гранью (1,0,0)--(0,0,1). Логически они объединяются в полигон.

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

    example2/example2c.html
    function setup() {
    	startMesh('mesh1');
    		// face 1
    		addVertex( 0, 0, 0); 
    		addVertex( 1, 0, 0); 
    		addVertex( 0, 0, 1); 
    		// face 2
    		addVertex( 1, 0, 1); 
    		addVertex( 1, 0, 0); 
    		addVertex( 0, 0, 1); 
    		// face 3
    		addVertex( 0, 1, 0); 
    		addVertex( 1, 1, 0); 
    		addVertex( 0, 1, 1); 
    		// face 4      
    		addVertex( 1, 1, 1); 
    		addVertex( 1, 1, 0); 
    		addVertex( 0, 1, 1); 
    	endMesh();
    }
    

    В результате получилась полигональная сетка с 12тью вершинами, 12тью гранями, 4мя сторонами и 2мя полигонами.

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

    example2/example2d.html
    function setup() {
    	startMesh('mesh1');
    		// face 1
    		addVertex( 0, 0, 0); 
    		addVertex( 1, 0, 0); 
    		addVertex( 0, 0, 1); 
    		// face 2
    		addVertex( 1, 0, 1); 
    		addVertex( 1, 0, 0); 
    		addVertex( 0, 0, 1); 
    		// face 3
    		addVertex( 0, 1, 0); 
    		addVertex( 1, 1, 0); 
    		addVertex( 0, 1, 1); 
    		// face 4     
    		addVertex( 1, 1, 1); 
    		addVertex( 1, 1, 0); 
    		addVertex( 0, 1, 1); 
    		// face 5
    		addVertex( 0, 0, 1); 
    		addVertex( 1, 0, 1); 
    		addVertex(.5,.5,1.5); 
    		// face 6
    		addVertex( 0, 1, 1); 
    		addVertex( 1, 1, 1); 
    		addVertex(.5,.5,1.5); 
    	endMesh();
    }
    

    В результате получилась полигональная сетка с 18тью вершинами, 18тью гранями, 6тью сторонами и 4мя полигонами. Визуально это похоже на каркас будущего самолета: пол, потолок, носовая кабина. Можно продолжить дополнять нашу полигональную сетку деталями.

    Варианты заданий

    Создайте одну полигональную сетку, моделирующую указанную фигуру и разместите ее на сцене.
    1. 2. 3.

    3. Текстуирование, UV-маппинг

    Неотъемлемой частью трехмерного моделирования является текстуирование, то есть - процесс описания наложения текстуры на трехмерный объект. Для того, чтобы загрузить текстуру в память мы будем использовать функции loadTexture(name, path) и startLoading(). Для того, чтобы назначить загруженную текстуру каким-либо объектам используются функции useTexture(name) и noTexture(). Параметры: name - имя для текстуры (словно текстура - новая переменная), path - путь к текстуре. Стороны у загружаемых текстур должны быть кратны степеням двойки, т.е. 2, 4, 8, 16, 32... 512, 1024 и так далее.

    example3/example3a.html
    function setup() {
    	loadTexture('texture1', 'texture1.png');
    	startLoading();
    }
     
    var zoom = -30;
    var rotX = 23, rotY = 50;
    function draw() {
    	translate(0, 0, zoom);
    	rotate(rotX/100, rotY/100, 0);
    	
    	useTexture('texture1');
    		cube(0, 0, 0, 1, 3, 2);
    		sphere(-5, 0, 0, 2, 2, 2);
    		pyramid(0, 0, 5, 2, 3, 2);	
    	noTexture();
    	
    	setColor(0.2); grid(); noColor();
    }
    

    Давайте рассмотрим полученную сцену внимательней. Текстура наложилась шесть раз на парралелепипед, по разу на каждую сторону. Текстура наложилась один раз на сферу, "размазавшись" по ней. Текстура на пирамиду визуально не наложилась, фактически, по пирамиде был лишь размазан первый (оранжевый) пиксел из текстуры. Дело в том, что стандартные примитивы библиотеки на самом деле так же состоят из полигональной сетки, но, в полигональных сетках парралелепипеда и сферах - указаны UV-координаты текстуирования, а в полигональной сетке пирамиды - нет. Не существует общепринятого представления как наложить квадратную текстуру на пирамиду, но мы можем выйти из этого затруднения, создав собственную полигональную сетку в форме пирамиды и указав для каждой вершины UV-координаты текстуры.

    UV-преобразование или развёртка в трёхмерной графике (англ. UV map) — соответствие между координатами на поверхности трёхмерного объекта (X, Y, Z) и координатами на текстуре (U, V). Координаты U и V обычно изменяются от 0 до 1, 0 соответствует началу рисунка по оси U или V, а 1 - концу рисунка.

    С помощью функции addTextureCoord( U, V); расположенной между функциями startMesh(...) и endMesh() можно задать UV-коорданаты текстуры для каждой точки (vertex) полигональной сетки.

    example3/example3b.html
    function setup() {
    	loadTexture('texture1', 'texture1.png');
    	startLoading();
    	startMesh('mesh1');
    		// face 1                                   
    		addVertex( 0, 0, 0); addTextureCoord( 0, 0);
    		addVertex( 1, 0, 0); addTextureCoord( 1, 0);
    		addVertex( 0, 1, 0); addTextureCoord( 0, 1);
    		// face 2
    		addVertex( 1, 0, 0);
    		addVertex( 0, 1, 0);
    		addVertex( 1, 1, 0);
    	endMesh();
    }
     
    var zoom = -5;
    var rotX = 23, rotY = 0;
    function draw() {
    	translate(0, 0, zoom);
    	rotate(rotX/100, rotY/100, 0);
    	
    	useTexture('texture1');
    		mesh('mesh1', TRIANGLES);
    	noTexture();
    	
    	setColor(0.2); grid(); noColor();
    }

    Мы создали полигон из двух треугольников и для первого треугольника описали UV-координаты, сопоставив их с каждой вершиной. В результате описаный треугольник (0,0)-(1,0)-(0,1) из текстуры наложился на первый треугольник.

    Продолжим текстуирование и наложим на следующий треугольник не эти же текстурные координаты ((0,0)-(1,0)-(0,1)), а другую треугольную половину текстуры.

    example3/example3c.html
    function setup() {
    	loadTexture('texture1', 'texture1.png');
    	startLoading();
    	startMesh('mesh1');
    		// face 1
    		addVertex( 0, 0, 0);  addTextureCoord( 0, 0);
    		addVertex( 1, 0, 0);  addTextureCoord( 1, 0);
    		addVertex( 0, 1, 0);  addTextureCoord( 0, 1);
    		// face 2
    		addVertex( 1, 0, 0);  addTextureCoord( 1, 0);
    		addVertex( 0, 1, 0);  addTextureCoord( 0, 1);
    		addVertex( 1, 1, 0);  addTextureCoord( 1, 1);
    	endMesh();
    }

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

    В качестве UV-координат можно указывать не только целые числа 0-1. Можно использовать дробные числа, тогда текстура будет положена на треугольник неполностью по указанному краю. Можно использовать числа больше еденицы или даже меньше нуля, тогда текстура будет наложена на поверхность несколько раз. Отмасштабируем нашу текстуру: в первом треугольнике положим текстуру в два раза плотнее по оси X (U=[0..2]), а во втором - в два раза реже по оси Y (V=[0..0.5]).

    example3/example3d.html
    function setup() {
    	loadTexture('texture1', 'texture1.png');
    	startLoading();
    	startMesh('mesh1');
    		// face 1
    		addVertex( 0, 0, 0);  addTextureCoord( 0, 0);
    		addVertex( 1, 0, 0);  addTextureCoord( 2, 0);
    		addVertex( 0, 1, 0);  addTextureCoord( 0, 1);
    		// face 2
    		addVertex( 1, 0, 0);  addTextureCoord( 1, 0);
    		addVertex( 0, 1, 0);  addTextureCoord( 0,.5);
    		addVertex( 1, 1, 0);  addTextureCoord( 1,.5);
    	endMesh();
    }

    Зачастую стоит задача растянуть одну текстуру равномерно на несколько связанных полигонов. Для демонстрации я создам еще один полигон под небольшим углом к предыдущему и равномерно распределю текстуру: по первому полигону U=[0..0.5], по второму полигону U=[0.5..1], и по обоим V=[0..1].

    example3/example3e.html
    function setup() {
    	loadTexture('texture1', 'texture1.png');
    	startLoading();
    	startMesh('mesh1');
    		// face 1
    		addVertex( 0, 0, 0);  addTextureCoord( 0, 0);
    		addVertex( 1, 0, 0);  addTextureCoord(.5, 0);
    		addVertex( 0, 1, 0);  addTextureCoord( 0, 1);
    		// face 2                               
    		addVertex( 1, 0, 0);  addTextureCoord(.5, 0);
    		addVertex( 0, 1, 0);  addTextureCoord( 0, 1);
    		addVertex( 1, 1, 0);  addTextureCoord(.5, 1);
    		// face 3                               
    		addVertex( 1, 1, 0);  addTextureCoord(.5, 0);
    		addVertex( 1, 1, 1);  addTextureCoord( 1, 0);
    		addVertex( 1, 0, 0);  addTextureCoord(.5, 1);
    		// face 4                               
    		addVertex( 1, 1, 1);  addTextureCoord( 1, 0);
    		addVertex( 1, 0, 0);  addTextureCoord(.5, 1);
    		addVertex( 1, 0, 1);  addTextureCoord( 1, 1);
    	endMesh();
    }

    В результате первая половина текстуры наложилась на первый полигон, а вторая - на второй.

    Варианты заданий

    1. Создайте кубическую полигональную сетку и наложите на нее текстуру игральной кости.
    2. Создайте пирамидаидальную полигональную сетку и реалистично наложите на нее текстуру из кирпичей.

    4. Создание кривых поверхностей

    Для имитации кривых поверхностей используют сложные полигональные сетки. Так, например, в трехмерном виде цилиндр отображается с помощью последовательных плоских прямоугольников, связанных по окружности. Степень кривизны и релеастичность зависят от сегментации цилиндра: чем больше сегментов в составе цилиндра, тем мягче углы между сегментами. Вак правило хватает 32х сегментов для успешного оптического обмана. В этой главе я создам две функции, одна будет создавать полигональную сетку цилиндра (без дна), вторая - конуса (без дна).

    Полигональные сетки могут быть отображены по разному на сцене. В конечном счете полигональная сетка состоит из последовательного индексированного массива вершин, а в каком виде этот массив вывести на экран - решает уже пользователь. Есть несколько вариантов вывода: вершины (POINTS), линии (LINES), треугольники. Выводы треугольников в свою очередь делятся на три режима:

  • 1) обычный вывод, когда вершины группируются в тройки, описывающие треугольники. Этот режим мы уже использовали ранее (TRIANGLES);
  • 2) TRIANGLE_STRIP - особый режим вывода. Первый треугольник формируется из первых трех вершин. Все последующие треугольники формируются из каждой новой последующей вершины плюс две предыдущие. Таким образом, если наша полигональная сетка состоит из 6ти вершин, будет создано 4ре прямоугольника. Такой режим вывода очень удобен, если мы планируем создать связанную поверхность: в обычном режиме (TRIANGLES) нам пришлось бы множество раз вводить общие точки для нескольких связанных полигонов. Стоит отметить, что этот режим неподходит для куба, потому что в кубе невозможно создать неизбыточную связанную сетку, зато этот режим очень хорошо подходит для создания цилиндра.
  • 3) TRIANGLE_FAN - особый режим вывода. Первый треугольник формируется из первых трех вершин. Каждый последующий треугольник формируется из каждой новой последующей вершины, плюс одна последняя вершина, плюс всегда самая первая вершина. Этот режим идеально подходит для создания конуса.
  • Я создам функцию cylinder(name, radius, height, segments), которая будет создавать полигональную сетку с заданым именем и остальными параметрами. Основные соображения в этой функции это: 1) первым делом создать две первые "опорные точки"; 2) в последующем создавать по паре точек снизу и сверху цилиндра, с рассчетом на то, что цилиндр будет выводиться режимом TRIANGLE_STRIP, а точки автоматически будут линковаться на предыдущие две; 3) координаты точек по кругу цилиндра рассчитываются с помощью математических функций и зависят от радиуса цилиндра и количества желаемых сегментов.

    example4/example4a.html
    function cylinder(name, radius, height, segments){
    	startMesh(name);
    		z = height;
    		addVertex( radius, 0, 0); addTextureCoord( 1, 0);
    		addVertex( radius, 0, z); addTextureCoord( 1, 1);
    		for (i=0; i<=segments; i++){
    			x = Math.cos(i*2*Math.PI/segments)*radius;
    			y = Math.sin(i*2*Math.PI/segments)*radius;
    			u = i / segments;
    			addVertex( x, y, 0); addTextureCoord( u, 0);
    			addVertex( x, y, z); addTextureCoord( u, 1);
    		}
    	endMesh();
    }
    function setup() {
    	loadTexture('texture1', 'texture1.png');
    	startLoading();
    	cylinder('mesh1', 7, 13, 32);
    }
    
    var zoom = -30;
    var rotX = -23, rotY = 100;
    function draw() {
    	translate(0, 0, zoom);
    	rotate(rotX/100, rotY/100, 0);
    	
    	useTexture('texture1');
    		mesh('mesh1', TRIANGLE_STRIP);
    	noTexture();
    }

    Конус строится еще проще. Я создам функцию cone(name, radius, height, segments), которая будет создавать полигональную сетку конуса с заданым именем и остальными параметрами. Сетка заполняется точками на основе следующих соображений: 1) первым делом нужно создать опорные первые точки, одну - в центре наверху конуса, вторую - у основания на краю; 2) в дальнейшем создавать точки по кругу, с рассчетом на то, что выводиться они будут в режиме TRIANGLE_FAN, автоматически линкуясь на предыдущую точку и на самую первую, опорную, в вершине конуса; 3) координаты точек по кругу конуса рассчитываются с помощью математических функций и зависят от радиуса конуса и количества желаемых сегментов.

    example4/example4b.html
    function cone(name, radius, height, segments){
    	startMesh(name);
    		addVertex( 0, 0, height); addTextureCoord( 1, 1);
    		addVertex( radius, 0, 0); addTextureCoord( 0, 0);
    		for (i=0; i<=segments; i++){
    			x = Math.cos(i*2*Math.PI/segments)*radius;
    			y = Math.sin(i*2*Math.PI/segments)*radius;
    			u = i / segments;
    			addVertex( x, y, 0); addTextureCoord( u, 0);
    		}
    	endMesh();
    }
    function setup() {
    	loadTexture('texture1', 'texture1.png');
    	startLoading();
    	cone('mesh2', 7, 13, 32);
    }
    
    var zoom = -30;
    var rotX = -23, rotY = 100;
    function draw() {
    	translate(0, 0, zoom);
    	rotate(rotX/100, rotY/100, 0);
    	
    	useTexture('texture1');
    		mesh('mesh2', TRIANGLE_FAN);
    	noTexture();
    }

    Варианты заданий.

    Создайте набор полигональных сеток, моделирующих указанную фигуру и разместите их на сцене.
    1. Монета, состоящая из 32х сегментов по радиусу. На лицевой стороне должна быть наложена текстура как указано на рисунке.
    2. Пирог, состоящий из 8ми сегментов по радиусу. Один сегмент должен отсутствовать. На все полигоны должна быть наложена текстура как указано на рисунке.

    5. Анимация

    Анимирование в общем случае реализуется с помощью последовательного бесконечного вызова специальной функции. Например, создана функция animate(), которая на каких то внутренних критериях а так же на внешних глобальных переменных принимает решения по изменению этих переменных. Эти переменные в свою очередь используются в функции draw(), а их изменение от вызова к вызову функции animate() порождает анимацию. Обе функции вызываются раз в 15мс например, для этого используется встроенная JavaScript функция: setInterval(animate, 15); setInterval(draw, 15);

    В случае работы с библиотекой EnergizeGL все несколько проще: при инициализации библиотеки функция draw() уже входит в бесконечный цикл вызовов, она вызывается по умолчанию 60 раз в секунду, обеспечивая производительность 60fps (частоту вызовов можно изменить с помощью библиотечной функции fps(fps)), кроме этого библиотека обеспечивает нас внешней глобальной переменной frame в которой содержится номер текущего проигрываемого кадра. Таким образом анимацию можно определить прямо внутри функции draw() опираясь на номер текущего кадра.

    example5/example5a.html
    var zoom = -10, rotX = 23, rotY = 100;
    function draw() {
    	a = frame/30;
    	
    	translate(0, 0, zoom);
    	rotate(rotX/100, rotY/100, 0);
    	setColor(.2); grid(); 
    	
    	pushMatrix();
    		translate(0, 2, 0);
    		rotate(a, 0, 0);
    		useTexture('texture1');
    			cube();
    		noTexture();
    	popMatrix();
    }
    ...
    StartEnergizeGL('appcanvas', '', 'applog'); fps(30);
    

    В результате рисуемый куб завертелся. Завертелся он вокруг себя по оси X на значение переменной a, изменяющейся по времени в зависимости от текущего кадра. В следующем примере я закручу куб не вокруг себя, а вокруг центра мировой сетки, для этого я просто поменяю местами вызовы функции translate и rotate. Так как поворот рисуемой сцены будет происходить до перемещения, то само перемещение будет учитывать новый угол поворота сцены и перемещать по новой оси Y (повернутой на угол) на +2 от центра сцены.

    example5/example5b.html
    var zoom = -10, rotX = 23, rotY = 100;
    function draw() {
    	a = frame/30;
    	
    	translate(0, 0, zoom);
    	rotate(rotX/100, rotY/100, 0);
    	setColor(.2); grid(); 
    	
    	pushMatrix();
    		rotate(a, 0, 0);
    		translate(0, 2, 0);
    		useTexture('texture1');
    			cube();
    		noTexture();
    	popMatrix();
    }

    Наиболее легким способом вычисления циклического изменения координаты объекта относительно бесконечно возрастающего номера кадра является математический синус или косинус. Например, конструкция b = 5*Math.sin(frame/30); предоставит мне переменную b, переодически изменяющуюся по времени в промежутке [-5..+5]. Я назначу смещение куба на переменную b, в результате получу куб, перемещающийся из стороны в сторону.

    example5/example5c.html
    var zoom = -10, rotX = 23, rotY = 100;
    function draw() {
    	a = frame/30;
    	b = 5*Math.sin(frame/30);
    	
    	translate(0, 0, zoom);
    	rotate(rotX/100, rotY/100, 0);
    	setColor(.2); grid(); 
    	
    	pushMatrix();
    		translate(b, 0, 0);
    		rotate(0, 0, 0);
    		useTexture('texture1');
    			cube();
    		noTexture();
    	popMatrix();
    }

    Варианты заданий

    1. Создайте сцену из неподвижного стола и катающегося по нему шарика. Шарик и стол должны иметь текстуру. Шарик должен кататься по кругу.
    2. Создайте сцену из неподвижного стола и перемещающегося по нему куба. Куб и стол должны иметь текстуру. Куб должен перемещаться по квадратному пути.

    Распределение заданий

    Вариант
    Задание 01020304050607080910111213141516
    1. Размещение простейших примитивов на сцене 1231231231231231
    2. Создание сложных объектов: полигональная сетка 1112223331112223
    3. Текстуирование, UV-маппинг 1111111112222222
    4. Создание кривых поверхностей 2121212121212121
    5. Анимация 2211221122112211
    Сайт управляется системой uCoz