3次元空間では物体の前後判定が必要になります。特に2つの物体がめり込んでいる時にはそれぞれのピクセルごとにどちらの物体の方が前にあるのかを調べなければいけなくなります。
もちろんDirectXにはこれを簡単に行うための方法があります。それがZバッファです。
Zバッファはカメラの位置からの遠さを記憶するバッファです。最初は一番遠くの値を設定しておいて、描画する物体がその値よりも小さい値だったときにはZバッファの距離を書き換え、さらに書き込みを行います。もしも遠かった場合には何もせずに次の処理に移ります。それだけです。
前回利用したメッシュを使います。
using System; using System.Drawing; using System.Windows.Forms; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace Project2 { /// <summary> /// md3d2 の概要の説明です。 /// </summary> public class md3d2:Form { public md3d2():base() { //最小サイズを設定 this.MinimumSize=new Size(80,60); //ウィンドウの名前(かっこいいのを付けてあげてください) this.Text ="Direct3D-My"; } private Device device_; private PresentParameters presentParam_; /// <summary> /// 立方体メッシュ /// </summary> private Mesh boxMesh_; /// <summary> /// Direct3Dの初期化を行います。 /// </summary> /// <returns>初期化が成功したかどうか</returns> public bool DXInitialize() { try { presentParam_ = new PresentParameters(); presentParam_.Windowed =true; presentParam_.SwapEffect = SwapEffect.Discard; //Zバッファを使うための設定 presentParam_.AutoDepthStencilFormat = DepthFormat.D16; presentParam_.EnableAutoDepthStencil = true; device_ = new Device(0,DeviceType.Hardware,this ,CreateFlags.HardwareVertexProcessing,presentParam_); //メッシュを作成 creatMesh(); return true; } catch { return false; } } /// <summary> /// メッシュを作成する /// </summary> private void creatMesh() { boxMesh_ = Mesh.Box(device_,2,2,2); } public void Render() { if(device_==null)return; if(this.WindowState ==FormWindowState.Minimized)return; device_.RenderState.ZBufferEnable =true; device_.RenderState.ZBufferWriteEnable =true; //カメラの設定を行う device_.Transform.View = Matrix.LookAtLH( new Vector3( (float)Math.Sin(Environment.TickCount /600f)*8f, 3.0f, (float)Math.Cos(Environment.TickCount /600f)*8f ), new Vector3( 0.0f, 0.0f, 0.0f ), new Vector3( 0.0f, 1.0f, 0.0f ) ); device_.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, (float)this.ClientSize.Width / (float)this.ClientSize.Height, 3.0f, 100.0f); //device_.RenderState.Lighting =false; //ライトの設定 device_.Lights[0].Direction =Vector3.Normalize(new Vector3(-1,-2,-3)); device_.Lights[0].Type = LightType.Directional; device_.Lights[0].Diffuse = Color.FromArgb(255,255,255); device_.Lights[0].Ambient = Color.FromArgb(40,40,40); device_.Lights[0].Enabled =true; device_.Lights[0].Update(); //マテリアルの設定 Material mat =new Material(); //クリア device_.Clear(ClearFlags.Target|ClearFlags.ZBuffer,Color.Blue,1.0f,0); //シーンの開始 device_.BeginScene(); //色設定 mat.AmbientColor = new ColorValue(0.0f,1.0f,1.0f); mat.DiffuseColor = new ColorValue(0.0f,1.0f,1.0f); device_.Material = mat; //変換設定 device_.Transform.World = Matrix.Translation(0,0,-3); //描画(後ろのオブジェクト) boxMesh_.DrawSubset(0); //色設定 mat.AmbientColor = new ColorValue(1.0f,1.0f,1.0f); mat.DiffuseColor = new ColorValue(1.0f,1.0f,1.0f); device_.Material = mat; //変換設定 device_.Transform.World = Matrix.Identity; //描画(前のオブジェクト) boxMesh_.DrawSubset(0); device_.EndScene(); try { //更新 device_.Present(); } catch(DeviceLostException) { resetDevice(); } } /// <summary> /// デバイスのリセットを行う /// </summary> private void resetDevice() { int result; if(!device_.CheckCooperativeLevel(out result)) { if(result ==(int)ResultCode.DeviceLost) { //ちょっと待つ System.Threading.Thread.Sleep(10); } else if(result ==(int)ResultCode.DeviceNotReset) { device_.Reset(presentParam_); } } } } /// <summary> /// エントリクラス /// </summary> class Program { public static void Main() { using(md3d2 dxform =new md3d2()) { if(!dxform.DXInitialize()) { MessageBox.Show("Diret3Dの初期化に失敗しました。" ,"初期化の失敗"); return; } dxform.Show(); while(dxform.Created) { dxform.Render(); Application.DoEvents(); } } } } }
長くてすみません。クラス化すればいいのですが、そうすると逆に見通しが悪くなるので…。
皆さん「PresentParameters」を覚えているでしょうか?言いたいことは分かりますね?そこで設定すべき事があるのです。
//Zバッファを使うための設定 presentParam_.AutoDepthStencilFormat = DepthFormat.D16; presentParam_.EnableAutoDepthStencil = true;
ここです。Zバッファは深度バッファの1つらしいです。「深度」つまり深さな訳で「Depth」です。要するにカメラからどのくらいの遠さにあるかの値を保存するのです。
それでこの「DepthFormat」にはいくつかの種類があってビデオカードによって対応していない場合があります。大抵「DepthFormat.D16」には対応しているらしいのですが、「DepthFormat.D16」は精度が悪いです。できれば「D24S8」や「D32」が良いらしいです。ですが、ここではそんな厳密な値を使うわけではありませんし、Zバッファの値の分布の仕方を理解しているわけでもないのでD16で行かせてもらいます。
後半の1行はDirectXに自動で深度バッファを管理してもらうための物です。リファレンスから察するに自分で深度バッファを作れるようです。
「AutoDepthStencilFormat」どう考えても名前が変ですよね?「Stencil(ステンシル)」という言葉が入っていますね。これ、私もまだ分かりません。後で調べておきます。とりあえず無視しても影響がないようなので今は無視して下さい。
device_.RenderState.ZBufferEnable =true; device_.RenderState.ZBufferWriteEnable =true;
実際の所この2行は無くても動きます。ですが、ちょっとZバッファを止めたいときにここをいじればいいと言うことを言っておきたかったので書きました。変数の名前から察するに、書き込みと読みとりを別々に有効無効させることができるようです。
device_.Clear(ClearFlags.Target | ClearFlags.ZBuffer , Color.Blue , 1.0f , 0);
説明無く使ってしまったメソッドですね。とりあえず今回は「Target」+「ZBuffer」を初期化したいので「OR」をしています。それと、Zバッファの値は3番目の引数の値で初期化されます。
ここで1つ注意です。DirectXでは最終的にスクリーンに投影される前にある大きさの直方体の中に座標が入るように調整されます。その時、カメラから見て一番近くになる前方投影面側の深さが「0.0f」、一番遠い後方投影面の深さが「1.0f」になるように調整されます。というわけで、下手に0.5fなんかで初期化してしまうと、全部描かれなくなってしまいます。そういうわけで大抵この値は「1.0f」です。
ここら辺のことは3Dの本を読む方が分かり易いかもしれません。その時はちゃんとした図の入った物を買うと良いですよ。
今回はせっかくZバッファが使えると言うことで回転させてちゃんと表示されるか試そうと思いました。ですが、もう「World」の方は使われているので今回はカメラを回転させています。
device_.Transform.View = Matrix.LookAtLH( new Vector3( (float)Math.Sin(Environment.TickCount /600f)*8f, 3.0f, (float)Math.Cos(Environment.TickCount /600f)*8f ), new Vector3( 0.0f, 0.0f, 0.0f ), new Vector3( 0.0f, 1.0f, 0.0f ) );
Sin、Cos覚えているでしょうか?まあ、ここではあんまり気にしないでとにかくこの設定で回してみて下さい。そして、それから自分でいじると分かるかもしれませんから。
以外と簡単に説明できた深度バッファです。この解説を書きながらWバッファなる物があることを知りましたが、まあZバッファで十分でしょう。もしもあなたが実用的な物を開発するのであれば、その時に知っておけばいいと思います。