【翻译】Managed DirectX(第十章)

[原创]Managed DirectX 基础使用教程(一) hangtao_me@yahoo.com.cn 本次内容:如何用Managed  DirectX 显示图片(2D),以及一些变形.上回我们已经学习了MDX的一些基本知识,现在我们就要来使用它了.准备:创建一个Windows 窗体应用程序然后在引用中右键添加引用然后选择需要的.NET项目然后在代码中写上相应的using .......现在准备工作已经做好了,那么在 阅读详情

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

 

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

绘制直线

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

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.X8B 8G 8R8,true,DepthFormat.D16);

     renderTexture = new Texture(dev,RenderSurfaceSize,RenderSurfaceSize,1,Usage.RenderTarget,Format.X8B 8G 8R8,Pool.Managed);

     renderSurface = renderTexture.GetSurfaceLevel(0);

}

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

创建了灯光之后,通过之前定义的常量创建助手对象。你可能注意到了,这个构造函数所需的参数大都能从device的presentaion 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);

     return true;         

}

         这里主要的改变就是InitializeGraphics方法现在会返回一个布尔值来显示是否执行成功了。同时他订阅了DeviceReset事件,并使用一个新的LoadMesh方法来加载模型。在编写这几个方法之前,更新main()方法来处理InitializeGraphics返回的值。

static void Main ()

{

     using (Form1 frm = new Form1())

     {

         frm.Show();

         if(!frm.InitializeGraphics())                 

              MessageBox.Show("your card does not support cube maps.");

         else

              Application.Run(frm);

       }

}

         这里没有什么特别内容,接下来看看LoadMesh方法。和之前的方法差不多。加载了Mesh,纹理,材质之后,保证数据包含法线信息。之后,返回mesh。添加代码:

private Mesh LoadMesh(string file,ref Material[] meshMaterials,ref Texture[] meshTextures)

{

    ExtendedMaterial[] mtrl;

    Mesh mesh = Mesh.FromFile(file, MeshFlags.Managed, device, out mtrl);

    if ((mtrl != null) && (mtrl.Length > 0))

    {

        meshMaterials = new Material[mtrl.Length];

        meshTextures = new Texture[mtrl.Length];

        for (int i = 0; i < mtrl.Length; i++)

        {

             meshMaterials[i] = mtrl[i].Material3D;

             if ((mtrl[i].TextureFilename != null) && (mtrl[i].TextureFilename != string.Empty))

meshTextures[i] = TextureLoader.FromFile(device, @"../../" + mtrl[i].TextureFilename);           

          }

     }

     if((mesh.VertexFormat & VertexFormats.Normal) != VertexFormats.Normal)

     {

         Mesh tempMesh = mesh.Clone(mesh.Options.Value,mesh.VertexFormat | VertexFormats.Normal,device);

         tempMesh.ComputeNormals();

         mesh.Dispose();

         mesh = tempMesh;

     }

     return mesh;

}

         接下来,就像上一个例子,创建渲染目标表面来储存环境贴图。在OnDeviceReset方法中实现:

private void OnDeviceReset(object sender, EventArgs e)

{

     Device dev = (Device)sender;

     rte = new RenderToEnvironmentMap(dev, CubeMapSize, 1, Format.X8R 8G 8B8, true, DepthFormat.D16);

     environment = new CubeTexture(dev, CubeMapSize, 1,Usage.RenderTarget, Format.X8R 8G 8B8, Pool.Default);

}

         这里同时创建了助手对象和立体纹理。这里使用的尺寸越大(这里使用了CubeMapSize常理),环境贴图的细节就越丰富。通常情况下,把环境贴图保存为静态的会更加高兴,这样就可以直接通过一个文件(或其他数据源)加载立体纹理。这里为了展示如何在渲染时动态创建环境贴图才这么做,接下来创建所渲染的目标纹理。

         和上一个知渲染一个纹理的例子差不多,这里我们需要在场景中进行多次渲染。这里需要额外的六次渲染。(立方体的每个面一次),添加以下代码来渲染环境贴图):

private void RenderSceneIntoEnvMap

