VRML - с чего начать

Создание VRML-сцены с ноля.
01.24.2024

VRML - это...

VRML - это язык моделирования виртуальной реальности (Virtual Reality Modeling Language).
Спецификация VRML97 (ISO/IEC 14772-1:1997 и ISO/IEC 14772-2:2004) на сайте Web3D-консорциума.

Там же ZIP-версия спецификации.

Геометрия

VRML-файл начинается с первой строчки:
#VRML V2.0 utf8
Это заголовок, с которого должен начинаться любой VRML-файл, причём с первой позиции первой строки.
#VRML V2.0 utf8

Shape {}
		
						
VRML описывает виртуальную реальность в терминах объектов. Shape - это объект описывающий некую геометрию.
#VRML V2.0 utf8

Shape {
  geometry Cone {}
}
		
						
Получился объект с геометрией Cone - конус.
Вы можете навигировать в сцене левой кнопкой мыши и колёсиком. Всё честно - это 3D.

Внешний вид

Надо отметить, что конус получился какой-то невыразительный... "плоский". Это произошло от того, что мы определили только геометрию, и не задали оптические свойства объекта.
Исправим этот недостаток:
#VRML V2.0 utf8

Shape {
  geometry Cone {}
  appearance Appearance {
    material Material {
      diffuseColor 0 0 1
    }
  }
}
						
В корне сцены находится объект Shape, у него есть два свойства (поля) geometry - геометрия и appearance - внешний вид.
Что бы задать геометрию, полю geometry присвоено значение объекта Cone.
Что бы задать внешний вид, полю appearance присвоено значение объекта Appearance.
Объект Appearance обладает собственными полями, мы используем одно из них - material.
Ему присвоено значение объекта Material, и уже в нём мы используем поле diffuseColor, значение которого и определяет цвет конуса.
Цвет задаётся как комбинация трёх составляющих: красной (0), зелёной(0) и синей(1).

Анимация

Теперь анимируем конус. Для этого надо:
  1. создать новый объект, отвечающий за геометрические трансформации;
  2. присвоить этому объекту имя;
  3. создать объект - генератор событий и поименовать его;
  4. создать объект - описывающий траекторию и поименовать его;
  5. и установить связи между этими объектами;

Шаг 1:

#VRML V2.0 utf8

Transform {
  children [
    Shape {
      geometry Cone {}
      appearance Appearance {
        material Material {
          diffuseColor 0 0 1
        }
      }
    }
	]
}
						
У объекта Shape нет полей, позволяющих менять его положение в пространстве, поэтому приходиться надстраивать над ним специальный объект Transform. Его можно вращать, двигать и масштабировать, а вместе с ним и конус.

Шаг 2:

#VRML V2.0 utf8

DEF main_TR Transform {
  children [
    Shape {
      geometry Cone {}
      appearance Appearance {
        material Material {
          diffuseColor 0 0 1
        }
      }
    }
  ]
}
						
При помощи DEF мы присвоили объекту Transform уникальное имя "main_TR". В VRML это необходимо, если мы хотим динамически изменять значения полей объекта, например, вращать Transform.

Шаг 3:

#VRML V2.0 utf8

DEF main_TR Transform {
  children [
    Shape {
      geometry Cone {}
      appearance Appearance {
        material Material {
          diffuseColor 0 0 1
        }
      }
    }
  ]
}
DEF main_TIS TimeSensor {
  cycleInterval 3
  loop TRUE
}
						
Мы добавили в корень сцены новый объект TimeSensor. Он послужит генератором событий, которые, в конце концов, приведут к вращению Transform-а.
Поле cycleInterval задаёт временной интервал цикла, а поле loop определяет будет ли этот цикл бесконечным.

Шаг 4:

#VRML V2.0 utf8

DEF main_TR Transform {
  children [
    Shape {
      geometry Cone {}
      appearance Appearance {
        material Material {
          diffuseColor 0 0 1
        }
      }
    }
  ]
}
DEF main_TIS TimeSensor {
  cycleInterval 3
  loop TRUE
}
DEF main_OI OrientationInterpolator	{
  key [0, 0.5, 1]
  keyValue [1 0 0 0, 1 0 0 3.14, 1 0 0 6.28]
}
						
