WebGPU学习(二): 学习“绘制一个三角形”示例

大家好,本文学习Chrome->webgl-samplers->helloTriangle示例。

上一篇文章:WebGPU学习(一): 开篇

准备Sample代码

克隆webgl-samplers Github Repo到本地。
(备注:当前的version为0.0.2)

实际的sample代码在src/examples/文件夹中,是typescript代码写的:

学习helloTriangle.ts

打开helloTriangle.ts文件,我们来看下init函数的内容。

首先是shader代码

    const vertexShaderGLSL = `#version 450
      const vec2 pos[3] = vec2[3](vec2(0.0f, 0.5f), vec2(-0.5f, -0.5f), vec2(0.5f, -0.5f));

      void main() {
          gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
      }
    `;

    const fragmentShaderGLSL = `#version 450
      layout(location = 0) out vec4 outColor;

      void main() {
          outColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    `;

这里是vertex shader和fragment shader的glsl代码。

(webgpu支持vertex shader、fragment shader、compute shader,这里只使用了前面两个)

“#version 450”声明了glsl版本为4.5(它要放在glsl的第一行)

第2行定义了三角形的三个顶点坐标,使用2维数组保存(每个元素为vec2类型)。因为都在一个平面,所以顶点只定义了x、y坐标(顶点的z为0.0)

第5行的gl_VertexIndex为顶点序号,每次执行时值依次为0、1、2(vertex shader被执行了3次,因为只有3个顶点)(具体见本文末尾对draw的分析)

第9行是fragment shader,因为三角形为一个颜色,所以所有片段的颜色为同一个固定值

然后我们继续看下面的代码

    const adapter = await navigator.gpu.requestAdapter();
    const device = await adapter.requestDevice();
    // 准备编译glsl的库
    const glslang = await glslangModule();
    // 获得webgpu上下文
    const context = canvas.getContext('gpupresent');

第4行的glslangModule是import的第三方库:

import glslangModule from '../glslang';

继续往下看

    // 定义swapbuffer的格式为RGBA8位的无符号归一化格式
    const swapChainFormat = "bgra8unorm";

    // @ts-ignore:
    const swapChain: GPUSwapChain = context.configureSwapChain({
      device,
      format: swapChainFormat,
    });

@ts-ignore是typescript用来忽略错误的。因为context的类型是RenderingContext,它没有定义configureSwapChain函数,如果编译该行typescript会报错,所以需要忽略错误。

第5行配置了swap chain。vulkan tutorial对此进行了说明:
swap chain是一个缓冲结构,webgpu会先将内容渲染到swap chain的buffer中,然后再将其显示到屏幕上;
swap chain本质上是等待呈现在屏幕上的一个图片队列。

接下来就是创建render pipeline

    const pipeline = device.createRenderPipeline({
      layout: device.createPipelineLayout({ bindGroupLayouts: [] }),

      vertexStage: {
        module: device.createShaderModule({
          code: glslang.compileGLSL(vertexShaderGLSL, "vertex"),

          // @ts-ignore
          source: vertexShaderGLSL,
          transform: source => glslang.compileGLSL(source, "vertex"),
        }),
        entryPoint: "main"
      },
      fragmentStage: {
        module: device.createShaderModule({
          code: glslang.compileGLSL(fragmentShaderGLSL, "fragment"),

          // @ts-ignore
          source: fragmentShaderGLSL,
          transform: source => glslang.compileGLSL(source, "fragment"),
        }),
        entryPoint: "main"
      },

      primitiveTopology: "triangle-list",

      colorStates: [{
        format: swapChainFormat,
      }],
    });

了解pipeline

WebGPU有两种pipeline:render pipeline和compute pipeline,这里只用了render pipeline

这里使用render pipeline descriptor来创建render pipeline,它的定义如下:

dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase {
    required GPUPipelineLayout layout;
};

...

dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase {
    required GPUProgrammableStageDescriptor vertexStage;
    GPUProgrammableStageDescriptor fragmentStage;

    required GPUPrimitiveTopology primitiveTopology;
    GPURasterizationStateDescriptor rasterizationState = {};
    required sequence<GPUColorStateDescriptor> colorStates;
    GPUDepthStencilStateDescriptor depthStencilState;
    GPUVertexStateDescriptor vertexState = {};

    unsigned long sampleCount = 1;
    unsigned long sampleMask = 0xFFFFFFFF;
    boolean alphaToCoverageEnabled = false;
    // TODO: other properties
};

render pipeline可以设置绑定的资源布局、编译的shader、fixed functions(如混合、深度、模版、cullMode等各种状态和顶点数据的格式vertexState),相对于WebGL(WebGL的一个API只能设置一个,如使用gl.cullFace设置cull mode),提升了性能(静态设置了各种状态,不需要在运行时设置),便于管理(把各个状态集中到了一起设置)。

分析render pipeline descriptor

vertexStage和fragmentStage分别设置vertex shader和fragment shader:
使用第三方库,将glsl编译为字节码(格式为SPIR-V);
source和transform字段是多余的,可以删除。

