The normals that I added to the previous cube were hardcoded into the vertices. Ideally, the normal should be flexible depending on the shape. For that reason, I have a custom normal calculating function.
This one calculates the normal of a triangle with the coordinates specified by A, B, and C.
 ``` Private Function GetNormal(ByVal A As Vector3, ByVal B As Vector3, ByVal C As Vector3) As Vector3 Dim Pu As Vector3, Pv As Vector3 Pu = Vector3.Subtract(B, A) 'Final minus initial. Pv = Vector3.Subtract(C, A) 'Final minus initial. 'Pu and Pv are difference vectors that describe the shape and orientation of the triangle. 'Therefore, the normal is the cross product of these two vectors: N = Pu × Pv Return Vector3.Cross(Pu, Pv) End Function```
This one installs normals for all of the vertices in an array (for a triangle list):
 ``` Private Sub NormalizeTriangles(ByRef vs() As CustomVertex.PositionNormal, ByVal PrT As PrimitiveType) Dim N As Vector3, LV As Integer If PrT = PrimitiveType.TriangleList Then For LV = 2 To vs.GetUpperBound(0) Step 3 N = GetNormal(vs(LV - 2).GetPosition, vs(LV - 1).GetPosition, vs(LV).GetPosition) N.Normalize() vs(LV - 2).SetNormal(N) vs(LV - 1).SetNormal(N) vs(LV).SetNormal(N) Next End If End Sub```
For new versions of DX9, .Position and .Normal are properties each with Set and Get, so the .GetPosition can be replaced with a .Position, and the .SetNormal(N) can become .Normal = N.
Our new vertex buffer initialization:
 ``` Private Sub SetVertexBufferData(ByRef vs As CustomVertex.PositionNormal(), ByVal vxbf As VertexBuffer, ByVal D9 As Device) '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.PositionNormal()) 'So we have to convert it to an array of TransformedColored vertices. 'Remember: TransformColored vertices come with X, Y, Z, and Regularly Holding Wone, along with color. 'PositionColored has no RHW: only X, Y, Z (position) and Color. 'z = 0, 0,0,0 vs(0) = New CustomVertex.PositionNormal(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F) vs(1) = New CustomVertex.PositionNormal(2.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F) vs(2) = New CustomVertex.PositionNormal(0.0F, 2.0F, 0.0F, 0.0F, 0.0F, 0.0F) 'z = 0, 2,2,0 vs(3) = New CustomVertex.PositionNormal(2.0F, 2.0F, 0.0F, 0.0F, 0.0F, 0.0F) vs(4) = vs(2) vs(5) = vs(1) 'x = 0, 0,0,0 vs(6) = vs(0) vs(7) = vs(2) vs(8) = New CustomVertex.PositionNormal(0.0F, 0.0F, 2.0F, 0.0F, 0.0F, 0.0F) 'x = 0, 0,2,2 vs(9) = New CustomVertex.PositionNormal(0.0F, 2.0F, 2.0F, 0.0F, 0.0F, 0.0F) vs(10) = vs(8) vs(11) = vs(2) 'y = 0, 0,0,0 vs(12) = vs(0) vs(13) = vs(8) vs(14) = vs(1) 'y = 0, 2,0,2 vs(15) = New CustomVertex.PositionNormal(2.0F, 0.0F, 2.0F, 0.0F, 0.0F, 0.0F) vs(16) = vs(1) vs(17) = vs(8) 'z = 2, 0,0,2 vs(18) = vs(8) vs(19) = vs(9) vs(20) = vs(15) 'z = 2, 2,2,2 vs(21) = New CustomVertex.PositionNormal(2.0F, 2.0F, 2.0F, 0.0F, 0.0F, 0.0F) vs(22) = vs(15) vs(23) = vs(9) 'y = 2, 0,2,0 vs(24) = vs(2) vs(25) = vs(3) vs(26) = vs(9) 'y = 2, 2,2,2 vs(27) = vs(21) vs(28) = vs(9) vs(29) = vs(3) 'x = 2, 2,0,0 vs(30) = vs(1) vs(31) = vs(15) vs(32) = vs(3) 'x = 2, 2,2,2 vs(33) = vs(21) vs(34) = vs(3) vs(35) = vs(15) 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```
Note that the normals of all vertices start off at 0,0,0, but then the call to NormalizeTriangles installs the proper normal onto each vertex.
 ``` Dim PrT As PrimitiveType ```
The primitive type is now in the declarations sections for use by the vertex buffer initializer. It is set before the call to the vertex buffer initializer.
 ``` VertexCount = 36 PrT = PrimitiveType.TriangleList VxB = New VertexBuffer(GetType(CustomVertex.PositionNormal), VertexCount, D9, Usage.WriteOnly, CustomVertex.PositionNormal.Format, Pool.Managed) 'That is a LOT of non-trivial parameters.```

.vb file