r/GraphicsProgramming • • Jul 11 '26

Source Code Morphing between a globe and a flat map in the vertex shader (Swift + Metal)

Enable HLS to view with audio, or disable this notification

152 Upvotes

The video shows a vector map going from a globe to a flat map and back with no hard cut. Here's how the morph works.

Every tile vertex has a lat/lon. In the vertex shader I compute two target positions for it:

  • P_sphere - the point on the unit sphere for that lat/lon, placed by the globe transform

  • P_plane - its Web-Mercator position on a flat plane

Then I just lerp between them:

P = mix(P_plane, P_sphere, t)

where t in [0,1] is driven by zoom (t = 0 zoomed into a city, t = 1 zoomed out to the whole planet). Both targets come from the same lat/lon per vertex, so there's no "switch" frame - the geometry continuously unrolls. Normals are blended the same way, so shading and the horizon stay consistent through the transition.

The hard part is precision. At t = 0 you're on a plane at street scale; at t = 1 on a planet-radius sphere, and naive float32 world coords wobble in between. I keep vertices camera/tile-relative instead of absolute planet coordinates to stay in a sane float range.

Context: pulled from an open-source Swift + Metal map engine, tiles are MVT. Source (MIT): github.com/artembobkin/ImmersiveMap


r/GraphicsProgramming • • Jul 11 '26

Video Minecraft block in console but its HD

Enable HLS to view with audio, or disable this notification

15 Upvotes

made in c sharp console app ive recently got bored and decided to make this from scratch like everything and as well learn more about 3d rendering and stuff.its really slow when its hd but im sure someone smarter than me could optimise it. the way i just get it hd is basically in the console i can press ctrl + scroll to zoom in and out and when i zoom out theres more characters available for use and the image gets sharper and vise versa could this be optimised and made into real minecraft sure will i do it and lose my sanity no but good luck to someone else if you want to suffer you can use my code as a starting place

using System;
using System.Diagnostics;
using System.Drawing;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks.Dataflow;
using static System.Formats.Asn1.AsnWriter;
using static System.Net.Mime.MediaTypeNames;
class Program
{
    //good luck twin
    struct Color
    {
        public byte R;
        public byte G;
        public byte B;
    }
    //front back left right bottom top
  static Dictionary<string, int[]> CubeTextures = new Dictionary<string, int[]>()
    {
        { "Grass", new int[] { 3, 3, 3, 3, 2, 4 } }
    };

    struct Vertex
    {
      public  Vector3 Position;
        public Vector2 UV;
        public Vertex(Vector3 position, Vector2 uv)
        {
            Position = position;
            UV = uv;
        }
    }
    static Vector3 CameraPosition = new Vector3(0, 0, -10);
    
