自機などを固定位置に描画させて、敵などは自機からの相対位置になるように描画したいときもあるかもしれません。そこで視界を設定するとき自機のつねに後ろ、距離も一定にして、自機を真後ろから追いかけるような描画をするプログラムを作成します。これは他のアプリケーションを作成するときに使い回しができるかもしれません。
まずは自機としてサイコロを描画します。1の目があるほうが前、6の目があるほうが後ろです。つねに後ろから見た様子を描画するので6の目しかみえません。
サイコロを描画する準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
using OpenTK; using OpenTK.Graphics.OpenGL; public partial class Form1 : Form { List<int> Textures = new List<int>(); private void glControlEx1_Load(object sender, EventArgs e) { GL.ClearColor(glControl.BackColor); // Projection の設定 SetProjection(); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); Lighting(); CreateTexture(Properties.Resources._1); CreateTexture(Properties.Resources._2); CreateTexture(Properties.Resources._3); CreateTexture(Properties.Resources._4); CreateTexture(Properties.Resources._5); CreateTexture(Properties.Resources._6); } void CreateTexture(Bitmap bmp) { // テクスチャ有効化 GL.Enable(EnableCap.Texture2D); // 領域確保 int texture; GL.GenTextures(1, out texture); Textures.Add(texture); // アクティブ設定 GL.BindTexture(TextureTarget.Texture2D, texture); System.Drawing.Imaging.BitmapData data = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height), // 画像全体をロック System.Drawing.Imaging.ImageLockMode.ReadOnly, // 読み取り専用 System.Drawing.Imaging.PixelFormat.Format24bppRgb); // ロックをしたら必ずアンロックする bmp.UnlockBits(data); // テクスチャ領域にピクセルデータを貼り付ける GL.TexImage2D( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, data.Width, data.Height, 0, PixelFormat.Bgr, // Rgbaにすると赤と青が反転するので注意 PixelType.UnsignedByte, data.Scan0); GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); } private void DrawDice(double size) { double w = size / 2, h = size / 2, d = size / 2; GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.White); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.White); // 手前の面(6) GL.BindTexture(TextureTarget.Texture2D, Textures[5]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(Vector3.UnitZ); GL.TexCoord2(0, 0); GL.Vertex3(w, h, d); GL.TexCoord2(1, 0); GL.Vertex3(-w, h, d); GL.TexCoord2(0, 1); GL.Vertex3(w, -h, d); GL.TexCoord2(1, 1); GL.Vertex3(-w, -h, d); } GL.End(); // 奥の面(1) GL.BindTexture(TextureTarget.Texture2D, Textures[0]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(-Vector3.UnitZ); GL.TexCoord2(0, 0); GL.Vertex3(w, -h, -d); GL.TexCoord2(1, 0); GL.Vertex3(-w, -h, -d); GL.TexCoord2(0, 1); GL.Vertex3(w, h, -d); GL.TexCoord2(1, 1); GL.Vertex3(-w, h, -d); } GL.End(); // 右面(2) GL.BindTexture(TextureTarget.Texture2D, Textures[1]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(Vector3.UnitX); GL.TexCoord2(0, 0); GL.Vertex3(w, h, -d); GL.TexCoord2(1, 0); GL.Vertex3(w, h, d); GL.TexCoord2(0, 1); GL.Vertex3(w, -h, -d); GL.TexCoord2(1, 1); GL.Vertex3(w, -h, d); } GL.End(); // 左面(5) GL.BindTexture(TextureTarget.Texture2D, Textures[4]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(-Vector3.UnitX); GL.TexCoord2(0, 0); GL.Vertex3(-w, -h, -d); GL.TexCoord2(1, 0); GL.Vertex3(-w, -h, d); GL.TexCoord2(0, 1); GL.Vertex3(-w, h, -d); GL.TexCoord2(1, 1); GL.Vertex3(-w, h, d); } GL.End(); // 上面(3) GL.BindTexture(TextureTarget.Texture2D, Textures[2]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(Vector3.UnitY); GL.TexCoord2(0, 0); GL.Vertex3(w, h, -d); GL.TexCoord2(1, 0); GL.Vertex3(-w, h, -d); GL.TexCoord2(0, 1); GL.Vertex3(w, h, d); GL.TexCoord2(1, 1); GL.Vertex3(-w, h, d); } GL.End(); // 下面(4) GL.BindTexture(TextureTarget.Texture2D, Textures[3]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(-Vector3.UnitY); GL.TexCoord2(0, 0); GL.Vertex3(-w, -h, d); GL.TexCoord2(1, 0); GL.Vertex3(-w, -h, -d); GL.TexCoord2(0, 1); GL.Vertex3(w, -h, d); GL.TexCoord2(1, 1); GL.Vertex3(w, -h, -d); } GL.End(); } } |
これでサイコロを描画することはできるようになりました。次にサイコロを動かし、このサイコロを描画する方法を考えます。
サイコロの位置と回転状態を管理するためのプロパティを作成します。それから常に後方の一定の距離からみた状態を描画するわけですが、ここでは「後方の一定の距離」は6.0とします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
public partial class Form1 : Form { //「後方の一定の距離」は6.0 const float Distance = 6.0f; float _x = 0; float X { get { return _x; } set { _x = (float)Math.Round(value, 2); } } float _y = 0; float Y { get { return _y; } set { _y = (float) Math.Round(value, 2); } } float _z = 0; float Z { get { return _z; } set { _z = (float)Math.Round(value, 2); } } float _rotateX = 0; float RotateX { get { return _rotateX; } set { _rotateX = value; if(_rotateX >= 360) _rotateX -= 360; if(_rotateX < 0) _rotateX += 360; } } float _rotateY = 0; float RotateY { get { return _rotateY; } set { _rotateY = value; if(_rotateY >= 360) _rotateY -= 360; if(_rotateY < 0) _rotateY += 360; } } float _rotateZ = 0; float RotateZ { get { return _rotateZ; } set { _rotateZ = value; if(_rotateZ >= 360) _rotateZ -= 360; if(_rotateZ < 0) _rotateZ += 360; } } } |
サイコロの位置や回転状態を変更するメソッドを作成します。「前にターゲットを移動させる」とはサイコロをみている人からみてサイコロを遠ざける方向です。Z座標のマイナス方向への移動を意味しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
public partial class Form1 : Form { // 上にターゲットを移動させる void MoveUpTarget(float value) { Y += value; glControl.Refresh(); } // 下にターゲットを移動させる void MoveDownTarget(float value) { Y -= value; glControl.Refresh(); } // 左にターゲットを移動させる void MoveLeftTarget(float value) { X -= value; glControl.Refresh(); } // 右にターゲットを移動させる void MoveRightTarget(float value) { X += value; glControl.Refresh(); } // 前にターゲットを移動させる void MoveFrontTarget(float value) { Z -= value; glControl.Refresh(); } // 後ろにターゲットを移動させる void MoveBackTarget(float value) { Z += value; glControl.Refresh(); } // ターゲットを上向きに方向転換させる void RotateUpTarget(float value) { RotateX += value; glControl.Refresh(); } // ターゲットを下向きに方向転換させる void RotateDownTarget(float value) { RotateX -= value; glControl.Refresh(); } // ターゲットを右向きに方向転換させる void RotateRightTarget(float value) { RotateY -= value; glControl.Refresh(); } // ターゲットを左向きに方向転換させる void RotateLeftTarget(float value) { RotateY += value; glControl.Refresh(); } // ターゲットをZ軸を中心に回転させる void RotateZPlusTarget(float value) { RotateZ += value; glControl.Refresh(); } // ターゲットの進行方向に沿って前進させる void MoveTargetDirect(float value) { float radY = (float)(2f * Math.PI * RotateY / 360); float radX = (float)(2f * Math.PI * RotateX / 360); X += -value * (float)Math.Sin(radY) * (float)Math.Cos(radX); Y += value * (float)Math.Sin(radX); Z += -value * (float)Math.Cos(radY) * (float)Math.Cos(radX); glControl.Refresh(); } } |
以下は一般的な初期設定です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using OpenTK; using OpenTK.Graphics.OpenGL; public partial class Form1 : Form { public Form1() { InitializeComponent(); } // Projection の設定 private void SetProjection() { // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); // 視体積の設定 GL.MatrixMode(MatrixMode.Projection); Matrix4 proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)glControl.Size.Width / (float)glControl.Size.Height, 0.01f, 300.0f); GL.LoadMatrix(ref proj); // MatrixMode を元に戻す GL.MatrixMode(MatrixMode.Modelview); } private void glControl_Resize(object sender, EventArgs e) { SetProjection(); GL.Enable(EnableCap.PolygonSmooth); GL.Enable(EnableCap.LineSmooth); // 再描画 glControl.Refresh(); } void Lighting() { // 光源の使用 GL.Enable(EnableCap.Lighting); // ライト 0 の設定と使用 float[] position = new float[] { 1.0f, 1.0f, 1.0f, 0.0f }; GL.Light(LightName.Light0, LightParameter.Position, position); float[] ambientColor = new float[] { 0.1f, 0.1f, 0.1f, 1.0f }; GL.Light(LightName.Light0, LightParameter.Ambient, ambientColor); GL.Light(LightName.Light0, LightParameter.Diffuse, Color.White); GL.Enable(EnableCap.Light0); } } |
1 2 3 4 5 6 7 8 |
public class GLControlEx: OpenTK.GLControl { public GLControlEx() { // ないとForm1でKeyDown、KeyUpイベントが捕捉できない SetStyle(ControlStyles.Selectable, false); } } |
X,Y,Zプロパティ、RotateX,RotateY,RotateZプロパティが設定されたらあとは描画するだけです。
描画するときにサイコロだけでなく、X軸、Y軸、Z軸、XZ平面には格子状に黄色い線を描画しています。視界の設定はサイコロの真後ろなのでサイコロ自体は動きません。まわりに動かない格子模様を描画することで、どのような描画がされているのかわかりやすくなってくれればということで、このような処理をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
public partial class Form1 : Form { private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); Lighting(); SetSight(); // X、Y、Z軸を描画 GL.Begin(BeginMode.Lines); { // X軸 GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.Red); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.Red); GL.Vertex3(-200, 0, 0); GL.Vertex3(200, 0, 0); // Y軸を描画 GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.Green); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.Green); GL.Vertex3(0, -200, 0); GL.Vertex3(0, 200, 0); // Z軸を描画 GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.LightBlue); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.LightBlue); GL.Vertex3(0, 0, 200); GL.Vertex3(0, 0, -200); // 方眼を描画 GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.Yellow); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.Yellow); for(int i = -50; i < 50; i++) { if(i == 0) continue; GL.Vertex3(4 * i, 0, 200); GL.Vertex3(4 * i, 0, -200); } for(int i = -100; i < 100; i++) { if(i == 0) continue; GL.Vertex3( 200, 0, 4 * i); GL.Vertex3(-200, 0, 4 * i); } } GL.End(); // サイコロを描画 GL.PushMatrix(); { GL.Translate(X, Y, Z); GL.Rotate(RotateY, 0, 1, 0); GL.Rotate(RotateX, 1, 0, 0); GL.Rotate(RotateZ, 0, 0, 1); DrawDice0(1); } GL.PopMatrix(); glControl.SwapBuffers(); } } |
イベントハンドラ glControlEx1_Paint(object sender, PaintEventArgs e)のなかで視界の設定をおこなっています。
このとき単に後ろからサイコロを描画するだけであれば
1 2 3 4 5 6 7 8 9 |
void SetSight() { Vector3 eye = new Vector3(X, Y, Z + Distance); Vector3 target = new Vector3(X, Y, Z); Vector3 up = Vector3.UnitY; Matrix4 look = Matrix4.LookAt(eye, target, up); GL.LoadMatrix(ref look); } |
これだけでかまいません。しかし自機には向きがあります。これだとサイコロを回転させると回転したサイコロが描画されます。
では視点を自機が向いている方向からみた後ろにするにはどうすればいいのでしょうか? そうなるとちょっと難しいです。おそらくこうなるのではないでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
public partial class Form1 : Form { void SetSight() { // 視点はどこになるか? // RotateXプロパティ、RotateYプロパティから考える // RotateXプロパティ、RotateYプロパティは度数法なので、弧度法に変換する float radY = (float)(2f * Math.PI * RotateY / 360); float radX = (float)(2f * Math.PI * RotateX / 360); // 視点はこの位置? float eyeX = Distance * (float)Math.Sin(radY) * (float)Math.Cos(radX) + X; float eyeY = -Distance * (float)Math.Sin(radX) + Y; float eyeZ = Distance * (float)Math.Cos(radY) * (float)Math.Cos(radX) + Z; // 視界の設定 Vector3 eye = new Vector3(eyeX, eyeY, eyeZ); Vector3 target = new Vector3(X, Y, Z); Vector3 up = Vector3.UnitY; // カメラの上側の方向だがradXの値によって切り分ける必要がある if(radX > Math.PI / 2 && radX < Math.PI * 3 / 2) up = -Vector3.UnitY; Matrix4 look = Matrix4.LookAt(eye, target, up); GL.LoadMatrix(ref look); // デバッグ用 各値をフォーム上に表示させる ShowLabels(eye); } // これはデバックのための情報を表示させるメソッドです void ShowLabels(Vector3 eye) { labelPosX.Text = "オブジェクトのX座標 " + X.ToString(); labelPosY.Text = "オブジェクトのY座標 " + Y.ToString(); labelPosZ.Text = "オブジェクトのZ座標 " + Z.ToString(); labelEyeX.Text = "視点X座標 " + eye.X.ToString(); labelEyeY.Text = "視点Y座標 " + eye.Y.ToString(); labelEyeZ.Text = "視点Z座標 " + eye.Z.ToString(); labelAngleA.Text = "水平角度 " + Math.Round(RotateY, 0).ToString(); labelAngleB.Text = "垂直角度 " + Math.Round(RotateX, 0).ToString(); labelAngleC.Text = "左右の傾斜角度 " + Math.Round(RotateZ, 0).ToString(); } } |
あとはキー操作などでサイコロを移動、回転させ、正しく描画されているか確認します。つねにサイコロの後ろからの景色を描画するのでサイコロは中心から動かないようにみえます。また光源は一定なので、サイコロの1の目がある方向によって明るさが変動します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public partial class Form1 : Form { protected override void OnKeyDown(KeyEventArgs e) { if(e.KeyCode == Keys.Up) { if(!e.Control && !e.Shift) MoveUpTarget(0.1f); else if(e.Control && !e.Shift) RottateUpTarget(5f); else MoveBackTarget(0.1f); } if(e.KeyCode == Keys.Down) { if(!e.Control && !e.Shift) MoveDownTarget(0.1f); else if(e.Control && !e.Shift) RottateDownTarget(5f); else MoveFrontTarget(0.1f); } if(e.KeyCode == Keys.Right) { if(!e.Control && !e.Shift) MoveRightTarget(0.1f); else if(e.Control && !e.Shift) RottateRightTarget(5f); else if(e.Shift) RottateZMinusTarget(5f); } if(e.KeyCode == Keys.Left) { if(!e.Control && !e.Shift) MoveLeftTarget(0.1f); else if(e.Control && !e.Shift) RottateLeftTarget(5f); else if(e.Shift) RottateZPlusTarget(5f); } if(e.KeyCode == Keys.Space) { MoveTargetDirect(0.1f); } base.OnKeyDown(e); } } |