Papervision3Dの描画順序

papervison3Dを使って、単純なPlaneに接するようにCubeを描画した際に、Cubeの一部が欠けて表示されてしまいました。大きなプレーンがあって、その上に立方体が置いてあるようなイメージです。Cubeの欠け方を観察していると、Cubeの面を三角形に分割した形状で欠けが発生しています。
推測ですが、papervison3Dは描画にZソートを用いている事と、PlaneもCubeも三角形単位で描画している事が影響しているのだと思われます。大きなPlaneの三角形の重心の位置と小さなCubeの三角形の重心の位置の前後関係かなと。

なので、まず、単純なPlane同士の描画テストをしてみました。

pv3layer01

package {
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	import fl.controls.Slider;
	import fl.events.SliderEvent;
	import fl.controls.SliderDirection;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFieldType;

	import org.papervision3d.core.proto.CameraObject3D;
	import org.papervision3d.view.Viewport3D;
	import org.papervision3d.cameras.*;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.render.BasicRenderEngine;
	import org.papervision3d.objects.primitives.Plane;
	import org.papervision3d.materials.*;
	import org.papervision3d.materials.utils.*;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.core.proto.MaterialObject3D;

	import com.flashdynamix.utils.SWFProfiler;
	import caurina.transitions.*;

	public class Main extends MovieClip {
		public var scene:Scene3D;
		public var renderer:BasicRenderEngine;
		public var viewport:Viewport3D;
		public var camera:Camera3D;

		private var txtFld:TextField;
		private var nPlaneY:Number = 0;

		public function Main() {
			SWFProfiler.init(stage, this);
			init();
		}

		public function init():void {
			init3D();

			var aSlider:Slider = new Slider();
			aSlider.direction = SliderDirection.VERTICAL;
			aSlider.height = 360/2;
			aSlider.x = 640-10;
			aSlider.y = 360/4;
			aSlider.tickInterval = 10;
			aSlider.minimum = 0;
			aSlider.maximum = 250;
			aSlider.liveDragging = true;
			aSlider.addEventListener(SliderEvent.CHANGE, aSliderChangeHandler);
			addChild(aSlider);

			txtFld = new TextField();
			txtFld.background = true;
			txtFld.width = 30;
			txtFld.height = 18;
			txtFld.x = 640-30;
			txtFld.y = 360-18;
			txtFld.text = "0";
			addChild(txtFld);

			addEventListener(Event.ENTER_FRAME, loop);
			var timer:Timer = new Timer(250);
			timer.addEventListener(TimerEvent.TIMER, timerFunc);
			timer.start();
		}

		private function aSliderChangeHandler(evt:SliderEvent)
		{
			nPlaneY = evt.value;
			txtFld.text = nPlaneY.toString();
		}

		public function timerFunc(e:TimerEvent):void {
			var material:ColorMaterial = new ColorMaterial(0xa0a0e0);
			var myPlane:Plane = new Plane(material, 50, 50, 1, 1);
			myPlane.x = Math.random()*400 - 200;
			myPlane.y = nPlaneY;
			myPlane.z = 1500;
			myPlane.rotationX = 90;
			scene.addChild(myPlane);
			Tweener.addTween( myPlane, { z:-1500, time:25, onComplete:animEnd, onCompleteParams:[ myPlane ] } );
		}

		public function animEnd(varPlane:Plane):void {
			scene.removeChild(varPlane);
			varPlane = null;
		}

		public function init3D():void {
			viewport = new Viewport3D(0, 0, true, true);
			addChild(viewport);
			renderer = new BasicRenderEngine();
			scene = new Scene3D();

			var material:ColorMaterial = new ColorMaterial( 0x2080e0 );
			material.oneSide = false;
			var myPlane:Plane = new Plane(material, 500, 500, 1, 1);
			myPlane.rotationX = 90;
			scene.addChild(myPlane);

			camera = new Camera3D();
			camera.zoom = 50;
			camera.y = 200;
			camera.orbit(20, -30, true, myPlane);
		}

		public function loop(event:Event):void {
			renderer.renderScene(scene, camera, viewport);
		}
	}
}