{

     Matrix matProj;

     matProj = Matrix.PerspectiveFovLH((float)Math.PI * 0.5f , 1.0f , 0.5f , 1000.0f );

     Matrix matViewDir = ViewMatrix;

     matViewDir.M41 = 0.0f ; matViewDir.M42 = 0.0f ; matViewDir.M43 = 0.0f ;

     if(environment != null)

         rte.BeginCube(environment);

     for(int i=0;i<6;i++)

     {

         rte.Face((CubeMapFace)i,1);

         Matrix matView = Matrix.Multiply(matViewDir,GetCubeMapViewMatrix((CubeMapFace)i));

enderScene(matView,matProj,false);

     }

     rte.End(1);

}

         这个方法完成了很多工作。首先,我们创建了一个视野范围为90度的投影矩阵,因为立方体每个面之间的夹角恰好为90度。接下来保存这个矩阵,并且修改了最后一行,以保证对于每个面,都可以使用这个矩阵和观察矩阵相乘。

         接下来,调用助手类的BeginCube方法,让系统知道我们即将开始创建立体的环境贴图。这个类还有一些其他的方法可以用来创建不同类型的环境贴图,包括BeginHemisphere、BeginParabolic(它将使用2个面,一个在z轴正向,一个位于z轴负方向)、以及BeginSphere(使用一个单独的面)。

         在准备好了渲染环境贴图之后,对每个面作一次迭代:对每个面来调用face方法,这个方法和渲染时的BeginScene方法类似,表示上一个面已经处理完毕,可以渲染新的面了。之后,把当前矩阵与通过GetCubeMapViewMatrix获得的矩阵相乘。这个方法的定义如下:

private Matrix GetCubeMapViewMatrix(CubeMapFace face)

{

     Vector3 vEyept = new Vector3();

     Vector3 vLookDir = new Vector3();

     Vector3 vUpDir = new Vector3();

     switch(face)

     {

         case CubeMapFace.PositiveX:

              vLookDir = new Vector3( 1.0f , 0.0f , 0.0f );

              vUpDir   = new Vector3( 0.0f , 1.0f , 0.0f );

              break;

         case CubeMapFace.NegativeX:

              vLookDir = new Vector3( -1.0f , 0.0f , 0.0f );

              vUpDir   = new Vector3( 0.0f , 1.0f , 0.0f );

              break;

         case CubeMapFace.PositiveY:

              vLookDir = new Vector3( 0.0f , 1.0f , 0.0f );

              vUpDir   = new Vector3( 0.0f , 0.0f , -1.0f );

              break;

         case CubeMapFace.NegativeY:

              vLookDir = new Vector3( 0.0f , -1.0f , 0.0f );

              vUpDir   = new Vector3( 0.0f , 0.0f , 1.0f );

              break;

         case CubeMapFace.PositiveZ:

              vLookDir = new Vector3( 0.0f , 0.0f , 1.0f );

              vUpDir   = new Vector3( 0.0f , 1.0f , 0.0f );

              break;

         case CubeMapFace.NegativeZ:

              vLookDir = new Vector3( 0.0f , 0.0f , -1.0f );

              vUpDir   = new Vector3( 0.0f , 1.0f , 0.0f );

              break;

     }       

     Matrix matView = Matrix.LookAtLH(vEyePt,vLookDir,vUpDir);

     return matView;

}

         根据不同的面,我们修改观察参数,最后依据这些点返回观察矩阵。到这里,做完了这些之后,就可以开始渲染一个还没有车的场景了。添加RenderScene方法:

private void RenderScene(Matrix View,Matrix Project,bool shouldRenderCar)

