This time we will look at how to generate a sphere in code, I got tired of the cubes and wanted a little variation. The decision fell on IcoSpheres, mostly as they seem to be more flexible in the long run.

This is part 13 of my series on OpenGL4 with OpenTK.

**For other posts in this series:**

OpenGL 4 with OpenTK in C# Part 1: Initialize the GameWindow

OpenGL 4 with OpenTK in C# Part 2: Compiling shaders and linking them

OpenGL 4 with OpenTK in C# Part 3: Passing data to shaders

OpenGL 4 with OpenTK in C# Part 4: Refactoring and adding error handling

OpenGL 4 with OpenTK in C# Part 5: Buffers and Triangle

OpenGL 4 with OpenTK in C# Part 6: Rotations and Movement of objects

OpenGL 4 with OpenTK in C# Part 7: Vectors and Matrices

OpenGL 4 with OpenTK in C# Part 8: Drawing multiple objects

OpenGL 4 with OpenTK in C# Part 9: Texturing

OpenGL 4 with OpenTK in C# Part 10: Asteroid Invaders

Basic bullet movement patterns in Asteroid Invaders

OpenGL 4 with OpenTK in C# Part 11: Mipmap

OpenGL 4 with OpenTK in C# Part 12: Basic Moveable Camera

OpenGL 4 with OpenTK in C# Part 13: IcoSphere

OpenGL 4 with OpenTK in C# Part 14: Basic Text

OpenGL 4 with OpenTK in C# Part 15: Object picking by mouse

As stated in the previous post, I am in no way an expert in OpenGL. I write these posts as a way to learn and if someone else finds these posts useful then all the better :)

If you think that the progress is slow, then know that I am a slow learner :P

This part will build upon the game window and shaders from part 12.

## Introduction

So I take no credit for this algorithm to generate the sphere. I found a WPF version over at catch 22, Andreas Kahlers blog. I ported it to work with our OpenTK implementation of vertex buffers and texturing.Texturing fix for the common artefact of a funny looking stripe that goes from pole to pole is based on a solution described over at sol.gfxil.net.

So, now that credit is where it belongs, lets look at the code.

## Code

private struct Face { public Vector3 V1; public Vector3 V2; public Vector3 V3; public Face(Vector3 v1, Vector3 v2, Vector3 v3) { V1 = v1; V2 = v2; V3 = v3; } }First we need a struct where we will store each face of our sphere. Basically just contains 3 vectors for each point in a triangle.

The algorithm from Khalers blog. As noted above, this does not use vertex indexing. Meaning that we have a little memory overhead as every vertex is doubled instead of reused.

Basic algorithm is to generate the initial points manually and then for each iteration split each face into 4 new faces and project them into the unit sphere by normalizing them.

