diff --git a/tinyrender2/main.go b/tinyrender2/main.go index 2136d40..50e30c9 100644 --- a/tinyrender2/main.go +++ b/tinyrender2/main.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "log" + // "math/rand" "os" "runtime/pprof" // For performance profiling (unnecessary) ) @@ -12,7 +13,7 @@ const ( Width = 512 Height = 512 ObjectFile = "head.obj" - Repeat = 100_000 + Repeat = 600 ) func must(err error) { @@ -41,44 +42,50 @@ func main() { fb := NewFramebuffer(Width, Height) - /* - log.Printf("Loading obj %s", ObjectFile) + log.Printf("Loading obj %s", ObjectFile) - of, err := os.Open(ObjectFile) - must(err) - defer of.Close() - o, err := ParseObj(of) - must(err) - */ + of, err := os.Open(ObjectFile) + must(err) + defer of.Close() + o, err := ParseObj(of) + must(err) log.Printf("Running render") - //halfwidth := float32(fb.Width / 2) - //halfheight := float32(fb.Height / 2) - for range Repeat { - Triangle2(&fb, 0xFF0000, Vec2i{10, 70}, Vec2i{50, 160}, Vec2i{70, 80}) - Triangle2(&fb, 0xFFFFFF, Vec2i{180, 50}, Vec2i{150, 1}, Vec2i{70, 180}) - Triangle2(&fb, 0x00FF00, Vec2i{180, 150}, Vec2i{120, 160}, Vec2i{130, 180}) - } + light := Vec3f{0, 0, -1} - /* - var x [3]int - var y [3]int - var hi = int(fb.Height - 1) - for range Repeat { - for _, f := range o.Faces { - // Precompute perspective for vertices to save time. Notice Z - // is not considered: is this orthographic projection? Yeah probably... - for i := range 3 { // Triangles, bro - x[i] = int((f[i].X + 1) * halfwidth) - y[i] = hi - int((f[i].Y+1)*halfheight) - } - Bresenham2(&fb, 0xFFFFFF, x[0], y[0], x[1], y[1]) - Bresenham2(&fb, 0xFFFFFF, x[1], y[1], x[2], y[2]) - Bresenham2(&fb, 0xFFFFFF, x[2], y[2], x[0], y[0]) + // for range Repeat { + // Triangle2(&fb, 0xFF0000, Vec2i{10, 70}, Vec2i{50, 160}, Vec2i{70, 80}) + // Triangle2(&fb, 0xFFFFFF, Vec2i{180, 50}, Vec2i{150, 1}, Vec2i{70, 180}) + // Triangle2(&fb, 0x00FF00, Vec2i{180, 150}, Vec2i{120, 160}, Vec2i{130, 180}) + // } + + halfwidth := float32(fb.Width / 2) + halfheight := float32(fb.Height / 2) + var sc [3]Vec2i + var hi = int(fb.Height - 1) + for range Repeat { + for _, f := range o.Faces { + // Precompute perspective for vertices to save time. Notice Z + // is not considered: is this orthographic projection? Yeah probably... + for i := range 3 { // Triangles, bro + sc[i].X = int((f[i].X + 1) * halfwidth) + sc[i].Y = hi - int((f[i].Y+1)*halfheight) } + l1 := f[2].Sub(f[0]) + n := l1.CrossProduct(f[1].Sub(f[0])) + n = n.Normalize() + intensity := n.MultSimp(&light) + if intensity > 0 { + Triangle2(&fb, Col2Uint(byte(255*intensity), byte(255*intensity), byte(255*intensity)), sc[0], sc[1], sc[2]) + //Triangle2(&fb, 0xFFFFFF, sc[0], sc[1], sc[2]) + } + // Vec2i{10, 70}, Vec2i{50, 160}, Vec2i{70, 80}) + // Bresenham2(&fb, 0xFFFFFF, x[0], y[0], x[1], y[1]) + // Bresenham2(&fb, 0xFFFFFF, x[1], y[1], x[2], y[2]) + // Bresenham2(&fb, 0xFFFFFF, x[2], y[2], x[0], y[0]) } - */ + } log.Printf("Exporting ppm to stdout") fmt.Print(fb.ExportPPM()) diff --git a/tinyrender2/obj.go b/tinyrender2/obj.go index 14086ae..05c56af 100644 --- a/tinyrender2/obj.go +++ b/tinyrender2/obj.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log" + "math" "strings" ) @@ -32,9 +33,34 @@ func (vi *Vec2i) ToF() Vec2f { return Vec2f{float32(vi.X), float32(vi.Y)} } -// func CrossProduct(v0, v1 Vec3f) Vec3f { -// -// } +func (v0 *Vec3f) Sub(v1 Vec3f) Vec3f { + return Vec3f{ + X: v0.X - v1.X, + Y: v0.Y - v1.Y, + Z: v0.Z - v1.Z, + } +} + +func (v0 *Vec3f) CrossProduct(v1 Vec3f) Vec3f { + return Vec3f{ + X: v0.Y*v1.Z - v0.Z*v1.Y, + Y: v0.Z*v1.X - v0.X*v1.Z, + Z: v0.X*v1.Y - v0.Y*v1.X, + } +} + +func (v *Vec3f) Normalize() Vec3f { + l := float32(math.Sqrt(float64(v.MultSimp(v)))) + return Vec3f{ + X: v.X / l, + Y: v.Y / l, + Z: v.Z / l, + } +} + +func (v0 *Vec3f) MultSimp(v1 *Vec3f) float32 { + return v0.X*v1.X + v0.Y*v1.Y + v0.Z*v1.Z +} // Parse an obj file at the given reader. Only handles v and f right now func ParseObj(reader io.Reader) (*ObjModel, error) { diff --git a/tinyrender2/render.go b/tinyrender2/render.go index 9b24563..490e476 100644 --- a/tinyrender2/render.go +++ b/tinyrender2/render.go @@ -135,6 +135,18 @@ func EdgeIncrement(v1, v2 Vec2f) (float32, float32) { return (v2.Y - v1.Y), -(v2.X - v1.X) } +// The generic edge function, returning positive if P is on the right side of +// the line drawn between v1 and v2. This is counter clockwise +func EdgeFunctioni(v1, v2, p Vec2i) int { + return (p.X-v1.X)*(v2.Y-v1.Y) - (p.Y-v1.Y)*(v2.X-v1.X) +} + +// This computes the x and y per-pixel increment for the line going +// between v1 and v2 (also counter clockwise) +func EdgeIncrementi(v1, v2 Vec2i) (int, int) { + return (v2.Y - v1.Y), -(v2.X - v1.X) +} + func Triangle2(fb *Framebuffer, color uint, v0 Vec2i, v1 Vec2i, v2 Vec2i) { boundsTL, boundsBR := ComputeBoundingBox(v0, v1, v2) if boundsTL.Y < 0 { @@ -150,35 +162,46 @@ func Triangle2(fb *Framebuffer, color uint, v0 Vec2i, v1 Vec2i, v2 Vec2i) { boundsBR.X = int(fb.Width - 1) } // Where to start our scanning - pstart := Vec2f{float32(boundsTL.X) + 0.5, float32(boundsTL.Y) + 0.5} + pstart := Vec2i{boundsTL.X, boundsTL.Y} //log.Print(boundsTL, boundsBR) - v0f := v0.ToF() - v1f := v1.ToF() - v2f := v2.ToF() - parea := EdgeFunction(v0f, v1f, v2f) - invarea := 1 / parea - w0_y := EdgeFunction(v1f, v2f, pstart) - w1_y := EdgeFunction(v2f, v0f, pstart) - w2_y := EdgeFunction(v0f, v1f, pstart) - w0_xi, w0_yi := EdgeIncrement(v1f, v2f) - w1_xi, w1_yi := EdgeIncrement(v2f, v0f) - w2_xi, w2_yi := EdgeIncrement(v0f, v1f) + // v0f := v0.ToF() + // v1f := v1.ToF() + // v2f := v2.ToF() + // parea := EdgeFunction(v0f, v1f, v2f) + // invarea := 1 / parea + w0_y := EdgeFunctioni(v1, v2, pstart) + w1_y := EdgeFunctioni(v2, v0, pstart) + w2_y := EdgeFunctioni(v0, v1, pstart) + w0_xi, w0_yi := EdgeIncrementi(v1, v2) + w1_xi, w1_yi := EdgeIncrementi(v2, v0) + w2_xi, w2_yi := EdgeIncrementi(v0, v1) + //dyi := int(fb.Width) + //dy := boundsTL.X + dyi*boundsTL.Y for y := uint(boundsTL.Y); y <= uint(boundsBR.Y); y++ { w0 := w0_y w1 := w1_y w2 := w2_y + //di := dy + //done := false for x := uint(boundsTL.X); x <= uint(boundsBR.X); x++ { - if w0 >= 0 && w1 >= 0 && w2 >= 0 { - w0a := w0 * invarea - w1a := w1 * invarea - w2a := w2 * invarea - fb.Set(x, y, Col2Uint(byte(255*w0a), byte(255*w1a), byte(255*w2a))) - } + if (w0 | w1 | w2) >= 0 { + //fb.Data[di] = color + fb.Set(x, y, color) + //done = true + // w0a := w0 * invarea + // w1a := w1 * invarea + // w2a := w2 * invarea + // fb.Set(x, y, Col2Uint(byte(255*w0a), byte(255*w1a), byte(255*w2a))) + } /*else if done { + break + }*/ + //di += 1 w0 += w0_xi w1 += w1_xi w2 += w2_xi } + //dy += dyi w0_y += w0_yi w1_y += w1_yi w2_y += w2_yi