因为shader没有绑定资源(如uniform buffer, texture等),所以第2行的bindGroupLayouts为空数组,不需要bind group和bind group layout

第25行的primitiveTopology指定片元的拓扑结构,此处为三角形。
它可以为以下值:

enum GPUPrimitiveTopology {
    "point-list",
    "line-list",
    "line-strip",
    "triangle-list",
    "triangle-strip"
};

现在先忽略colorStates

我们继续分析后面的代码,接下来定义了frame函数

frame函数定义了每帧执行的逻辑:

    function frame() {
      const commandEncoder = device.createCommandEncoder({});
      const textureView = swapChain.getCurrentTexture().createView();

      const renderPassDescriptor: GPURenderPassDescriptor = {
        colorAttachments: [{
          attachment: textureView,
          loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
        }],
      };

      const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
      passEncoder.setPipeline(pipeline);
      passEncoder.draw(3, 1, 0, 0);
      passEncoder.endPass();

      device.defaultQueue.submit([commandEncoder.finish()]);
    }

    return frame;

学习command buffer

我们不能直接操作command buffer,需要创建command encoder,使用它将多个commands(如render pass的draw)设置到一个command buffer中,然后执行submit,把command buffer提交到gpu driver的队列中。

根据 webgpu设计文档->Command Submission:

Command buffers carry sequences of user commands on the CPU side. They can be recorded independently of the work done on GPU, or each other. They go through the following stages:
creation -> "recording" -> "ready" -> "executing" -> done

我们知道,command buffer有
creation, recording,ready,executing,done五种状态。

根据该文档,结合代码来分析command buffer的操作流程:
第2行创建command encoder时,应该是创建了command buffer,它的状态为creation;
第12行开始render pass(webgpu还支持compute pass,不过这里没用到),command buffer的状态变为recording;
13-14行将“设置pipeline”、“绘制”的commands设置到command buffer中;
第15行结束render pass,(可以设置下一个pass,如compute pass,不过这里只用了一个pass);
第17行“commandEncoder.finish()”将command buffer的状态变为ready;
然后执行subimit,command buffer状态变为executing,被提交到gpu driver的队列中,不能再在cpu端被操作;
如果提交成功,gpu会决定在某个时间处理它。

分析render pass

第5行的renderPassDescriptor描述了render pass,它的定义为:

dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase {
    required sequence<GPURenderPassColorAttachmentDescriptor> colorAttachments;
    GPURenderPassDepthStencilAttachmentDescriptor depthStencilAttachment;
};

这里只用到了colorAttachments。它类似于WebGL->framebuffer的colorAttachments。这里只用到了一个color buffer attachment。

我们来看下colorAttachment的定义:

dictionary GPURenderPassColorAttachmentDescriptor {
    required GPUTextureView attachment;
    GPUTextureView resolveTarget;

    required (GPULoadOp or GPUColor) loadValue;
    GPUStoreOp storeOp = "store";
};

这里设置attachment,将其与swap chain关联:

          attachment: textureView,

我们现在忽略resolveTarget。

loadValue和storeOp决定渲染前和渲染后怎样处理attachment中的数据。
我们看下它的类型:

enum GPULoadOp {
    "load"
};
enum GPUStoreOp {
    "store",
    "clear"
};

...
dictionary GPUColorDict {
    required double r;
    required double g;
    required double b;
    required double a;
};
typedef (sequence<double> or GPUColorDict) GPUColor;

loadValue如果为GPULoadOp类型,则只有一个值:“load”,它的意思是渲染前保留attachment中的数据;
如果为GPUColor类型(如这里的{ r: 0.0, g: 0.0, b: 0.0, a: 1.0 }),则不仅为"load",而且设置了渲染前的初始值,类似于WebGL的clearColor。

storeOp如果为“store”,意思是渲染后保存被渲染的内容到内存中,后面可以被读取;
如果为“clear”,意思是渲染后清空内容。

现在我们回头看下render pipeline中的colorStates:

      colorStates: [{
        format: swapChainFormat,
      }],

colorStates与colorAttachments对应,也只有一个,它的format应该与swap chain的format相同

我们继续看render pass代码:

      const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
      passEncoder.setPipeline(pipeline);
      passEncoder.draw(3, 1, 0, 0);
      passEncoder.endPass();

draw的定义为:

void draw(unsigned long vertexCount, unsigned long instanceCount,
              unsigned long firstVertex, unsigned long firstInstance);

三角形有3个顶点,这里只绘制1个实例,两者都从0开始(所以vertex shader中的gl_VertexIndex依次为0、1、2),所以第3行为“draw(3, 1, 0, 0)”

最终渲染结果

参考资料

webgl-samplers Github Repo
vulkan tutorial
webgpu设计文档->Command Submission
WebGPU-4

原文地址:https://www.cnblogs.com/chaogex/p/11993144.html

时间: 2024-10-12 04:54:33

WebGPU学习(二): 学习“绘制一个三角形”示例的相关文章

