最近在公司里实习,有个项目的接合点需要用到三维处理的东西,还是比较麻烦的,虽然之前也学过一点图形学的理论知识,但都是皮毛,研究得也不深入。所以趁现在把一些基本的概念拾起来,开发平台用微软的DirectX框架并结合C#,很多东西都是现学的。下面给出几个基本的概念。
图形卡即显卡,计算机和显示器之间的接口。很多图形卡都有自己的处理器,称为GPU,图形处理器。GPU是针对图形和图像所需要的计算进行过优化的专用处理器,与CPU并行工作。每个图形卡都有自己的显存,即计算机系统的显卡内存。其实一些桌面版的3D游戏,只是经过处理器进行大量的数学计算,将3D物体转换为具有立体感的2D图形,在计算机显示器屏幕上显示。
计算机通过图形卡控制显示器,图形卡能够控制显示器屏幕每一点的颜色。所采用的方法是,在图形卡的显存中分配一个区域,区域中每一个单元存储的颜色值跟屏幕中每一点的颜色一一对应,即如果程序修改了这个显存区域中一个单元中存储的颜色值,也就修改了和其对应屏幕点的颜色。如果直接修改显存区域中的数据,图像会发生抖动,因为显示器是按照行进行扫描的。为了避免抖动,一般在后备缓存区(back buffer)中修改图像,修改完成后再把后备缓存区中的图像送到屏幕显示区中显示。将3D物体转换为具有立体感的平面图形,实际上是将3D物体投影到XY平面上,Z轴表示3D物体距离观察者的距离。在投影的过程中,前面的物体可能会遮挡后面的物体,也就是说只有在Z轴方向上最靠近观察者的物体才嫩被投影到XY平面上,这里用到一个称为深度缓存区(Z-buffer,depthbuffer,w-buffer)的东西,它的每一个单元记录了每个投影到XY平面的点到观察者的距离。新的3D物体要投影到XY平面,首先要计算该点到观察者的距离,然后和深度缓存区中相应单元的值进行比较,只有小于时,才能进行投影,Direct 3D把这个操作称为深度测试。把3D物体在2D显示器中显示的过程叫渲染。
上面的东西都是概念上的,其实从开发者的角度来看,上面所有的东西都是被封装成相应的类、函数、参数等。在Direct 3D中,Device类是所有绘图操作必须使用的类。可以把这个类的对象假想成真实的图形卡。Device类把真实的图形卡从具体的硬件中抽象出来,在类中定义一组通用函数,这些函数直接操作图形卡硬件。通过调用统一标准的Device类可以操作每一种图形卡,这样做的好处就是既保证了应用程序和硬件无关,又能直接控制硬件,加快应用程序的运行速度。下面给出一个Direct3D基本例子,显示蓝色屏幕背景。
安装的.NetFramework为4.0,x86架构,程序配置文件为:
<?xml version="1.0"?> <configuration> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0"/> </startup> </configuration>
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace Direct3D { public partial class Form1 : Form { private Device device = null; public Form1() { InitializeComponent(); } public bool InitializeGraphics() { try { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; //不是全屏显示,在一个窗口显示 presentParams.SwapEffect = SwapEffect.Discard; //后备缓存交换的方式 presentParams.EnableAutoDepthStencil = true; //允许使用自动深度模板测试 //深度缓冲区单元为16位二进制数 presentParams.AutoDepthStencilFormat = DepthFormat.D16; device = new Device(0, DeviceType.Hardware, this, //建立设备类对象 CreateFlags.SoftwareVertexProcessing, presentParams); //设置设备重置事件(device.DeviceReset)事件函数为this.OnResetDevice device.DeviceReset += new System.EventHandler(this.OnResetDevice); this.OnCreateDevice(device, null);//自定义方法,初始化Device的工作放到这个方法中 this.OnResetDevice(device, null);//调用设备重置事件(device.DeviceReset)事件函数 } //设备重置事件函数要设置Device参数,初始函数中必须调用该函数 catch (DirectXException) { return false; } return true; } public void OnCreateDevice(object sender, EventArgs e) { } public void OnResetDevice(object sender, EventArgs e) { Render(); } public void Render() //渲染方法,本方法没有任何渲染代码,可认为是渲染方法的框架 { if (device == null) //如果未建立设备对象,退出 return; //下边函数将显示区域初始化为蓝色,第1个参数指定要初始化目标窗口包括深度缓冲区 //第2个参数是我们所要填充的颜色。第3、第4个参数一般为1.0f, 0。 device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.Color.Blue, 1.0f, 0); device.BeginScene(); //开始渲染 //渲染代码必须放在device.BeginScene()和device.Present()之间 device.EndScene(); //渲染结束 device.Present(); //更新显示区域,把后备缓存的3D图形送到屏幕显示区中显示 } private void Form1_Load(object sender, EventArgs e) { InitializeGraphics(); Show(); Render(); } } }
利用透视图的原理,可以在显示器屏幕上直接绘制具有立体感的平面图形。具体就是预先根据透视原理人工计算出3D物体在计算机显示器屏幕的显示坐标,然后再利用此坐标进行绘制。在Direct 3D中,CustomVertex.TransformedColored结构记录了已经根据透视原理计算后的顶点。
下面绘制一个静止的三角形,过程类似。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace Triangle { public partial class Form1 : Form { private Device device = null; CustomVertex.TransformedColored[] verts; public Form1() { InitializeComponent(); } public bool InitializeGraphics() { try { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; //不是全屏显示,在一个窗口显示 presentParams.SwapEffect = SwapEffect.Discard; //后备缓存交换的方式 presentParams.EnableAutoDepthStencil = true; //允许使用自动深度模板测试 //深度缓冲区单元为16位二进制数 presentParams.AutoDepthStencilFormat = DepthFormat.D16; device = new Device(0, DeviceType.Hardware, this, //建立设备类对象 CreateFlags.SoftwareVertexProcessing, presentParams); //设置设备重置事件(device.DeviceReset)事件函数为this.OnResetDevice device.DeviceReset += new System.EventHandler(this.OnResetDevice); this.OnCreateDevice(device, null);//自定义方法,初始化Device的工作放到这个方法中 this.OnResetDevice(device, null);//调用设备重置事件(device.DeviceReset)事件函数 } //设备重置事件函数要设置Device参数,初始函数中必须调用该函数 catch (DirectXException) { return false; } return true; } public void OnCreateDevice(object sender, EventArgs e) { verts = new CustomVertex.TransformedColored[3]; verts[0].Position = new Vector4(150.0f, 50.0f, 0.5f, 1.0f);//三角形的第1个顶点坐标 verts[0].Color = Color.Aqua.ToArgb(); //三角形的第1个顶点颜色 verts[1].Position = new Vector4(250.0f, 250.0f, 0.5f, 1.0f); //第2个顶点坐标 verts[1].Color = Color.Brown.ToArgb(); verts[2].Position = new Vector4(50.0f, 250.0f, 0.5f, 1.0f); //第3个顶点坐标 verts[2].Color = Color.LightPink.ToArgb(); } public void OnResetDevice(object sender, EventArgs e) { Render(); } public void Render() //渲染方法,本方法没有任何渲染代码,可认为是渲染方法的框架 { if (device == null) //如果未建立设备对象,退出 return; //下边函数将显示区域初始化为蓝色,第1个参数指定要初始化目标窗口 //第2个参数是我们所要填充的颜色。第3、第4个参数一般为1.0f, 0。 device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.Color.Blue, 1.0f, 0); device.BeginScene(); //开始渲染 //渲染代码必须放在device.BeginScene()和device.Present()之间 device.VertexFormat = CustomVertex.TransformedColored.Format; //渲染代码 device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts); device.EndScene(); //渲染结束 device.Present(); //更新显示区域,把后备缓存的3D图形送到图形卡的显存中显示 } private void Form1_Load(object sender, EventArgs e) { InitializeGraphics(); Show(); Render(); } } }
DrawUserPrimitives方法的第一个参数primitiveType可以取不同的值,可以绘制点、线段和三角形。
如果顶点的数组放到图形卡的显存中,将能极大地增加绘制图形的速度。可以用VertexBuffer类为数组申请存储空间,指定顶点数组的存放位置。如果3D程序运行在窗口模式,可能有多个运行程序共同占有计算机的图形卡和显存。窗体改变大小、窗体最小化后再最大化、全屏模式和窗体模式之间的切换等情况发生时,Device对象被重新设置,所以放到图形卡显存中顶点数组的数据可能会丢失,必须恢复VertexBuffer缓存区中的数据。比如说,最小化后程序使用的显存将被其他程序占用,所以在最大化后必须重建VertexBuffer类对象中顶点数组的数据。在需要重建VertexBuffer类对象时,系统产生CreateVertexBuffer事件,通知程序在事件处理函数中重建VertexBuffer类对象中的数据。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace VertexBufferTest { public partial class Form1 : Form { private Device device = null; bool pause = false; VertexBuffer vertexBuffer = null; public Form1() { InitializeComponent(); } public bool InitializeGraphics() { try { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; //不是全屏显示,在一个窗口显示 presentParams.SwapEffect = SwapEffect.Discard; //后备缓存交换的方式 presentParams.EnableAutoDepthStencil = true; //允许使用自动深度模板测试 //深度缓冲区单元为16位二进制数 presentParams.AutoDepthStencilFormat = DepthFormat.D16; device = new Device(0, DeviceType.Hardware, this, //建立设备类对象 CreateFlags.SoftwareVertexProcessing, presentParams); //设置设备重置事件(device.DeviceReset)事件函数为this.OnResetDevice device.DeviceReset += new System.EventHandler(this.OnResetDevice); this.OnCreateDevice(device, null);//自定义方法,初始化Device的工作放到这个方法中 this.OnResetDevice(device, null);//调用设备重置事件(device.DeviceReset)事件函数 } //设备重置事件函数要设置Device参数,初始函数中必须调用该函数 catch (DirectXException) { return false; } return true; } public void OnCreateDevice(object sender, EventArgs e) { Device dev = (Device)sender; vertexBuffer = new VertexBuffer(typeof(CustomVertex.TransformedColored), 3, dev, 0, CustomVertex.TransformedColored.Format, Pool.Default); //事件的预订,指定OnCreateVertexBuffer函数是vertexBuffer.Created事件函数 vertexBuffer.Created += new System.EventHandler(this.OnCreateVertexBuffer); this.OnCreateVertexBuffer(vertexBuffer, null); //创建顶点数组 } public void OnResetDevice(object sender, EventArgs e) { } public void Render() //渲染方法,本方法没有任何渲染代码,可认为是渲染方法的框架 { if (device == null) //如果未建立设备对象,退出 return; if (pause) return; device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.Color.Blue, 1.0f, 0); device.BeginScene(); //开始渲染 device.SetStreamSource(0, vertexBuffer, 0); //使用vertexBuffer中定义的顶点 device.VertexFormat = CustomVertex.TransformedColored.Format; //顶点格式 device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); device.EndScene(); //渲染结束 device.Present(); //更新显示区域,把后备缓存的D图形送到图形卡的显存中显示 } public void OnCreateVertexBuffer(object sender, EventArgs e) { CustomVertex.TransformedColored[] verts = (CustomVertex.TransformedColored[])vertexBuffer.Lock(0, 0); verts[0].X = 150; verts[0].Y = 50; //顶点0位置 verts[0].Z = 0.5f; verts[0].Rhw = 1; verts[0].Color = System.Drawing.Color.Aqua.ToArgb(); //顶点0颜色 verts[1].X = 250; verts[1].Y = 250; verts[1].Z = 0.5f; verts[1].Rhw = 1; verts[1].Color = System.Drawing.Color.Brown.ToArgb(); verts[2].X = 50; verts[2].Y = 250; verts[2].Z = 0.5f; verts[2].Rhw = 1; verts[2].Color = System.Drawing.Color.LightPink.ToArgb(); vertexBuffer.Unlock(); } private void Form1_Load(object sender, EventArgs e)//重写Load事件 { InitializeGraphics(); Show(); Render(); } private void Form1_Paint(object sender, PaintEventArgs e) //重写Paint事件 { this.Render(); } private void Form1_Resize(object sender, EventArgs e) //重写Resize事件 { pause = ((this.WindowState == FormWindowState.Minimized) || !this.Visible); } } }
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace 静止正方体 { public partial class Form1 : Form { private Device device = null; bool pause = false; VertexBuffer vertexBuffer = null; public Form1() { InitializeComponent(); } public bool InitializeGraphics() { try { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; //不是全屏显示,在一个窗口显示 presentParams.SwapEffect = SwapEffect.Discard; //后备缓存交换的方式 presentParams.EnableAutoDepthStencil = true; //允许使用自动深度模板测试 //深度缓冲区单元为16位二进制数 presentParams.AutoDepthStencilFormat = DepthFormat.D16; device = new Device(0, DeviceType.Hardware, this, //建立设备类对象 CreateFlags.SoftwareVertexProcessing, presentParams); //设置设备重置事件(device.DeviceReset)事件函数为this.OnResetDevice device.DeviceReset += new System.EventHandler(this.OnResetDevice); this.OnCreateDevice(device, null);//自定义方法,初始化Device的工作放到这个方法中 this.OnResetDevice(device, null);//调用设备重置事件(device.DeviceReset)事件函数 } //设备重置事件函数要设置Device参数,初始函数中必须调用该函数 catch (DirectXException) { return false; } return true; } public void OnCreateDevice(object sender, EventArgs e) { Device dev = (Device)sender; vertexBuffer = new VertexBuffer(typeof(CustomVertex.TransformedColored), 18, dev, 0, CustomVertex.TransformedColored.Format, Pool.Default); vertexBuffer.Created += new System.EventHandler(this.OnCreateVertexBuffer); this.OnCreateVertexBuffer(vertexBuffer, null); } public void OnResetDevice(object sender, EventArgs e) { } public void Render() //渲染方法,本方法没有任何渲染代码,可认为是渲染方法的框架 { if (device == null) //如果未建立设备对象,退出 return; if (pause) return; device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.LightBlue, 1.0f, 0); device.BeginScene(); //开始渲染 device.SetStreamSource(0, vertexBuffer, 0); device.VertexFormat = CustomVertex.TransformedColored.Format; device.DrawPrimitives(PrimitiveType.TriangleList, 0, 6); device.EndScene(); //渲染结束 device.Present(); //更新显示区域,把后备缓存的D图形送到图形卡的显存中显示 } public void OnCreateVertexBuffer(object sender, EventArgs e) { CustomVertex.TransformedColored[] verts = (CustomVertex.TransformedColored[])vertexBuffer.Lock(0, 0); verts[0].Position = new Vector4(100.0f, 50.0f, 0.5f, 1.0f); verts[0].Color = Color.Red.ToArgb(); verts[1].Position = new Vector4(200.0f, 50.0f, 0.5f, 1.0f); verts[1].Color = Color.Red.ToArgb(); verts[2].Position = new Vector4(50.0f, 100.0f, 0.5f, 1.0f); verts[2].Color = Color.Red.ToArgb(); verts[3].Position = new Vector4(50.0f, 100.0f, 0.5f, 1.0f); verts[3].Color = Color.Red.ToArgb(); verts[4].Position = new Vector4(200.0f, 50.0f, 0.5f, 1.0f); verts[4].Color = Color.Red.ToArgb(); verts[5].Position = new Vector4(150.0f, 100.0f, 0.5f, 1.0f); verts[5].Color = Color.Red.ToArgb(); verts[6].Position = new Vector4(50.0f, 100.0f, 0.5f, 1.0f); verts[6].Color = Color.Green.ToArgb(); verts[7].Position = new Vector4(150.0f, 100.0f, 0.5f, 1.0f); verts[7].Color = Color.Green.ToArgb(); verts[8].Position = new Vector4(50.0f, 200.0f, 0.5f, 1.0f); verts[8].Color = Color.Green.ToArgb(); verts[9].Position = new Vector4(50.0f, 200.0f, 0.5f, 1.0f); verts[9].Color = Color.Green.ToArgb(); verts[10].Position = new Vector4(150.0f, 100.0f, 0.5f, 1.0f); verts[10].Color = Color.Green.ToArgb(); verts[11].Position = new Vector4(150.0f, 200.0f, 0.5f, 1.0f); verts[11].Color = Color.Green.ToArgb(); verts[12].Position = new Vector4(150.0f, 100.0f, 0.5f, 1.0f); verts[12].Color = Color.Yellow.ToArgb(); verts[13].Position = new Vector4(200.0f, 50.0f, 0.5f, 1.0f); verts[13].Color = Color.Yellow.ToArgb(); verts[14].Position = new Vector4(150.0f, 200.0f, 0.5f, 1.0f); verts[14].Color = Color.Yellow.ToArgb(); verts[15].Position = new Vector4(150.0f, 200.0f, 0.5f, 1.0f); verts[15].Color = Color.Yellow.ToArgb(); verts[16].Position = new Vector4(200.0f, 50.0f, 0.5f, 1.0f); verts[16].Color = Color.Yellow.ToArgb(); verts[17].Position = new Vector4(200.0f, 150.0f, 0.5f, 1.0f); verts[17].Color = Color.Yellow.ToArgb(); vertexBuffer.Unlock(); } private void Form1_Load(object sender, EventArgs e) { InitializeGraphics(); Show(); Render(); } private void Form1_Paint(object sender, PaintEventArgs e) { this.Render(); } private void Form1_Resize(object sender, EventArgs e) { pause = ((this.WindowState == FormWindowState.Minimized) || !this.Visible); } } }
任何一个平面都可以由若干个三角形组成,用三个顶点定义三角形平面,Direct 3D在渲染着两个三角形时,左手笛卡尔坐标系默认情况下按顺时针方向绘制三角形,即只显示按顺时针绘制的三角形,这就是背面剔除。Device属性的RenderState.CullMode控制背面剔除方式。