Away3D TypeScriptではじめる3次元表現

第15回素材に凹凸と反射の強弱を与える─⁠─法線マップとスペキュラマップ

前回の第14回床に映る炎の光をアニメーションさせるでは、炎が映り込む光を床に加え、さらにその光にも揺らぐようなアニメーションを与えた。今回は、床の素材に凹凸や反射の強弱といった細かな表現を加えたい。⁠法線マップ」「スペキュラマップ」という画像により光の反射を扱う技術だ。

光が反射する向きで凹凸を表現する─⁠─法線マップ

平坦な面の素材に細かな凹凸を加えると、質感は大きく変わる。とはいえ、3次元オブジェクトのかたちそのものを細かくいじったら、負荷が上がるばかりだ。また、あまりに小さな凹凸の違いは、表現しきれず無駄になる。かたちを変えなくても、光の反射の向きが変われば面にでこぼこがあるように見える。面の方向をピクセル単位で定めるのが「法線マップ」だ。

法線マップは、ノーマルマップ(normal map)とも呼ばれる。"normal"は「普通」ではなく、面に垂直なベクトル「法線」で、面の向きを示す。法線マップの画像は、テクスチャの各ピクセル(テクスチャの画素は「テクセル」ともいう)に対応した法線ベクトルのxyz座標値をRGBチャネルの値で表す図1⁠。

