65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 F# 进行托管 DirectX

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (4投票s)

2010年10月24日

CPOL

4分钟阅读

viewsIcon

42729

downloadIcon

500

一篇说明如何在 F# 应用程序中引用 DirectX 库的文章。

在 F# 应用程序中引用 DirectX 库

尽管 WPF 比 Windows Forms 拥有更强大的图形引擎,但商业游戏软件的开发是通过 DirectX 或在某些情况下通过 OpenGL 完成的。 Microsoft 从 .NET Framework 提供了一个到 DirectX 的高级接口:Managed DirectX。 即使这是一个高级接口,使用 Managed DirectX 的程序也包含大量的“样板”代码,这些代码是让任何东西工作所必需的。 本文将重点介绍编写 F# 代码以从 Managed DirectX 可重用库中进行绘制。 使用 Visual Studio 时,通常通过突出显示代码并按 Alt-Enter 将代码发送到 F# 交互式环境来测试 F# 代码。 包含这些 DLL 的目录是 C:\Windows\Microsoft.NET\DirectX for Managed Code\1.0.2902.0 。 因此,如果我们使用 F#,一种据称具有重要未来的函数式编程语言,我们可以通过以下方式将库加载到包含中

  • #I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2902.0"
  • #I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2903.0"
  • #I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2904.0"
  • #I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2907.0"

这些行指定包含路径,相当于 F# 编译器的 -I 开关。 现在指定了这些路径,我们将引用适当的 DLL

  • #r @"Microsoft.DirectX.dll"
  • #r @"Microsoft.DirectX.Direct3D.dll"
  • #r @"Microsoft.DirectX.Direct3Dx.dll"

这些指定 DLL 引用,相当于 -r 命令行选项。 与任何 Visual Studio 托管代码解决方案容器一样,我们还将右键单击引用,浏览到包含这些 DLL 的文件夹,并将它们添加到引用部分。 我们将要检查的程序是用户交互式的,通过鼠标点击来执行图形显示位置的一些基本调整。 这种形状集合将用于展示运动来描绘动画,但通常不以这种方式记录。 但是如何管理源代码文件来执行包含上述引用 DLL 规范的 F# 文件呢? 我们使用 load 命令

  • #load @"BindAsLegacyV2Runtime.fs"
  • #load @"dxlib.fs"

这两个文件包含在解决方案中,以便在执行 Script.fsx 文件时加载到交互式环境中。 通常,一次突出显示一行或一块来构建可执行文件是没有意义的,但这样做会导致非凡的 DirectX 风格的图形显示。

此示例包含一个脚本,该脚本首先指导用户设置一个支持 DirectX 的窗口,该窗口适合渲染 3D 函数。 然后向用户展示如何绘制和动画几个不同复杂度的函数。 最后,提供物理例程,允许用户模拟对象在绘制曲线的表面上滑动。 一些有用的实用函数在脚本文件之外的模块中提供,这些函数有助于设置窗口、执行一些矩阵计算以及处理顶点着色。

Capture.JPG

加载图像后,您将看到转换整个场景的移动。 使用您的鼠标和鼠标左键来检查这些效果。 执行此代码需要使用 Visual Studio 2008 或 2010 以及最新的 DirectX SDK。 让我们检查一下 Script.fsx 文件(回想一下,为了执行它,您需要突出显示代码,按 Alt-Enter 将其发送到交互式环境,它将编译)

#load @"BindAsLegacyV2Runtime.fs"
// adjust these as needed for your latest installed version of ManagedDirectX
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2902.0" 
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2903.0"  
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2904.0"  
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2907.0"  

#r @"Microsoft.DirectX.dll"
#r @"Microsoft.DirectX.Direct3D.dll" 
#r @"Microsoft.DirectX.Direct3Dx.dll" 

#load @"dxlib.fs"
 
open System
open System.Drawing
open System.Windows.Forms
open Microsoft.DirectX
open Microsoft.DirectX.Direct3D
open Microsoft.FSharp.Control.CommonExtensions
open Sample.DirectX
open Sample.DirectX.MathOps
open Sample.DirectX.VectorOps