Ещё один новый объект OrientationInterpolator. Именно в нём заложена информация о траектории объекта.
В поле keyValue хранятся опорные координаты, между которыми OrientationInterpolator и будет, собственно, интерполировать.
А, в поле key определяется относительная скорость перемещения объекта между опорными точками.
Ну, вот... всё готово, чтобы связать все эти объекты вместе.

Шаг 5:

#VRML V2.0 utf8

DEF main_TR Transform {
  children [
    Shape {
      geometry Cone {}
      appearance Appearance {
        material Material {
          diffuseColor 0 0 1
        }
      }
    }
  ]
}
DEF main_TIS TimeSensor {
  cycleInterval 3
  loop TRUE
}
DEF main_OI OrientationInterpolator	{
  key [0, 0.5, 1]
  keyValue [1 0 0 0, 1 0 0 3.14, 1 0 0 6.28]
}
ROUTE main_TIS.fraction_changed TO main_OI.set_fraction
ROUTE main_OI.value_changed TO main_TR.rotation
						
При помощи конструкции ROUTE задаются маршруты следования событий в VRML-сцене. В нашем случае от поля fraction_changed объекта TimeSensor-а к полю set_fraction OrientationInterpolator-а, а от поля value_changed OrientationInterpolator-а к полю rotation объекта Transform-у.
Причём обязательно указывается уникальное имя объекта, ведь, например, Transform-ов в сцене может быть много.

Интерактивность

Интерактивность - это взаимодействие пользователя со сценой.
Пример простейшей интерактивности - навигация в сцене. Вы двигаете мышкой или пальцем по сенсорному экрану и, тем самым, меняете своё положение в VRML-сцене. Если навигация запрещена, то 3D-пространство превращается в векторный аналог анимированного GIF-а.
Обычно, под интерактивностью понимают несколько большее, чем просто возможность навигировать. А, именно - изменение геометрии, материала или анимации, как реакция на действия пользователя.
Остановим вращение конуса по щелчку мыши (здесь и далее опускается упоминание о сенсорном экране) в примере на анимацию. Для этого надо:
  1. создать объект, отвечающий за обработку событий от мышки и поименовать его;
  2. установить связь между этим объектом и генератором событий;

Шаг 1:

	#VRML V2.0 utf8

	DEF main_TS TouchSensor {}
	DEF main_TR Transform {
	  children [
		Shape {
		  geometry Cone {}
		  appearance Appearance {
			material Material {
			  diffuseColor 0 0 1
			}
		  }
		}
	  ]
	}
	DEF main_TIS TimeSensor {
	  cycleInterval 3
	  loop TRUE
	}
	DEF main_OI OrientationInterpolator	{
	  key [0, 0.5, 1]
	  keyValue [1 0 0 0, 1 0 0 3.14, 1 0 0 6.28]
	}
	ROUTE main_TIS.fraction_changed TO main_OI.set_fraction
	ROUTE main_OI.value_changed TO main_TR.rotation
							
Мы добавили в корень сцены новый объект TouchSensor. Он отслеживает события от мыши, которые мы будем использовать для остановки анимации.

Шаг 2:

	#VRML V2.0 utf8

	DEF main_TS TouchSensor {}
	DEF main_TR Transform {
	  children [
		Shape {
		  geometry Cone {}
		  appearance Appearance {
			material Material {
			  diffuseColor 0 0 1
			}
		  }
		}
	  ]
	}
	DEF main_TIS TimeSensor {
	  cycleInterval 3
	  loop TRUE
	}
	DEF main_OI OrientationInterpolator	{
	  key [0, 0.5, 1]
	  keyValue [1 0 0 0, 1 0 0 3.14, 1 0 0 6.28]
	}
	ROUTE main_TIS.fraction_changed TO main_OI.set_fraction
	ROUTE main_OI.value_changed TO main_TR.rotation
	ROUTE main_TS.touchTime	TO main_TIS.stopTime
							
