Now that we have Meshes and Vertex Buffer, I figure that it's time to work on some movement skills in 3D. Before we do that though, let's set the stage by having our tree on a little piece of field.
            VertexCount = 3456
            PrT = PrimitiveType.TriangleList
            VxB = New VertexBuffer(GetType(CustomVertex.PositionNormalTextured), VertexCount, D9, Usage.WriteOnly, CustomVertex.PositionNormalTextured.Format, Pool.Managed)
            'That is a LOT of non-trivial parameters.

            TextureAndMeshSetup()
            'Set up our textures.


            SetVertexBufferData(Vertices, VxB, D9)
The landscape is just a stretched-out version of the old landscape and also, the function F is 0:
    Private Sub SetVertexBufferData(ByRef vs() As CustomVertex.PositionNormalTextured, ByVal vxbf As VertexBuffer, ByVal D9 As Device)
        Dim LX, LY As Single, Ctr As Integer
        Const dL As Single = 1.0F

        'After that workout, we now have to actually set the vertices.
        'We do this by locking the surface out of D9 while we "operate" on it.
        Dim A As Array = vxbf.Lock(0, LockFlags.None)
        'It returns a vanilla array...
        vs = DirectCast(A, CustomVertex.PositionNormalTextured())
        'So we have to convert it to an array of the appropriate vertices.

        'Z = F(X, Y), but this time with texture.
        'Our texture should be 0,0 at X,Y = -3,-3 and 1,1 at X,Y = 3,3
        'So, we will simply do an interpolation during this process.
        'An interpolation is, again, like this:
        'Vf = L + Percent * (H - L), where Vf is the texture value, L is the lowest texture value, and H is the highest texture value.
        'Percent would also be an interpolation: Percent = (Vo - L) / (H - L)
        'where Vo is the loop variable value, L is the lowest loop value, and H is the highest loop value.
        'The first formula would be Vf = 0 + Percent * (1 - 0) = Percent.
        'The second formula would be Percent = (Vo - -3) / (3 - -3) = (Vo + 3) / 6,
        'Therefore, Vf = (Vo + 3) / 6
        'with L + dL
        'Vf = (Vo + dVo + 3) / 6


        For LY = -12.0F To 11.999F Step dL
            For LX = -12.0F To 11.999F Step dL
                'triangle 1:
                vs(Ctr) = New CustomVertex.PositionNormalTextured(LX, LY, F(LX, LY), 0.0F, 0.0F, 0.0F, (LX + 3.0F) / 6.0F, (LY + 3.0F) / 6.0F)
                vs(Ctr + 1) = New CustomVertex.PositionNormalTextured(LX + dL, LY, F(LX + 0.25F, LY), 0.0F, 0.0F, 0.0F, (LX + 3.0F + dL) / 6.0F, (LY + 3.0F) / 6.0F)
                vs(Ctr + 2) = New CustomVertex.PositionNormalTextured(LX, LY + dL, F(LX, LY + 0.25F), 0.0F, 0.0F, 0.0F, (LX + 3.0F) / 6.0F, (LY + 3.0F + dL) / 6.0F)
                'triangle 2:
                vs(Ctr + 3) = New CustomVertex.PositionNormalTextured(LX + dL, LY + dL, F(LX + dL, LY + dL), 0.0F, 0.0F, 0.0F, (LX + 3.0F + dL) / 6.0F, (LY + 3.0F + dL) / 6.0F)
                vs(Ctr + 4) = vs(Ctr + 2)
                vs(Ctr + 5) = vs(Ctr + 1)
                Ctr += 6
            Next
        Next

        NormalizeTriangles(vs, PrT)

        'It's important to unlock the vertex buffer, so that D9 can see what we put in there.
        vxbf.Unlock()

    End Sub
    Private Function F(ByVal X As Single, ByVal Y As Single) As Single
        'Try out your own functions of X and Y.
        'Return 0.05F * X * X  'Z = 1/4 (X²)... parabolic sheet of paper.
        'Return 0.25F * X * Y   'Origami?  Looks like the beginning of a paper bird.
        'Return 0.25F * X * X + 0.25F * Y * Y  'A hammock with an invisible person sitting in it.
        'Return Convert.ToSingle(Math.Sin(X * PiHalf))  'This looks more sinusoidal at a higher resolution.
        'Return 0.25F * X * X - 0.25F * Y * Y  'Saddle paper.
        'Return Convert.ToSingle(Math.Cos(X * PiHalf)) * Convert.ToSingle(Math.Sin(Y * PiHalf))  'Eggshell bed comforter.
        Return 0.0F  'Plane Z = 0

    End Function
