您的当前位置:首页正文

Opengl绘制我们的小屋(二)第一人称漫游

2024-11-20 来源:个人技术集锦

这章我们先讲第一人称漫游的实现.在openTK里,我们用函数Matrix4.LookAt(caram.Eye,caram.Target,Vector3.UnitY)来放置摄像机,其中三个参数分别与摄像机位置,摄像机朝向,摄像机向上的向量.与opengl里的glulookat其实是一样的.

本来为了查找漫游的功能,在网上找了些,发现相关讲解都很少,更多只是写出了代码,花了一些时间查找相关概念与调试,其中把我的理解会说明上,有不对的地方欢迎大家指出.

漫游最基本的功能,我们包括相关步进,前进,后退,左进,右进.还有就是视角旋转,包含水平方向旋转,以及垂直方向上的旋转.在讲下面之前,我们先来看下什么是球面坐标系.

引用维基百科里的

在里,球坐标系(:Spherical coordinate system)是一种利用球坐标

右图显示了球坐标的几何意义:原点与点 P 之间的径向距离

上面的这些大家都好理解,我们的漫游模型可以根据这个来,eye是摄像机位置,p摄像机朝向向量.但是我们的坐标应该是这样的.

根据上面的我们来完成我们摄像机类,如下.

 1     type Camera() = 
 2         let mutable eye = Vector3.Zero
 3         let mutable eyeLength = 1.
 4         let mutable yangle = 0.
 5         let mutable xangle= Math.PI/2.
 6         member this.Eye 
 7             with get() = eye 
 8             and set value = eye <- value
 9         member this.EyeLength 
10             with get() = eyeLength 
11             and set value = 
12                 if value < 0. then eyeLength <- 0.1
13                 eyeLength <- value
14         member this.YAngle 
15             with get() = yangle
16             and set value = 
17                 if value > Math.PI then yangle <- 0.
18                 //elif value < 0. then yangle <- Math.PI 
19                 else yangle <- value
20         member this.XAngle 
21             with get() = xangle
22             and set value = 
23                 printfn "xangle:%f" value
24                 if value > 2.* Math.PI then xangle <- 0.
25                 elif value < 0. then xangle <- 2. * Math.PI
26                 else xangle <- value
27         member this.Target 
28             with get() = 
29                 //printfn "%f" this.XAngle 
30                 let xyLength = Math.Cos(this.YAngle)
31                 let x:float =float eye.X + eyeLength * xyLength * Math.Cos(this.XAngle)
32                 let y:float =float eye.Y + eyeLength * Math.Sin(this.YAngle)
33                 let z:float =float eye.Z + eyeLength * xyLength * Math.Sin(this.XAngle)
34                 Vector3(float32 x,float32 y,float32 z)
35         member this.Transelt (x,y,z) = 
36             let sinX = Math.Sin(this.XAngle)
37             let cosX = Math.Cos(this.XAngle)
38             let x1 = float this.Eye.X + x * sinX + z * cosX
39             let y1 = float this.Eye.Y + y
40             let z1 = float this.Eye.Z + z * sinX - x * cosX
41             printfn "angle:%f, sinx:%f, cosx:%f" this.XAngle sinX cosX
42             printfn "x:%f, y:%f, z:%f" x1 y1 z1
43             this.Eye <- new Vector3(float32 x1,float32 y1,float32 z1)
44         member this.UpAndDown y =
45             let ya = this.YAngle + y
46             this.YAngle <- ya
47         member this.RightAndLeft x =
48             let xa = this.XAngle + x
49             this.XAngle <- xa
50         member this.Rotate (x,y) =
51             let xa = this.XAngle + x
52             let ya = this.YAngle + y
53             this.YAngle <- ya
54             this.XAngle <- xa
View Code

可以看到我们的Target,就是看的方向,根据球面坐标,我们可以通过xangle与yangle来求得。而xangle与yangle可以根据上面给出的UpAndDown,RightAndLeft,Rotate这三个方法来增加对应我们在水平与垂直方向上的旋转角度.其中Xangle默认是PI/2,就是90度,是因为默认我们是向前看,也就是向Z轴看.

然后是左右,上下前进的代码,这部分逻辑代码在this.Transelt (x,y,z) ,x,y,z分别表示左右,上下,前后上的走的值,最开始我用的(x1,z1) = this.Eye.(X,Z) + (x,z)这样发现如果没有进行旋转,那么是对的,但是如果我旋转90度后来看,前后变成左右了,大家可以试着分析下相关原因,比如前进,我们增加的是z的值.但是我们旋转90度后,我们再来看,这是前进对应的是x轴的值,所以实际上,我们前后左右走,还与我们的左右旋转的角度xangle有关,实际我们应该是以方向向量为xangle方向在对应的x与z值的投影值.