При помощи конструкции ROUTE установим связь между полем touchTime объекта TouchSensor и полем stopTime объекта TimeSensor.
Поле touchTime в момент щелчка передаст текущее время в поле stopTime, что послужит сигналом для остановки TimeSensor-а.
Теперь, если щёлкнуть мышкой по конусу, то он перестанет вращаться.

Сценарии

Дя задания более-менее сложного поведения в VRML используются внешние языки программирования.
Обычно, сценарии пишутся на JavaScript (ECMAScript).
Реализуем такой сценарий:
  1. по первому щелчку конус останавливается, как и в предыдущем примере;
  2. по второму - меняет цвет на зелёный;
  3. по третьему - меняет цвет на красный;
  4. по четвёртому - меняет цвет на исходный синий.
  5. далее шаги 2-4 повторяются.

Шаг 1:

#VRML V2.0 utf8

DEF main_TS TouchSensor {}
DEF main_TR Transform {
  children [
	Shape {
	  geometry Cone {}
	  appearance Appearance {
		material DEF main_MT Material {
		  diffuseColor 0 0 1
		}
	  }
	}
  ]
}
DEF main_TIS TimeSensor {
  cycleInterval 3
  loop TRUE
}
DEF main_OI OrientationInterpolator	{
  key [0, 0.5, 1]
  keyValue [1 0 0 0, 1 0 0 3.14, 1 0 0 6.28]
}
DEF main_SCR Script {
  eventIn SFTime go
  field SFNode mt USE main_MT
}
ROUTE main_TIS.fraction_changed TO main_OI.set_fraction
ROUTE main_OI.value_changed TO main_TR.rotation
ROUTE main_TS.touchTime	TO main_TIS.stopTime
ROUTE main_TS.touchTime TO main_SCR.go
							
Мы добавили в корень сцены новый объект Script.
Для обработки клика в Script объявим входное событие eventIn типа SFTime с именем go.
При помощи конструкции ROUTE установим связь между полем touchTime объекта TouchSensor и полем go объекта Script.
Чтобы изменить цвет конуса надо получить доступ к полю diffuseColor объекта Material, для чего, во-первых, именуем его main_MT. А, во-вторых, добавляем в объект Script поле field типа SFNode с именем mt и передаём туда наш материал при помощи конструкции USE.

Шаг 2:

#VRML V2.0 utf8

DEF main_TS TouchSensor {}
DEF main_TR Transform {
  children [
	Shape {
	  geometry Cone {}
	  appearance Appearance {
		material DEF main_MT Material {
		  diffuseColor 0 0 1
		}
	  }
	}
  ]
}
DEF main_TIS TimeSensor {
  cycleInterval 3
  loop TRUE
}
DEF main_OI OrientationInterpolator	{
  key [0, 0.5, 1]
  keyValue [1 0 0 0, 1 0 0 3.14, 1 0 0 6.28]
}
DEF main_SCR Script {
  eventIn SFTime go
  field SFNode mt USE main_MT
  url "javascript:
  var counter = 0;
  function go(){
    switch(counter){
      case 0: break;
      case 1: mt.diffuseColor = new SFColor(0,1,0); break;
      case 2: mt.diffuseColor = new SFColor(1,0,0); break;
      default: mt.diffuseColor = new SFColor(0,0,1); counter = 0;
    }
    counter++;
  } 
  "
}
ROUTE main_TIS.fraction_changed TO main_OI.set_fraction
ROUTE main_OI.value_changed TO main_TR.rotation
ROUTE main_TS.touchTime	TO main_TIS.stopTime
ROUTE main_TS.touchTime TO main_SCR.go
							
Осталось только реализовать логику на JavaScript.
Для этого в поле url объекта Script объявляем функцию, имя которой должно совпадать с именем входного события eventIn - в нашем случае это go.
В теле этой функции реализован счётчик срабатываний объекта TouchSensor и запись соответствующего значения типа SFColor в поле diffuseColor.