And the vertex buffer is drawn as usual:
        'So, we'd be drawing here.
        'To draw a vertex buffer, we set the device to receive from a vertex stream.
        'The data is ready to stream out of the vertex buffer into the device and the device should draw it onto the screen.
        D9.SetStreamSource(0, VxB, 0)
        'StreamNumber is usually zero while we only have one vertex buffer that streams vertex data.
        'Of course, we also have to tell it which vertex buffer to get vertex data from.
        'Offset as 0 just tells the device how many vertices to skip before drawing.

        D9.VertexFormat = CustomVertex.PositionNormalTextured.Format
        'We also need to tell the device what kind of vertices to draw.
        'We have not told the device which vertices these are (that was the vertex buffer).
        'This could actually come out of the loop, but there will usually be many different types of vertex types
        'set within this loop.

        D9.DrawPrimitives(PrT, 0, GetPrimitiveCount(PrT, VertexCount))
The primitive type is still a triangle list. The lighting will be modified:
        D9.Lights(0).Type = LightType.Point  'Regular lightbulb.
        D9.Lights(0).Position = New Vector3(0.0F, 0.0F, 2.0F) 'Place the bulb here.
        D9.Lights(0).Range = 48.8F
        D9.Lights(0).Attenuation0 = 0.0F
        D9.Lights(0).Attenuation1 = 0.45F
        D9.Lights(0).Ambient = Color.White

Once that is set up, let's do some movement: First, we set up our camera so that it is facing in some direction and the distance from the Camera to the FocusPt is one. Below, our camera is at 3,3 with a height of 1 and is looking south (due to the FocusPt Y value being 1 larger than the camera's point).
            Camera = New Vector3(3.0F, 3.0F, 1.0F)
            FocusPt = New Vector3(3.0F, 4.0F, 1.0F)
            CamNormal = New Vector3(0.0F, 0.0F, 1.0F)
We need to have an angle that is telling us what angle the camera is facing (doing the trigonometric operations with the camera and focus point are not very efficient) - in fact, we'll do two angles, since we want our player to have the ability to look up and look down.
    Dim AnglePl, AngleEv As Double
The location of the person can easily be found by the camera location. Given both angles, the FocusPt can be calculated by these (or any variant of these) spherical formulas:
                        FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv))
AnglePl = 0° means the player is looking along the positive X axis and AngleEv = 0° means the player is looking straight ahead (not up of down). And remember that the angles are actually in radians. The corresponding degree measurement can be found by replacing Pi with 180°. Now, let's do some movement.
Our first movement is a "look up". When you press a "look up" button, your character should look up. This can be done by altering the AngleEv, which tells us the angle above horizontal that the player is currently looking. In some games, this is done with the up arrow - in some it is done with the down arrow - and of course, it can be done with the mouse as well (but that is beyond this lesson). After every movement, we update the focus point and the camera, if applicable (not in this case since the character is not changing location).
                    Case Keys.NumPad2 'Down
                        If AngleEv < PiHalf Then
                            AngleEv += (Pi / 30)
                        End If
                        FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv))
. "Look down" is the same as a "look up", except the signs change:
                    Case Keys.NumPad8 'Up
                        If AngleEv > -PiHalf Then
                            AngleEv -= (Pi / 30)
                        End If
                        FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv))