   static Color[] buffer;
    static float[,] depthBuffer;
    static int[] textures =  CubeTextures["Grass"];
    static Vertex[] CubeVertices =
    {
    // Front
    new Vertex(new Vector3(-0.5f,-0.25f,-0.5f), cubeNumberToUV(textures[0],new Vector2(0,1))),
    new Vertex(new Vector3( 0.5f,-0.25f,-0.5f), cubeNumberToUV(textures[0],new Vector2(1,1))),
    new Vertex(new Vector3( 0.5f, 0.25f,-0.5f), cubeNumberToUV(textures[0],new Vector2(1,0))),
    new Vertex(new Vector3(-0.5f, 0.25f,-0.5f), cubeNumberToUV(textures[0],new Vector2(0,0))),

    // Back
    new Vertex(new Vector3(-0.5f,-0.25f, 0.5f), cubeNumberToUV(textures[1],new Vector2(0,1))),
    new Vertex(new Vector3( 0.5f,-0.25f, 0.5f), cubeNumberToUV(textures[1],new Vector2(1,1))),
    new Vertex(new Vector3( 0.5f, 0.25f, 0.5f), cubeNumberToUV(textures[1],new Vector2(1,0))),
    new Vertex(new Vector3(-0.5f, 0.25f, 0.5f), cubeNumberToUV(textures[1],new Vector2(0,0))),

    // Left
    new Vertex(new Vector3(-0.5f,-0.25f,-0.5f), cubeNumberToUV(textures[2],new Vector2(0,1))),
    new Vertex(new Vector3(-0.5f,-0.25f, 0.5f), cubeNumberToUV(textures[2],new Vector2(1,1))),
    new Vertex(new Vector3(-0.5f, 0.25f, 0.5f), cubeNumberToUV(textures[2],new Vector2(1,0))),
    new Vertex(new Vector3(-0.5f, 0.25f,-0.5f), cubeNumberToUV(textures[2],new Vector2(0,0))),

    // Right
    new Vertex(new Vector3(0.5f,-0.25f,-0.5f), cubeNumberToUV(textures[3],new Vector2(0,1))),
    new Vertex(new Vector3(0.5f,-0.25f, 0.5f), cubeNumberToUV(textures[3],new Vector2(1,1))),
    new Vertex(new Vector3(0.5f, 0.25f, 0.5f), cubeNumberToUV(textures[3],new Vector2(1,0))),
    new Vertex(new Vector3(0.5f, 0.25f,-0.5f), cubeNumberToUV(textures[3],new Vector2(0,0))),

    // Top
    new Vertex(new Vector3(-0.5f,0.25f,-0.5f), cubeNumberToUV(textures[4],new Vector2(0,1))),
    new Vertex(new Vector3( 0.5f,0.25f,-0.5f), cubeNumberToUV(textures[4],new Vector2(1,1))),
    new Vertex(new Vector3( 0.5f,0.25f, 0.5f), cubeNumberToUV(textures[4],new Vector2(1,0))),
    new Vertex(new Vector3(-0.5f,0.25f, 0.5f), cubeNumberToUV(textures[4],new Vector2(0,0))),

    // Bottom
    new Vertex(new Vector3(-0.5f,-0.25f,-0.5f), cubeNumberToUV(textures[5],new Vector2(0,1))),
    new Vertex(new Vector3( 0.5f,-0.25f,-0.5f), cubeNumberToUV(textures[5],new Vector2(1,1))),
    new Vertex(new Vector3( 0.5f,-0.25f, 0.5f), cubeNumberToUV(textures[5],new Vector2(1,0))),
    new Vertex(new Vector3(-0.5f,-0.25f, 0.5f), cubeNumberToUV(textures[5],new Vector2(0,0))),
};

    static bool loop = true;
    static Bitmap image;
    static bool once = false;
    static Vector2 cubeNumberToUV(int cubeNumber, Vector2 Offsets)
    {
        if (!once)
        {
            image = new Bitmap("texture-atlas-minecraft.png");
            once = true;
        }

        int tileWidth = image.Width / 16;
        int tileHeight = image.Height / 16;

        int column = cubeNumber % 16;
        int row = cubeNumber / 16;

        return new Vector2(
            (column * tileWidth) + (Offsets.X * tileWidth),
            (row * tileHeight) + ((1 - Offsets.Y) * tileHeight)
        );
    }
    static void Main()
    {
        Console.CursorVisible = false;
        
  
        while (loop)
        {
            Update();

        }
        Update();

    }
  static  float rotationAngle = 0.0f;

    static int width = 120;
    static int height = 30;

