You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET环境下使用System.Drawing实现带3D效果的三角形纹理映射

How to Apply 3D Textured Triangles with System.Drawing

Alright, let's break this down step by step—since you're building a 3D editor and want to map BMP textures to triangles using only System.Drawing, the core technique here is barycentric coordinate-based texture mapping. This method lets you tie each pixel in the triangle to its corresponding point on the texture. Here's exactly how to implement it:

1. Define Your Data Structures

First, create a simple struct to hold both the 2D-projected vertex coordinates (after you've converted your 3D points to screen space) and their matching texture UV coordinates:

public struct TexturedVertex
{
    // 2D screen position (after 3D → 2D projection)
    public float ScreenX;
    public float ScreenY;
    // Texture UV coordinates (0-1 range; (0,0) = top-left of the BMP)
    public float U;
    public float V;
    // Optional: Add a Z value here for depth testing (z-buffering)
    public float Z;
}

2. Load Your BMP Texture

Use System.Drawing.Bitmap to load your texture. For better performance, lock the bitmap's bits directly (avoid slow GetPixel/SetPixel calls):

// Load the BMP texture
Bitmap texture = new Bitmap("your-texture-file.bmp");

// Lock texture bits for fast pixel access
BitmapData textureData = texture.LockBits(
    new Rectangle(0, 0, texture.Width, texture.Height),
    ImageLockMode.ReadOnly,
    PixelFormat.Format32bppArgb);

3. Implement Barycentric Texture Mapping

The key part is calculating barycentric coordinates for each pixel in the triangle's bounding box. These coordinates let you interpolate the UV values across the triangle, then sample the corresponding texture pixel.

Here's a complete implementation for drawing a textured triangle to a target canvas (your editor's viewport):

// Assume this is your editor's canvas bitmap
Bitmap viewport = new Bitmap(1280, 720);
BitmapData viewportData = viewport.LockBits(
    new Rectangle(0, 0, viewport.Width, viewport.Height),
    ImageLockMode.WriteOnly,
    PixelFormat.Format32bppArgb);

// Define your triangle's textured vertices (replace with your projected 3D points)
TexturedVertex v1 = new TexturedVertex { ScreenX = 200, ScreenY = 150, U = 0, V = 0 };
TexturedVertex v2 = new TexturedVertex { ScreenX = 600, ScreenY = 600, U = 1, V = 1 };
TexturedVertex v3 = new TexturedVertex { ScreenX = 100, ScreenY = 550, U = 0, V = 1 };

// Calculate the triangle's bounding box to limit pixel checks
int minX = (int)Math.Min(Math.Min(v1.ScreenX, v2.ScreenX), v3.ScreenX);
int maxX = (int)Math.Max(Math.Max(v1.ScreenX, v2.ScreenX), v3.ScreenX);
int minY = (int)Math.Min(Math.Min(v1.ScreenY, v2.ScreenY), v3.ScreenY);
int maxY = (int)Math.Max(Math.Max(v1.ScreenY, v2.ScreenY), v3.ScreenY);

// Clamp bounds to avoid drawing outside the viewport
minX = Math.Max(minX, 0);
maxX = Math.Min(maxX, viewport.Width - 1);
minY = Math.Max(minY, 0);
maxY = Math.Min(maxY, viewport.Height - 1);

// Precompute denominator for barycentric calculations (avoids repeated division)
float denom = ((v2.ScreenY - v3.ScreenY) * (v1.ScreenX - v3.ScreenX)) + 
              ((v3.ScreenX - v2.ScreenX) * (v1.ScreenY - v3.ScreenY));

// Skip degenerate triangles (zero area)
if (Math.Abs(denom) < float.Epsilon)
    goto Cleanup;

// Iterate over every pixel in the bounding box
for (int y = minY; y <= maxY; y++)
{
    for (int x = minX; x <= maxX; x++)
    {
        // Calculate barycentric weights (alpha, beta, gamma)
        float alpha = ((v2.ScreenY - v3.ScreenY) * (x - v3.ScreenX) + 
                       (v3.ScreenX - v2.ScreenX) * (y - v3.ScreenY)) / denom;
        float beta = ((v3.ScreenY - v1.ScreenY) * (x - v3.ScreenX) + 
                      (v1.ScreenX - v3.ScreenX) * (y - v3.ScreenY)) / denom;
        float gamma = 1 - alpha - beta;

        // Check if the pixel is inside the triangle (all weights ≥ 0)
        if (alpha >= 0 && beta >= 0 && gamma >= 0)
        {
            // Interpolate UV coordinates using barycentric weights
            float u = (alpha * v1.U) + (beta * v2.U) + (gamma * v3.U);
            float v = (alpha * v1.V) + (beta * v2.V) + (gamma * v3.V);

            // Convert UV to texture pixel coordinates (clamp to avoid out-of-bounds)
            int texX = (int)Math.Clamp(u * texture.Width, 0, texture.Width - 1);
            int texY = (int)Math.Clamp(v * texture.Height, 0, texture.Height - 1);

            // Get the texture pixel color from locked bits
            int texPixelOffset = texY * textureData.Stride + texX * 4;
            Color texColor = Color.FromArgb(
                Marshal.ReadByte(textureData.Scan0, texPixelOffset + 3), // Alpha
                Marshal.ReadByte(textureData.Scan0, texPixelOffset + 2), // Red
                Marshal.ReadByte(textureData.Scan0, texPixelOffset + 1), // Green
                Marshal.ReadByte(textureData.Scan0, texPixelOffset));    // Blue

            // Write the color to the viewport bitmap
            int viewportPixelOffset = y * viewportData.Stride + x * 4;
            Marshal.WriteByte(viewportData.Scan0, viewportPixelOffset + 3, texColor.A);
            Marshal.WriteByte(viewportData.Scan0, viewportPixelOffset + 2, texColor.R);
            Marshal.WriteByte(viewportData.Scan0, viewportPixelOffset + 1, texColor.G);
            Marshal.WriteByte(viewportData.Scan0, viewportPixelOffset, texColor.B);
        }
    }
}

Cleanup:
// Unlock bitmaps to free resources
texture.UnlockBits(textureData);
viewport.UnlockBits(viewportData);

// To display the result, draw the viewport to your editor's Graphics context
using (Graphics g = yourEditorPanel.CreateGraphics())
{
    g.DrawImage(viewport, 0, 0);
}

4. Critical Enhancements for 3D Quality

To make this feel truly 3D, add these touches:

  • Z-Buffering: Track the depth (Z value) of each pixel. Only draw a pixel if its Z is closer to the camera than the existing pixel in the z-buffer. This fixes occlusion issues (objects behind other objects won't draw over them).
  • Perspective-Correct Interpolation: The basic barycentric interpolation above is linear, which can cause texture distortion in perspective. For better results, interpolate 1/Z, U/Z, and V/Z, then compute U = (U/Z)/(1/Z) and V = (V/Z)/(1/Z) for perspective-correct UVs.
  • Texture Filtering: Add bilinear filtering by sampling the four nearest texture pixels and blending their colors, instead of just picking the closest one. This smooths out jagged texture edges.

Key Notes

  • Projection First: Remember, you need to project your 3D vertices to 2D screen coordinates before using this code. Implement a perspective or orthogonal projection matrix to convert 3D world positions to 2D screen positions.
  • Performance: Locking bitmap bits is non-negotiable for speed—GetPixel/SetPixel are way too slow for real-time 3D editing.
  • UV Range: Ensure your UV coordinates are in the 0-1 range. For texture tiling, replace the clamp with u % 1 and v % 1.

内容的提问来源于stack exchange,提问作者MrOlegus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 09:57:49