这章我们先讲第一人称漫游的实现.在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
可以看到我们的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()
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,游戏里的快捷键习惯).
后面我会介绍灯光与纹理贴图的相关应用.