深入Managed DirectX9(十七)
第十章 使用助手类(Using the Helper Classes)
绘制直线
在第四章里我们就讨论过关于绘制直线的问题:使用基本图元里的line list或line strip绘制直线。但是这两种直线都不能改变宽度,也没有抗锯齿功能(除非整个场景都使用了抗锯齿)。
对于不同类型的应用程序来说,绘制直线可能是最普通常见的操作,也可能根本不需要绘制他们。无论如何,有一个方便的Line类能在任何时候满足我们的需要。为了展现绘制线条是多么方便,我们将快速写一个程序来随即绘制一些线条。
创建一个新工程,为编写Direct3D程序做好准备。不需要再次重复这些简单的操作了吧。
public void InitializeGraphics()(略)
protected override void OnPaint(PaintEventArgs e)
{
device.Clear(ClearFlags.Target,Color.Black,1.0f,0);
device.BeginScene();
//Draw some lines
DrawRandomLines();
device.EndScene();
device.Present();
System.Threading.Thread.Sleep(500);
this.Invalidate();
}
这里没有什么新内容。只是在最后我们让线程休眠一小段时间,这样可以看清我们所绘制的线,接下来再次开始循环。显然,还没有定义DrawRandomLines方法,添加代码:
private void DrawRandomLines()
{
Random r = new Random();
int numberLines = r.Next(50);
using(Line l = new Line(device))
{
for(int i=0; i<numberLines; i++)
{
int numVectors = 0;
while(numVectors < 2)
numVectors = r.Next(4);
Vector2[] vecs = new Vector2[numVectors];
for(int inner = 0; inner < vecs.Length; inner++)
vecs[inner] = new Vector2(r.Next(this.Width),r.Next(this.Height));
Color c = Color.FromArgb(r.Next(byte.Maxvalue),r.Next(byte.Maxvalue),r.Next(byte.Maxvalue));
int width = 0;
while(width == 0)
width = r.Next(this.Width / 100);
l.Width = width;
l.Antialias = r.Next(50) > 25 ? true : false;
l.Begin();
l.Draw(vecs,c);
l.End();
}
}
}
每次调用这个方法的时候,都先创建一个随机数作为所要绘制线条的数量。创建一个line对象来分别绘制每一根线条。可以为每一条线都创建一个line对象,可一个创建一个“全局”的line对象,当然前者让代码更容易看懂。
接下来,随机选择这条线条中的点。必须保证最少有2个点。在决定了线条中将有几个点之后,根据当先窗口的高度和宽度产生随机数,作为线条中线段的终点和起点。我们还随机选择了线条的宽度以及颜色。当然,线条的宽度也是基于窗口宽度生成的。同样,是否抗锯齿也是随机选择的。可以看到没有抗锯齿的线条(特别是很宽的那种)呈明显锯齿状。最后,绘制直线。Draw方法前后的begin和end方法让Direct3D知道所绘制的是直线。
虽然这里没有提到,但还有一些其他属性可以用来控制如何绘制直线。一个名为GlLines的布尔变量可以用来选择时候绘制OpenGl风格的线条(默认值为false)。还可以使用DrawTransform方法在三维空间里绘制。
绘制文本
同样,绘制文本也是前面讨论过的内容。但只学了一点点而已,这次我们将会讨论的深入一些。在前面几章里,我们知道Microsoft.DirectX.Direct3D名称空间和System.Drawing名称空间下都有一个Font类。使用如下的语句来帮助区别他们:
using Direct3D = Microsoft.DirectX.Direct3D;
这样可以把整个名称空间缩写为Direct3D。创建新工程,添加如下变量:
这里我们声明了将要在屏幕表面绘制的字体,以及一个mesh和相应的材质对象。Mesh将作为一个拉伸的三维文本模型。Angle参数用于控制3维文本的旋转。现在初始化图形:
public void InitializeGraphics()
{
PresentParameters presentParams = new PresentParameters ();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;
presentParams.EnableAutoDepthStencil = true;
device = new Device(0,DeviceType.Hardware,this,CreateFlags.HardwareVertexProcessing,presentParams);
device.DeviceReset +=new EventHandler(this.OnDeviceReset);
OnDeviceReset(device,null);
System.Drawing.Font localFont = new System.Drawing.Font("Arial",14.0f,FontStyle.Italic);
mesh = Mesh.TextFromFont(device,localFont,"Managed DirectX",0.001f,0.4f);
meshMaterial = new Material();
meshMaterial.Diffuse = Color.Peru;
font = new Microsoft.DirectX.Direct3D.Font(device,localFont);
}
我们创建了一个拥有深度缓冲的设备,并为他订阅了DeviceReset事件。因为每次重置设备时,只需要设置灯光和摄像机,所以我们把它放到单独的事件处理程序中。最后,创建了System.Drawing.Font对象作为2维和3维文本的基础。我们选择了14个像素大小的Arial字体。首先使用字体对象拉伸出了三维字体的mesh。我们使用了字符串“Managed DirectX”来拉伸。当然你可以使用其它任何喜欢的字符串。接下来,设置了材质的颜色,创建2维字体。
在OnDeviceReset方法中设置摄像机以及灯光:
private void OnDeviceReset(object sender, EventArgs e)
{
Device dev = (Device)sender;
dev.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI/4, this.Width/this.Height,1.0f,100.0f);
dev.Transform.View = Matrix.LookAtLH(new Vector3(0,0,9.0f),new Vector3(),new Vector3(0,1,0));
dev.Lights[0].Type = LightType.Directional;
dev.Lights[0].Diffuse = Color.White;
dev.Lights[0].Direction = new Vector3(0,0,1);
dev.Lights[0].Update();
dev.Lights[0].Enabled = true;
}
摄像机和灯光都是为了拉伸的三维字体才创建的。二维的字体已经经过变换而起是照亮了的。但是,拉伸的三维字体是真实的模型,所以需要设置灯光和摄像机。添加绘制三维字体的方法:
private void Draw3DText(Vector3 axis, Vector3 location)
{
device.Transform.World = Matrix.RotationAxis(axis,angle) * Matrix.Translation(location);
device.Material = meshMaterial;
mesh.DrawSubset(0);
angle += 0.01f;
}
如你所见,我们传入mesh在世界坐标中的位置,以及旋转轴。这个方法和之前的DrawMeshff是很相似的:设置材质,绘制第一个子集。我们还增加了旋转角度,这样做的结果是动画将基于帧速率。接下来添加绘制2为字体的代码:
private void Draw2DText(string text,int x,int y,Color c)
{
font.DrawText(null,text,new Rectangle(x,y,this.Width,this.Heightk),DrawTextFormat.NoClip | DrawTextFormat.ExpandTabs| DrawTextFormat.WordBreak, c);
}
这里的代码也很简单吧。你可能注意到了我们把使用窗口宽度和高度创建的矩形作为参数。这样做的原因是使用了WordBreak标志,可以在文本超出了绑定的矩形范围之后自动换行。我们也希望文本中的制表符被正确的拉伸,同时,字体不会被裁减了。
有了这两个主要的绘制字体的方法,在OnPaint中添加代码:
protected override void OnPaint(PaintEventArgs e)
{
device.Clear(ClearFlags.Target,Color.Black,1.0f,0);
device.BeginScene();
Draw2DText("Here's some text",10,10,Color.WhiteSmoke);
Draw2DText("Here's some text\t\nwith\r\nhard\r\nline breaks",100,80,Color.Violet);
Draw2DText("This\tis\tsome\ttext\twith\ttabs.",this.Width/2,this.Height - 80,Color.RoyalBlue);
Draw2DText("If you type enough words in a single sentecne you may notice that tha text begins to warp."+"Try resizing the window to notice how the text changes as you size it.",this.Width/2+this.Width/4,this.Height/4,Color.Yellow);
Draw3DText(new Vector3(1.0f, 1.0f, 0.0f), new Vector3(-3.0f, 0.0f, 0.0f));
Draw3DText(new Vector3(0.0f, 1.0f, 1.0f), new Vector3(0.0f, -1.0f, 1.0f));
device.EndScene();
device.Present();
this.Invalidate();
}
这里我们绘制了几种不同的字符串:包含换函符和回车符的,包含制表符的,以及长句。对于长句,我们希望他会正确的换行。(注:调试程序的时候,长句的换行总是不正确,包括作者的源码显示也不正确,书上的结图却是正确的,郁闷了-_-#)
特别提示:提高字体性能
Font用于绘制文本的字体是基于纹理的。把这些字体绘制为纹理是通过GDI来完成的,相当缓慢。最好在开始时使用font类的预载方法,保证不会再运行时遇到几次这样的加载。可以调用PreloadCharacters方法来加载指定的字体,或者使用PreloadText方法加载指定的字符串。
~~~~~~~~~~~~~~~~~未完待续~~~~~~~~~~~~~~~~~~
下载代码
绘制直线
在第四章里我们就讨论过关于绘制直线的问题:使用基本图元里的line list或line strip绘制直线。但是这两种直线都不能改变宽度,也没有抗锯齿功能(除非整个场景都使用了抗锯齿)。
对于不同类型的应用程序来说,绘制直线可能是最普通常见的操作,也可能根本不需要绘制他们。无论如何,有一个方便的Line类能在任何时候满足我们的需要。为了展现绘制线条是多么方便,我们将快速写一个程序来随即绘制一些线条。
创建一个新工程,为编写Direct3D程序做好准备。不需要再次重复这些简单的操作了吧。
public void InitializeGraphics()(略)
protected override void OnPaint(PaintEventArgs e)
{
device.Clear(ClearFlags.Target,Color.Black,1.0f,0);
device.BeginScene();
//Draw some lines
DrawRandomLines();
device.EndScene();
device.Present();
System.Threading.Thread.Sleep(500);
this.Invalidate();
}
这里没有什么新内容。只是在最后我们让线程休眠一小段时间,这样可以看清我们所绘制的线,接下来再次开始循环。显然,还没有定义DrawRandomLines方法,添加代码:
private void DrawRandomLines()
{
Random r = new Random();
int numberLines = r.Next(50);
using(Line l = new Line(device))
{
for(int i=0; i<numberLines; i++)
{
int numVectors = 0;
while(numVectors < 2)
numVectors = r.Next(4);
Vector2[] vecs = new Vector2[numVectors];
for(int inner = 0; inner < vecs.Length; inner++)
vecs[inner] = new Vector2(r.Next(this.Width),r.Next(this.Height));
Color c = Color.FromArgb(r.Next(byte.Maxvalue),r.Next(byte.Maxvalue),r.Next(byte.Maxvalue));
int width = 0;
while(width == 0)
width = r.Next(this.Width / 100);
l.Width = width;
l.Antialias = r.Next(50) > 25 ? true : false;
l.Begin();
l.Draw(vecs,c);
l.End();
}
}
}
每次调用这个方法的时候,都先创建一个随机数作为所要绘制线条的数量。创建一个line对象来分别绘制每一根线条。可以为每一条线都创建一个line对象,可一个创建一个“全局”的line对象,当然前者让代码更容易看懂。
接下来,随机选择这条线条中的点。必须保证最少有2个点。在决定了线条中将有几个点之后,根据当先窗口的高度和宽度产生随机数,作为线条中线段的终点和起点。我们还随机选择了线条的宽度以及颜色。当然,线条的宽度也是基于窗口宽度生成的。同样,是否抗锯齿也是随机选择的。可以看到没有抗锯齿的线条(特别是很宽的那种)呈明显锯齿状。最后,绘制直线。Draw方法前后的begin和end方法让Direct3D知道所绘制的是直线。
虽然这里没有提到,但还有一些其他属性可以用来控制如何绘制直线。一个名为GlLines的布尔变量可以用来选择时候绘制OpenGl风格的线条(默认值为false)。还可以使用DrawTransform方法在三维空间里绘制。
绘制文本
同样,绘制文本也是前面讨论过的内容。但只学了一点点而已,这次我们将会讨论的深入一些。在前面几章里,我们知道Microsoft.DirectX.Direct3D名称空间和System.Drawing名称空间下都有一个Font类。使用如下的语句来帮助区别他们:
using Direct3D = Microsoft.DirectX.Direct3D;
这样可以把整个名称空间缩写为Direct3D。创建新工程,添加如下变量:
这里我们声明了将要在屏幕表面绘制的字体,以及一个mesh和相应的材质对象。Mesh将作为一个拉伸的三维文本模型。Angle参数用于控制3维文本的旋转。现在初始化图形:
public void InitializeGraphics()
{
PresentParameters presentParams = new PresentParameters ();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;
presentParams.EnableAutoDepthStencil = true;
device = new Device(0,DeviceType.Hardware,this,CreateFlags.HardwareVertexProcessing,presentParams);
device.DeviceReset +=new EventHandler(this.OnDeviceReset);
OnDeviceReset(device,null);
System.Drawing.Font localFont = new System.Drawing.Font("Arial",14.0f,FontStyle.Italic);
mesh = Mesh.TextFromFont(device,localFont,"Managed DirectX",0.001f,0.4f);
meshMaterial = new Material();
meshMaterial.Diffuse = Color.Peru;
font = new Microsoft.DirectX.Direct3D.Font(device,localFont);
}
我们创建了一个拥有深度缓冲的设备,并为他订阅了DeviceReset事件。因为每次重置设备时,只需要设置灯光和摄像机,所以我们把它放到单独的事件处理程序中。最后,创建了System.Drawing.Font对象作为2维和3维文本的基础。我们选择了14个像素大小的Arial字体。首先使用字体对象拉伸出了三维字体的mesh。我们使用了字符串“Managed DirectX”来拉伸。当然你可以使用其它任何喜欢的字符串。接下来,设置了材质的颜色,创建2维字体。
在OnDeviceReset方法中设置摄像机以及灯光:
private void OnDeviceReset(object sender, EventArgs e)
{
Device dev = (Device)sender;
dev.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI/4, this.Width/this.Height,1.0f,100.0f);
dev.Transform.View = Matrix.LookAtLH(new Vector3(0,0,9.0f),new Vector3(),new Vector3(0,1,0));
dev.Lights[0].Type = LightType.Directional;
dev.Lights[0].Diffuse = Color.White;
dev.Lights[0].Direction = new Vector3(0,0,1);
dev.Lights[0].Update();
dev.Lights[0].Enabled = true;
}
摄像机和灯光都是为了拉伸的三维字体才创建的。二维的字体已经经过变换而起是照亮了的。但是,拉伸的三维字体是真实的模型,所以需要设置灯光和摄像机。添加绘制三维字体的方法:
private void Draw3DText(Vector3 axis, Vector3 location)
{
device.Transform.World = Matrix.RotationAxis(axis,angle) * Matrix.Translation(location);
device.Material = meshMaterial;
mesh.DrawSubset(0);
angle += 0.01f;
}
如你所见,我们传入mesh在世界坐标中的位置,以及旋转轴。这个方法和之前的DrawMeshff是很相似的:设置材质,绘制第一个子集。我们还增加了旋转角度,这样做的结果是动画将基于帧速率。接下来添加绘制2为字体的代码:
private void Draw2DText(string text,int x,int y,Color c)
{
font.DrawText(null,text,new Rectangle(x,y,this.Width,this.Heightk),DrawTextFormat.NoClip | DrawTextFormat.ExpandTabs| DrawTextFormat.WordBreak, c);
}
这里的代码也很简单吧。你可能注意到了我们把使用窗口宽度和高度创建的矩形作为参数。这样做的原因是使用了WordBreak标志,可以在文本超出了绑定的矩形范围之后自动换行。我们也希望文本中的制表符被正确的拉伸,同时,字体不会被裁减了。
有了这两个主要的绘制字体的方法,在OnPaint中添加代码:
protected override void OnPaint(PaintEventArgs e)
{
device.Clear(ClearFlags.Target,Color.Black,1.0f,0);
device.BeginScene();
Draw2DText("Here's some text",10,10,Color.WhiteSmoke);
Draw2DText("Here's some text\t\nwith\r\nhard\r\nline breaks",100,80,Color.Violet);
Draw2DText("This\tis\tsome\ttext\twith\ttabs.",this.Width/2,this.Height - 80,Color.RoyalBlue);
Draw2DText("If you type enough words in a single sentecne you may notice that tha text begins to warp."+"Try resizing the window to notice how the text changes as you size it.",this.Width/2+this.Width/4,this.Height/4,Color.Yellow);
Draw3DText(new Vector3(1.0f, 1.0f, 0.0f), new Vector3(-3.0f, 0.0f, 0.0f));
Draw3DText(new Vector3(0.0f, 1.0f, 1.0f), new Vector3(0.0f, -1.0f, 1.0f));
device.EndScene();
device.Present();
this.Invalidate();
}
这里我们绘制了几种不同的字符串:包含换函符和回车符的,包含制表符的,以及长句。对于长句,我们希望他会正确的换行。(注:调试程序的时候,长句的换行总是不正确,包括作者的源码显示也不正确,书上的结图却是正确的,郁闷了-_-#)
特别提示:提高字体性能
Font用于绘制文本的字体是基于纹理的。把这些字体绘制为纹理是通过GDI来完成的,相当缓慢。最好在开始时使用font类的预载方法,保证不会再运行时遇到几次这样的加载。可以调用PreloadCharacters方法来加载指定的字体,或者使用PreloadText方法加载指定的字符串。
~~~~~~~~~~~~~~~~~未完待续~~~~~~~~~~~~~~~~~~
下载代码