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

使用Rust的wgpu crate渲染时z坐标被忽略的问题排查

问题原因与解决方法

核心问题:未启用深度测试

wgpu 默认不会自动启用深度测试,此时 GPU 只会按照绘制顺序渲染物体,后绘制的三角形会直接覆盖先绘制的,完全忽略 Z 坐标的遮挡关系。这是你遇到问题的主要原因。

解决步骤

1. 创建深度纹理及视图

需要创建一个深度纹理来存储每个像素的深度值,代码示例:

let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
    size: wgpu::Extent3d {
        width: config.width,
        height: config.height,
        depth_or_array_layers: 1,
    },
    mip_level_count: 1,
    sample_count: 1,
    dimension: wgpu::TextureDimension::D2,
    format: wgpu::TextureFormat::Depth32Float, // 选择合适的深度格式
    usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
    label: Some("depth_texture"),
    view_formats: &[],
});
let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());

2. 配置渲染管线的深度状态

在创建渲染管线时,必须指定深度模板状态,开启深度测试与深度写入:

let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
    label: Some("pipeline_layout"),
    bind_group_layouts: &[], // 若使用投影矩阵,需添加对应的bind group
    push_constant_ranges: &[],
});

let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
    label: Some("render_pipeline"),
    layout: Some(&pipeline_layout),
    vertex: wgpu::VertexState {
        module: &vertex_shader_module,
        entry_point: "vs_main",
        buffers: &[/* 你的顶点缓冲区描述 */],
    },
    fragment: Some(wgpu::FragmentState {
        module: &fragment_shader_module,
        entry_point: "fs_main",
        targets: &[Some(wgpu::ColorTargetState {
            format: config.format,
            blend: Some(wgpu::BlendState::REPLACE),
            write_mask: wgpu::ColorWrites::ALL,
        })],
    }),
    primitive: wgpu::PrimitiveState::default(),
    depth_stencil: Some(wgpu::DepthStencilState {
        format: wgpu::TextureFormat::Depth32Float, // 需与深度纹理格式一致
        depth_write_enabled: true,
        depth_compare: wgpu::CompareFunction::Less, // 近物体遮挡远物体,可按需修改
        stencil: wgpu::StencilState::default(),
        bias: wgpu::DepthBiasState::default(),
    }),
    multisample: wgpu::MultisampleState::default(),
    multiview: None,
});

3. 修正坐标空间问题

wgpu 基于 Vulkan,其标准化设备坐标(NDC)为左手系,Z 轴范围是 [0.0, 1.0]:

  • Z=0.0 代表近平面(离相机最近)
  • Z=1.0 代表远平面(离相机最远)

你之前直接将顶点 Z 值作为裁剪空间坐标,导致:

  • Z 为负数时超出 NDC 范围,被 GPU 裁剪,所以看不到
  • 即便 Z 值在有效范围,因为没开深度测试,依然按绘制顺序覆盖

如果你的顶点是世界空间/视图空间坐标,需要通过投影矩阵转换到裁剪空间。例如使用正交投影的顶点着色器修改:

struct VertexInput {
    @location(0) position: vec3<f32>,
};

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
};

@group(0) @binding(0)
var<uniform> projection: mat4x4<f32>;

@vertex
fn vs_main(model: VertexInput) -> VertexOutput {
    var out: VertexOutput;
    // 通过投影矩阵将顶点转换到裁剪空间
    out.clip_position = projection * vec4<f32>(model.position, 1.0);
    return out;
}

CPU 端可以用 cgmath 或 glam 库生成投影矩阵:

use glam::Mat4;

// 正交投影参数:左、右、下、上、近平面、远平面
let projection = Mat4::orthographic_rh_gl(-1.0, 1.0, -1.0, 1.0, 0.0, 1.0);
// 将矩阵上传到 uniform buffer(需提前创建对应的 bind group)

4. 渲染时绑定深度纹理

在开始渲染 pass 时,需要将深度纹理视图作为深度附件传入:

let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
    label: Some("render_pass"),
    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
        view: &texture_view,
        resolve_target: None,
        ops: wgpu::Operations {
            load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
            store: true,
        },
    })],
    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
        view: &depth_texture_view,
        depth_ops: Some(wgpu::Operations {
            load: wgpu::LoadOp::Clear(1.0), // 初始化为远平面深度值
            store: true,
        }),
        stencil_ops: None,
    }),
});

验证效果

开启深度测试后,Z 值更小(更靠近相机)的三角形会遮挡 Z 值更大的三角形,不再受绘制顺序影响。同时确保你的顶点 Z 值落在 [0.0, 1.0] 范围内(或通过投影矩阵转换到该范围),避免被裁剪。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:02:43