惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
B
Blog
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
爱范儿
爱范儿
博客园_首页
博客园 - 聂微东
量子位
V
Visual Studio Blog
aimingoo的专栏
aimingoo的专栏
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
Netflix TechBlog - Medium
F
Fortinet All Blogs
The Cloudflare Blog
T
Tailwind CSS Blog
G
Google Developers Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
腾讯CDC

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Software Rendering Pipeline with Backface Culling
yubin yang · 2026-05-31 · via DEV Community

yubin yang

1. Overview

  • In this project, I implemented a simple software renderer using Python and Pygame.
  • The renderer follows the fundamental stages of a 3D rendering pipeline.
Local Space
↓
World Space
↓
View Space
↓
Clip Space
↓
Screen Space

  • Model Transformation (Local Space → World Space)
  • View Transformation (World Space → View Space)
  • Perspective Projection (View Space → Clip Space)
  • Viewport Transformation (Clip Space → Screen Space)
  • Backface Culling
  • Painter's Algorithm for depth sorting

2. Rendering Pipeline Implementation

- The original code is too long, so I only posted the important parts.
-Please check GitHub.

Transforming World Space into View Space using the View Matrix

def GetViewMatrix(CamPos, TargetPos, Up):
    ViewZ = TargetPos - CamPos
    ViewZ = ViewZ / np.linalg.norm(ViewZ)

    ViewX = np.cross(Up, ViewZ)
    ViewX = ViewX / np.linalg.norm(ViewX)

    ViewY = np.cross(ViewZ, ViewX)

    CamInv = np.array([
        [ViewX[0], ViewX[1], ViewX[2], -np.dot(ViewX, CamPos)],
        [ViewY[0], ViewY[1], ViewY[2], -np.dot(ViewY, CamPos)],
        [ViewZ[0], ViewZ[1], ViewZ[2], -np.dot(ViewZ, CamPos)],
        [0, 0, 0, 1]
    ])

    FlipY = np.array([
        [-1, 0, 0, 0],
        [0, 1, 0, 0],
        [0, 0, -1, 0],
        [0, 0, 0, 1]
    ])

    return np.matmul(FlipY, CamInv)

  • GetViewMatrix() creates a View Matrix that transforms objects from World Space into View Space.
  • The camera position, target position, and up vector are used to calculate the camera's local coordinate system. Once the View Matrix is applied, all objects are transformed into coordinates relative to the camera.

Transforming View Space into Clip Space using the Projection Matrix

def GetProjectionMatrix(FovDeg, Width, Height, Near=0.1, Far=1000):
    Fov = math.radians(FovDeg)
    Aspect = Width / Height
    Distance = 1 / math.tan(Fov / 2)

    return np.array([
        [Distance / Aspect, 0, 0, 0],
        [0, Distance, 0, 0],
        [0, 0, (Near + Far) / (Near - Far), (2 * Near * Far) / (Near - Far)],
        [0, 0, -1, 0]
    ])

  • GetProjectionMatrix() creates a Perspective Projection Matrix.
  • This matrix applies perspective to the scene, making distant objects appear smaller and nearby objects appear larger. It transforms coordinates from View Space into Clip Space.

Transforming Normalized Coordinates into Screen Coordinates using the Viewport Matrix

def GetViewportMatrix(Width, Height):
    return np.array([
        [Width / 2, 0, 0, Width / 2],
        [0, -Height / 2, 0, Height / 2],
        [0, 0, 0.5, 0.5],
        [0, 0, 0, 1]
    ])

  • GetViewportMatrix() converts normalized device coordinates (NDC) into actual screen coordinates.
  • After projection, coordinates exist in a normalized range of -1 to 1. This matrix maps those coordinates into the screen resolution so that they can be rendered.

Determining Visible Faces using Backface Culling

def IsFrontFace(V0, V1, V2):
    Edge1 = V1[:3] - V0[:3]
    Edge2 = V2[:3] - V0[:3]

    Normal = np.cross(Edge1, Edge2)

    Center = (V0[:3] + V1[:3] + V2[:3]) / 3

    # Camera is placed at the origin in view space
    ViewDirection = -Center

    # Visible when the face normal points toward the camera
    return np.dot(Normal, ViewDirection) > 0

  • IsFrontFace() determines whether a triangle is facing the camera.
  • The function calculates the face normal using a cross product and compares it with the camera direction using a dot product. If the triangle is facing away from the camera, it is discarded before rendering.
  • This process is known as Backface Culling and helps reduce unnecessary rendering work.

Transforming Cube Vertices and Rendering Visible Triangles

    ViewVertices = []
    ProjectedVertices = []

    for Vertex in Vertices:
        # Local space -> World space
        WorldVertex = np.matmul(Translate, Vertex)

        # World space -> View space
        ViewVertex = np.matmul(ViewMatrix, WorldVertex)
        ViewVertices.append(ViewVertex)

        # View space -> Clip space
        ClipVertex = np.matmul(ProjectionMatrix, ViewVertex)

        # Perspective divide
        if ClipVertex[3] != 0:
            NdcVertex = ClipVertex / ClipVertex[3]
        else:
            NdcVertex = ClipVertex

        # NDC -> Screen space
        ScreenVertex = np.matmul(ViewportMatrix, NdcVertex)
        ProjectedVertices.append((int(ScreenVertex[0]), int(ScreenVertex[1]), ScreenVertex[2]))

    FaceDepths = []

    for Face in Faces:
        V0 = ViewVertices[Face[0]]
        V1 = ViewVertices[Face[1]]
        V2 = ViewVertices[Face[2]]

        # Skip triangles facing away from the camera
        if not IsFrontFace(V0, V1, V2):
            continue

        AvgDepth = sum(ProjectedVertices[Index][2] for Index in Face) / 3
        FaceDepths.append((AvgDepth, Face))

        FaceDepths.sort(key=lambda Item: Item[0], reverse=True)

        for _, Face in FaceDepths:
            Points = [
                (ProjectedVertices[Index][0],
                ProjectedVertices[Index][1])
                for Index in Face
            ]

            pygame.draw.polygon(Screen, Color, Points)
            pygame.draw.polygon(Screen, (0, 0, 0), Points, 2)

            # Update screen immediately
            pygame.display.flip()

  • DrawCube() contains the main software rendering pipeline.
  • The cube vertices are first created in Local Space and then transformed through multiple coordinate spaces.
Local Space
→ World Space
→ View Space
→ Clip Space
→ NDC
→ Screen Space

  • After the transformations, Backface Culling is applied to remove invisible faces.
  • The remaining triangles are depth-sorted using Painter's Algorithm and finally rendered.

Results

The final renderer displays multiple cubes in the 3D scene by applying perspective projection and back culling.

Although simple compared to modern pipelines, manually implementing these steps allowed me to gain a much deeper understanding of how the rendering system works internally.

Through this project, I was able to learn various knowledge necessary for rendering, such as computer graphics mathematics, matrix transformations, coordinate space, and visibility.


Preview gif

Full Video

YouTube Video

Github Repository

Github - Graphics