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 (); } }
// apply perspective projection to vector using the specified camera public static Vector3 Project (this Vector3 v, Camera camera) { Vector3 v3; float angle = camera.fov / 2 * Deg2Rad; // 1f/tan(alpha) float distanceFromCamera = (float)Math.Tan (angle); v3.y = v.y / (distanceFromCamera * v.z); v3.x = v.x / (aspectRatio * distanceFromCamera * v.z); // hold the original z value, as it will be used by z-buffering v3.z = v.z; return v3; }
// draw a 3d triangle in a 2d space public void Draw (Camera camera) { // transform local coordinates in world coordinates // remember: scaling is vector multiplication, translation is vector addition Vector3 aWorld = this.a.coordinates.Rotate(mesh.rotationEuler) * mesh.scale + mesh.position; Vector3 bWorld = this.b.coordinates.Rotate(mesh.rotationEuler) * mesh.scale + mesh.position; Vector3 cWorld = this.c.coordinates.Rotate(mesh.rotationEuler) * mesh.scale + mesh.position; // apply camera trasnformations (remember they are always inverted) Vector3 aCamera = aWorld - (camera.position); Vector3 bCamera = bWorld - (camera.position); Vector3 cCamera = cWorld - (camera.position); // project 3d vertices (in camera space) on 2d surface this.a.projected = aCamera.Project (camera).NDCtoPixel (); this.b.projected = bCamera.Project (camera).NDCtoPixel (); this.c.projected = cCamera.Project (camera).NDCtoPixel (); // now we have 2d triangles, time to rasterize them // first step is ordering vertices from top to bottom // order vertices (using projected coordinates [x and y are pixels, z is a copy of the camera transformation]) Vertex p1 = this.a; Vertex p2 = this.b; Vertex p3 = this.c; // we use a dumb swapping algorithm for performances if (p1.projected.y > p2.projected.y) { var tmp = p2; p2 = p1; p1 = tmp; } if (p2.projected.y > p3.projected.y) { var tmp = p2; p2 = p3; p3 = tmp; } if (p1.projected.y > p2.projected.y) { var tmp = p2; p2 = p1; p1 = tmp; } // find slopes of the triangle edges, it is required to understand if P2 is on the left or on the right float slopeP1P2 = (p2.projected.x - p1.projected.x) / (p2.projected.y - p1.projected.y); float slopeP1P3 = (p3.projected.x - p1.projected.x) / (p3.projected.y - p1.projected.y); // iterate y from p1 to p3 for (int y = (int)p1.projected.y; y <= (int)p3.projected.y; y++) { // p2 on the left if (slopeP1P3 > slopeP1P2) { if (y < p2.projected.y) { Device.ScanLine (y, p1, p2, p1, p3, mesh.texture); } else { Device.ScanLine (y, p2, p3, p1, p3, mesh.texture); } } else { //p2 on the right if (y < p2.projected.y) { Device.ScanLine (y, p1, p3, p1, p2, mesh.texture); } else { Device.ScanLine (y, p1, p3, p2, p3, mesh.texture); } } } }
public void Draw (Camera camera) { foreach (Triangle face in this.faces) { face.Draw (camera); } }