Draw a rectangle using WebGPU

618 Views Asked by At

I'm new to WebGPU and the 3D rendering world.

What I am trying to do is to draw a rectangle in Typescript and wgsl.

I understand that a mesh is a set of vertices. A mesh of 3 vertices draws a triangle. Doesn't a mesh of 4 vertices draws a rectangle? That's what I've done here but the result is still giving me a triangle on the browser. The last vertex is not considered.

This is my code (based from GetIntoGameDev):

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, // This vertex is not working
            ]
        );

        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, // row_num * 4bytes
            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: wrapper around (physical) GPU.
    //Describes features and limits
    const adapter : GPUAdapter = <GPUAdapter> await navigator.gpu?.requestAdapter();

    //device: wrapper around GPU functionality
    //Function calls are made through the device
    const device : GPUDevice = <GPUDevice> await adapter?.requestDevice();

    //context: similar to vulkan instance (or OpenGL context)
    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: records draw commands for submission
    const commandEncoder : GPUCommandEncoder = device.createCommandEncoder();

    //texture view: image view to the color buffer in this case
    const textureView : GPUTextureView = context.getCurrentTexture().createView();

    //renderpass: holds draw commands, allocated from command encoder
    const renderpass : GPURenderPassEncoder = commandEncoder.beginRenderPass({
        // set the background color
        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) // use the buffer from triangle
    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();

This is the output: enter image description here

1

There are 1 best solutions below

0
On

So, after some research I discovered that a rectangle is just 2 triangles set together.

vertices should be like this

        const vertices: Float32Array = new Float32Array(
            [
                -0.5, -0.5,    1, 0, 0,  // vertex a
                0.5, -0.5,    0, 1, 0,  // vertex b
               -0.5,  0.5,    1, 1, 0,  // vertex d
               -0.5,  0.5,    1, 1, 0,  // vertex d
                0.5, -0.5,    0, 1, 0,  // vertex b
                0.5,  0.5,    0, 0, 1   // vertex c
            ]
        );

and we should not forget to update the number of vertices in the draw() method

    renderpass.draw(6, 1, 0, 0);