Next, we'll do a "turn left" or "look left". In this one, we only alter the AnglePl to give the appearance that the player turned or looked to the left. The trick is to remember which angle increase corresponds to left. AnglePl = 90°, according to the above spherical functions, is in the positive Y direction (Hint: at 0°, Cos(AnglePl) dominates, and at 90°, Sin(AnglePl) dominates). So, if we were to increase the value of AnglePl, the player would turn from the positive X direction to the positive Y direction. In a right-hand system, this would be turning to the left. So, we would have to add to the angle to make the player turn left.
                    Case Keys.NumPad6 'Left
                        AnglePl += (Pi / 30)
                        FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv))
To "turn right", we must subtract from the angle:
                    Case Keys.NumPad4 'Left
                        AnglePl -= (Pi / 30)
                        FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv))
                        FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv))

Next, we'll do walking forward. This one is a little more challenging since we need the angle that the player is facing to actually determine how far the player should move in each direction, X, and Y. Fortunately, with a little trigonometry, we can figure out the difference in both directions with Cosine and Sine. To make it easy to keep track of these changes we can declare some quick reference values:
        Dim dX, dY, dZ As Single
And our "walking forward" command looks like this:
                    Case Keys.Up
                        dX = Convert.ToSingle(Math.Cos(AnglePl))
                        dY = Convert.ToSingle(Math.Sin(AnglePl))
                        Camera.X += dX
                        Camera.Y += dY
                        FocusPt.X += dX
                        FocusPt.Y += dY
To walk backward, we subtract the cosine and sine from the current positions:
                    Case Keys.Down
                        dX = Convert.ToSingle(Math.Cos(AnglePl))
                        dY = Convert.ToSingle(Math.Sin(AnglePl))
                        Camera.X -= dX
                        Camera.Y -= dY
                        FocusPt.X -= dX
                        FocusPt.Y -= dY

Next, we will do "strafe left". This is very similar to walking forward except that we have the challenge of figuring out which direction goes with which trigonometric function. Obviously, if the character is facing at 0°, strafing left means he should walk to the positive Y direction (or negative Y direction, for left-hand coordinate systems) and strafing right means negative Y direction (or positive). At any other angle, this change in the Y direction is smaller, so this must correspond to a maximum. So, what function is the maximum at 0°? You guessed it, cosine. So, we apply cosine to the Y direction. It would follow that sine would be used for the X direction. Now, what about the signs? Since strafing left at 90° (where Sine is at a maximum) would mean that you move in the negative X and strafing right at 90° means moving in the positive X direction, we can possibly see that strafing left means that we would subtract dX and add dY (figure this one out by asking if the character walks in the negative direction or in the positive direction). So, strafing left would be:
                    Case Keys.Right
                        dX = Convert.ToSingle(Math.Sin(AnglePl))
                        dY = Convert.ToSingle(Math.Cos(AnglePl))
                        Camera.X -= dX
                        Camera.Y += dY
                        FocusPt.X -= dX
                        FocusPt.Y += dY
Strafing right would be the opposite:
                    Case Keys.Left
                        dX = Convert.ToSingle(Math.Sin(AnglePl))
                        dY = Convert.ToSingle(Math.Cos(AnglePl))
                        Camera.X += dX
                        Camera.Y -= dY
                        FocusPt.X += dX
                        FocusPt.Y -= dY

Next, we will do a "fly forward" - this merely means the character goes forward in whatever direction the character is facing - even if the character is looking upwards. For this, we merely apply the spherical formula to figure out how far to move in each direction:
                        dX = Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv))
                        dY = Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv))
                        dZ = Convert.ToSingle(Math.Sin(AngleEv))
                        Camera.X += dX
                        Camera.Y += dY
                        Camera.Z += dZ
                        FocusPt.X += dX
                        FocusPt.Y += dY
                        FocusPt.Z += dZ
"Fly backward" would be the negative of everything here:
                    Case Keys.Down
                        dX = Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv))
                        dY = Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv))
                        dZ = Convert.ToSingle(Math.Sin(AngleEv))
                        Camera.X -= dX
                        Camera.Y -= dY
                        Camera.Z += dZ
                        FocusPt.X -= dX
                        FocusPt.Y -= dY
                        FocusPt.Z -= dZ
Flying left and flying right are identical to strafing left and strafing right - your left and right do not change if you look up or down.
.vb file