{

     device.Transform.World = Matrix.Scaling( 10.0f , 10.0f , 10.0f );

     Matrix matView = View;

     matView.M41 = matView.M42 = matView.M43 = 0.0f ;

     device.Transform.View = matView;

     device.Transform.Projection = Profect;

     device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;

     device.TextureState[0].ColorOperation = TextureOperation.SelectArg1;

     device.SamplerState[0].MinFilter = TextureFilter.Linear;

     device.SamplerState[0].MagFilter = TextureFilter.Linear;

     device.SamplerState[0].AddressU = TextureAddress.Mirror;

     device.SamplerState[0].AddressV = TextureAddress.Mirror;

     device.RenderState.ZBufferFunction = Compare.Always;

     DrawSkyBox();

     device.RenderState.ZBufferFunction = Compare.LessEqual;

     if(shouldRenderCar)

     {

         device.Transform.View = View;

         device.Transform.Projection = Project;

         using(VertexBuffer vb = car.VertexBuffer)

         {

              using(IndexBuffer ib = car.IndexBuffer)

              {

                   device.SetStreamSource(0,vb,0,VertexInformation.GetFormatSize(car.VertexFormat));

                   device.VertexFormat = car.VertexFormat;

                   device.Indces = ib;

                   device.SetTexture(0,environment);

                   device.SamplerState[0].MinFilter = TextureFilter.Linear;

                   device.SamplerState[0].MagFilter = TextureFilter.Linear;

                   device.SamplerState[0].AddressV = TextureAddress.Clamp;

                   device.SamplerState[0].AddressU = TextureAddress.Clamp;

                   device.SamplerState[0].AddressW = TextureAddress.Clamp;

                   device.TextureState[0].ColorOperation = TextureOperation.SelectArg1;

                   device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;

                   device.TextureState[0].TextureCoordinateIndex =(int)TextureCoordinateIndex.CameraSpaceReflectionVector;

                   device.TextureState[0].TextureTransform = TextureTransform.Count3;

                   device.Transform.World = Matrix.RotationYawPitchRoll(angel/(float)Math.PI,angel/(float)Math.PI* 2.0f ,

                   angle/(float)Math.PI/ 4.0f );

                   angle += 0.01f ;

                   device.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0,car.NumberVertices,0,car.NumberFaces);

         }

     }

}

         来看看这个方法干了些什么。首先,渲染天空盒。由于知道天空盒看起来有点小,因此先把它放大10倍。然后把TextureState以及SamplerState设置为渲染天空盒纹理所必须的值(对于每个状态的具体用法请查阅MSDN)。

         注意到没有,我们没有对device进行任何clear操作。因为我们不想渲染每个面时都调用这个方法,所以在渲染天空盒(它的深度值将是场景中最大的)之前改变RenderState,允许渲染天空盒的时候总是跳过深度缓冲。之后,就渲染天空盒,并且让深度缓冲功能恢复到正常状态。渲染天空盒的方法如下:

private void DrawSkyBox()

{

     for(int i=0;i<skyboxMaterials.Length;i++)

     {

         device.Material = skyboxMaterial[i];

         device.SetTexture(0,skyboxTextures[i]);

         skybox.DrawSubset(i);

     }

}

         渲染了天空和之后,我们查看否需要渲染赛车。如果需要,就重置观察和投影变换。这里,由于我们需要控制自动纹理坐标的生成(automatic texture coordinate generation),所以不使用mesh的DrawSubset方法,而是直接使用顶点和索要缓冲,并且通过device的DrawIndexedPrimitiv方法。首先获得这两个缓冲,确保device为渲染mesh做好准备。

         首先,把流的目标设置为mesh的顶点缓冲。根据mesh把顶点格式设置为正确类型,填充device的索引缓冲。最后,在设置其它状态和渲染之前,把立体纹理设置为第一状态。

         这里最重要的是下面这两行代码:

device.TextureState[0].TextureCoordinateIndex =(int)TextureCoordinateIndex.CameraSpaceReflectionVector;

device.TextureState[0].TextureTransform = TextureTransform.Count3;

         由于在赛车的mesh里没有定义纹理坐标(实际上就算定义过也一样),第一行代码表示不使用任何预先定义的纹理坐标,而使用(已经变换为摄像机坐标的)反射向量(reflection vector)作为纹理坐标。这些信息都是通过固定功能渲染管道使用定点位置和法线来自动产生的,所以之前须要保证mesh包含法线信息。最后,由于使用3D立体映射,所以纹理变换告诉纹理使用3D纹理坐标。

         纹理映射完成了之后,就可以更新世界坐标变换,同时调用绘制图元的方法了。马上就完成了,添加如下代码:

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)