public class IcoSphereFactory { private List<Vector3> _points; private int _index; private Dictionary<long, int> _middlePointIndexCache; public TexturedVertex[] Create(int recursionLevel) { _middlePointIndexCache = new Dictionary<long, int>(); _points = new List<Vector3>(); _index = 0; var t = (float)((1.0 + Math.Sqrt(5.0)) / 2.0); var s = 1; AddVertex(new Vector3(-s, t, 0)); AddVertex(new Vector3(s, t, 0)); AddVertex(new Vector3(-s, -t, 0)); AddVertex(new Vector3(s, -t, 0)); AddVertex(new Vector3(0, -s, t)); AddVertex(new Vector3(0, s, t)); AddVertex(new Vector3(0, -s, -t)); AddVertex(new Vector3(0, s, -t)); AddVertex(new Vector3(t, 0, -s)); AddVertex(new Vector3(t, 0, s)); AddVertex(new Vector3(-t, 0, -s)); AddVertex(new Vector3(-t, 0, s)); var faces = new List<Face>(); // 5 faces around point 0 faces.Add(new Face(_points[0], _points[11], _points[5])); faces.Add(new Face(_points[0], _points[5], _points[1])); faces.Add(new Face(_points[0], _points[1], _points[7])); faces.Add(new Face(_points[0], _points[7], _points[10])); faces.Add(new Face(_points[0], _points[10], _points[11])); // 5 adjacent faces faces.Add(new Face(_points[1], _points[5], _points[9])); faces.Add(new Face(_points[5], _points[11], _points[4])); faces.Add(new Face(_points[11], _points[10], _points[2])); faces.Add(new Face(_points[10], _points[7], _points[6])); faces.Add(new Face(_points[7], _points[1], _points[8])); // 5 faces around point 3 faces.Add(new Face(_points[3], _points[9], _points[4])); faces.Add(new Face(_points[3], _points[4], _points[2])); faces.Add(new Face(_points[3], _points[2], _points[6])); faces.Add(new Face(_points[3], _points[6], _points[8])); faces.Add(new Face(_points[3], _points[8], _points[9])); // 5 adjacent faces faces.Add(new Face(_points[4], _points[9], _points[5])); faces.Add(new Face(_points[2], _points[4], _points[11])); faces.Add(new Face(_points[6], _points[2], _points[10])); faces.Add(new Face(_points[8], _points[6], _points[7])); faces.Add(new Face(_points[9], _points[8], _points[1])); // refine triangles for (int i = 0; i < recursionLevel; i++) { var faces2 = new List<Face>(); foreach (var tri in faces) { // replace triangle by 4 triangles int a = GetMiddlePoint(tri.V1, tri.V2); int b = GetMiddlePoint(tri.V2, tri.V3); int c = GetMiddlePoint(tri.V3, tri.V1); faces2.Add(new Face(tri.V1, _points[a], _points[c])); faces2.Add(new Face(tri.V2, _points[b], _points[a])); faces2.Add(new Face(tri.V3, _points[c], _points[b])); faces2.Add(new Face(_points[a], _points[b], _points[c])); } faces = faces2; } // done, now add triangles to mesh var vertices = new List<TexturedVertex>(); foreach (var tri in faces) { var uv1 = GetSphereCoord(tri.V1); var uv2 = GetSphereCoord(tri.V2); var uv3 = GetSphereCoord(tri.V3); vertices.Add(new TexturedVertex(new Vector4(tri.V1, 1), uv1)); vertices.Add(new TexturedVertex(new Vector4(tri.V2, 1), uv2)); vertices.Add(new TexturedVertex(new Vector4(tri.V3, 1), uv3)); } return vertices.ToArray(); } private int AddVertex(Vector3 p) { _points.Add(p.Normalized()); return _index++; } // return index of point in the middle of p1 and p2 private int GetMiddlePoint(Vector3 point1, Vector3 point2) { long i1 = _points.IndexOf(point1); long i2 = _points.IndexOf(point2); // first check if we have it already var firstIsSmaller = i1 < i2; long smallerIndex = firstIsSmaller ? i1 : i2; long greaterIndex = firstIsSmaller ? i2 : i1; long key = (smallerIndex << 32) + greaterIndex; int ret; if (_middlePointIndexCache.TryGetValue(key, out ret)) { return ret; } // not in cache, calculate it var middle = new Vector3( (point1.X + point2.X) / 2.0f, (point1.Y + point2.Y) / 2.0f, (point1.Z + point2.Z) / 2.0f); // add vertex makes sure point is on unit sphere int i = AddVertex(middle); // store it, return index _middlePointIndexCache.Add(key, i); return i; } }Get sphere coordinate is my own addition for calculating the texture coordinate for each vertex.

public static Vector2 GetSphereCoord(Vector3 i) { var len = i.Length; Vector2 uv; uv.Y = (float)(Math.Acos(i.Y / len) / Math.PI); uv.X = -(float)((Math.Atan2(i.Z, i.X) / Math.PI + 1.0f) * 0.5f); return uv; }

At this point it looks like the following:

Icosphere that shows the texturing glitch from pole to pole |

private static void FixColorStrip(ref Vector2 uv1, ref Vector2 uv2, ref Vector2 uv3) { if ((uv1.X - uv2.X) >= 0.8f) uv1.X -= 1; if ((uv2.X - uv3.X) >= 0.8f) uv2.X -= 1; if ((uv3.X - uv1.X) >= 0.8f) uv3.X -= 1; if ((uv1.X - uv2.X) >= 0.8f) uv1.X -= 1; if ((uv2.X - uv3.X) >= 0.8f) uv2.X -= 1; if ((uv3.X - uv1.X) >= 0.8f) uv3.X -= 1; }

And call it from here in the Create function

foreach (var tri in faces) { var uv1 = GetSphereCoord(tri.V1); var uv2 = GetSphereCoord(tri.V2); var uv3 = GetSphereCoord(tri.V3); FixColorStrip(ref uv1, ref uv2, ref uv3); vertices.Add(new TexturedVertex(new Vector4(tri.V1, 1), uv1)); vertices.Add(new TexturedVertex(new Vector4(tri.V2, 1), uv2)); vertices.Add(new TexturedVertex(new Vector4(tri.V3, 1), uv3)); }

Now the texture should look OK like this:

Icosphere that shows the texturing glitch fixed |

models.Add("Wooden", new MipMapGeneratedRenderObject(new IcoSphereFactory().Create(3), _texturedProgram.Id, @"Components\Textures\wooden.png", 8)); models.Add("Golden", new MipMapGeneratedRenderObject(new IcoSphereFactory().Create(3), _texturedProgram.Id, @"Components\Textures\golden.bmp", 8)); models.Add("Asteroid", new MipMapGeneratedRenderObject(new IcoSphereFactory().Create(3), _texturedProgram.Id, @"Components\Textures\moonmap1k.jpg", 8)); models.Add("Spacecraft", new MipMapGeneratedRenderObject(RenderObjectFactory.CreateTexturedCube6(1, 1, 1), _texturedProgram.Id, @"Components\Textures\spacecraft.png", 8)); models.Add("Gameover", new MipMapGeneratedRenderObject(RenderObjectFactory.CreateTexturedCube6(1, 1, 1), _texturedProgram.Id, @"Components\Textures\gameover.png", 8)); models.Add("Bullet", new MipMapGeneratedRenderObject(new IcoSphereFactory().Create(3), _texturedProgram.Id, @"Components\Textures\dotted.png", 8));

End result should be as in the following video:

## Known issues that still need to be fixed:

Texturing at the poles is still glitchy. I was unable to get the solution described at sol.gfxil.net for it to work.. Yet.Maybe implement the vertex index solution as soon as I figure it out. As this currently fits my needs that might take a while.

For the complete source code for the tutorial at the end of this part, go to: https://github.com/eowind/dreamstatecoding

So there, thank you for reading. Hope this helps someone out there : )

Until next time: Work to Live, Don’t Live to Work

## No comments:

## Post a Comment