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

使用wgpu结合lyon时三角形无法渲染的问题排查

使用wgpu + lyon绘制图形无显示问题排查

问题描述

我用wgpu结合lyon开发画线系统,调用draw_indexed()加载lyon生成的VertexBuffers时,画面完全没有内容显示。

相关实现代码

impl LineDrawer {
fn build(self, device: &Device) -> Vec<Buffer> {
    let mut buffers = Vec::new();

    for i in self.lines {
        let buffer = i.build();
        let vertex = device.create_buffer_init(&BufferInitDescriptor { 
            label: None, 
            contents: bytemuck::cast_slice(&buffer.vertices),
            usage: wgpu::BufferUsages::VERTEX,
        });
        let index_len = buffer.indices.len();
        let index = device.create_buffer_init(&BufferInitDescriptor { 
            label: None, 
            contents: bytemuck::cast_slice(&buffer.indices),
            usage: wgpu::BufferUsages::INDEX,
        });
        buffers.push(Buffer { index, vertex, index_len });
    }

    buffers
}

pub fn render(
    self,
    device: &Device,
    encoder: &mut wgpu::CommandEncoder,
    view: &wgpu::TextureView,
    camera_bind_group: &wgpu::BindGroup,
) {
    let shader = device.create_shader_module(wgpu::include_wgsl!("line_drawer.wgsl"));
    let render_pipeline_layout =
        device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("Line Drawer Pipeline Layout"),
            bind_group_layouts: &[camera_bind_group_layout],
            push_constant_ranges: &[],
        });

    let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
        label: Some("Line Drawer Pipeline"),
        layout: Some(&render_pipeline_layout),
        vertex: wgpu::VertexState {
            module: &shader,
            entry_point: "vs_main",
            buffers: &[ColorVertex::desc()],
        },
        fragment: Some(wgpu::FragmentState {
            module: &shader,
            entry_point: "fs_main",
            targets: &[Some(wgpu::ColorTargetState {
                format: config.format,
                blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                write_mask: wgpu::ColorWrites::ALL,
            })],
        }),
        primitive: wgpu::PrimitiveState {
            topology: wgpu::PrimitiveTopology::TriangleList,
            polygon_mode: wgpu::PolygonMode::Fill,
            front_face: wgpu::FrontFace::Ccw,
            strip_index_format: None,
            cull_mode: Some(wgpu::Face::Back),
            conservative: false,
            unclipped_depth: false,
        },
        depth_stencil: None,
        multisample: wgpu::MultisampleState {
            count: 1,
            mask: !0,
            alpha_to_coverage_enabled: false,
        },
        multiview: None,
    });

    let buffers = self.build(device);
    {
        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: Some("Render Pass"),
            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                view,
                resolve_target: None,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Load,
                    store: true,
                },
            })],
            depth_stencil_attachment: None,
        });

        render_pass.set_pipeline(pipeline);
        render_pass.set_bind_group(0, camera_bind_group, &[]);

        for i in 0..buffers.len() {
            render_pass.set_index_buffer(buffers[i].index.slice(..), wgpu::IndexFormat::Uint16);
            render_pass.set_vertex_buffer(0, buffers[i].vertex.slice(..));

            render_pass.draw_indexed(0..buffers[i].index_len as u32, 0, 0..1)
        }
    }
}

pub struct Line {
    buffer: VertexBuffers<ColorVertex, u32>,
}

struct Buffer {
    index: wgpu::Buffer,
    vertex: wgpu::Buffer,
    index_len: usize,
}


impl Line {
pub fn rectangle(mut self, bounds: Rect, color: Color) -> Self {
    {
        let buffers = &mut self.buffer;
        let rect = lyon::math::Box2D::from_origin_and_size(
            lyon::math::point(bounds.x, bounds.y),
            lyon::math::size(bounds.width, bounds.height),
        );
        let vb = VertexBuilder {
            color: LinearColor::from(color),
        };
        let builder = &mut lyon::tessellation::BuffersBuilder::new(buffers, vb);
        let mut tessellator = lyon::tessellation::FillTessellator::new();
        tessellator.tessellate_rectangle(
             &rect, 
             &lyon::lyon_tessellation::FillOptions::default(), 
             builder
        )
        .expect("could not create rectangle");
    }
    self
}


pub fn build(self) -> VertexBuffers<ColorVertex, u32> {
    dbg!(&self.buffer);
    self.buffer
}

调试输出

&self.buffer = VertexBuffers {
    vertices: [
        ColorVertex {
            position: [
                0.0,
                0.0,
                0.0,
            ],
            color: [
                1.0,
                1.0,
                1.0,
                1.0,
            ],
        },
        ColorVertex {
            position: [
                0.0,
                100.0,
                0.0,
            ],
            color: [
                1.0,
                1.0,
                1.0,
                1.0,
            ],
        },
        ColorVertex {
            position: [
                100.0,
                100.0,
                0.0,
            ],
            color: [
                1.0,
                1.0,
                1.0,
                1.0,
            ],
        },
        ColorVertex {
            position: [
                100.0,
                0.0,
                0.0,
            ],
            color: [
                1.0,
                1.0,
                1.0,
                1.0,
            ],
        },
    ],
    indices: [
        0,
        1,
        2,
        0,
        2,
        3,
    ],
    }

问题定位与修复

1. 索引格式不匹配(核心问题)

lyon生成的索引是u32类型,但渲染时设置的索引格式是Uint16,导致索引数据解析错误,无法正确绘制三角形。
修复:修改render方法中的索引格式设置:

render_pass.set_index_buffer(buffers[i].index.slice(..), wgpu::IndexFormat::Uint32);

2. 渲染管线重复创建

每次调用render都重新创建shader和渲染管线,不仅效率低下,还可能引发状态不一致问题。
修复:将shader和渲染管线的创建逻辑移到LineDrawer的初始化阶段,复用已有资源。

3. 顶点坐标空间转换缺失

如果顶点着色器没有将本地坐标转换到裁剪空间,图形会被视锥体裁剪,无法显示在屏幕上。
修复:确保顶点着色器包含视图-投影矩阵转换逻辑,示例如下:

struct CameraUniform {
    view_proj: mat4x4<f32>,
};

@group(0) @binding(0) var<uniform> camera: CameraUniform;

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

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

@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
    var out: VertexOutput;
    out.color = input.color;
    out.clip_position = camera.view_proj * vec4(input.position, 1.0);
    return out;
}

4. 相机视锥体范围验证

检查相机投影矩阵是否覆盖了图形的坐标范围(示例中矩形坐标为0-100),如果视锥体不包含该区域,图形会被裁剪。例如使用正交投影时:

let proj = glam::Mat4::orthographic_rh_gl(0.0, screen_width, screen_height, 0.0, 0.0, 1000.0);

5. 渲染通道初始状态检查

当前渲染通道使用LoadOp::Load,如果帧缓冲未正确初始化,可能导致显示异常。可以尝试改为清空初始状态:

ops: wgpu::Operations {
    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
    store: true,
},

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 13:59:59