Clayman的专栏

It's all about XNA & GPU Programming

用户操作
[即时聊天] [发私信] [加为好友]
claymanID:soilwork
192831次访问,排名382好友0人,关注者13
soilwork的文章
原创 85 篇
翻译 15 篇
转载 0 篇
评论 330 篇
clayman的公告
嘿嘿 ^o^....
最近评论
ffffk:研究到这的都是高手
jym5596337:我也不知道我怎么就走到了你的路上来了呵呵...
太晕了,我们专业就学的C#... 那我就凑合着用它学习MDX喽,但到了2008你的这个时期,感觉形式有点尴尬,以前的人说 在中国搞软件是 前有微软,后有盗版。
现在是 前有XNA后有C++ ... MDX 学习资料太太太难找了。。太太太少了。 师兄给介绍下你学历路途中的资料目录咯。。。我好找来学习咯。。 感谢哦感谢。。
jym5596337:好象没有继续哇 ...
shapin:ATI的网站有个支持HLSL语法高亮的vs插件,可以支持其他版本,只要修改相应的那个注册表就行
flip:To linxv :
貼圖座標有用投影嗎?
文章分类
收藏
    相册
    blogs
    David Weller
    nVidia Developer blog
    Rico Mariani
    Shawn Hargreaves
    XNA Team blog
    XNA资源
    XNA Creators Club
    ZBuffer
    Ziggyware XNA Resources
    中国XNA开发网
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    翻译 【翻译】Managed DirectX(第十章)收藏

    新一篇: 【翻译】Managed DirectX(第十一章)  | 旧一篇: 使用Managed DirectX编写游戏(-)

    第十章 使用助手类(Using the Helper Classes

     

    翻译:clayman
    clayman_joe@yahoo.com.cn
    仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^

    绘制直线

             在第四章里我们就讨论过关于绘制直线的问题:使用基本图元里的line listline 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方法前后的beginend方法让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方法加载指定的字符串.

    Rendering to Surfaces

             你是否玩过那种可以打开一个倒视镜的赛车游戏?或者可以在屏幕表面显示当前赛道的赛车游戏。这些效果都是通过把同一个场景(通常使用不同的摄像机)渲染为一个纹理来实现的。事实上,这虽然听起来很复杂,却相当容易实现。再从第五章的例子开始。

             首先自然先声明将用来渲染的纹理,添加代码:

    private Texture renderTexture = null;

    private Surface renderSurface = null;

    private RenderToSurface rts = null;

    private const int RenderSurfaceSize = 128;

    这里声明了用于渲染的纹理,实际所要渲染的表面,以及用于绘制表面的助手对象。我们同时还声明了将要创建的纹理大小。在InitializeGraphics方法中,订阅device reste事件来创建纹理以及表面。添加代码:

    device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);

    device.DeviceReset +=new EventHandler(OnDeviceReset);

    this.OnDeviceReset();

    接下来添加事件处理程序:

    private void OnDeviceReset(object sender, EventArgs e)

    {

         Device dev = (Device)sender;

         if(dev.DeviceCaps.VertexProcessingCaps.SupportsDirectionalLights)

         {

             uint masLights = (uint)dev.DeviceCaps.MaxActiveLights;

             if(maxLights > 0)

             {

                  dev.Lights[0].Type = LightType.Directional;

                  dev.Lights[0].Diffuse = Color.White;

                  dev.Lights[0].Direction = new Vector3(0,-1,-1);

                  dev.Lights[0].Update();

                  dev.Lights[0].Enabled = true;

             }

             if(maxLights > 1)

             {

                  dev.Lights[1].Type = LightType.Directional;

                  dev.Lights[1].Diffuse = Color.White;

                  dev.Lights[1].Direction = new Vector3(0,-1,1);

                  dev.Lights[1].Update();

                  dev.Lights[1].Enabled = true;

             }

         }

         rts = new RenderToSurface(dev,RenderSurfaceSize,RenderSurfaceSize,Format.X8B8G8R8,true,DepthFormat.D16);

         renderTexture = new Texture(dev,RenderSurfaceSize,RenderSurfaceSize,1,Usage.RenderTarget,Format.X8B8G8R8,Pool.Managed);

         renderSurface = renderTexture.GetSurfaceLevel(0);

    }

    这里对系统作了一些检查。首先看看它是否支持方向光,如果可以,就打开这些灯光,并假设他支持足够的可用灯光。我们使用了2个方法光,分别在模型的前面和后面。

    创建了灯光之后,通过之前定义的常量创建助手对象。你可能注意到了,这个构造函数所需的参数大都能从devicepresentaion parameter获得。这里使用了最常用的值,但通过presentParameter结构获得同样的值也是可以的。

    最后,创建纹理。注意把Usage设置为RenderTarger,因为我们很快就要在这张纹理上渲染。所有的渲染目标纹理都必须位于默认托管内存池中。同时,通过纹理获得实际的表面。

    既然这里设置好的灯光,把选来SetupCamera方法中的代码删除。接下来,添加一个方法来绘制表面。代码如下:

    private void RenderIntoSurface()

    {

         // Render to this surface

         Viewport view = new Viewport();

         view.Width = RenderSurfaceSize;

         view.Height = RenderSurfaceSize;

         view.MaxZ = 1.0f;

         rts.BeginScene(renderSurface, view);

         device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);

         device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1.0f, 10000.0f);

    device.Transform.View = Matrix.LookAtLH(new Vector3(0,0, -580.0f), new Vector3(), new Vector3(0, 1,0));

         DrawMesh(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f, angle / (float)Math.PI / 4.0f, 0.0f, 0.0f, 0.0f);

         DrawMesh(angle / (float)Math.PI, angle / (float)Math.PI / 2.0f, angle / (float)Math.PI * 4.0f, 150.0f, -100.0f, 175.0f);

    rts.EndScene(Filter.None);

    }

    这里和一般的渲染方法很类似。调用了BeginScene方法和EndScene方法,设置摄像机变换,绘制mesh。当绘制纹理时,实际上就是把场景中我们需要的对象渲染到纹理上。在这里,你应该注意到我们使用了同一个device,只是把它移动到了模型的另一面而已,这样就可以把模型的背面渲染为纹理。另外我们还在场景中绘制了2个模型。这样可以模拟场景中有两个模型:默认的摄像机之后还有一个模型,只能通过另一个面向相反方向的摄像机才能同时看到2个模型。

    注意,BeginScene方法使用了即将要渲染的表面作为参数。因为我们是通过纹理来获得这个表面的,任何对这个表面的更新都将会映射到纹理上。EndScene方法可以把一个mipmap过滤器应用到纹理上。为了避免检测显卡的能力,暂时不使用任何过滤器。最后要注意的一点是我们改天了纹理场景的clear color。这样做可以清楚的显示出“真实”的场景和“其他的”场景。

    自然,最后还需要稍微修改一下渲染方法。首先,把纹理渲染到主窗口之前对纹理进行渲染。在OnPaint方法的最前面添加如下代码:

    RenderIntoSurface();

    最后,可以真正把纹理显示到屏幕上了有一个我们将在以后章节讨论的Sprite类可以完成这个任务,他可以方便的使用屏幕坐标绘制纹理。在EndScene方法之前,添加如下代码:

    using (Sprite s = new Sprite(device))

    {

          s.Begin(SpriteFlags.None);

          s.Draw(renderTexture, new Rectangle(0, 0, RenderSurfaceSize, RenderSurfaceSize),new Vector3(0, 0, 0), new Vector3(0, 0, 1.0f), Color.White);

          s.End();

    }

    这段代码把纹理渲染到了屏幕的左上方,现在运行程序来看看吧。

     

    渲染环境贴图(Rendering Environment Maps

             环境贴图是用来模拟反光很强烈的表面的一门技术。你因该在赛场游戏里见过这种效果,赛车表面反映出天上的云,或者冰面上映出冰球选手的影子。实现环境贴图最常见的方法就是使用立体纹理(cube texture)也称天空盒(一个有六面的立方体纹理),现在就来学习这门技术吧。

             在开始编写代码之前,创建一个新工程,做好各种必要准备。我们将用到SDK中的两个模型:一个赛车模型,一个天空盒模型(带有纹理)。接下来就可以写代码了,先添加变量:

    private Mesh skybox = null;

    private Material[] skyboxMaterials;

    private Texture[] skyboxTexture;

    private Mesh car = null;

    private Material[] carMaterials;

    private Texture[] carTexture;

    private CubeTexture environment = null;

    private RenderToEnvironmentMap rte = null;

    private const int CubeMapSize = 128;

    private readonly Matrix ViewMatrix = Matrix.Translation(0.0f,0.0f,13.0f);

             这里声明了将要绘制的两个模型:天空盒(“环境”)与我们希望环境所反射到的对象——车。同时还需要cube texture对象来保存环境,以及用于渲染环境贴图的助手类。

             由于并不是所有图形卡都支持立体纹理,需要对显卡做一点点检测。在InitializeGraphics方法中完成这个任务,添加代码:

    public bool InitializeGraphics()

    {

         // Set our presentation parameters

         PresentParameters presentParams = new PresentParameters();

         presentParams.Windowed = true;

         presentParams.SwapEffect = SwapEffect.Discard;

         presentParams.AutoDepthStencilFormat = DepthFormat.D16;

         presentParams.EnableAutoDepthStencil = true;

         // Create our device

         device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);

         device.DeviceReset += new EventHandler(OnDeviceReset);

         OnDeviceReset(device,null);

         if(!device.DeviceCaps.TextureCaps.SupportsCubeMap)

             return false;

         //load our mesh

         skybox = LoadMesh(@"..\..\skybox2.x",ref skyboxMaterials,ref skyboxTextures);

         car = LoadMesh(@"..\..\car.x",ref carMaterials,ref carTexture);