** 第十章 使用助手类( ** ** Using the Helper Classes ** ** ) **
** 翻译:clayman
[email protected]
仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^ **
**绘制直线
**
在第四章里我们就讨论过关于绘制直线的问题:使用基本图元里的 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
1<numberlines; 100);="" 2)="" <="" c="Color.FromArgb(r.Next(byte.MaxValue),r.Next(byte.MaxValue),r.Next(byte.MaxValue));" color="" for(int="" i++)="" inner="" inner++)="" int="" l.antialias="r.Next(50)" l.width="width;" numvectors="r.Next(4);" vecs="new" vecs.length;="" vecs[inner]="new" vector2(r.next(this.width),r.next(this.height));="" vector2[]="" vector2[numvectors];="" while(numvectors="" while(width="0)" width="r.Next(this.Width" {=""> 25 ? true : false;
2
3l.Begin();
4
5l.Draw(vecs,c);
6
7l.End();
8
9}
10
11}
12
13}
14
15每次调用这个方法的时候,都先创建一个随机数作为所要绘制 线条 的数量。创建一个 line 对象来分别绘制每一根 线条 。可以为每一条线都创建一个 line 对象,可一个创建一个“全局”的 line 对象,当然前者让代码更容易看懂。
16
17接下来,随机选择这条 线条 中的点。必须保证最少有 2 个点。在决定了线条中将有几个点之后,根据当先窗口的高度和宽度产生随机数,作为线条中线段的终点和起点。我们还随机选择了线条的宽度以及颜色。当然,线条的宽度也是基于窗口宽度生成的。同样,是否抗锯齿也是随机选择的。可以看到没有抗锯齿的线条(特别是很宽的那种)呈明显锯齿状。最后,绘制直线。 Draw 方法前后的 begin 和 end 方法让 Direct3D 知道所绘制的是直线。
18
19
20
21虽然这里没有提到,但还有一些其他属性可以用来控制如何绘制直线。一个名为 GlLines 的布尔变量可以用来选择时候绘制 OpenGl 风格的线条(默认值为 false )。还可以使用 DrawTransform 方法在三维空间里绘制。
22
23**绘制文本
24
25**
26
27同样,绘制文本也是前面讨论过的内容。但只学了一点点而已,这次我们将会讨论的深入一些。在前面几章里,我们知道 Microsoft.DirectX.Direct3D 名称空间和 System.Drawing 名称空间下都有一个 Font 类。使用如下的语句来帮助区别他们:
28
29using Direct3D = Microsoft.DirectX.Direct3D;
30
31这样可以把整个名称空间缩写为 Direct3D 。创建新工程,添加如下变量:
32
33这里我们声明了将要在屏幕表面绘制的字体,以及一个 mesh 和相应的材质对象。 Mesh 将作为一个拉伸的三维文本模型。 Angle 参数用于控制 3 维文本的旋转。现在初始化图形:
34
35public void InitializeGraphics()
36
37{
38
39PresentParameters presentParams = new PresentParameters ();
40
41presentParams.Windowed = true;
42
43presentParams.SwapEffect = SwapEffect.Discard;
44
45presentParams.AutoDepthStencilFormat = DepthFormat.D16;
46
47presentParams.EnableAutoDepthStencil = true;
48
49device = new Device(0,DeviceType.Hardware,this,CreateFlags.HardwareVertexProcessing,presentParams);
50
51device.DeviceReset +=new EventHandler(this.OnDeviceReset);
52
53OnDeviceReset(device,null);
54
55System.Drawing.Font localFont = new System.Drawing.Font("Arial", 14.0f ,FontStyle.Italic);
56
57mesh = Mesh.TextFromFont(device,localFont,"Managed DirectX", 0.001f , 0.4f );
58
59meshMaterial = new Material();
60
61meshMaterial.Diffuse = Color.Peru;
62
63font = new Microsoft.DirectX.Direct3D.Font(device,localFont);
64
65}
66
67我们创建了一个拥有深度缓冲的设备,并为他订阅了 DeviceReset 事件。因为每次重置设备时,只需要设置灯光和摄像机,所以我们把它放到单独的事件处理程序中。最后,创建了 System.Drawing.Font 对象作为 2 维和 3 维文本的基础。我们选择了 14 个像素大小的 Arial 字体。首先使用字体对象拉伸出了三维字体的 mesh 。我们使用了字符串“ Managed DirectX ”来拉伸。当然你可以使用其它任何喜欢的字符串。接下来,设置了材质的颜色,创建 2 维字体。
68
69在 OnDeviceReset 方法中设置摄像机以及灯光:
70
71private void OnDeviceReset(object sender, EventArgs e)
72
73{
74
75Device dev = (Device)sender;
76
77dev.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI/4, this.Width/this.Height, 1.0f , 100.0f );
78
79dev.Transform.View = Matrix.LookAtLH(new Vector3(0,0, 9.0f ),new Vector3(),new Vector3(0,1,0));
80
81dev.Lights[0].Type = LightType.Directional;
82
83dev.Lights[0].Diffuse = Color.White;
84
85dev.Lights[0].Direction = new Vector3(0,0,1);
86
87dev.Lights[0].Update();
88
89dev.Lights[0].Enabled = true;
90
91}
92
93摄像机和灯光都是为了拉伸的三维字体才创建的。二维的字体已经经过变换而起是照亮了的。但是,拉伸的三维字体是真实的模型,所以需要设置灯光和摄像机。添加绘制三维字体的方法:
94
95private void Draw3DText(Vector3 axis, Vector3 location)
96
97{
98
99device.Transform.World = Matrix.RotationAxis(axis,angle) * Matrix.Translation(location);
100
101device.Material = meshMaterial;
102
103mesh.DrawSubset(0);
104
105angle += 0.01f ;
106
107}
108
109如你所见,我们传入 mesh 在世界坐标中的位置,以及旋转轴。这个方法和之前的 DrawMeshff 是很相似的:设置材质,绘制第一个子集。我们还增加了旋转角度,这样做的结果是动画将基于帧速率。接下来添加绘制 2 为字体的代码:
110
111private void Draw2DText(string text,int x,int y,Color c)
112
113{
114
115font.DrawText(null,text,new Rectangle(x,y,this.Width,this.Heightk),DrawTextFormat.NoClip | DrawTextFormat.ExpandTabs| DrawTextFormat.WordBreak, c);
116
117}
118
119这里的代码也很简单吧。你可能注意到了我们把使用窗口宽度和高度创建的矩形作为参数。这样做的原因是使用了 WordBreak 标志,可以在文本超出了绑定的矩形范围之后自动换行。我们也希望文本中的制表符被正确的拉伸,同时,字体不会被裁减了。
120
121有了这两个主要的绘制字体的方法,在 OnPaint 中添加代码:
122
123protected override void OnPaint(PaintEventArgs e)
124
125{
126
127device.Clear(ClearFlags.Target,Color.Black, 1.0f ,0);
128
129device.BeginScene();
130
131Draw2DText("Here's some text",10,10,Color.WhiteSmoke);
132
133Draw2DText("Here's some text\t\nwith\r\nhard\r\nline breaks",100,80,Color.Violet);
134
135Draw2DText("This\tis\tsome\ttext\twith\ttabs.",this.Width/2,this.Height - 80,Color.RoyalBlue);
136
137Draw2DText("If you type enough words in a single sentecne you may notice that tha text begins to warp."+
138
139"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);
140
141Draw3DText(new Vector3( 1.0f , 1.0f , 0.0f ), new Vector3( -3.0f , 0.0f , 0.0f ));
142
143Draw3DText(new Vector3( 0.0f , 1.0f , 1.0f ), new Vector3( 0.0f , -1.0f , 1.0f ));
144
145device.EndScene();
146
147device.Present();
148
149this.Invalidate();
150
151}
152
153这里我们绘制了几种不同的字符串:包含换函符和回车符的,包含制表符的,以及长句。对于长句,我们希望他会正确的换行。(注:调试程序的时候,长句的换行总是不正确,包括作者的源码显示也不正确,书上的结图却是正确的,郁闷了 -_-# )
154
155
156
157特别提示:提高字体性能
158
159Font 用于绘制文本的字体是基于纹理的。把这些字体绘制为纹理是通过 GDI 来完成的,相当缓慢。最好在开始时使用 font 类的预载方法,保证不会再运行时遇到几次这样的加载。可以调用 PreloadCharacters 方法来加载指定的字体,或者使用 PreloadText 方法加载指定的字符串。
160
161** Rendering to Surfaces
162
163**
164
165你是否玩过那种可以打开一个倒视镜的赛车游戏?或者可以在屏幕表面显示当前赛道的赛车游戏。这些效果都是通过把同一个场景(通常使用不同的摄像机)渲染为一个纹理来实现的。事实上,这虽然听起来很复杂,却相当容易实现。再从第五章的例子开始。
166
167首先自然先声明将用来渲染的纹理,添加代码:
168
169private Texture renderTexture = null;
170
171private Surface renderSurface = null;
172
173private RenderToSurface rts = null;
174
175private const int RenderSurfaceSize = 128;
176
177这里声明了用于渲染的纹理,实际所要渲染的表面,以及用于绘制表面的助手对象。我们同时还声明了将要创建的纹理大小。在 InitializeGraphics 方法中,订阅 device reste 事件来创建纹理以及表面。添加代码:
178
179device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
180
181device.DeviceReset +=new EventHandler(OnDeviceReset);
182
183this.OnDeviceReset();</numberlines;>