使用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
相关产品推荐
相关产品推荐