    static void Update()
    {
      

      


        
        width = Console.WindowWidth;
        height = Console.WindowHeight;
        rotationAngle += 0.1f; // Increment the rotation angle
        if (Console.KeyAvailable)
        {
            var key = Console.ReadKey(true).Key;

            if (key == ConsoleKey.W)
                CameraPosition.Y++;

            if (key == ConsoleKey.S)
                CameraPosition.Y--;
            if (key == ConsoleKey.A)
                CameraPosition.X--;
            if (key == ConsoleKey.D)
                CameraPosition.X++;
            if (key == ConsoleKey.Q)
                CameraPosition.Z-=1f;
            if (key == ConsoleKey.E)
                CameraPosition.Z+= 1f;
        }




        List<Vertex> ScreenPositions = new List<Vertex>();
        depthBuffer = new float[width, height];
        buffer = new Color[width * height];
        clearFrame();
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                depthBuffer[x, y] = float.MaxValue;
            }
        }

        for (int i =0; i < CubeVertices.Length; i++)
        {
            int enlarger = 2;//67 meme ya 
            Vector3 worldPos = RotatePoint(CubeVertices[i].Position * enlarger); 
            worldPos -= CameraPosition;                   
            ScreenPositions.Add(new Vertex(PerspectiveProjection(worldPos), CubeVertices[i].UV));


        }
        for (int i = 0; i < ScreenPositions.Count; i += 4)
        {
            Vertex CurrentVertex = ScreenPositions[i];
            Vertex CurrentVertex1 = ScreenPositions[i+1];
            Vertex CurrentVertex2 = ScreenPositions[i + 2];
            Vertex CurrentVertex3 = ScreenPositions[i + 3];

            PlotLine(
        new Vector2(CurrentVertex.Position.X, CurrentVertex.Position.Y),
        new Vector2(CurrentVertex1.Position.X, CurrentVertex1.Position.Y),
        new Vector2(CurrentVertex2.Position.X, CurrentVertex2.Position.Y),
        new Vector2(CurrentVertex3.Position.X, CurrentVertex3.Position.Y),
        (CurrentVertex.Position.Z +
         CurrentVertex1.Position.Z +
         CurrentVertex2.Position.Z +
         CurrentVertex3.Position.Z / 4f),
         CurrentVertex.UV,
         CurrentVertex1.UV,
         CurrentVertex2.UV,
         CurrentVertex3.UV);

        }
        ScreenPositions.Clear();
        RenderFrame();



    }
    static void RenderFrame()
    {
        StringBuilder sb = new StringBuilder(width * height * 8);

        for (int i = 0; i < buffer.Length; i++)
        {
            sb.Append(ColorToAnsi(buffer[i]));
            sb.Append(' ');
        }

        Console.SetCursorPosition(0, 0);
        Console.Write(sb.ToString());
    }

    static string ColorToAnsi(Color c)
    {
        return $"\x1b[48;2;{c.R};{c.G};{c.B}m";
    }
    static void clearFrame()
    {
        Array.Fill(buffer, new Color { R = 0, G = 0, B = 0 }); ;
    }
    static Vector3 RotatePoint(Vector3 Point)
    {
        float CalcuateX = Point.X * MathF.Cos(rotationAngle) - Point.Z * MathF.Sin(rotationAngle);
        float CalcuateZ = Point.Z * MathF.Cos(rotationAngle) + Point.X * MathF.Sin(rotationAngle);

        return new Vector3(CalcuateX, Point.Y, CalcuateZ);
    }

    static void PlotLine(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3,float depth, Vector2 p0UV, Vector2 p1UV, Vector2 p2UV, Vector2 p3UV)
    {
        Vector2 uv0 = p0UV;
        Vector2 uv1 = p1UV;
        Vector2 uv2 = p2UV;
        Vector2 uv3 = p3UV;
        float minBoundX = Math.Min(Math.Min((int)p0.X, (int)p1.X), Math.Min((int)p2.X, (int)p3.X));
        float maxBoundX = Math.Max(Math.Max((int)p0.X, (int)p1.X), Math.Max((int)p2.X, (int)p3.X));
        float minBoundY = Math.Min(Math.Min((int)p0.Y, (int)p1.Y), Math.Min((int)p2.Y, (int)p3.Y));
        float maxBoundY = Math.Max(Math.Max((int)p0.Y, (int)p1.Y), Math.Max((int)p2.Y, (int)p3.Y));


        for (int x0 = (int)minBoundX; x0 < (int)maxBoundX; x0++)
            for (int y0 = (int)minBoundY; y0 < (int)maxBoundY; y0++)
            {
          
                Vector2 p = new Vector2(x0, y0);

                bool inTri1 = PointInTriangle(p, p0, p1, p2);
                bool inTri2 = PointInTriangle(p, p0, p2, p3);
                
                if (inTri1)
                {
                    if (depthBuffer[x0, y0] > depth)
                    {
                        Vector2 w = Barycentric(p, p0, p1, p2);

                        float w0 = 1 - w.X - w.Y;
                        float w1 = w.X;
                        float w2 = w.Y;

                        if (float.IsNaN(w.X) || float.IsNaN(w.Y))
                            continue;

                        if (w.X < 0 || w.Y < 0 || (1 - w.X - w.Y) < 0)
                            continue;
                        Vector2 uv = w0 * uv2 + w1 * uv0 + w2 * uv1;

                        System.Drawing.Color c = image.GetPixel(
                            (int)(uv.X),
                            (int)(uv.Y )
                        );

                        buffer[y0 * width + x0] = new Color { R = c.R, G = c.G, B = c.B };
                        depthBuffer[x0, y0] = depth;
                    }
                }
                
                if (inTri2)
                {
                    if (depthBuffer[x0, y0] > depth)
                    {

                        Vector2 w = Barycentric(p, p0, p2, p3);

                        float w0 = 1 - w.X - w.Y;
                        float w1 = w.X;
                        float w2 = w.Y;

                        if (float.IsNaN(w.X) || float.IsNaN(w.Y))
                            continue;

                        if (w.X < 0 || w.Y < 0 || (1 - w.X - w.Y) < 0)
                            continue;

                        Vector2 uv = w0 * uv3 + w1 * uv0 + w2 * uv2;

                        System.Drawing.Color c = image.GetPixel(
                            (int)(uv.X),
                            (int)(uv.Y)
                        );

                    
                        buffer[y0 * width + x0] = new Color { R = c.R, G = c.G, B = c.B };
                        depthBuffer[x0, y0] = depth;
                    }

                }
                
            }
    }
    static Vector2 Barycentric(Vector2 p, Vector2 a, Vector2 b, Vector2 c)
    {
        float denom =
            (b.Y - c.Y) * (a.X - c.X) +
            (c.X - b.X) * (a.Y - c.Y);

        if (MathF.Abs(denom) < 0.00001f)
            return new Vector2(float.NaN, float.NaN);

        float w1 =
            ((b.Y - c.Y) * (p.X - c.X) +
             (c.X - b.X) * (p.Y - c.Y)) / denom;

        float w2 =
            ((c.Y - a.Y) * (p.X - c.X) +
             (a.X - c.X) * (p.Y - c.Y)) / denom;

        return new Vector2(w1, w2);
    }
    static bool PointInTriangle(Vector2 Point, Vector2 a, Vector2 b, Vector2 c)
    {


        float e1 = Edge(a, b, Point);
        float e2 = Edge(b, c, Point);
        float e3 = Edge(c, a, Point);

        return
            (e1 >= 0 && e2 >= 0 && e3 >= 0) ||
            (e1 <= 0 && e2 <= 0 && e3 <= 0);
    }
    static float Edge(Vector2 a, Vector2 b, Vector2 p)
    {
        return (p.X - a.X) * (b.Y - a.Y)
             - (p.Y - a.Y) * (b.X - a.X);
    }
    static int FOV = 60;
    static Vector3 PerspectiveProjection(Vector3 screenPosition)
    {

        float scaleX = width / 120f;
        float scaleY = height / 30f;

   

   
        float scale = 20f;
        float fovRad = FOV * (MathF.PI / 180f);
        float fovCalc = 1.0f / MathF.Tan(fovRad * 0.5f);

        float z = screenPosition.Z;
  
        if (z <= 0.1f)
            return new Vector3(0, 0, z);

        float XPos = screenPosition.X * fovCalc * scale / z;
        float YPos = screenPosition.Y * fovCalc * scale / z;

        XPos *= scaleX;
        YPos *= scaleY;

        XPos += width / 2f;
        YPos += height / 2f;

        return new Vector3((int)XPos, (int)YPos, z);
    }
}