大きな板の上を小さな板が通過しています。右側のライダーを動かすと、小さな板の高さ方向の位置が変化するのですが、100ぐらいまで移動させたところで欠けがなくなりますから、表示位置を調整する事で欠けを回避できます。
ただし、板の位置が100だけ上に移動しているので、大きな板と小さな板の間には100の隙間があるということになります。なので、小さな板の見え方が変わりますし(カメラに寄るので大きくなっています)、カメラが固定なら問題ありませんが、カメラが正面にでも移動したら、隙間が見えてしまうのです。

papervison3Dの描画処理は、Zソートと三角形の描画です。カメラの位置よりも遠い(と判断された)三角形から順に画面に描かれます。つまり、手前の三角形を上に上描きすることで前後関係の描画をしているのです。なので、前後関係の判定結果が想定したものにならないと見た目が破綻します。

それならもういっそのこと、とにかく下にある板を先に描画して、その後に小さな板を描画してくれればいいいわけで、papervison3DのViewportLayerを使うと実現できるようです。

描画順位を指定したサンプルSWF

package {
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	import org.papervision3d.core.proto.CameraObject3D;
	import org.papervision3d.view.Viewport3D;
	import org.papervision3d.cameras.*;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.render.BasicRenderEngine;
	import org.papervision3d.objects.primitives.Plane;
	import org.papervision3d.materials.*;
	import org.papervision3d.materials.utils.*;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.core.proto.MaterialObject3D;

	import org.papervision3d.view.layer.ViewportLayer;
	import org.papervision3d.view.layer.util.ViewportLayerSortMode;

	import com.flashdynamix.utils.SWFProfiler;
	import caurina.transitions.*;

	public class Main extends MovieClip {
		public var scene:Scene3D;
		public var renderer:BasicRenderEngine;
		public var viewport:Viewport3D;
		public var camera:Camera3D;

		public function Main() {
			SWFProfiler.init(stage, this);
			init();
		}

		public function init():void {
			init3D();
			addEventListener(Event.ENTER_FRAME, loop);
			var timer:Timer = new Timer(250);
			timer.addEventListener(TimerEvent.TIMER, timerFunc);
			timer.start();
		}

		public function timerFunc(e:TimerEvent):void {
			var material:ColorMaterial = new ColorMaterial(0xa0a0e0);
			var myPlane:Plane = new Plane(material, 50, 50, 1, 1);
			myPlane.x = Math.random()*400 - 200;
			myPlane.y = 1;
			myPlane.z = 1500;
			myPlane.rotationX = 90;

			var viewportLayer:ViewportLayer = new ViewportLayer(viewport, myPlane);
			viewportLayer.layerIndex = 1;
			viewport.containerSprite.addLayer(viewportLayer);

			scene.addChild(myPlane);
			Tweener.addTween( myPlane, { z:-1500, time:25, onComplete:animEnd, onCompleteParams:[ myPlane ] } );
		}

		public function animEnd(varPlane:Plane):void {
			scene.removeChild(varPlane);
			varPlane = null;
		}

		public function init3D():void {
			viewport = new Viewport3D(0, 0, true, true);
			addChild(viewport);
			renderer = new BasicRenderEngine();
			scene = new Scene3D();

			var material:ColorMaterial = new ColorMaterial( 0x2080e0 );
			material.oneSide = false;
			var myPlane:Plane = new Plane(material, 500, 500, 1, 1);
			myPlane.rotationX = 90;
			scene.addChild(myPlane);

			camera = new Camera3D();
			camera.zoom = 50;
			camera.y = 200;
			camera.orbit(20, -30, true, myPlane);

			viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT;

			var viewportLayer:ViewportLayer = new ViewportLayer(viewport, myPlane);
			viewportLayer.layerIndex = 0;
			viewport.containerSprite.addLayer(viewportLayer);
		}

		public function loop(event:Event):void {
			renderer.renderScene(scene, camera, viewport);
		}
	}
}