Android快乐贪吃蛇游戏实战项目开发教程-03虚拟方向键(二)绘制一个三角形

该系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html 一.绘制三角形 在上一篇文章中,我们已经新建了虚拟方向键的自定义控件DirectionKeys类,下面我们继续. 本项目中的虚拟方向键的背景是4个三角形组成的矩形,其实是4个三角形的按钮. 系统自带的按钮是矩形的,怎么做一个三角形按钮呢? 首先我需要了解,所有控件的外观都是画出来的,当然不是我们手工去画而是用程序去画. 用程序怎么画呢? 很多技术平台上都有绘图功能,用起来也很相

基于Qt的OpenGL可编程管线学习(1)- 绘制一个三角形

0.写在前面的话 这里只是学习的时候做的笔记记录方便日后的查看,如果有大神看到觉得有问题的地方希望能给予指出,方便日后的学习,谢谢! 我是用的Qt版本为Qt5.6,开发环境为Qt Creator 1.QOpenGLWidget 在Qt开发环境下,使用OpenGL的可编程管线绘制一个三角形 效果如下图所示: 这里使用QOpenGLWidget进行绘制的,在QOpenGLWidget中需要重写 void initializeGL(void); void resizeGL(int w, int h);

OpenGL学习脚印: 绘制一个三角形

写在前面 接着上一节内容,开发环境搭建好后,我们当然想立即编写3D应用程序了.不过我们还需要些耐心,因为OpenGL是一套底层的API,因而我们要掌握的基本知识稍微多一点,在开始绘制3D图形之前,本节我们将通过绘制一个三角形的程序来熟悉现代OpenGL的概念和流程. 通过本节可以了解到: 缓存对象VAO和VBO GLSL着色器程序的编译.链接和使用方法 OpenGL绘图的基本流程 绘图流水线简要了解 与使用高级绘图API(例如java里swing绘图,MFC里的绘图)不同,使用OpenGL绘制图

179在屏幕中绘制一个三角形

效果如下: ViewController.h 1 #import <UIKit/UIKit.h> 2 3 @interface ViewController : UIViewController 4 @end ViewController.m 1 #import "ViewController.h" 2 #import "KMTriangleView.h" 3 4 @interface ViewController () 5 - (void)layout

Stage3D学习笔记(二):使用GPU绘制一个三角形

我们需要使用到Adobe自家提供的AGALMiniAssembler代码类,可以在网下进行下载: 关于AGAL的入门知识可以参考下面的文章: AGAL介绍系列文章(第一部分)AGAL介绍系列文章(第二部分)AGAL介绍系列文章(第三部分) 最终效果如下: 直接上代码了,亲们请查看相关的注释说明: 1 package 2 { 3 import com.adobe.utils.AGALMiniAssembler; 4 5 import flash.display.Sprite; 6 import f

react学习(二)试写一个例子

一.render方法 参考https://itbilu.com/javascript/react/EJiqU81te.html React的组件最终通过render方法渲染到DOM中,该方法由ReactDOM类库提供.API如下: ReactComponent render( ReactElement element,//需要渲染的元素 DOMElement container,//渲染结果在DOM中插入的位置 [function callback]//渲染完成后的回调函数 ) 二.动手写一个H

springboot学习笔记-1 第一个springboot示例

springboot是一个微框架,其设计的目的是为了简化spring框架的搭建和配置过程.从而使开发人员不再需要定义样板化的配置.下面是springboot的入门案例:它演示了利用springboot省去配置文件,然后通过运行Java程序,使得内置在springboot上面的tomcat运行,然后访问contoller的过程. 1.在eclipse在建立maven工程,配置pom.xml:pom.xml按照如下配置: <project xmlns="http://maven.apache.

SSIS 学习之旅 第一个SSIS 示例(一)(下)

继上一章咱们继续. 6.创建包变量注意:由于以后文章里的实例全部建立在一个解决方案下,而且 数据库是本地数据库 我使用包变量存放数据库连接串,DB连接管理器 会转换成 项目连接 以后的文章就不详细讲解这一方面了.(大家也可以不使用包变量这种方式根据情况而定); 7.把数据库连接器 连接方式 更改成变量形式 8.创建DB源 注意:在数据访问模式 选择SQL命令的情况下  不能使用临时表当结果表 当出现处理复杂逻辑获取结果的时候 最好使用存储过程 最后结果导入到一张实体表中 9.创建输出的平面文件目

tensorflow 基础学习二:实现一个神经网络

tensorflow变量 在tensorflow中,变量(tf.Variable)的作用就是用来保存和更新神经网络中的参数,在声明变量的同时需要指定其初始值. tensorflow中支持的随机数生成器: 函数名称 随机数分布 主要参数 tf.random_normal 正态分布 平均值.标准差.取值类型 tf.truncated_normal 正态分布,但如果随机出来的值偏离平均值超过2个标准差,那么这个数将会被重新随机 平均值.标准差.取值类型 tf.random_uniform 平均分布 最