如果这个图还是不理解,可以看第一张图有更详细的说明.

下面我们进入我们主题,画一个室内景,下面是我在网上找的一个室内模型图,用手机照的,可能不是很清晰. 

我们根据上面的模型来建模,相关立方体用上文提供的绘制方法,相关代码如下.

  1 #r "F:\3D\1.0\Binaries\OpenTK\Debug\OpenTK.dll"
  2 #r "F:\3D\1.0\Binaries\OpenTK\Debug\OpenTK.GLControl.dll"
  3 #r "F:\3D\1.0\Binaries\OpenTK\Debug\Examples.exe"
  4 #load "Shape.fsx"
  5 
  6 open System
  7 open System.Collections.Generic
  8 open System.Windows.Forms
  9 open System.Threading
 10 open System.Drawing
 11 open System.Drawing.Imaging
 12 open OpenTK
 13 open OpenTK.Graphics
 14 open OpenTK.Graphics.OpenGL
 15 open Shape
 16 
 17 type loopForm() as form=
 18     inherit Form()
 19     let caram = Shape.Camera()
 20     let offest = 0.1f
 21     let offestd = float offest
 22     let glControl = new OpenTK.GLControl()
 23 
 24     let cubeEnter = Shape.Cube(1.5f,0.2f,1.8f,3)
 25     let cubeParlor = Shape.Cube(4.9f,0.2f,6.3f,3)
 26     let cubeBalcony1 = Shape.Cube(4.9f,0.2f,1.9f,3)
 27     let cubeBalcony2 = Shape.Cube(1.6f,0.2f,2.1f,3)
 28     let cubeBalcony3 = Shape.Cube(1.8f,0.2f,3.0f,3)
 29     let cubeKitchen = Shape.Cube(4.5f,0.2f,1.8f,3)
 30     let cubeBathroom = Shape.Cube(2.1f,0.2f,2.1f,3)
 31     let cubeGallery = Shape.Cube(2.1f,0.2f,1.2f,3)
 32     let cubeMasterBedroom = Shape.Cube(3.6f,0.2f,3.9f,3)
 33     let cubeSecondbedroom = Shape.Cube(3.0f,0.2f,3.3f,3)
 34 
 35     let cubeWall1 = Shape.Cube(0.2f,3.f,8.1f,0)
 36     let cubeWall2 = Shape.Cube(4.5f,3.f,0.2f,0)
 37     let cubeWall3 = Shape.Cube(0.2f,3.f,1.8f,0)
 38     let cubeWall4 = Shape.Cube(0.2f,1.8f,1.8f,0)
 39     let cubeWall5 = Shape.Cube(3.0f,3.f,0.2f,0)
 40     let cubeWall6 = Shape.Cube(0.2f,3.f,3.7f,0)
 41     let cubeBathroom1 = Shape.Cube(2.1f,3.f,0.2f,0)
 42     let cubeBathroom2 = Shape.Cube(0.2f,3.f,2.1f,0)
 43     let cubeFence1 = Shape.Cube(0.05f,0.02f,1.9f,0)
 44     let cubeFence2 = Shape.Cube(2.3f,0.02f,0.05f,0)
 45     let cubeFence3 = Shape.Cube(0.05f,0.02f,2.1f,0)
 46     let cubeFence4 = Shape.Cube(3.4f,0.02f,0.05f,0)
 47     let cubeWall7 = Shape.Cube(2.1f,3.f,0.2f,4)
 48     let cubeWall8 = Shape.Cube(0.2f,3.f,3.0f,0)
 49     let cubePillar = Shape.Cube(0.2f,3.f,0.2f,4)
 50     let cubePillar2 = Shape.Cube(0.1f,1.1f,0.2f,4)
 51     let mutable oldMouseLocation = Vector2.Zero
 52     do
 53         caram.Transelt(1.,1.7,0.0)
 54         form.SuspendLayout()
 55         glControl.Location <- new Point(10,40)
 56         glControl.Size <- new Size(400,300)
 57         glControl.BackColor <- Color.Red
 58         glControl.Resize.Add(form.resize)
 59         glControl.Paint.Add(form.paint)
 60         glControl.KeyDown.Add(form.KeyDown)
 61         glControl.MouseMove.Add(form.MouseDownv)
 62         form.ClientSize <- new Size(450,350)
 63         form.Text <- "opengl"
 64         form.StartPosition <- FormStartPosition.Manual
 65         form.Location <- new Point(1200,600)
 66         form.Controls.Add(glControl)
 67         form.ResumeLayout(false)
 68         //#endregion 
 69     override v.OnLoad e =
 70         base.OnLoad e
 71         GL.ClearColor Color.MidnightBlue
 72         Application.Idle.Add(v.AIdle)
 73         GL.FrontFace FrontFaceDirection.Ccw
 74         GL.Enable( EnableCap.PointSmooth )
 75         //踢除正反面
 76         GL.Enable EnableCap.CullFace
 77         GL.CullFace CullFaceMode.Back
 78         //设置材料面填充模式
 79         GL.PolygonMode(MaterialFace.Front,PolygonMode.Fill)
 80         GL.PolygonMode(MaterialFace.Back,PolygonMode.Line)
 81 //#region
 82     member v.resize (e:EventArgs) =
 83         GL.Viewport(0,0,glControl.ClientSize.Width,glControl.ClientSize.Height)
 84         let aspect = float32 glControl.ClientSize.Width /float32 glControl.ClientSize.Height
 85         let mutable projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver2,aspect,0.1f,30.f)
 86         GL.MatrixMode MatrixMode.Projection
 87         GL.LoadMatrix(&projection)
 88     member v.paint (e:PaintEventArgs) =
 89         v.Render()
 90     member v.AIdle (e:EventArgs) =
 91         while (glControl.IsIdle) do
 92             v.Render()
 93     member v.MouseDownv(e:MouseEventArgs) =
 94         if e.Button = MouseButtons.Right then
 95             if oldMouseLocation = Vector2.Zero then
 96                 oldMouseLocation <- Vector2(float32 e.X,float32 e.Y)
 97             else
 98                 let nx = (float32 e.X - oldMouseLocation.X) * offest * 0.1f
 99                 let ny = (float32 e.Y - oldMouseLocation.Y) * offest * -0.1f