let form = new SmoothForm(Visible = true, TopMost = true, 
                          Text = "F# surface plot",
                          ClientSize = Size(600,400),
                          FormBorderStyle=FormBorderStyle.FixedSingle)

let renderer = new DirectXRenderer(form)

renderer.DrawScene.Add(fun _ -> renderer.DrawCubeAxis())

renderer.DrawScene.Add(fun _ -> renderer.SetupLights())

let mutable view = 
   { YawPitchRoll   = Matrix.RotationYawPitchRoll(0.0f,0.0f,0.0f);
     Focus          = scale 0.5f (X1 + Y1 + Z1);
     Zoom           = 4.0 }

renderer.DrawScene.Add(fun _ -> renderer.SetView(view))

let mouseTrack = MouseTracker(form)

mouseTrack.Add(fun (a,b) -> 
    let view2 = 
        let dx = b.X - a.X
        let dy = b.Y - a.Y
        match b.Button, Form.ModifierKeys with 
        | MouseButtons.Left, Keys.Shift -> view.AdjustZoom(dx,dy)
        | MouseButtons.Left, _          -> view.AdjustYawPitchRoll(dx,dy)
        | _                             -> view.AdjustFocus(dx,dy)  
    view <- view2
)

let mutable ff    = (fun (t:float32) x y -> x * (1.0f - y))

/// Z-range
let mutable range = (0.0f,1.0f)

/// XY-mesh
let mutable mesh = BaseMesh.Grid(20,20)

//mesh

// Scale w.r.t. range ... 
let scalef (min,max) (z:float32) = (z-min) / (max-min) 

// Get the function and scale it 
let theFunction t x y = ff t x y |> scalef range

renderer.DrawScene.Add(fun t -> renderer.DrawSurface mesh (theFunction t))

//----------------------------------------------------------------------------
// PART 2 - change the function

ff <- (fun t x y -> sqr (x - 0.5f) * sqr (y - 0.5f) * 16.0f)
ff <- (fun t x y -> 0.5f * sin(x * 4.5f + t / 2.0f) * cos(y * 8.0f) * x + 0.5f)

range <- (-1.0f,1.0f)
range <- (0.0f,1.0f)

let ripple t x y =
   let x,y = x - 0.5f,y - 0.5f 
   let r = sqrt (x*x + y*y) 
   exp(-5.0f * r) * sin(6.0f * pi * r + t) + 0.5f

ff <- ripple
  
mesh <- BaseMesh.Grid (50,50)

mesh <- BaseMesh.Grid (20,20)

let surfacePoint f x y = Vector3(x,y,f x y)

let surfaceNormal f x y =
    let dx,dy = 0.01f,0.01f 
    let pA    = surfacePoint f x y 
    let pA_dx = surfacePoint f (x+dx) y - pA 
    let pA_dy = surfacePoint f x (y+dy) - pA 
    normalize (cross pA_dx pA_dy)

let gravity = Vector3(0.0f,0.0f,-9.81f)

// A ball is a pair of position/velocity vectors
type ball = Ball of Vector3 * Vector3 

let radiusA = 0.010f 
let radiusB = 0.005f     

let moveBall f timeDelta (Ball (position,velocity)) =
   
    let nHat     = surfaceNormal f position.X position.Y  

    let acc      = planeProject nHat gravity              // acceleration in plane 
    let velocity = planeProject nHat velocity             // velocity     in plane 
    
    // Compute the new position
    let position = position + Vector3.Scale(velocity,timeDelta)  // iterate 
    let velocity = velocity + Vector3.Scale(acc     ,timeDelta)  // iterate 

    // Handle the bounce!
    let bounce (p,v) =                                        
        if   (p < 0.0f + radiusA) then (2.0f * (0.0f + radiusA) - p,-v) 
        elif (p > 1.0f - radiusA) then (2.0f * (1.0f - radiusA) - p,-v) 
        else                           (p,v)  
    let px,vx = bounce (position.X,velocity.X)              // bounce X edges 
    let py,vy = bounce (position.Y,velocity.Y)              // bounce Y edges 
    let position = surfacePoint f px py                     // keep to surface 
    let velocity = Vector3 (vx,vy,velocity.Z) 
    let velocity = planeProject nHat velocity               // velocity in plane     

    Ball (position,velocity)