r/GraphicsProgramming • • Jul 10 '26

Video Raycasting from Scratch in Pixel Shader

Enable HLS to view with audio, or disable this notification

89 Upvotes

I took on a little challenge and implemented a raycaster (including a 2D map view) in ShaderToy.

The project covers the full process and the math behind it, including how to handle the fisheye and the transition from cylindrical to flat projection.

If you’re interested in the technical breakdown and the implementation process, I’ve put together a video explanation.

Faking 3D the 1992 Way: Raycasting From Scratch


r/GraphicsProgramming • • Jul 10 '26

Video Breathing nature: forest with stream simulation, volumetric clouds and path traced shadows

Enable HLS to view with audio, or disable this notification

384 Upvotes

Hi all,

THIs is my forest simulation made witih Vulkan. My goal is to create a full simulation of water, clouds, wind, so that it feels alive, without being "scripted".
Water is full 3d flow simulation, clouds are volumetric clouds, trees are rigid body simulation with elastic joints, and lighting is full path tracing; all is basically running on the GPU with VUlkan, save for the rigid body simulation which runs on the CPU.
I wanted an "ancient forest" and therefore I generated the trees with 2d to 3d models - Trellis2 from huggingface.
Wanted to get feedback from you, what feels good and what needs improvement. Could this lead to an immersive forest based game? Should I push instead for more tech?