100                 caram.Rotate(float nx,float ny)
101         oldMouseLocation <- Vector2(float32 e.X,float32 e.Y)     
102     member v.KeyDown(e:KeyEventArgs) =
103         //let keys = e.KeyData
104         match e.KeyCode with
105         | Keys.E ->caram.Transelt(0.,0.,1.* offestd)
106         | Keys.D ->caram.Transelt(0.,0.,-1.* offestd)
107         | Keys.S ->caram.Transelt(1.* offestd,0.,0.0)
108         | Keys.F ->caram.Transelt(-1.* offestd,0.,0.)
109         | _ -> ()
110     member x.UIValue
111         with get (text:TextBox) = 
112             let mutable value = 0.f
113             if System.Single.TryParse(text.Text,&value) then
114                 value <- value
115             value
116         and set (text:TextBox) (value:float32) = text.Text<- value.ToString()
117 //#endregion        
118     member v.Render =
119         //Keyboard[OpenTK.Input.Key.F11]
120         GL.Clear(ClearBufferMask.ColorBufferBit ||| ClearBufferMask.DepthBufferBit)
121         //地面
122         v.DrawCube(cubeEnter,Vector3.Zero)
123         v.DrawCube(cubeParlor,0.f,0.f,1.8f)
124         v.DrawCube(cubeBalcony1,0.f,0.f,8.1f)
125         v.DrawCube(cubeBalcony2,2.3f,0.f,10.f)
126         v.DrawCube(cubeBalcony3,3.9f,0.f,9.f)
127         v.DrawCube(cubeKitchen,1.5f,0.f,0.f)
128         v.DrawCube(cubeBathroom,3.9f,0.f,1.8f)
129         v.DrawCube(cubeGallery,3.9f,0.f,3.9f)
130         v.DrawCube(cubeMasterBedroom,3.9f,0.f,5.1f)
131         v.DrawCube(cubeSecondbedroom,6.0f,0.f,1.8f)  
132         //
133         v.DrawCube(cubeWall1,Vector3.Zero)
134         v.DrawCube(cubeWall2,Vector3(1.5f,0.f,0.f))
135         v.DrawCube(cubeWall3,Vector3(1.5f,0.f,0.f))
136         v.DrawCube(cubeWall4,Vector3(6.0f,0.f,0.f))
137         v.DrawCube(cubeWall5,Vector3(6.0f,0.f,1.8f))
138         v.DrawCube(cubeWall6,Vector3(9.0f,0.f,1.8f))
139         v.DrawCube(cubeWall6,Vector3(7.5f,0.f,5.1f))
140         v.DrawCube(cubeWall6,Vector3(3.9f,0.f,5.1f))        
141         v.DrawCube(cubeWall7,Vector3(3.9f,0.f,9.0f))
142         v.DrawCube(cubeWall8,Vector3(6.0f,0.f,9.0f))
143         v.DrawCube(cubeBathroom1,Vector3(3.9f,0.f,1.8f))
144         //v.DrawCube(cubeBathroom1,Vector3(3.9f,0.f,3.9f))
145         v.DrawCube(cubeBathroom2,Vector3(3.9f,0.f,1.8f))
146         v.DrawCube(cubeBathroom2,Vector3(6.0f,0.f,1.8f))
147         v.DrawCube(cubeFence1,0.0f,1.f,8.1f)  
148         v.DrawCube(cubeFence2,0.0f,1.f,10.f)  
149         v.DrawCube(cubeFence3,2.3f,1.f,10.f)  
150         v.DrawCube(cubeFence4,2.3f,1.f,12.1f)  
151         v.DrawCube(cubePillar,Vector3(2.3f,0.f,12.f))
152         v.DrawCube(cubePillar,Vector3(2.3f,0.f,9.9f))
153         v.DrawCube(cubePillar2,Vector3(0.f,0.f,9.9f))
154         //房顶
155         GL.PushMatrix()
156         GL.Translate(0.f,3.f,0.f)
157         v.DrawCube(cubeEnter,Vector3.Zero)
158         v.DrawCube(cubeParlor,0.f,0.f,1.8f)
159         v.DrawCube(cubeBalcony1,0.f,0.f,8.1f)
160         v.DrawCube(cubeBalcony2,2.3f,0.f,10.f)
161         v.DrawCube(cubeBalcony3,3.9f,0.f,9.f)
162         v.DrawCube(cubeKitchen,1.5f,0.f,0.f)
163         v.DrawCube(cubeBathroom,3.9f,0.f,1.8f)
164         v.DrawCube(cubeGallery,3.9f,0.f,3.9f)
165         v.DrawCube(cubeMasterBedroom,3.9f,0.f,5.1f)
166         v.DrawCube(cubeSecondbedroom,6.0f,0.f,1.8f)   
167         GL.PopMatrix()             
168         let mutable lookat = Matrix4.LookAt(caram.Eye,caram.Target,Vector3.UnitY)
169         GL.MatrixMode(MatrixMode.Modelview)
170         GL.LoadMatrix(&lookat)  
171         glControl.SwapBuffers()
172         ignore
173     member v.DrawCube(cube:Shape.Cube,pos:Vector3) =
174         v.DrawCube(cube,pos,cube.Index)
175     member v.DrawCube(cube:Shape.Cube,x,y,z) =
176         v.DrawCube(cube,new Vector3(x,y,z),cube.Index)
177     member v.DrawCube(cube:Shape.Cube,x,y,z,ind) =
178         v.DrawCube(cube,new Vector3(x,y,z),ind)
179     member v.DrawCube(cube:Shape.Cube, pos:Vector3, ind:int) =
180         GL.PushMatrix()
181         cube.Index <- ind
182         GL.Translate(pos)
183         cube.Draw()
184         GL.PopMatrix()
185 let t = new loopForm()
186 t.Show()
View Code

v.MouseDownv(e:MouseEventArgs)实际对应的鼠标移动,作用是检测鼠标右键按下后,鼠标移动向量,然后进行上下,左右的旋转.

v.KeyDown(e:KeyEventArgs) 就是EDSF行走.对应前后左右.

在Render里,就是我们绘制的模型代码,先画模型,然后设置相机.别的如投影矩阵,大家如果有兴趣,可以自己查找相关资料.

let mutable lookat = Matrix4.LookAt(caram.Eye,caram.Target,Vector3.UnitY)
GL.MatrixMode(MatrixMode.Modelview)
GL.LoadMatrix(&lookat)

效果图.我不否认这很丑,可能下一章节还要丑,下一章节如果没意外,会先讲灯光的应用,然后才是纹理.

 

附件为相关代码以及可运行程序,操作方式和网游一样,鼠标右键按下,上下左右移动是视角.WASD行走,(代码里是EDSF,游戏里的快捷键习惯).

后面我会介绍灯光与纹理贴图的相关应用.

 

显示全文