小さな板の高さ座標は1にしてあります。位置が0の大きな板よりも1だけ離れた位置です。大きな板と同じ位置の0でもいいのですが同じ座標だと埋まっている印象なので、ちょっとだけ上にある感じです。大きな板のlayerIndexは0、小さな板のlayerIndexは生成するたびに1に設定しています。このようにすると、どんな状態であっても、大きな板を描いてから小さな板を描くようです。

「どんな状態であっても」ということは、小さな板が大きな板の下にあるような状態であっても、小さな板が後に(手前に)描画されます。例えば、大きな板の下にカメラが移動して大きな板を見上げた場合でも、小さな板が後に描画されるので、前後関係がおかしくなります。こういう単純なサンプルの場合は、大きな板の位置が0で固定されているので、カメラの位置が0未満の場合は大きな板のlayerIndexを小さな板のlayerIndexよりも大きな値に設定すれば済みます。

これがViewportLayer本来の使い方なのかどうかはわかりません。ひだちのいろの日記さんの3D オブジェクトの ViewportLayerにアクセスするを読むとオブジェクト単位で何か処理を施す場合にViewportLayerを使うようですし、layerIndexが同じ3Dオブジェクトの描画順序はどうなるのか(Zソートされる?)というような別の疑問は残っています。

コメント / トラックバック3件

  1. kon より:

    はじめまして。興味深い記事ありがとうございます。

    最近PV3Dを始め、いろいろ勉強しているところでして、ちょうどこのブログで紹介されたようなポリゴン欠けの現象がでてましたので、早速参考にさせてもらったのですが、実行しようとすると

    「1020 override としてマークされたメソッドは、別のメソッドをオーバーライドする必要があります。」

    とコンパイルエラーが発生しました。管理人さんがやられた際も同様のエラーなどは起こりませんでしたでしょうか?もし起こったのであれば、解決方法を教えていただけると非常に助かります(><)

    ちなみに、PV3Dのバージョンは、サブバージョンで6/22現在のリビジョンを使用しているので最新のものとなっています。

  2. moriyan より:

    konさん。はじめまして。

    このBlogを書いた時は、FLASH CS3と当時の最新のPaperVison3dでした(subversionから取得)。
    現在の手元の環境である、FLASH CS4と最新のPaperVison3dで試してみたのですが、問題なくコンパイルできました。参考になるかわかりませんが、FLAを含めたソース一式を用意してみました。
    http://www.retropc.net/mm/wp/wp-content/uploads/2009/04/PV3-layer02.zip
    もしかすると、konさんはFlexを利用されているかもしれませんので、あまり役には立たないかもしれませんが、こちらで確認していただければと思います。
    papervison3dは、CS4環境下でもas3\trunk\srcを使っています(branches/as4ではありません)。
    環境や、できたらコンパイルできないソースを教えていただけると、もう少し、詳細な原因を調べることができるかもしれません。

    それにしてもFLASHのエラーメッセージってわかりにくいですよね。(^^;

  3. kon より:

    moriyanさん。

    早速の返答ありがとうございます!

    いただいたFLAも実行したら上手くいかなかったので、
    何か設定が間違っていると思い、「環境設定」の「Action Script3.0 設定」を
    見直したところ、新しいバージョン(最新のもの)と古いバージョンの
    2つのPV3Dを設定をしていたため、上手く動いていなかったみたいです。
    (ちなみに私の環境はFLASH CS3です)

    古いバージョンの設定を削除し、新しいバージョンのみにしてから
    いただいたFLAを実行したところ、上手くいきました!

    いろいろ悩み(丸3日間ぐらいです・・・)これが原因とはお恥ずかしい限りですが、
    moriyanさんにはいい気づきを与えてもらえました!本当にありがとうございました!!