public static void Main (string[] args) { Device.Init (1024, 576, "Software Renderer"); // initialize a camera with 60 degrees field of view // the second argument is the position of the camera (left handed) Camera camera = new Camera (60, new Vector3 (0, 0, -10)); // load the storm trooper texture Texture stormTrooperTexture = new Texture("../../Assets/Stormtrooper.png"); // load the stormtrooper mesh, and apply the specified texture Mesh stormTrooper = new Mesh ("../../Assets/Stormtrooper.obj", stormTrooperTexture); // scale in half stormTrooper.scale = new Vector3 (0.5f, 0.5f, 0.5f); // place y at -1 to have better view of the storm tropper stormTrooper.position = new Vector3 (0, -1, 0); // start with a 0 rotation stormTrooper.rotationEuler = new Vector3 (0, 0, 0); float z = camera.position.z; float y = stormTrooper.rotationEuler.y; while (Device.Active) { // red background (remember, colors are not normalized in device space) Device.Clear (255, 0, 0); // input management, we rotate the mesh with left and right, and we zoom in/out with up and down if (Device.GetKey (KeyCode.Right)) y+=3; if (Device.GetKey (KeyCode.Left)) y-=3; if (Device.GetKey (KeyCode.Up)) z+=1; if (Device.GetKey (KeyCode.Down)) z-=1; // set z of the camera camera.position = new Vector3 (0, 0, z); // apply rotation to the model stormTrooper.rotationEuler = new Vector3(0, y, 0); // draw the model stormTrooper.Draw (camera); // update the screen Device.Update (); } }
// rasterization procedure public static void ScanLine (int y, Vertex vertexLeftTop, Vertex vertexLeftBottom, Vertex vertexRightTop, Vertex vertexRightBottom, Texture texture) { // assumes a flat triangle float gradientLeft = 1; float gradientRight = 1; // non flat triangle ? if (vertexLeftTop.projected.y != vertexLeftBottom.projected.y) { gradientLeft = (y - vertexLeftTop.projected.y) / (vertexLeftBottom.projected.y - vertexLeftTop.projected.y); } if (vertexRightTop.projected.y != vertexRightBottom.projected.y) { gradientRight = (y - vertexRightTop.projected.y) / (vertexRightBottom.projected.y - vertexRightTop.projected.y); } // find x start position and end position using the y gradient int left = (int)vertexLeftTop.projected.x.Interpolate (vertexLeftBottom.projected.x, gradientLeft); int right = (int)vertexRightTop.projected.x.Interpolate (vertexRightBottom.projected.x, gradientRight); // find the z start and value using the same interpolation float zStart = vertexLeftTop.projected.z.Interpolate (vertexLeftBottom.projected.z, gradientLeft); float zEnd = vertexRightTop.projected.z.Interpolate (vertexRightBottom.projected.z, gradientRight); // get U start and end float uStart = vertexLeftTop.uv.x.Interpolate (vertexLeftBottom.uv.x, gradientLeft); float uEnd = vertexRightTop.uv.x.Interpolate (vertexRightBottom.uv.x, gradientRight); // get V start and end float vStart = vertexLeftTop.uv.y.Interpolate (vertexLeftBottom.uv.y, gradientLeft); float vEnd = vertexRightTop.uv.y.Interpolate (vertexRightBottom.uv.y, gradientRight); for (int x = left; x < right; x++) { // while we do not need interpolation for horizontal pixels // we need it for texturing and for getting correct z value // zGradient name can be a bit misleading, as it will be used for UV's too float zGradient = ((float)x - (float)left) / ((float)right - (float)left); // get the z value using interpolation float z = zStart.Interpolate (zEnd, zGradient); // find texture uv coordinates using interpolation // we can resue the zGradient float u = uStart.Interpolate (uEnd, zGradient); float v = vStart.Interpolate (vEnd, zGradient); // get texture pixel color using UV system // note the (1f - v), it is required as V coordinate is reversed (0 on bottom, 1 on top) Vector3 color = texture.Map (u, 1f - v); // write pixel on the device, de-normalizing colors PutPixel (x, (int)y, z, (byte)(color.x * 255), (byte)(color.y * 255), (byte)(color.z * 255)); } }
public Mesh (string fileName, Texture texture) { this.position = Vector3.zero; this.rotationEuler = Vector3.zero; // initialize with default scaling this.scale = new Vector3(1, 1, 1); this.vertices = new List<Vector3> (); this.uvs = new List<Vector2> (); this.normals = new List<Vector3> (); this.faces = new List<Triangle> (); this.texture = texture; using (StreamReader reader = new StreamReader (fileName)) { while (true) { string line = reader.ReadLine (); if (line == null) break; // manage vertices if (line.StartsWith ("v ")) { string[] items = line.Split (' '); // check for right handed and left handed !!! this.vertices.Add (new Vector3 ( float.Parse (items [1], CultureInfo.InvariantCulture), float.Parse (items [2], CultureInfo.InvariantCulture), float.Parse (items [3], CultureInfo.InvariantCulture) * -1 )); } // manage texture coordinates (uv) if (line.StartsWith ("vt ")) { string[] items = line.Split (' '); this.uvs.Add (new Vector2 ( float.Parse (items [1], CultureInfo.InvariantCulture), float.Parse (items [2], CultureInfo.InvariantCulture) )); } // manage normals if (line.StartsWith ("vn ")) { string[] items = line.Split (' '); // check for right handed and left handed !!! this.normals.Add (new Vector3 ( float.Parse (items [1], CultureInfo.InvariantCulture), float.Parse (items [2], CultureInfo.InvariantCulture), float.Parse (items [3], CultureInfo.InvariantCulture) )); } if (line.StartsWith ("f ")) { string[] items = line.Split (' '); string []id1 = items [1].Split ('/'); string []id2 = items [2].Split ('/'); string []id3 = items [3].Split ('/'); // vertex Vector3 av = this.vertices [int.Parse(id1[0]) - 1]; Vector3 bv = this.vertices [int.Parse(id2[0]) - 1]; Vector3 cv = this.vertices [int.Parse(id3[0]) - 1]; // uv Vector2 auv = this.uvs [int.Parse(id1[1]) - 1]; Vector2 buv = this.uvs [int.Parse(id2[1]) - 1]; Vector2 cuv = this.uvs [int.Parse(id3[1]) - 1]; // normal Vector3 an = this.normals [int.Parse(id1[2]) - 1]; Vector3 bn = this.normals [int.Parse(id2[2]) - 1]; Vector3 cn = this.normals [int.Parse(id3[2]) - 1]; Vertex a = new Vertex (av, auv, an); Vertex b = new Vertex (bv, buv, bn); Vertex c = new Vertex (cv, cuv, cn); this.faces.Add (new Triangle (this, a, b, c)); } } } }