From 4a399c7bb78b67a11c44a546b21230c47264b565 Mon Sep 17 00:00:00 2001 From: Carlos Sanchez Date: Wed, 31 Jul 2024 01:02:05 -0400 Subject: [PATCH] Actually working oh wow --- renderer1/hrend/math.go | 34 ++++++++++--- renderer1/hrend/render.go | 3 ++ renderer1/main.go | 103 +++++++++++++++++++++++++++++++++----- renderer1/raybuffer.go | 26 +++++++--- 4 files changed, 139 insertions(+), 27 deletions(-) diff --git a/renderer1/hrend/math.go b/renderer1/hrend/math.go index d89f08c..d8e644e 100644 --- a/renderer1/hrend/math.go +++ b/renderer1/hrend/math.go @@ -42,22 +42,40 @@ func (m *Mat44f) SetIdentity() { // instead of creating a new one all the time (garbage collection) // Compute the projection matrix, filling the given matrix. FOV is in degrees -func (m *Mat44f) SetProjection(fov float32, near float32, far float32) { +func (m *Mat44f) SetProjection(fov float32, aspect float32, near float32, far float32) { // Projection matrix is (ROW MAJOR!) // S 0 0 0 // 0 S 0 0 // 0 0 -f/(f-n) -1 // 0 0 -fn/(f-n) 0 // where S (scale) is 1 / tan(fov / 2) (assuming fov is radians) - // NOTE: -1 there is actually -1/c, where c is distance from viewer to - // projection plane. We fix it at 1 for now but... + // // NOTE: -1 there is actually -1/c, where c is distance from viewer to + // // projection plane. We fix it at 1 for now but... + // m.ZeroFill() + // scale := float32(1 / math.Tan(float64(fov)*0.5*math.Pi/180)) + // m.Set(0, 0, scale) + // m.Set(1, 1, scale) + // m.Set(2, 2, -far/(far-near)) + // m.Set(3, 2, -1) + // m.Set(2, 3, -far*near/(far-near)) + // OK apparently I suck, let's use somebody else's projection matrix: m.ZeroFill() - scale := float32(1 / math.Tan(float64(fov)*0.5*math.Pi/180)) - m.Set(0, 0, scale) - m.Set(1, 1, scale) - m.Set(2, 2, -far/(far-near)) + + DEG2RAD := math.Acos(-1.0) / 180.0 + tangent := math.Tan(float64(fov/2.0) * DEG2RAD) // tangent of half fovY + top := near * float32(tangent) // half height of near plane + right := top * aspect // half width of near plane + // Column major maybe??? + // n/r 0 0 0 + // 0 n/t 0 0 + // 0 0 -(f+n)/(f-n) -1 + // 0 0 -(2fn)/(f-n) 0 + + m.Set(0, 0, near/right) + m.Set(1, 1, near/top) + m.Set(2, 2, -(far+near)/(far-near)) m.Set(3, 2, -1) - m.Set(2, 3, -far*near/(far-near)) + m.Set(2, 3, -(2*far*near)/(far-near)) } func (m *Mat44f) SetTranslation(x, y, z float32) { diff --git a/renderer1/hrend/render.go b/renderer1/hrend/render.go index e5231ad..e4b601c 100644 --- a/renderer1/hrend/render.go +++ b/renderer1/hrend/render.go @@ -42,6 +42,9 @@ func TriangleFlat(fb *RenderBuffer, color uint, v0f Vec3f, v1f Vec3f, v2f Vec3f) v2 := v2f.ToVec2i() r, g, b := Uint2Col(color) boundsTL, boundsBR := ComputeBoundingBox(v0, v1, v2) + if boundsBR.Y < 0 || boundsBR.X < 0 || boundsTL.X >= int(fb.Width) || boundsTL.Y >= int(fb.Height) { + return + } if boundsTL.Y < 0 { boundsTL.Y = 0 } diff --git a/renderer1/main.go b/renderer1/main.go index f58e019..46d2018 100644 --- a/renderer1/main.go +++ b/renderer1/main.go @@ -2,11 +2,14 @@ package main import ( "flag" + "fmt" "image" "log" + "math" "os" "renderer1/hrend" "runtime/pprof" // For performance profiling (unnecessary) + "time" _ "image/jpeg" @@ -20,7 +23,9 @@ const ( FarClip = 100 FOV = 90.0 ZOffset = -1.5 - ObjectFile = "head.obj" + Movement = 1.0 + Fps = 60 + ObjectFile = "../head.obj" TextureFile = "../head.jpg" ) @@ -73,38 +78,98 @@ func main() { defer pprof.StopCPUProfile() } - fb := NewRaylibBuffer(Width, Height) - rb := hrend.NewRenderbuffer(fb, Width, Height) - o, texture := loadDefault() - - light := hrend.Vec3f{0, 0, -1} - - var projection, worldToCamera, viewport hrend.Mat44f - rl.InitWindow(Width, Height, "Simple renderer with raylib") defer rl.CloseWindow() - rl.SetTargetFPS(60) + rl.SetTargetFPS(Fps) + + fb := NewRaylibBuffer(Width, Height) + defer rl.UnloadTexture(fb.Texture) + defer rl.UnloadImageColors(fb.Data) + defer rl.UnloadImage(fb.Image) + + rb := hrend.NewRenderbuffer(fb, Width, Height) + o, texture := loadDefault() + + light := hrend.Vec3f{ + X: 0, + Y: 0, + Z: -1, + } var thing hrend.Vec2i log.Print(thing) + var projection, worldToCamera, viewport hrend.Mat44f + + // These don't really change + projection.SetProjection(float32(FOV), float32(Width)/float32(Height), NearClip, FarClip) + viewport.SetViewportSimple(int(fb.Width), int(fb.Height), 1) //65535) + + log.Printf("Starting raylib loop") + + frameSum := 0.0 + frameCount := 0 + frameAverage := 0.0 + + xofs := float32(0.0) + yofs := float32(0.0) + zofs := float32(ZOffset) + for !rl.WindowShouldClose() { - rl.BeginDrawing() + + start := time.Now() + + if rl.IsKeyDown(rl.KeyD) { + xofs += Movement / Fps + } + if rl.IsKeyDown(rl.KeyA) { + xofs -= Movement / Fps + } + if rl.IsKeyDown(rl.KeyW) { + zofs += Movement / Fps + } + if rl.IsKeyDown(rl.KeyS) { + zofs -= Movement / Fps + } + if rl.IsKeyDown(rl.KeySpace) { + yofs -= Movement / Fps + } + if rl.IsKeyDown(rl.KeyLeftShift) { + yofs += Movement / Fps + } + + // This might (thought not currently) + worldToCamera.SetTranslation(xofs, yofs, zofs) screenmat := worldToCamera.Multiply(&projection) screenmat = screenmat.Multiply(&viewport) + for i := 0; i < Width*Height; i++ { + fb.Data[i].R = 0 + fb.Data[i].G = 0 + fb.Data[i].B = 0 + } + //rl.ImageDrawRectangle(fb.Image, 0, 0, Width, Height, rl.Black) + //rl.ImageClearBackground(fb.Image, rl.Black) + var sc [3]hrend.Vertex rb.ResetZBuffer() for _, f := range o.Faces { // Precompute perspective for vertices to save time. Notice Z // is not considered: is this orthographic projection? Yeah probably... var fpt [3]hrend.Vec3f + maxz := float32(-math.MaxFloat32) for i := range 3 { // Triangles, bro sc[i] = f[i] sc[i].Pos = screenmat.MultiplyPoint3(f[i].Pos) fpt[i] = worldToCamera.MultiplyPoint3(f[i].Pos) + maxz = max(maxz, sc[i].Pos.Z) + } + + if maxz < 0 || maxz > 1 { + //log.Printf("Clipping %f %f %f", sc[0].Pos.Z, sc[1].Pos.Z, sc[2].Pos.Z) + continue } l1 := fpt[2].Sub(fpt[0]) @@ -113,12 +178,24 @@ func main() { intensity := n.MultSimp(&light) if intensity > 0 { hrend.TriangleTextured(&rb, texture, intensity, sc[0], sc[1], sc[2]) + //hrend.TriangleFlat(&rb, hrend.Col2Uint(byte(255*intensity), byte(255*intensity), byte(255*intensity)), sc[0].Pos, sc[1].Pos, sc[2].Pos) } } - rl.ClearBackground(rl.RayWhite) - rl.DrawText("Congrats! You created your first window!", 190, 200, 20, rl.LightGray) + rl.UpdateTexture(fb.Texture, fb.Data) + frameSum += time.Since(start).Seconds() + frameCount += 1 + if frameCount&0x7 == 0 { + frameAverage = frameSum / float64(frameCount) + frameSum = 0 + frameCount = 0 + } + + rl.BeginDrawing() + rl.ClearBackground(rl.RayWhite) + rl.DrawTexture(fb.Texture, 0, 0, rl.White) + rl.DrawText(fmt.Sprintf("Frame: %fms", frameAverage*1000), 5, 5, 20, rl.Red) rl.EndDrawing() } } diff --git a/renderer1/raybuffer.go b/renderer1/raybuffer.go index e003451..f175c65 100644 --- a/renderer1/raybuffer.go +++ b/renderer1/raybuffer.go @@ -2,19 +2,33 @@ package main import ( rl "github.com/gen2brain/raylib-go/raylib" + "log" ) type RaylibBuffer struct { - Data []rl.Color - Width uint - Height uint + Data []rl.Color + Image *rl.Image + Texture rl.Texture2D + Width uint + Height uint } func NewRaylibBuffer(width uint, height uint) *RaylibBuffer { + log.Printf("Creating new raylib framebuffer using texture + image") + //rl.NewTexture2D(1, Width, Height, 0, ) + rlimage := rl.GenImageColor(Width, Height, rl.Black) + rl.ImageFormat(rlimage, rl.UncompressedR8g8b8a8) + log.Printf("Generated baseline image: %v", rlimage) + rltexture := rl.LoadTextureFromImage(rlimage) + log.Printf("Generated texture from image") + data := rl.LoadImageColors(rlimage) + log.Printf("Generated pixel data from image") return &RaylibBuffer{ - Data: make([]rl.Color, width*height), - Width: width, - Height: height, + Data: data, + Image: rlimage, + Texture: rltexture, + Width: width, + Height: height, } }