let drawBall t (Ball (p,v)) =
    let n    = surfaceNormal (theFunction t) p.X p.Y 
    // position XY-projection 
    let p0   = Vector3(p.X,p.Y,0.0f) 
    // unit velocity XY-projection 
    let pV   = Vector3(v.X,v.Y,0.0f)                  
    // and it's XY-perpendicular 
    let pVxZ = Vector3.Cross(pV,Z1)                         
    // vertical line 
    renderer.DrawLines (Array.map (Vertex.Colored Color.Gray) [| p0;p |])
    // velocity arrow on floor 
    renderer.DrawPlaneArrow Z1 p0 pV      
    // normal arrow at point     
    renderer.DrawPlaneArrow (cross n X1) p  (scale 0.8f n)
    renderer.Device.Transform.World <- 
        (let m = Matrix.LookAtLH(p + scale radiusB n,p+n,X1) 
         Matrix.Invert(m))
      
    // Now draw the mesh
    using (Mesh .Torus(renderer.Device,radiusB,radiusA,20,20)) (fun mesh -> 
        mesh.ComputeNormals()
        mesh.DrawSubset(0))
      
    renderer.Device.Transform.World <- Matrix.Identity

let mutable active = [] : ball list
let addBall ball = active <- (ball :: active)  
let drawBalls t =  active |> List.iter(drawBall t) 
let mutable timeDelta = 0.008f
let moveBalls t = 
         let active' = active |> List.map (moveBall (theFunction t) timeDelta)
         active <- active'

//timeDelta <- 0.014f
renderer.DrawScene.Add(fun t -> moveBalls t) 
renderer.DrawScene.Add(fun t -> drawBalls t)

let bowl t x y = 
   let f phi u = ((1.0f + cos(2.0f * pi * u + phi )) / 2.0f) 
   f t x * f 0.0f y + 1.0f

range <- (0.0f,2.0f)
ff    <- (fun t -> bowl 0.0f)

// Second, add a ball 
addBall (Ball (Vector3(0.1f,0.1f,0.1f),
               Vector3(0.6f,0.5f,0.0f)))

// Add a ball train. 

Async.Start
    (async { for i in 0 .. 6 do 
                do addBall (Ball (Vector3(0.1f,0.1f,0.1f),
                                  Vector3(0.6f,0.5f,0.0f)))
                do! Async.Sleep(100)  })

// Now move the floor!

let mutable rate = 0.25f 
ff <- (fun t x y -> bowl (rate * t) x y)
rate <- 1.0f
rate <- 2.0f

ff <- ripple
range <- (0.0f,1.0f)

mesh <- BaseMesh.Grid (30,30)

#if COMPILED
[<stathread>]
do Application.Run(form)

do Application.Exit()
#endif

从数学上讲,一个表面在感兴趣区域的每个 X 和 Y 坐标上绘制一个函数。 对于每个 X 和 Y 值,一个简单的表面最多可以有一个值。 通常,您可以通过 X-Z 平面中矩形网格上方的点的 Y 坐标来定义一个简单的表面。 该表面是通过使用直线连接相邻点形成的。 因此,请确保安装了 DirectX SDK。 之后,下载源文件 zip 文件,将其解压缩并将文件提取到 Visual Studio 2010 文件夹的 Projects 目录中的新创建的文件夹中。 双击解决方案文件以让 Visual Studio 启动并加载这些文件。 请记住右键单击解决方案资源管理器中的“引用”选项卡并添加适当的 DLL。 本文中包含的引用信息来自 F# 语言的主要研究员 Don Syme 的博客和站点。

历史

  • 2010 年 10 月 24 日:首次发布
© . All rights reserved.