{

     RenderSceneIntoEnvMap();

     device.BeginScene();

     RenderScene(ViewMatrix,Matrix.PerspectiveFovLH((float)Math.PI/4,this.Width/this.Height, 1.0f , 1000.0f ),true);

     device.EndScene();

     device.Present();

     this.Invalidate();

}

         对每次渲染来说,先渲染环境贴图,接下来才真正渲染场景:再次渲染环境贴图,以及赛车。运行程序看看吧。

CS算法实战:用MATLAB从零实现SAR点目标成像(附完整代码) 本文详细介绍了如何使用MATLAB从零实现SAR点目标CS成像算法,涵盖Chirp Scaling(CS)算法的核心理论、距离徙动校正(RCMC)和方位压缩等关键步骤。通过完整的代码示例和实战技巧,帮助读者掌握SAR成像处理算法,提升遥感图像处理能力。 阅读详情

相关推荐

MDX之平移与旋转(1)

简介 ============================================== 做项目一年,虽然说没有学得很好,但一年来,自己学的东西也比较多,也有了一些经验,正如我前面 说的现在没有事做就多写,等过两天就不会有这么多的时间了,项目一开始,那可就没有多少时间了。 我做的那个项目是地质相关的项目,就会有一些必不可少的图形的显示,二维方面老师自己做的非常好了,...

weixin_30593443的博客 132

中兴交换机配置SSH登录

SSH是在传统的Telnet协议基础上发展起来的一种安全的远程登录协议。发送和接收数据时, SSH会自动对数据进行加密及解密。相比于Telnet,SSH能防止“中间人”攻击、DNS欺骗、IP 欺骗等,无论是在认证方式或者数据传输的安全性上都有很大的提高。SSH用户认证方式有3种,分别为本地(Local)认证、RADIUS认证和TACACS+认证。以表规划数据为例,说明配置SSH登录的步骤。

才怪的博客 7922

Directx 3D编程实例:绘制3DMesh

 最近朋友建议我写一些关于微软云技术的博客留给学校下一届的学生们看,怕下一届的MSTC断档。于是我也觉的有这个必要。写了几篇博客之后,我觉得也有必要把这一年的学习内容放在博客做个纪念,就这样写了本篇博客。 第一步:判断显卡是否支持 using System; using System.Collections.Generic; using System.Linq; using Sys...

ithome 397

从零开始之stm32之CAN通信

CAN通信极简教程(小白友好版),从小白的视角了解并实现简单的STM32F103的CAN通信,直接上手。

qq_30993593的博客 1306

智能优化电池:基于单片机的电池管理系统

设计简介: 本设计是基于单片机的电池管理系统,主要实现以下功能: 可通过LCD1602显示温度、电压电流值; 可通过按键调整温度、电压和电流的阈值; 用ADC0832将模拟量转换为数字量。 标签:51单片机、LCD1602、ADC0832 效果图: 总体资料: 原理图: 软件设计流程: 系统框图: 本设计以STC89C52单片机为核心控制器,加上其他的模块一起组成电动汽车的电池管理的整个系统,...

专注单片机实战教学,分享单片机学习资料 375

unity切换TextMesh的Font之后,文字错乱

最近在弄多语言,发现游戏中的字体库里面没有俄文导致显示俄文的时候不正常,没办法,只能切换语言的时候也切换支持俄文的字体库了,项目中使用NGUI的UILabel的地方,直接切换label.bitmapFont = realFont;就搞定了。没想到使用了TextMesh的地方,直接label.font=realFont;之后,文字显示就错乱了。如下图: 弄了大半天,考虑是不是Font.te

鱼蛋的开发小站 3759

在pycharm2021.2中学习opencv图像处理,基于python3.9.7、pycharm完成配置

在pycharm中学习opencv图像处理安装python下载python安装包开始安装安装pycharm社区版下载pycharm开始安装如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 安装python 使用pycharm学习opencv应该是最简单的了,对新手比较友好,

steveqobs 7707

DirectX学习笔记_关于Sprite.Draw2D的说明

