From bd17bed40fb1b2771a88641e67e9f6c33f964801 Mon Sep 17 00:00:00 2001 From: Bobby Lucero Date: Mon, 2 Jun 2025 22:16:24 -0400 Subject: [PATCH] #3 Draw a triangle --- IPixelShader.cs | 5 ++++ MathHelpers.cs | 14 ++++++++++ MathTypes.cs | 5 ++++ RenderDevice.cs | 63 ++++++++++++++++++++++++++++++++++++++++--- SoftwareRasterizer.cs | 62 +++++++++++++++++++++++++++++++++++------- VertexColorShader.cs | 27 +++++++++++++++++++ 6 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 IPixelShader.cs create mode 100644 MathHelpers.cs create mode 100644 VertexColorShader.cs diff --git a/IPixelShader.cs b/IPixelShader.cs new file mode 100644 index 0000000..76eaf05 --- /dev/null +++ b/IPixelShader.cs @@ -0,0 +1,5 @@ +public interface IPixelShader +{ + + float3 GetColor(float3 barycentric, int screenX, int screenY, int frame); +} \ No newline at end of file diff --git a/MathHelpers.cs b/MathHelpers.cs new file mode 100644 index 0000000..da7bd77 --- /dev/null +++ b/MathHelpers.cs @@ -0,0 +1,14 @@ +public class MathHelpers +{ + public static float Edge(float2 a, float2 b, float2 c) + { + return (c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x); + } + + public static float2 NDCToScreen(float2 ndc, int width, int height) + { + float x = (ndc.x + 1f) * 0.5f * width; + float y = (1f - (ndc.y + 1f) * 0.5f) * height; // flip Y + return new float2(x, y); + } +} \ No newline at end of file diff --git a/MathTypes.cs b/MathTypes.cs index da06fe2..e868cdb 100644 --- a/MathTypes.cs +++ b/MathTypes.cs @@ -18,6 +18,11 @@ } public static float3 operator -(float3 a, float3 b) => new float3(a.x - b.x, a.y - b.y, a.z - b.y); + + public static float3 operator *(float scalar, float3 vec) => new float3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + + public static float3 operator +(float3 a, float3 b) => new float3(a.x + b.x, a.y + b.y, a.z + b.z); + public static float3 cross(float3 a, float3 b) => new float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); diff --git a/RenderDevice.cs b/RenderDevice.cs index ed33f2a..89c65fc 100644 --- a/RenderDevice.cs +++ b/RenderDevice.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System.Diagnostics; +using System.Numerics; using System.Runtime.InteropServices; using Raylib_cs; @@ -13,6 +14,8 @@ public class RenderDevice private Texture2D renderTarget; private IntPtr pixelPtr; private byte[] pixelData; + + private int frame = 1; public RenderDevice(int width, int height, int screenWidth, int screenHeight) { this.width = width; @@ -38,17 +41,69 @@ public class RenderDevice } Raylib.DrawTexturePro(renderTarget, new Rectangle(0,0, width, height), new Rectangle(0, 0, screenWidth, screenHeight), Vector2.Zero, 0f, Color.White); + + frame++; } public void SetPixel(int x, int y, float3 color) { + if (x < 0 || x >= width || y < 0 || y >= height) return; int idx = (y * width + x) * 4; - pixelData[idx] = (byte)color.r; - pixelData[idx + 1] = (byte)color.g; - pixelData[idx + 2] = (byte)color.b; + pixelData[idx] = (byte)(Math.Clamp(color.r * 255, 0, 255)); + pixelData[idx + 1] = (byte)(Math.Clamp(color.g * 255, 0, 255)); + pixelData[idx + 2] = (byte)(Math.Clamp(color.b * 255, 0, 255)); pixelData[idx + 3] = 0xFF; } + public void Clear() + { + for (int i = 0; i < pixelData.Length; i++) pixelData[i] = 0; + } + + public void DrawTriangle(float2 v0, float2 v1, float2 v2, IPixelShader pixelShader) + { + v0 = MathHelpers.NDCToScreen(v0, width, height); + v1 = MathHelpers.NDCToScreen(v1, width, height); + v2 = MathHelpers.NDCToScreen(v2, width, height); + + Debug.WriteLine(v0.x); + + int minX = (int)MathF.Floor(MathF.Min(v0.x, MathF.Min(v1.x, v2.x))); + int maxX = (int)MathF.Ceiling(MathF.Max(v0.x, MathF.Max(v1.x, v2.x))); + int minY = (int)MathF.Floor(MathF.Min(v0.y, MathF.Min(v1.y, v2.y))); + int maxY = (int)MathF.Ceiling(MathF.Max(v0.y, MathF.Max(v1.y, v2.y))); + + float area = MathHelpers.Edge(v0, v1, v2); + if (area == 0f) return; // Degenerate + + for (int y = minY; y <= maxY; y++) + { + for (int x = minX; x <= maxX; x++) + { + float2 p = new float2(x + 0.5f, y + 0.5f); + + float w0 = MathHelpers.Edge(v1, v2, p); + float w1 = MathHelpers.Edge(v2, v0, p); + float w2 = MathHelpers.Edge(v0, v1, p); + + + + if (w0 <= 0 && w1 <= 0 && w2 <= 0) + { + + w0 /= area; + w1 /= area; + w2 /= area; + float3 bary = new float3(w0, w1, w2); + + // Inside triangle + SetPixel(x, y, pixelShader.GetColor(bary, x, y, frame)); + } + } + } + + } + public void cleanup() { Raylib.UnloadTexture(renderTarget); diff --git a/SoftwareRasterizer.cs b/SoftwareRasterizer.cs index e46a0c2..00e3036 100644 --- a/SoftwareRasterizer.cs +++ b/SoftwareRasterizer.cs @@ -1,4 +1,5 @@ -using Raylib_cs; +using System.Runtime.CompilerServices; +using Raylib_cs; class SoftwareRasterizer { @@ -16,27 +17,66 @@ class SoftwareRasterizer sr.start(); } + struct BouncingPoint() + { + float posX, posY; + float velX, velY; + + + public float2 pos => new(posX, posY); + + public BouncingPoint(float posX, float posY, float velX, float velY) : this() + { + this.posX = posX; + this.posY = posY; + this.velX = velX; + this.velY = velY; + } + public void Update() + { + posX += velX; + posY += velY; + + if (posX >= 1.0 || posX <= -1.0f) + { + velX = -velX; + } + + if (posY >= 1.0 || posY <= -1.0f) + { + velY = -velY; + } + } + } + public void start() { Raylib.InitWindow(screenWidth, screenHeight, "Software Rasterizer"); - Raylib.SetTargetFPS(0); + Raylib.SetTargetFPS(60); renderer = new RenderDevice(screenWidth / scale, screenHeight / scale, screenWidth, screenHeight); + + VertexColorShader shader = new VertexColorShader(); int frameCounter = 0; Random rand = new Random(); + BouncingPoint v0 = new BouncingPoint( 0.0f, 0.8f, 0.01f, 0.01f); // Top middle + BouncingPoint v1 = new BouncingPoint( 0.8f, -0.8f, 0.01f, -0.01f); // Bottom right + BouncingPoint v2 = new BouncingPoint(-0.8f, -0.8f, -0.01f, 0.01f); // Bottom left + while (!Raylib.WindowShouldClose()) { - for (int i = 0; i < renderer.height; i++) - { - for (int j = 0; j < renderer.width; j++) - { - renderer.SetPixel(j, i, new float3((j / (float)(renderer.width)) * 255, (i / (float)(renderer.height)) * 255, rand.Next(1,256))); - } - } + + renderer.Clear(); + + v0.Update(); + v1.Update(); + v2.Update(); + + renderer.DrawTriangle(v0.pos, v1.pos, v2.pos, shader); // Render Raylib.BeginDrawing(); Raylib.ClearBackground(Color.Black); @@ -52,5 +92,9 @@ class SoftwareRasterizer renderer.cleanup(); Raylib.CloseWindow(); } + + + + } diff --git a/VertexColorShader.cs b/VertexColorShader.cs new file mode 100644 index 0000000..aa5733d --- /dev/null +++ b/VertexColorShader.cs @@ -0,0 +1,27 @@ +public class VertexColorShader : IPixelShader +{ + + float3[] vertexColors = new[] + { + new float3(1.0f, 0.0f, 0.0f), + new float3(0.0f, 1.0f, 0.0f), + new float3(0.0f, 0.0f, 1.0f), + }; + public float3 GetColor(float3 barycentric, int screenX, int screenY, int frame) + { + float amount1 = MathF.Cos(frame * 0.02f) * 0.5f + 0.5f; + float amount2 = MathF.Sin(frame * 0.02f) * 0.5f + 0.5f; + float3 c1 = new float3(vertexColors[0].x * amount1, vertexColors[0].y * amount1, vertexColors[0].z * amount1); + float3 c2 = new float3(vertexColors[1].x * amount2, vertexColors[1].y * amount2, vertexColors[1].z * amount2); + + float3 color = barycentric.x * c1 + + barycentric.y * c2 + + barycentric.z * vertexColors[2]; + + // if((int)(screenX + MathF.Cos(frame * 0.02f) * 30) % 5 == 0 && (int)(screenY + MathF.Sin(frame * 0.02f) * 30) % 5 == 0) color += new float3(0.0f, 0.25f, 0.0f); + if((int)(screenX) % 5 == 0 && (int)(screenY) % 5 == 0) color += new float3(0.0f, 0.25f, 0.0f); + + + return color; + } +} \ No newline at end of file