WebGPU绘制矩形异常:仅显示三角形的问题求助
WebGPU绘制矩形仅显示三角形的问题解决
问题描述
我是WebGPU与3D渲染领域的新手,尝试使用TypeScript和WGSL绘制矩形。我了解网格由顶点构成,3个顶点可绘制三角形,但设置4个顶点后浏览器仍仅显示三角形,最后一个顶点未被识别。
相关代码
rectangle_mesh.ts
export class RectangleMesh { public buffer: GPUBuffer; public bufferLayout: GPUVertexBufferLayout; constructor(public device: GPUDevice) { const vertices: Float32Array = new Float32Array( [ -0.5, 0.5, 1, 1, 1, 0.5, 0.5, 0, 1, 0, 0.5, -0.5, 1, 0, 1, -0.5, -0.5, 0, 0, 0, // 这个顶点未被使用 ] ); const usage: GPUBufferUsageFlags = GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST; const descriptor: GPUBufferDescriptor = { size: vertices.byteLength, usage: usage, mappedAtCreation: true }; this.buffer = device.createBuffer(descriptor); new Float32Array(this.buffer.getMappedRange()).set(vertices); this.buffer.unmap(); this.bufferLayout = { arrayStride: 20, // 每个顶点5个float,5*4=20字节 attributes: [ { shaderLocation: 0, format: "float32x2", offset: 0 }, { shaderLocation: 1, format: "float32x3", offset: 8 } ] } } }
shader.wgsl
struct Fragment { @builtin(position) Position : vec4<f32>, @location(0) Color : vec4<f32> }; @vertex fn vs_main(@location(0) vertexPosition: vec2<f32>, @location(1) vertexColor: vec3<f32>) -> Fragment { var output : Fragment; output.Position = vec4<f32>(vertexPosition, 0.0, 1.0); output.Color = vec4<f32>(vertexColor, 1.0); return output; } @fragment fn fs_main(@location(0) Color: vec4<f32>) -> @location(0) vec4<f32> { return Color; }
main.ts
import { RectangleMesh } from "./rectangle_mesh"; import shader from "./shaders/shader.wgsl"; import { TriangleMesh } from "./triangle_mesh"; const Initialize = async() => { const canvas : HTMLCanvasElement = <HTMLCanvasElement> document.getElementById("game_window"); //adapter: 物理GPU的包装,描述特性和限制 const adapter : GPUAdapter = <GPUAdapter> await navigator.gpu?.requestAdapter(); //device: GPU功能的操作入口 const device : GPUDevice = <GPUDevice> await adapter?.requestDevice(); //context: 类似Vulkan实例或OpenGL上下文 const context : GPUCanvasContext = <GPUCanvasContext><unknown>canvas.getContext("webgpu"); const format : GPUTextureFormat = "bgra8unorm"; context.configure({ device: device, format: format, alphaMode: "opaque" }); const bindGroupLayout = device.createBindGroupLayout({ entries: [], }); const bindGroup = device.createBindGroup({ layout: bindGroupLayout, entries: [] }); const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }); // const triangleMesh = new TriangleMesh(device); const rectangleMesh = new RectangleMesh(device); const pipeline = device.createRenderPipeline({ vertex : { module : device.createShaderModule({ code : shader }), entryPoint : "vs_main", buffers: [rectangleMesh.bufferLayout, ] }, fragment : { module : device.createShaderModule({ code : shader }), entryPoint : "fs_main", targets : [{ format : format }] }, primitive : { topology : "triangle-list" }, layout: pipelineLayout }); //command encoder: 记录绘制命令用于提交 const commandEncoder : GPUCommandEncoder = device.createCommandEncoder(); //texture view: 颜色缓冲区的视图 const textureView : GPUTextureView = context.getCurrentTexture().createView(); //renderpass: 包含绘制命令,从command encoder创建 const renderpass : GPURenderPassEncoder = commandEncoder.beginRenderPass({ // 设置背景色 colorAttachments: [{ view: textureView, clearValue: {r: 52/255, g: 124/255, b: 235/255, a: 1.0}, loadOp: "clear", storeOp: "store" }] }); renderpass.setPipeline(pipeline); renderpass.setBindGroup(0, bindGroup) renderpass.setVertexBuffer(0, rectangleMesh.buffer) renderpass.draw(4, 1, 0, 0); renderpass.end(); device.queue.submit([commandEncoder.finish()]); } function InitializeScreen() { console.log("Width = ", window.innerWidth.toString()); console.log("Height = ", window.innerHeight.toString()); document.getElementById("game_window")?.setAttribute("width", window.innerWidth.toString()); document.getElementById("game_window")?.setAttribute("height", window.innerHeight.toString()); } window.onresize = () => { console.log("Resizing ..."); InitializeScreen(); Initialize(); } InitializeScreen(); Initialize();
渲染效果
仅显示一个三角形,第四个顶点未被识别。
问题原因
当前渲染管线使用的是triangle-list拓扑,这种模式下GPU会每3个连续顶点组成一个独立三角形。你提供了4个顶点,GPU只会用前3个顶点绘制第一个三角形,第四个顶点因不足3个的数量要求被直接忽略,因此只显示单个三角形。
解决方案
以下三种方案都可以解决这个问题,你可以根据需求选择:
方案1:改用triangle-strip拓扑(最简单)
triangle-strip拓扑会自动复用相邻顶点绘制连续三角形,4个顶点即可组成两个三角形(矩形)。
修改main.ts中渲染管线的primitive配置:
primitive : { topology : "triangle-strip" // 替换原有的"triangle-list" }
draw调用的顶点数量保持4不变(renderpass.draw(4, 1, 0, 0);),修改后即可渲染出完整矩形。
方案2:使用两个三角形的完整顶点数据
直接提供两个三角形的6个顶点(包含重复顶点),保持triangle-list拓扑:
修改rectangle_mesh.ts中的顶点数组:
const vertices: Float32Array = new Float32Array( [ // 第一个三角形 -0.5, 0.5, 1, 1, 1, 0.5, 0.5, 0, 1, 0, 0.5, -0.5, 1, 0, 1, // 第二个三角形 -0.5, 0.5, 1, 1, 1, 0.5, -0.5, 1, 0, 1, -0.5, -0.5, 0, 0, 0, ] );
然后修改main.ts中的draw调用,将顶点数量改为6:
renderpass.draw(6, 1, 0, 0);
方案3:使用索引缓冲区(推荐,减少数据冗余)
创建索引缓冲区存储三角形的顶点索引,用4个顶点+6个索引绘制两个三角形,既节省内存又灵活:
步骤1:修改rectangle_mesh.ts,添加索引缓冲区
export class RectangleMesh { public vertexBuffer: GPUBuffer; public indexBuffer: GPUBuffer; public bufferLayout: GPUVertexBufferLayout; public indexCount: number; constructor(public device: GPUDevice) { // 原始4个顶点 const vertices: Float32Array = new Float32Array( [ -0.5, 0.5, 1, 1, 1, 0.5, 0.5, 0, 1, 0, 0.5, -0.5, 1, 0, 1, -0.5, -0.5, 0, 0, 0, ] ); // 索引:两个三角形,对应顶点索引0-1-2和0-2-3 const indices: Uint16Array = new Uint16Array([0, 1, 2, 0, 2, 3]); this.indexCount = indices.length; // 创建顶点缓冲区(逻辑不变) const vertexUsage: GPUBufferUsageFlags = GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST; const vertexDescriptor: GPUBufferDescriptor = { size: vertices.byteLength, usage: vertexUsage, mappedAtCreation: true }; this.vertexBuffer = device.createBuffer(vertexDescriptor); new Float32Array(this.vertexBuffer.getMappedRange()).set(vertices); this.vertexBuffer.unmap(); // 创建索引缓冲区 const indexUsage: GPUBufferUsageFlags = GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST; const indexDescriptor: GPUBufferDescriptor = { size: indices.byteLength, usage: indexUsage, mappedAtCreation: true }; this.indexBuffer = device.createBuffer(indexDescriptor); new Uint16Array(this.indexBuffer.getMappedRange()).set(indices); this.indexBuffer.unmap(); // 顶点布局保持不变 this.bufferLayout = { arrayStride: 20, attributes: [ { shaderLocation: 0, format: "float32x2", offset: 0 }, { shaderLocation: 1, format: "float32x3", offset: 8 } ] } } }
步骤2:修改main.ts中的绘制逻辑
替换原有的setVertexBuffer和draw调用:
renderpass.setVertexBuffer(0, rectangleMesh.vertexBuffer); renderpass.setIndexBuffer(rectangleMesh.indexBuffer, "uint16"); // 设置索引缓冲区 renderpass.drawIndexed(rectangleMesh.indexCount, 1, 0, 0, 0); // 使用索引绘制
内容的提问来源于stack exchange,提问作者Jean-Paul Abi-Ghosn
相关产品推荐
相关产品推荐