在DirectX的Sprite中提供一个Draw2D的方法,该方法绘制一个Sprite对象用于二维空间中显示,在DirectX 9.0C中,该方法有6个重载,分别是  1 public void Draw2D(Texture srcTexture, PointF r

anders0721的专栏 820

WSL 2 部署 Rocky Linux 9:从零到生产环境配置指南(2025版)

本文详细介绍了如何在WSL 2上部署Rocky Linux 9,从环境准备到生产环境配置的全流程指南。通过优化安装流程和系统配置,帮助开发者和运维工程师在Windows环境下高效运行企业级Linux系统,特别适合需要本地开发但部署到RHEL系服务器的项目。

weixin_28455801的博客 479

【EB_Tresos专题】-01-EB Tresos导入工程

<--返回「Autosar_BSW高阶配置」总目录--> <--返回「Autosar_BSW高阶配置」专栏主页--> <--返回「个人博客」首页--> <--返回「Autosar_BSW高阶配置」总目录--> <--返回「Autosar_BSW高阶配置」专栏主页--> <--返回「个人博客」首页--> 结尾 ...

「汽车电子助手」的博客 6433

Managed DirectX +C# 开发(入门篇)(一)

  前      言以前是用VC 作计算机图形学的,大约在一年前,公司接到一个用C#+DirecX的项目,从那时起便开始学习Mangaged DirectX ,现将在开发过程中的一点总结发表出来,和同行们共同参考,共同学习,共同进步,前面是一些基础内容,算是入门篇吧,现在正将开发过程中所用到的一些算法整理中,算是中级篇吧。1:在整理过程中,有些内容为从网上找的资料,部分为翻译国外文章,正所

dandanCool的专栏 8816

深度学习驱动的股票波动率预测:LSTM和Transformer模型的实战解析

股票市场的波动率一直是金融领域关注的焦点之一。波动率预测对于风险管理、期权定价和资产配置至关重要。深度学习模型,如长短时记忆网络(LSTM)和Transformer,已经在时间序列数据分析领域取得了显著的成就。本博客将向您展示如何使用这两种深度学习模型来进行股票波动率预测,提供清晰的思路和相应的Python代码,以帮助您理解和应用这一重要任务。

斌擎科技 974

Managed DirectX(第五章)

定义Mesh    虽然有很多时候,你需要手动创建顶点和索引数据,但更普遍的情况是从外部的资源加载已有的顶点数据,比如从一个文件。通常我们使用.X文件来保存这些信息。在前一章里,代码的大部分都用来创建几何体了。对于简单的三角形和立方体来说这似乎是完全可行的,但设想假如用相同的方式来创建拥有上万个顶点的物体将,所花费的时间和努力都将是很可怕的。     幸运的是,Managed DirectX里有一

sunkangta的专栏 1208

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

【翻译】Managed DirectX(第五章)

Rendering with Meshes翻译:claymanclayman_joe@yahoo.com.cn 定义Mesh    虽然有很多时候,你需要手动创建顶点和索引数据,但更普遍的情况是从外部的资源加载已有的顶点数据,比如从一个文件。通常我们使用.X文件来保存这些信息。在前一章里,代码的大部分都用来创建几何体了。对于简单的三角形和立方体来说这似乎是完全可行的,但设想假如用相同的方式来创建拥

Clayman的专栏 3645

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

【翻译】Managed DirectX(第九章)

第九章 使用其它Mesh类型翻译:claymanclayman_joe@yahoo.com.cn仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^简化Mesh(Simplifying Meshes)         我们花了大量的时间来学习扩展库中的基本Mesh类,但实际上,还有3种其它Mesh类型我们没有讨论过,在这一章中,我们就来看看这几个对象。         我们已经知道如何通过使

Clayman的专栏 3572

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

【翻译】Managed DirectX(第七章)

第七章 Using Advanced Mesh Features翻译:claymanclayman_joe@yahoo.com.cn仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^这一章,我们将要调论一些关于Mesh对象的高级特性,包括:n                        优化(Optimizing)mesh数据n                        简化(Sim

Clayman的专栏 3884

【翻译】Managed DirectX(第六章)

第六章 使用Managed DirectX编写游戏翻译:claymanclayman_joe@yahoo.com.cn仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^选择游戏      虽然很多关于3D游戏编程的高级主题还没有讨论,但我们已经有足够的背景知识来写一个简单游戏了。这一章,我们将使用至今学过的知识,再加上一点点新的东西来创建游戏。     真正开始写游戏之前,最好先拟一份计划

Clayman的专栏 4175

【翻译】Managed DirectX(第八章)

第八章 理解资源(Understanding Resoruces)翻译:claymanclayman_joe@yahoo.com.cn仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^资源是在Direct3D中渲染复杂对象的重要部分。在这一章的学习中,我们将会讨论关于资源的一些高级特性,包括:n          静态和动态的资源n          Updating the buffer

Clayman的专栏 3881

Rendering terrains with Managed DirectX翻译

《翻译》 原文链接:http://www.codeproject.com/Articles/8195/Rendering-terrains-with-Managed-DirectX (忽然发现自己翻译的自己都不知道说的什么。。。也罢,一个小时的产物,不要要求太高) (丫的,翻译完也不不知道怎么实现的,还是老老实实去看代码吧,话说我还没用过c#,容我编程c++的,龙书第13章不就讲的地...

weixin_30301183的博客 121

【翻译】Managed DirectX(第十一章)

第十一章 可编程渲染管道以及高级着色语言入门翻译:claymanclayman_joe@yahoo.com.cn仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^至今为止,我们都在使用固定功能的管道(fixed-function pipeline)进行渲染。回到那段古老的日子(DirectX 8.0之前),这是唯一渲染物体的方法。固定功能的渲染管道本质上就是一系列用来控制如何渲染特定类型数

Clayman的专栏 4205

翻译 managed DirectX9(第一章)

第一章 Introducing Direct 3D翻译:clayman创建设备  Device类是DirectX里的所有绘图操作所必须的。可以把这个类假想为真实的图形卡。场景里所有其他图形对象都依赖于device。你的计算机里可以有一个到几个device,在Mnaged DirctX3D里,你可以控制任意多个device。         Device共有三个构造函数,我们现在只讨论其中的一个,但

Clayman的专栏 9801

Managed Directx 9 kick start,第五章 纹理

Rendering with Meshes 我们又向前迈进了一大步,这可比总看着立方体旋转要有趣多了。下一章我们将使用Managed DirectX来写一个真正的游戏了,最然它可能看起来很简单,但毕竟是我们的第一个三维游戏^_^,第六章内容比较,大概3、4次才能翻译完^_^。  定义Mesh    虽然有很多时候,你需要手动创建顶

bbwolfcool的专栏 1181

关于Vs2010 C#使用DirectX的问题

转载的,就不翻译了…微软把精力放到xna去了。所以推荐大家用XNA,如果非要用托管的DirectX也可以,只不过版本一直是2006年的了。 具体方法: 安装SDK之后 他默认的位置在 C:\WINDOWS\Microsoft.NET\Managed DirectX 把里

leestar54的专栏 1万+

关于Managed DirectX kick start的源码

                  这本书是我很早以前就翻译的了,现在源码光盘 并不在身边。现在需要源码的同志可以到zbuffer去下载,至于其中的资源文件,除第六章的以外,大部分都可以在DirectX SDK里找到。另外,你也可以到gameres上去搜索我原来发帖子,标题是《深入Managed DirectX9》,每一章后面都带了完整代码和资源。            其实个人觉得既然X

Clayman的专栏 3833

【翻译】Managed DirectX(第四章)

更多渲染技术翻译:clayman     在讨论过了基础渲染方法之后,我们应该把注意力放到一些能提高性能,并且让场景看起来更好的渲染技术上来: 渲染各种图元类型     至今位置,我们只渲染过一种类型的图元,称为三角形集合。实际上,我们可以绘制很多种不同类型的图元,下边的列表描述了这些图原类型:     PointList――这是一个自我描述的图元类型,它把数据作为一系列离散的点来绘制。不能使用这

Clayman的专栏 3721
上一篇: 使用Managed DirectX编写游戏(-)
下一篇: 【翻译】Managed DirectX(第十一章)
soilwork
博客等级 码龄23年 290粉丝 116原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值