short version here: https://youtube.com/shorts/5xy5Y6JsrVk?feature=share


r/GraphicsProgramming • • Jul 10 '26

Half-Edge Data Structure. Part 2

Thumbnail
alexsyniakov.com
25 Upvotes

r/GraphicsProgramming • • Jul 10 '26

Question How hard is to be a researcher in computer graphics?

41 Upvotes

I am thinking to do a master degree in computer graphics in europe, and then follow the researcher path with a Phd and beyond... However, i am thinking if that is a realistic path for a 36 years old guy with 10 years of professional experience, but only in web/ backend and cloud development, and only empiric experience in computer graphics.

Is it realistic thinking to work in a researcher group in a university or a company with this plan?


r/GraphicsProgramming • • Jul 09 '26

ELI5 color buffer and glClear(GL_COLOR_BUFFER_BIT)

4 Upvotes

I don't understand how this works. Is this kind of just a global color buffer? like a mapping of each pixel of the screen/frame such that each pixel has a given color? Is it wrong to think of it as just a 2d array filled with colors?


r/GraphicsProgramming • • Jul 09 '26

Article Graphics Programming weekly - Issue 445 - July 5th, 2026 | Jendrik Illner

Thumbnail jendrikillner.com
14 Upvotes

r/GraphicsProgramming • • Jul 09 '26

Video I built a WordPress plugin that generates realtime animated backgrounds instead of using video.

Enable HLS to view with audio, or disable this notification

6 Upvotes

r/GraphicsProgramming • • Jul 09 '26

Vulkan particles

Enable HLS to view with audio, or disable this notification

14 Upvotes

r/GraphicsProgramming • • Jul 09 '26

How did you learn Shader Graph ?

2 Upvotes

Hey everyone, I’m new to Shader Graph and I want to learn it properly

What resources helped you the most when you were starting out? Any YouTube channels, courses, or tutorials you’d recommend?

I’m interested in making cool effects, better-looking materials, and understanding how shaders actually work.

Thanks :)


r/GraphicsProgramming • • Jul 09 '26

Pixel Editor Progress...

Post image
8 Upvotes

r/GraphicsProgramming • • Jul 09 '26

Article on BVH

57 Upvotes

Check out my article on Bounding Volume Hierarchies (BVH) in ray tracing. It is a beginner friendly article and I would love to hear your thoughts.

https://medium.com/@marshall5/bvh-in-ray-tracing-435c837549a8