今回のお題には、法線マップとしてawayjs-examplesfloor_normal.jpgを用いる図1⁠。法線ベクトルの座標値は、画面に対して左右(x)が赤(R⁠⁠、上下(y)は緑(G⁠⁠、前後(z)を青(B)で示す[1]⁠。画面に表示するための画像ではないので、見た目は少しばかりシュールだ。

図1 法線マップ
図1 法線マップ

読み込んだ法線マップのテクスチャは、素材のMethodMaterial.normalMapプロパティに与える。次項で解説するスペキュラマップを定めるプロパティは、MethodMaterial.specularMapだ。あわせて、つぎの表1に掲げておこう。なお、アニメーションさせる炎を定めるクラス(FireObject)は、とりあえず前回と同じまま用いる(再掲第14回コード2⁠。

表1 MethodMaterialクラスのマップを定めるプロパティ
MethodMaterialクラスのプロパティ値とマップの機能
normalMap法線マップとするテクスチャ。ピクセル(テクセル)ごとの面の向きを示し、陰影を与える。
specularMapスペキュラマップとするテクスチャ。ピクセル(テクセル)ごとの光の反射の強さを示す。
第14回 コード2 光のアニメーションを加えた炎のクラス(再掲)
function FireObject(mesh, animator) {
  this.strength = 0;
  this.mesh = mesh;
  this.animator = animator;
}
FireObject.prototype.startAnimation = function() {
  this.animator.start();
};
FireObject.prototype.animateLight = function(fallOff, radius, addition) {
  var light = this.light;
  if (light) {
    light.fallOff = fallOff;
    light.radius = radius;
    light.diffuse = light.specular = 1 + addition;
  }
};

法線マップの読み込み方は、他のテクスチャと変わらない。つぎのように、パスを変数(imageNormal)に与えて関数(loadAsset())でロードする。そして、素材を読込み終えたリスナー関数(onResourceComplete())は、法線マップの画像であることを確かめたら、床(plane)の素材(material)MethodMaterial.normalMapプロパティに定めればよい。

var imageNormal = "assets/floor_normal.jpg";

function initialize() {

  loadAsset(imageNormal);

}

function onResourceComplete(eventObject) {
  var assets = eventObject.assets;
  var material;
  var count = assets.length;
  var url = eventObject.url;
  for (var i = 0; i < count; i++) {
    var asset = assets[i];

    switch (url) {
      case (imageNormal):
        material = plane.material;
        material.normalMap = asset;
        break;

    }
  }
}

法線マップがピクセルごとに光が反射する向きを変えるため、平面なのに凹凸があるように見える図2⁠。ドラッグしてカメラの位置を変えてみると、反射の違いがわかりやすい。試せるように、jsdo.itにサンプル1を掲げたjsdo.itのサイトで再生してほしい⁠。ここまでの手を加えたスクリプトが以下のコード1(前掲第14回コード2のクラス定義を除く⁠⁠。

図2 床に映る光の反射に凹凸の表現が加わった
図2 床に映る光の反射に凹凸の表現が加わった
サンプル1 Away3D 15/03/13: Using a normal map to a plain primitive
コード1 床の平面に法線マップを加える
var BlendMode = require("awayjs-core/lib/data/BlendMode");
var LoaderEvent = require("awayjs-core/lib/events/LoaderEvent");
var TimerEvent = require("awayjs-core/lib/events/TimerEvent");
var ColorTransform = require("awayjs-core/lib/geom/ColorTransform");
var Vector3D = require("awayjs-core/lib/geom/Vector3D");
var AssetLibrary = require("awayjs-core/lib/library/AssetLibrary");
var URLRequest = require("awayjs-core/lib/net/URLRequest");
var RequestAnimationFrame = require("awayjs-core/lib/utils/RequestAnimationFrame");
var Timer = require("awayjs-core/lib/utils/Timer");
var View = require("awayjs-display/lib/containers/View");
var DirectionalLight = require("awayjs-display/lib/entities/DirectionalLight");
var Mesh = require("awayjs-display/lib/entities/Mesh");
var PointLight = require("awayjs-display/lib/entities/PointLight");
var StaticLightPicker = require("awayjs-display/lib/materials/lightpickers/StaticLightPicker");
var PrimitivePlanePrefab = require("awayjs-display/lib/prefabs/PrimitivePlanePrefab");
var ParticleAnimationSet = require("awayjs-renderergl/lib/animators/ParticleAnimationSet");
var ParticleAnimator = require("awayjs-renderergl/lib/animators/ParticleAnimator");
var ParticlePropertiesMode = require("awayjs-renderergl/lib/animators/data/ParticlePropertiesMode");
var ParticleBillboardNode = require("awayjs-renderergl/lib/animators/nodes/ParticleBillboardNode");
var ParticleScaleNode = require("awayjs-renderergl/lib/animators/nodes/ParticleScaleNode");
var ParticleVelocityNode = require("awayjs-renderergl/lib/animators/nodes/ParticleVelocityNode");
var ParticleColorNode = require("awayjs-renderergl/lib/animators/nodes/ParticleColorNode");
var DefaultRenderer = require("awayjs-renderergl/lib/DefaultRenderer");
var MethodMaterial = require("awayjs-methodmaterials/lib/MethodMaterial");
var MethodRendererPool = require("awayjs-methodmaterials/lib/pool/MethodRendererPool");
var MethodMaterialMode = require("awayjs-methodmaterials/lib/MethodMaterialMode");
var HoverController = require("awayjs-display/lib/controllers/HoverController");
var ParticleGeometryHelper = require("awayjs-renderergl/lib/utils/ParticleGeometryHelper");
var view;
var plane;
var cameraController;
var lightPicker;
var timer;
var planeDiffuse = "assets/floor_diffuse.jpg";
var imageNormal = "assets/floor_normal.jpg";
var imageParticle = "assets/blue.png";
var lastMouseX;
var lastMouseY;
var lastPanAngle;
var lastTiltAngle;
var fireObjects;
var particleMaterial;
function initialize() {
  var directionalLight = createDirectionalLight(0.25, 0xFFFFFF);
  view = createView(240, 180, 0x0);
  var scene = view.scene;
  lightPicker = new StaticLightPicker([directionalLight]);
  plane = createPlane(800, 800, lightPicker, -20);
  fireObjects = createParticles(3, 500, 300, 5, scene);
  scene.addChild(plane);
  cameraController = setupCameraController(view.camera, 1000, 0, 90, 45, 20);
  loadAsset(planeDiffuse);
  loadAsset(imageNormal);
  loadAsset(imageParticle);
  document.onmousedown = startDrag;
  timer = new RequestAnimationFrame(render);
  timer.start();
  var fireTimer = new Timer(1000, fireObjects.length);
  fireTimer.addEventListener(TimerEvent.TIMER, startFire);
  fireTimer.start();
}
function createView(width, height, backgroundColor) {
  var defaultRenderer = new DefaultRenderer(MethodRendererPool);
  var view = new View(defaultRenderer);
  view.width = width;
  view.height = height;
  view.backgroundColor = backgroundColor;
  return view;
}
function createPlane(width, height, light, y) {
  var material = new MethodMaterial();
  var plane = new PrimitivePlanePrefab(width, height).getNewObject();
  material.mode = MethodMaterialMode.MULTI_PASS;
  plane.material = material;
  material.lightPicker = light;
  plane.y = y;
  return plane;
}
function createParticles(numFires, numParticles, radius, y, scene) {
  var GLOBAL = ParticlePropertiesMode.GLOBAL;
  var startColor = new ColorTransform(0, 0, 0, 1, 0xFF, 0x33, 0x01);
  var endColor = new ColorTransform(0, 0, 0, 1, 0x99);
  var animations = [
    new ParticleBillboardNode(),
    new ParticleScaleNode(GLOBAL, false, false, 2.5, 0.5),
    new ParticleVelocityNode(GLOBAL, new Vector3D(0, 80, 0)),
    new ParticleColorNode(GLOBAL, true, true, false, false, startColor, endColor),
    new ParticleVelocityNode(ParticlePropertiesMode.LOCAL_STATIC)
  ];
  var animationSet = getParticleAnimationSet(animations, initParticle);
  var primitive = new PrimitivePlanePrefab(10, 10, 1, 1, false);
  var geometry = primitive.geometry;
  var material = particleMaterial = new MethodMaterial();
  var geometrySet = [];
  material.blendMode = BlendMode.ADD;
  for (var i = 0; i < numParticles; i++) {
    geometrySet[i] = geometry;
  }
  var fireObjects = getFireObjects(geometrySet, numFires, material, animationSet, radius, y, scene);
  return fireObjects;
}
function getFireObjects(geometrySet, numFires, material, animationSet, radius, y, scene) {
  var fireObjects = [];
  var particleGeometry = ParticleGeometryHelper.generateGeometry(geometrySet);
  var anglePerFire = Math.PI * 2 / numFires;
  for (var i = 0; i < numFires; i++) {
    var mesh = createAnimationParticle(particleGeometry, material, animationSet, fireObjects);
    var angle = i * anglePerFire;
    mesh.x = radius * Math.sin(angle);
    mesh.z = radius * Math.cos(angle);
    mesh.y = y;
    scene.addChild(mesh);
  }
  return fireObjects;
}
function getParticleAnimationSet(animations, initParticleFunc) {
  var animationSet = new ParticleAnimationSet(true, true);
  var count = animations.length;
  for (var i = 0; i < count; i++) {
    animationSet.addAnimation(animations[i]);
  }
  animationSet.initParticleFunc = initParticleFunc;
  return animationSet;
}
function createAnimationParticle(particleGeometry, material, animationSet, fireObjects) {
  var mesh = new Mesh(particleGeometry, material);
  var animator = new ParticleAnimator(animationSet);
  mesh.animator = animator;
  fireObjects.push(new FireObject(mesh, animator));
  return mesh;
}
function initParticle(prop) {
  var PIx2 = Math.PI * 2;
  var radian0 = Math.random() * PIx2;
  var radian1 = Math.random() * PIx2;
  var velocityVector = getVector3D(15, radian0, radian1);
  prop.startTime = Math.random() * 5;
  prop.duration = Math.random() * 4 + 0.1;
  prop[ParticleVelocityNode.VELOCITY_VECTOR3D] = velocityVector;
}
function getVector3D(radius, angleXY, angleXZ) {
  var sinXY = Math.sin(angleXY);
  var cosXY = Math.cos(angleXY);
  var sinXZ = Math.sin(angleXZ);
  var cosXZ = Math.cos(angleXZ);
  var vector = new Vector3D(radius * cosXY * cosXZ, radius * cosXY * sinXZ, radius * sinXY);
  return vector;
}
function startFire(eventObject) {
  var fireTimer = eventObject.target;
  var count = fireTimer.currentCount;
  var fireObject = fireObjects[count - 1];
  var lights = lightPicker.lights;
  var light = new PointLight();
  fireObject.startAnimation();
  light.color = 0xFF3301;
  light.transform.position = fireObject.mesh.transform.position;
  fireObject.light = light;
  lights.push(light);
  lightPicker.lights = lights;
  if (count >= fireTimer.repeatCount) {
    fireTimer.removeEventListener(TimerEvent.TIMER, startFire);
  }
}
function createDirectionalLight(ambient, color) {
  var light = new DirectionalLight();
  light.ambient = ambient;
  light.color = color;
  return light;
}
function setupCameraController(camera, distance, minTiltAngle, maxTiltAngle, panAngle, tiltAngle) {
  var cameraController = new HoverController(camera);
  cameraController.distance = distance;
  cameraController.minTiltAngle = minTiltAngle;
  cameraController.maxTiltAngle = maxTiltAngle;
  cameraController.panAngle = panAngle;
  cameraController.tiltAngle = tiltAngle;
  return cameraController;
}
function loadAsset(url) {
  AssetLibrary.addEventListener(LoaderEvent.RESOURCE_COMPLETE, onResourceComplete);
  AssetLibrary.load(new URLRequest(url));
}
function onResourceComplete(eventObject) {
  var assets = eventObject.assets;
  var material;
  var count = assets.length;
  var url = eventObject.url;
  for (var i = 0; i < count; i++) {
    var asset = assets[i];
    switch (url) {
      case (planeDiffuse):
        material = plane.material;
        material.texture = asset;
        break;
      case (imageNormal):
        material = plane.material;
        material.normalMap = asset;
        break;
      case (imageParticle):
        particleMaterial.texture = asset;
        break;
    }
  }
}
function render(timeStamp) {
  var count = fireObjects.length;
  for (var i = 0; i < count; i++) {
    var fireObject = fireObjects[i];
    fireObject.animateLight(380 + Math.random() * 100, 200 + Math.random() * 100, Math.random());
  }
  view.render();
}
function startDrag(eventObject) {
  lastMouseX = eventObject.clientX;
  lastMouseY = eventObject.clientY;
  lastPanAngle = cameraController.panAngle;
  lastTiltAngle = cameraController.tiltAngle;
  document.onmousemove = drag;
  document.onmouseup = stopDrag;
}
function drag(eventObject) {
  cameraController.panAngle = 0.5 * (eventObject.clientX - lastMouseX) + lastPanAngle;
  cameraController.tiltAngle = 0.3 * (eventObject.clientY - lastMouseY) + lastTiltAngle;
}
function stopDrag(eventObject) {
  document.onmousemove = null;
  document.onmouseup = null;
}

光が反射する強さをピクセルごとに定める─⁠─スペキュラマップ

素材の光の反射を扱うためにもうひとつ使うのは「スペキュラマップ」⁠specular maps)だ。"specular"は鏡のような反射を意味し、スペキュラマップはピクセルごとの反射の強さを定める。カラーが白に近いほど鏡のように反射し、黒に近いほど光は反射せずに吸収される。ここでは、awayjs-examplesfloor_specular.jpgを使う図3⁠。全体に暗い画像だが、石畳の目地(継ぎ目)の部分が黒に近く、ほとんど反射しない。

図3 スペキュラマップ
図3 スペキュラマップ

スペキュラマップのロードもやはり、つぎのようにパスを変数(imageSpecular)に与え、テクスチャを読み込む関数(loadAsset())に渡せばよい。そして、素材を読込み終えたリスナー関数(onResourceComplete())で、スペキュラマップの画像であることを確かめたうえで、(plane)の素材(material)MethodMaterial.specularMapプロパティに定める。

var imageSpecular = "assets/floor_specular.jpg";

function initialize() {

  loadAsset(imageSpecular);

}

function onResourceComplete(eventObject) {
  var assets = eventObject.assets;
  var material;
  var count = assets.length;
  var url = eventObject.url;
  for (var i = 0; i < count; i++) {
    var asset = assets[i];
    switch (url) {

      case (imageSpecular):
        material = plane.material;
        material.specularMap = asset;
        break;

    }
  }
}

これで、床の石畳と目地の反射の強さが変わる図4左⁠⁠。ただ、もとのマップ画像が暗いので、反射が弱くなってしまって違いがわかりにくい。もう少し炎の映り込みを強くするには、もちろんマップに手を加えてもよい。だが、ピクセルごとに反射の強さを変えるのでなく、全体を均一に強めるのなら、床の素材の光沢をMethodMaterial.specularプロパティで高くすればよい(デフォルト値1⁠⁠。

図4 炎が床に映り込む度合いはスペキュラマップと素材の反射で調整する
図4 炎が床に映り込む度合いはスペキュラマップと素材の反射で調整する 図4 炎が床に映り込む度合いはスペキュラマップと素材の反射で調整する

そこで、MethodMaterial.specularプロパティに与える値は、つぎのように床の平面をつくる関数(createPlane())の引数(specular)に加える。それを初期設定の関数(initialize())から呼び出し、高めの値を渡した。すると、床に映る炎の明るさが増す図4右⁠⁠。このように、スペキュラマップではピクセルごとの反射の違いを定め、素材全体の反射の強さはMethodMaterial.specularプロパティで調整すればよいだろう。

function initialize() {

  // plane = createPlane(800, 800, lightPicker, -20);
  plane = createPlane(800, 800, lightPicker, -20, 2.5);

}

// function createPlane(width, height, light, y) {
function createPlane(width, height, light, y, specular) {

  material.specular = specular;

}

前掲コード1にスペキュラマップを加え、MethodMaterial.specularプロパティで床の反射を調整したのがつぎのコード2だ。あわせて、サンプル2としてjsdo.itのコードを掲げた。ほぼでき上がりに近いものの、もう少しだけ手を加えたい。仕上げは次回としよう。

コード2 スペキュラマップとMethodMaterial.specularプロパティで床の反射を調整する
var BlendMode = require("awayjs-core/lib/data/BlendMode");
var LoaderEvent = require("awayjs-core/lib/events/LoaderEvent");
var TimerEvent = require("awayjs-core/lib/events/TimerEvent");
var ColorTransform = require("awayjs-core/lib/geom/ColorTransform");
var Vector3D = require("awayjs-core/lib/geom/Vector3D");
var AssetLibrary = require("awayjs-core/lib/library/AssetLibrary");
var URLRequest = require("awayjs-core/lib/net/URLRequest");
var RequestAnimationFrame = require("awayjs-core/lib/utils/RequestAnimationFrame");
var Timer = require("awayjs-core/lib/utils/Timer");
var View = require("awayjs-display/lib/containers/View");
var DirectionalLight = require("awayjs-display/lib/entities/DirectionalLight");
var Mesh = require("awayjs-display/lib/entities/Mesh");
var PointLight = require("awayjs-display/lib/entities/PointLight");
var StaticLightPicker = require("awayjs-display/lib/materials/lightpickers/StaticLightPicker");
var PrimitivePlanePrefab = require("awayjs-display/lib/prefabs/PrimitivePlanePrefab");
var ParticleAnimationSet = require("awayjs-renderergl/lib/animators/ParticleAnimationSet");
var ParticleAnimator = require("awayjs-renderergl/lib/animators/ParticleAnimator");
var ParticlePropertiesMode = require("awayjs-renderergl/lib/animators/data/ParticlePropertiesMode");
var ParticleBillboardNode = require("awayjs-renderergl/lib/animators/nodes/ParticleBillboardNode");
var ParticleScaleNode = require("awayjs-renderergl/lib/animators/nodes/ParticleScaleNode");
var ParticleVelocityNode = require("awayjs-renderergl/lib/animators/nodes/ParticleVelocityNode");
var ParticleColorNode = require("awayjs-renderergl/lib/animators/nodes/ParticleColorNode");
var DefaultRenderer = require("awayjs-renderergl/lib/DefaultRenderer");
var MethodMaterial = require("awayjs-methodmaterials/lib/MethodMaterial");
var MethodRendererPool = require("awayjs-methodmaterials/lib/pool/MethodRendererPool");
var MethodMaterialMode = require("awayjs-methodmaterials/lib/MethodMaterialMode");
var HoverController = require("awayjs-display/lib/controllers/HoverController");
var ParticleGeometryHelper = require("awayjs-renderergl/lib/utils/ParticleGeometryHelper");
var view;
var plane;
var cameraController;
var lightPicker;
var timer;
var planeDiffuse = "assets/floor_diffuse.jpg";
var imageNormal = "assets/floor_normal.jpg";
var imageSpecular = "assets/floor_specular.jpg";
var imageParticle = "assets/blue.png";
var lastMouseX;
var lastMouseY;
var lastPanAngle;
var lastTiltAngle;
var fireObjects;
var particleMaterial;
function initialize() {
  var directionalLight = createDirectionalLight(0.25, 0xFFFFFF);
  view = createView(240, 180, 0x0);
  var scene = view.scene;
  lightPicker = new StaticLightPicker([directionalLight]);
  plane = createPlane(800, 800, lightPicker, -20, 2.5);
  fireObjects = createParticles(3, 500, 300, 5, scene);
  scene.addChild(plane);
  cameraController = setupCameraController(view.camera, 1000, 0, 90, 45, 20);
  loadAsset(planeDiffuse);
  loadAsset(imageNormal);
  loadAsset(imageSpecular);
  loadAsset(imageParticle);
  document.onmousedown = startDrag;
  timer = new RequestAnimationFrame(render);
  timer.start();
  var fireTimer = new Timer(1000, fireObjects.length);
  fireTimer.addEventListener(TimerEvent.TIMER, startFire);
  fireTimer.start();
}
function createView(width, height, backgroundColor) {
  var defaultRenderer = new DefaultRenderer(MethodRendererPool);
  var view = new View(defaultRenderer);
  view.width = width;
  view.height = height;
  view.backgroundColor = backgroundColor;
  return view;
}
function createPlane(width, height, light, y, specular) {
  var material = new MethodMaterial();
  var plane = new PrimitivePlanePrefab(width, height).getNewObject();
  material.mode = MethodMaterialMode.MULTI_PASS;
  plane.material = material;
  material.lightPicker = light;
  material.specular = specular;
  plane.y = y;
  return plane;
}
function createParticles(numFires, numParticles, radius, y, scene) {
  var GLOBAL = ParticlePropertiesMode.GLOBAL;
  var startColor = new ColorTransform(0, 0, 0, 1, 0xFF, 0x33, 0x01);
  var endColor = new ColorTransform(0, 0, 0, 1, 0x99);
  var animations = [
    new ParticleBillboardNode(),
    new ParticleScaleNode(GLOBAL, false, false, 2.5, 0.5),
    new ParticleVelocityNode(GLOBAL, new Vector3D(0, 80, 0)),
    new ParticleColorNode(GLOBAL, true, true, false, false, startColor, endColor),
    new ParticleVelocityNode(ParticlePropertiesMode.LOCAL_STATIC)
  ];
  var animationSet = getParticleAnimationSet(animations, initParticle);
  var primitive = new PrimitivePlanePrefab(10, 10, 1, 1, false);
  var geometry = primitive.geometry;
  var material = particleMaterial = new MethodMaterial();
  var geometrySet = [];
  material.blendMode = BlendMode.ADD;
  for (var i = 0; i < numParticles; i++) {
    geometrySet[i] = geometry;
  }
  var fireObjects = getFireObjects(geometrySet, numFires, material, animationSet, radius, y, scene);
  return fireObjects;
}
function getFireObjects(geometrySet, numFires, material, animationSet, radius, y, scene) {
  var fireObjects = [];
  var particleGeometry = ParticleGeometryHelper.generateGeometry(geometrySet);
  var anglePerFire = Math.PI * 2 / numFires;
  for (var i = 0; i < numFires; i++) {
    var mesh = createAnimationParticle(particleGeometry, material, animationSet, fireObjects);
    var angle = i * anglePerFire;
    mesh.x = radius * Math.sin(angle);
    mesh.z = radius * Math.cos(angle);
    mesh.y = y;
    scene.addChild(mesh);
  }
  return fireObjects;
}
function getParticleAnimationSet(animations, initParticleFunc) {
  var animationSet = new ParticleAnimationSet(true, true);
  var count = animations.length;
  for (var i = 0; i < count; i++) {
    animationSet.addAnimation(animations[i]);
  }
  animationSet.initParticleFunc = initParticleFunc;
  return animationSet;
}
function createAnimationParticle(particleGeometry, material, animationSet, fireObjects) {
  var mesh = new Mesh(particleGeometry, material);
  var animator = new ParticleAnimator(animationSet);
  mesh.animator = animator;
  fireObjects.push(new FireObject(mesh, animator));
  return mesh;
}
function initParticle(prop) {
  var PIx2 = Math.PI * 2;
  var radian0 = Math.random() * PIx2;
  var radian1 = Math.random() * PIx2;
  var velocityVector = getVector3D(15, radian0, radian1);
  prop.startTime = Math.random() * 5;
  prop.duration = Math.random() * 4 + 0.1;
  prop[ParticleVelocityNode.VELOCITY_VECTOR3D] = velocityVector;
}
function getVector3D(radius, angleXY, angleXZ) {
  var sinXY = Math.sin(angleXY);
  var cosXY = Math.cos(angleXY);
  var sinXZ = Math.sin(angleXZ);
  var cosXZ = Math.cos(angleXZ);
  var vector = new Vector3D(radius * cosXY * cosXZ, radius * cosXY * sinXZ, radius * sinXY);
  return vector;
}
function startFire(eventObject) {
  var fireTimer = eventObject.target;
  var count = fireTimer.currentCount;
  var fireObject = fireObjects[count - 1];
  var lights = lightPicker.lights;
  var light = new PointLight();
  fireObject.startAnimation();
  light.color = 0xFF3301;
  light.transform.position = fireObject.mesh.transform.position;
  fireObject.light = light;
  lights.push(light);
  lightPicker.lights = lights;
  if (count >= fireTimer.repeatCount) {
    fireTimer.removeEventListener(TimerEvent.TIMER, startFire);
  }
}
function createDirectionalLight(ambient, color) {
  var light = new DirectionalLight();
  light.ambient = ambient;
  light.color = color;
  return light;
}
function setupCameraController(camera, distance, minTiltAngle, maxTiltAngle, panAngle, tiltAngle) {
  var cameraController = new HoverController(camera);
  cameraController.distance = distance;
  cameraController.minTiltAngle = minTiltAngle;
  cameraController.maxTiltAngle = maxTiltAngle;
  cameraController.panAngle = panAngle;
  cameraController.tiltAngle = tiltAngle;
  return cameraController;
}
function loadAsset(url) {
  AssetLibrary.addEventListener(LoaderEvent.RESOURCE_COMPLETE, onResourceComplete);
  AssetLibrary.load(new URLRequest(url));
}
function onResourceComplete(eventObject) {
  var assets = eventObject.assets;
  var material;
  var count = assets.length;
  var url = eventObject.url;
  for (var i = 0; i < count; i++) {
    var asset = assets[i];
    switch (url) {
      case (planeDiffuse):
        material = plane.material;
        material.texture = asset;
        break;
      case (imageNormal):
        material = plane.material;
        material.normalMap = asset;
        break;
      case (imageSpecular):
        material = plane.material;
        material.specularMap = asset;
        break;
      case (imageParticle):
        particleMaterial.texture = asset;
        break;
    }
  }
}
function render(timeStamp) {
  var count = fireObjects.length;
  for (var i = 0; i < count; i++) {
    var fireObject = fireObjects[i];
    fireObject.animateLight(380 + Math.random() * 100, 200 + Math.random() * 100, Math.random());
  }
  view.render();
}
function startDrag(eventObject) {
  lastMouseX = eventObject.clientX;
  lastMouseY = eventObject.clientY;
  lastPanAngle = cameraController.panAngle;
  lastTiltAngle = cameraController.tiltAngle;
  document.onmousemove = drag;
  document.onmouseup = stopDrag;
}
function drag(eventObject) {
  cameraController.panAngle = 0.5 * (eventObject.clientX - lastMouseX) + lastPanAngle;
  cameraController.tiltAngle = 0.3 * (eventObject.clientY - lastMouseY) + lastTiltAngle;
}
function stopDrag(eventObject) {
  document.onmousemove = null;
  document.onmouseup = null;
}
サンプル2 Away3D 15/03/13: Setting the MethodMaterial.specular porperty

おすすめ記事

記事・ニュース一覧