private Microsoft.DirectX.Direct3D.Device device = null; |
public void InitializeGraphics() { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Flip; presentParams.AutoDepthStencilFormat = DepthFormat.D16; presentParams.EnableAutoDepthStencil = true; device = new Microsoft.DirectX.Direct3D.Device(0, Microsoft.DirectX.Direct3D.DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, presentParams); } |
public void Render() { // 清空设备,并准备显示下一帧。 device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black , 1.0f, 0); // 设置照相机的位置 SetupCamera(); //开始场景 device.BeginScene(); if(meshLoaded) { mesh.Render(meshLoc); } device.EndScene(); //显示设备内容。 device.Present(); } |
private void SetupCamera() { device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1.0f, 1000.00f); device.Transform.View = Matrix.LookAtLH(new Vector3(0.0f ,0.0f, 20.0f), new Vector3(0.0f,0.0f, 0.0f), new Vector3(0,1,0)); } |
[STAThread] static void Main() { using (Form1 EarthForm = new Form1()) { EarthForm.InitializeGraphics(); EarthForm.Show(); while(EarthForm.Created) { EarthForm.Render(); Application.DoEvents(); } EarthForm.Dispose(); } |
public class Earth : BaseEarth { private Material[] mMaterials; //保存材质 private Texture[] mTextures; //保存纹理 private Matrix locationOffset; //用来保存网格对象的相对位置 private Mesh mMesh = null; //三角形网格对象 private Device meshDevice; //需要显示在哪个设备上。 } |
public Earth(ref Device device, Matrix location): base(ref device) { meshDevice = device; locationOffset = location; } |
public bool LoadMesh(string meshfile) { ExtendedMaterial[] mtrl; try { // 装载文件 mMesh = Mesh.FromFile(meshfile, MeshFlags.Managed, meshDevice, out mtrl); // 如果有材质的话,装入它们 if ((mtrl != null) && (mtrl.Length > 0)) { mMaterials = new Material[mtrl.Length]; mTextures = new Texture[mtrl.Length]; // 得到材质和纹理 for (int i = 0; i < mtrl.Length; i++) { mMaterials[i] = mtrl[i].Material3D; if ((mtrl[i].TextureFilename != null) && (mtrl[i].TextureFilename != string.Empty)) { //前面得到的纹理的路径是相对路径,需要保存的是绝对路径,通过应用程序路径可以获得 mTextures[i] = TextureLoader.FromFile(meshDevice, @"..\..\" + mtrl[i].TextureFilename); } } } return true; } catch { return false; } } |
public void Render(Matrix worldTransform) { /把位置变为世界坐标 meshDevice.Transform.World = Matrix.Multiply(locationOffset, worldTransform); //绘制网格 for (int i = 0; i < mMaterials.Length; i++) { meshDevice.Material = mMaterials[i]; meshDevice.SetTexture(0, mTextures[i]); mMesh.DrawSubset(i); } } |
private Earth mesh = null; private Matrix meshLoc; private bool meshLoaded = false; |
meshLoc = Matrix.Identity; meshLoc.M41 = 2.0f; mesh = new Earth(ref device, meshLoc); if (mesh.LoadMesh(@"..\..\earth.x")) { meshLoaded = true; } |
device.Lights[0].Type = LightType.Directional; device.Lights[0].Diffuse = Color.White; device.Lights[0].Direction = new Vector3(0, -1, -1); device.Lights[0].Update(); device.Lights[0].Enabled = true; |
此灯光较简单,仅为一个直射型白光灯。
最后,在Render()方法中,调用网格对象的Render()方法,以显示地球。
三、使地球旋转
前面用一个网格对象来建立地球,但此类没有平移,旋转及缩放等方法,下面就加入这些方法,因为这些方法具有通用性,因此可以新建一个类,把这些方法写在这些类中,使地球对象成为它的派生类。
在工程中新添加一个类:BaseEarth;
加入进行平移、旋转、缩放的变量:
private float xloc = 0.0f; private float yloc = 0.0f; private float zloc = 0.0f; private float xrot = 0.0f; private float yrot = 0.0f; private float zrot = 0.0f; private float xscale = 1.0f; private float yscale = 1.0f; private float zscale = 1.0f; |
加入相应的属性代码:
public float XLoc { get { return xloc; } set { xloc = value; } } ………… |
在Render()虚函数中,应用平移、旋转及缩放。
public virtual void Render() { objdevice.MultiplyTransform(TransformType.World,Matrix.Translation(xloc, yloc, zloc)); objdevice.MultiplyTransform(TransformType.World,Matrix.RotationAxis(new Vector3(1.0f, 0.0f, 0.0f), xrot)); objdevice.MultiplyTransform(TransformType.World,Matrix.RotationAxis(new Vector3(0.0f, 1.0f, 0.0f), yrot)); objdevice.MultiplyTransform(TransformType.World,Matrix.RotationAxis(new Vector3(0.0f, 0.0f, 1.0f), zrot)); objdevice.MultiplyTransform(TransformType.World,Matrix.Scaling(xscale, yscale, zscale)); return; } |
现在回到地球类,需要将其改为新类的派生类,同时更改构造函数,另外,在Render()方法中,应先调用基类的Render()方法:
public override void Render() { base.Render(); //把位置变为世界坐标 // meshDevice.Transform.World = Matrix.Multiply(locationOffset, worldTransform); //绘制网格 。。。。。。 } |
现在,由于在基类中可以设置对象位置,因此,可以把与locationOffset相关,即与设置位置的变量及语句注释掉。
四、加入月球
在这一步加入月球,实际上是再创建一个网格对象新实例,只是把纹理进行更改即可,为了代码模块性更好,把两个对象放在一个新类CModel中,在工程中新添加一个类CModel,并声明对象实例。
public class cModel { private cMeshObject mesh1 = null; private cMeshObject mesh2 = null; private bool modelloaded; } |
把窗口代码中的Load()事件,放在CModel中,这次不仅生成了地球,而且生成了月球。
public void Load(ref Device device) { mesh1 = new Earth(ref device); mesh2 = new Earth(ref device); if (mesh1.LoadMesh(@"..\..\earth2.x")) { modelloaded = true; } else { modelloaded = false; } if (mesh2.LoadMesh(@"..\..\moon.x")) { mesh2.XLoc += 20.0f; modelloaded = true; } else { modelloaded = false; } } |
下面的Update()方法中,参数dir 用来判断是顺时针旋转还是逆时针旋转,另外,地球和月球绕Y轴增加的角度大小不同,也就决定了二者旋转的速度不同。
public void Update(int dir) { if(dir > 0) { mesh1.YRot += 0.02f; mesh2.YRot += 0.05f; } else if(dir < 0) { mesh1.YRot -= 0.02f; mesh2.YRot -= 0.05f; } } |
在下面的render()方法中,生成显示月球和地球:
public void Render(ref Device device) { device.Transform.World = Matrix.Identity; if(modelloaded) { mesh1.Render(); mesh2.Render(); } } |
把窗口代码中的加入灯光的方法,也放在此类中:
public void LoadLights(ref Device device) { device.Lights[0].Type = LightType.Directional; device.Lights[0].Diffuse = Color.White; device.Lights[0].Position = new Vector3(0.0f, 0.0f, 25.0f); device.Lights[0].Direction = new Vector3(0, 0, -1); } public void Light(ref Device device) { device.Lights[0].Update(); device.Lights[0].Enabled = true; } |
五、与鼠标交互操作
为了实现与键盘、鼠标交互,新添加一个类:CMouse,添加引用Microsoft.DirectX.DirectInput,并添加命名空间。加入相关变量:
private Microsoft.DirectX.DirectInput.Device mouse = null; public System.Threading.AutoResetEvent MouseUpdated; private float x, y, z = 0.0f; private byte[] buttons; |
在下面的构造函数代码中,首先创建鼠标设备,并初始化回调事件:
public CMouse(System.Windows.Forms.Control control) { mouse = new Microsoft.DirectX.DirectInput.Device(SystemGuid.Mouse); mouse.SetCooperativeLevel(control, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive); mouse.Properties.AxisModeAbsolute = false; MouseUpdated = new System.Threading.AutoResetEvent(false); mouse.SetEventNotification(MouseUpdated); mouse.Acquire(); Update(); } |
下面的Update()方法中获得鼠标的坐标值,并赋给私有成员变量:
public void Update() { MouseState state = mouse.CurrentMouseState; x = state.X; y = state.Y; z = state.Z; buttons = state.GetMouseButtons(); } |
还需要有一个函数来检测鼠标左键是否按下:
public bool LeftButtonDown { get { bool a; return a = (buttons[0] != 0); } } |
六、大结局
现在已经做完了准备工作,返回到窗口代码中,需要对这里的代码重新进行一些调整:
在图形初始化函数中创建一个CModel类及CMouse类:
private CModel model = null; private CMouse mouse = null; private bool leftbuttondown = false; private float mousexloc; |
添加对鼠标初始化的方法:
public void InitializeInput() { mouse = new CMouse(this); } |
添加UpdateInputState()方法,当按下鼠标左键时,将leftbuttondown值设置为真,当鼠标抬起时,将mousexloc置0:
private void UpdateInputState() { mouse.Update(); if (mouse.LeftButtonDown) { if(leftbuttondown == false) { mousexloc = 0.0f; leftbuttondown = true; } else { mousexloc = -mouse.X; } } else { leftbuttondown = false; mousexloc = 0.0f; } } |
在此程序中,只对X值进行了操作,即只能左右转。
Render()方法更新如下:
public void Render() { UpdateInputState(); device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkGray, 1.0f, 0); SetupCamera(); device.BeginScene(); model.Update((int)mousexloc); model.Light(ref device); model.Render(ref device); device.EndScene(); device.Present(); } |
最后更改Main()主函数:
static void Main() { using (Form1 EarthForm = new Form1()) { EarthForm.InitializeGraphics(); EarthForm.InitializeInput(); EarthForm.Show(); while(EarthForm.Created) { EarthForm.Render(); Application.DoEvents(); } EarthForm.Dispose(); } |
运行程序,按下鼠标左键拖动,即可旋转月球与地球。