从WebGL迁移到WebGPU

迁移到即将推出的 WebGPU 不仅仅意味着切换图形 API。这也是迈向 Web 图形未来的一步。但如果做好准备并理解,迁移将会更加顺利 — 本文将帮助您做好准备。

从WebGL迁移到WebGPU

在本文中,我们将讨论 WebGL 和即将推出的 WebGPU 之间的区别,并阐述如何为项目迁移做好准备。

WebGL 和 WebGPU 的时间线

WebGL 与许多其他 Web 技术一样,其历史可以追溯到很久以前。要了解转向 WebGPU 背后的动态和动机,首先快速回顾一下 WebGL 开发的历史会有所帮助:

  • OpenGL 桌面版(1993 年) OpenGL 桌面版首次亮相。
  • WebGL 1.0(2011):这是 WebGL 的第一个稳定版本,基于 2007 年推出的 OpenGL ES 2.0。它为 Web 开发人员提供了直接在浏览器中使用 3D 图形的能力,而无需额外的插件。
  • WebGL 2.0(2017 年):WebGL 2.0 在第一版发布六年后推出,基于 OpenGL ES 3.0(2012 年)。此版本带来了许多改进和新功能,使 Web 上的 3D 图形更加强大。

近年来,人们对新的图形 API 的兴趣日益浓厚,这些 API 可以为开发人员提供更多的控制力和灵活性:

  • Vulkan(2016 年):由 Khronos 集团创建的跨平台 API 是 OpenGL 的“后继者”。Vulkan 提供对图形硬件资源的低级访问,让高性能应用程序能够更好地控制图形硬件。
  • D3D12(2015):此 API 由 Microsoft 创建,专用于 Windows 和 Xbox。D3D12 是 D3D10/11 的后继者,为开发人员提供了对图形资源的更深层次的控制。
  • Metal(2014):由 Apple 创建,是 Apple 设备的专属 API。其设计初衷是实现 Apple 硬件的最大性能。
从WebGL迁移到WebGPU

WebGPU 的现状以及未来发展

如今,从 113 版本开始,WebGPU 可通过 Google Chrome 和 Microsoft Edge 浏览器在 Windows、Mac 和 ChromeOS 等多个平台上使用。预计在不久的将来会支持 Linux 和 Android。

以下是一些已经支持(或提供实验性支持)WebGPU 的引擎:

  • Babylon JS:全面支持 WebGPU。
  • ThreeJS:目前正在实验性支持。
  • PlayCanvas:正在开发中,但前景非常光明。
  • Unity:在 2023.2 alpha 版本中宣布了非常早期且实验性的 WebGPU 支持。
  • Cocos Creator 3.6.2:正式支持 WebGPU,成为该领域的先驱之一。
  • Construct:目前仅支持 Windows、macOS 和 ChromeOS 的 v113+ 版本。
从WebGL迁移到WebGPU

考虑到这一点,过渡到 WebGPU 或至少为这种过渡准备项目似乎是近期及时的一步。

高层概念差异

让我们缩小范围,看看 WebGL 和 WebGPU 之间的一些高级概念差异,从初始化开始。

初始化

开始使用图形 API 时,第一步是初始化交互的主要对象。此过程在 WebGL 和 WebGPU 之间有所不同,两个系统都有一些特殊之处。

WebGL:上下文模型

在 WebGL 中,此对象称为“上下文”,它本质上表示在 HTML5 Canvas 元素上绘图的界面。获取此上下文非常简单:

const gl = canvas.getContext('webgl');

WebGL 的上下文实际上是与特定画布绑定的。这意味着如果您需要在多个画布上进行渲染,则需要多个上下文。

WebGPU:设备模型

WebGPU 引入了一个名为“设备”的新概念。此设备代表您将与之交互的 GPU 抽象。初始化过程比 WebGL 稍微复杂一些,但它提供了更大的灵活性:

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

const context = canvas.getContext('webgpu');
context.configure({
   device,
   format: 'bgra8unorm',
});
此模型的优点之一是,一台设备可以在多个画布上渲染,甚至可以不渲染任何画布。这提供了额外的灵活性;例如,一台设备可以控制多个窗口或上下文中的渲染。
从WebGL迁移到WebGPU

程序和管道

WebGL 和 WebGPU 代表管理和组织图形管道的不同方法。

WebGL:程序

在 WebGL 中,主要关注的是着色器程序。该程序结合了顶点和片段着色器,定义了顶点应如何变换以及每个像素应如何着色。


const program = gl.createProgram();
gl.attachShader(program, vertShader);
gl.attachShader(program, fragShader);
gl.bindAttribLocation(program, 'position', 0);
gl.linkProgram(program);

在 WebGL 中创建程序的步骤:

  1. 创建着色器:编写并编译着色器的源代码。
  2. 创建程序:将编译好的着色器附加到程序中,然后链接起来。
  3. 使用程序:渲染之前激活该程序。
  4. 数据传输:数据被传输到激活的程序。

该过程允许灵活的图形控制,但也可能很复杂并且容易出错,特别是对于大型复杂的项目。

WebGPU:管道

WebGPU 引入了“管道”的概念,而不是单独的程序。该管道不仅结合了着色器,还结合了其他信息,在 WebGL 中,这些信息以状态的形式建立。因此,在 WebGPU 中创建管道看起来更复杂:


const pipeline = device.createRenderPipeline({
 layout: 'auto',
 vertex: {
   module: shaderModule, entryPoint: 'vertexMain',
   buffers: [{
     arrayStride: 12,
     attributes: [{
       shaderLocation: 0, offset: 0, format: 'float32x3'
     }]
   }],
 },
 fragment: {
   module: shaderModule, entryPoint: 'fragmentMain',
   targets: [{ format, }],
 },
});

在 WebGPU 中创建管道的步骤:

  1. 着色器定义:着色器源代码的编写和编译方式与在 WebGL 中类似。
  2. 管道创建:着色器和其他渲染参数被组合成管道。
  3. 管道使用:管道在渲染之前被激活。

WebGL 将渲染的各个方面分开,而 WebGPU 则试图将更多方面封装到单个对象中,从而使系统更加模块化和灵活。WebGPU 不会像 WebGL 那样单独管理着色器和渲染状态,而是将所有内容组合到一个管道对象中。这使得该过程更加可预测,并且不容易出错:

从WebGL迁移到WebGPU

Uniform

统一变量提供可供所有着色器实例使用的常量数据。

WebGL 1 中的 Uniforms

在基本的 WebGL 中,我们能够uniform直接通过 API 调用来设置变量。

GLSL

uniform vec3 u_LightPos;
uniform vec3 u_LightDir;
uniform vec3 u_LightColor;

JavaScript

const location = gl.getUniformLocation(p, "u_LightPos");
gl.uniform3fv(location, [100, 300, 500]);

这种方法很简单,但是每个uniform变量都需要多次调用 API。

WebGL 2 中的 Uniforms

随着 WebGL 2 的到来,我们现在能够将uniform变量分组到缓冲区中。虽然您仍然可以使用单独的统一着色器,但更好的选择是使用统一缓冲区将不同的统一分组到更大的结构中。然后,您可以一次性将所有这些统一数据发送到 GPU,类似于在 WebGL 1 中加载顶点缓冲区的方式。这具有多种性能优势,例如减少 API 调用并更接近现代 GPU 的工作方式。

GLSL

layout(std140) uniform ub_Params {
   vec4 u_LightPos;
   vec4 u_LightDir;
   vec4 u_LightColor;
};

JavaScript

gl.bindBufferBase ( gl.UNIFORM_BUFFER , 1 , gl.createBuffer ( ) ) ;

要在 WebGL 2 中绑定大型统一缓冲区的子集,您可以使用称为 的特殊 API 调用bindBufferRange。在 WebGPU 中,有一种类似的东西,称为动态统一缓冲区偏移,您可以在调用setBindGroupAPI 时传递偏移列表。

从WebGL迁移到WebGPU

WebGPU 中的统一规则

WebGPU 为我们提供了一种更好的方法。在这种情况下,uniform不再支持单个变量,工作完全通过uniform缓冲区完成。

工作流级别

[[block]] struct Params {
   u_LightPos : vec4<f32>;
   u_LightColor : vec4<f32>;
   u_LightDirection : vec4<f32>;
};
[[group(0), binding(0)]] var<uniform> ub_Params : Params;

JavaScript

const buffer = device.createBuffer({
  usage: GPUBufferUsage.UNIFORM,
  size: 8
});

现代 GPU 倾向于将数据加载到一个大块中,而不是多个小块中。与其每次都重新创建和重新绑定小缓冲区,不如考虑创建一个大缓冲区,并将其不同部分用于不同的绘制调用。这种方法可以显著提高性能。

WebGL 更具命令性,每次调用时都会重置全局状态,并力求尽可能简单。另一方面,WebGPU 的目标是更加面向对象,并专注于资源重用,从而提高效率。

由于方法不同,从 WebGL 过渡到 WebGPU 可能看起来很困难。但是,从过渡到 WebGL 2 作为中间步骤可以简化您的生活。

从WebGL迁移到WebGPU

着色器

从 WebGL 迁移到 WebGPU 不仅需要更改 API,还需要更改着色器。WGSL 规范旨在使这一过渡顺畅而直观,同时保持现代 GPU 的效率和性能。

着色器语言:GLSL 与 WGSL

WGSL 旨在成为 WebGPU 和原生图形 API 之间的桥梁。与 GLSL 相比,WGSL 看起来更冗长一些,但结构仍然很熟悉。

以下是纹理着色器的示例:

全局语言资源管理器(GLSL):

sampler2D myTexture;
varying vec2 vTexCoord;
void main() {
  return texture(myTexture, vTexCoord);
}

工作组:

[[group(0), binding(0)]] var mySampler: sampler;
[[group(0), binding(1)]] var myTexture: texture_2d<f32>;
[[stage(fragment)]]
fn main([[location(0)]] vTexCoord: vec2<f32>) -> [[location(0)]] vec4<f32> {
  return textureSample(myTexture, mySampler, vTexCoord);
}
从WebGL迁移到WebGPU

数据类型比较

下表显示了 GLSL 和 WGSL 中基本数据类型和矩阵数据类型的比较:

从WebGL迁移到WebGPU

从 GLSL 过渡到 WGSL 表明了对更严格的类型和数据大小的明确定义的需求,这可以提高代码的可读性并降低出现错误的可能性。

从WebGL迁移到WebGPU

结构

声明结构的语法也发生了变化:

// GLSL

struct Light {
  vec3 position;
  vec4 color;
  float attenuation;
  vec3 direction;
  float innerAngle;
  float angle;
  float range;
};
// WGSL

struct Light {
  position: vec3<f32>,
  color: vec4<f32>,
  attenuation: f32,
  direction: vec3<f32>,
  innerAngle: f32,
  angle: f32,
  range: f32,
};

引入用于声明 WGSL 结构中字段的明确语法强调了对更高清晰度的渴望,并简化了对着色器中数据结构的理解。

从WebGL迁移到WebGPU

函数声明

全局语言资源管理器(GLSL):

float saturate(float x) {
	return clamp(x, 0.0, 1.0);
}

工作组:

fn saturate(x: f32) -> f32 {
  return clamp(x, 0.0, 1.0);
}

WGSL 中函数语法的改变反映了声明和返回值方法的统一,使得代码更加一致和可预测。

从WebGL迁移到WebGPU

内置函数

在 WGSL 中,许多内置的 GLSL 函数已被重命名或替换。例如:

从WebGL迁移到WebGPU

重命名 WGSL 中的内置函数不仅简化了它们的名称,而且使它们更加直观,这可以方便熟悉其他图形 API 的开发人员的过渡过程。

从WebGL迁移到WebGPU

着色器转换

对于那些计划将项目从 WebGL 转换为 WebGPU 的人来说,重要的是要知道有一些工具可以自动将 GLSL 转换为 WGSL,例如Naga,这是一个用于将 GLSL 转换为 WGSL 的 Rust 库。它甚至可以在 WebAssembly 的帮助下在您的浏览器中运行。

以下是 Naga 支持的端点:

从WebGL迁移到WebGPU

惯例差异

纹理

迁移后,您可能会遇到图像翻转的意外情况。曾经将应用程序从 OpenGL 移植到 Direct3D(或反之亦然)的人已经遇到过这个经典问题。

在 OpenGL 和 WebGL 中,纹理通常以起始像素对应于左下角的方式加载。然而,在实践中,许多开发人员从左上角开始加载图像,这会导致翻转图像错误。不过,这个错误可以通过其他因素来补偿,最终解决问题。

从WebGL迁移到WebGPU

与 OpenGL 不同,Direct3D 和 Metal 等系统传统上将左上角用作纹理的起点。考虑到这种方法对许多开发人员来说似乎是最直观的,WebGPU 的创建者决定遵循这种做法。

视口空间

如果您的 WebGL 代码从帧缓冲区中选择像素,请做好准备,因为 WebGPU 使用不同的坐标系。您可能需要应用简单的“y = 1.0 – y”操作来更正坐标。

从WebGL迁移到WebGPU

剪辑空间

当开发人员遇到对象被剪切或比预期更早消失的问题时,这通常与深度域的差异有关。WebGL 和 WebGPU 在定义剪切空间的深度范围方面存在差异。WebGL 使用从 -1 到 1 的范围,而 WebGPU 使用从 0 到 1 的范围,类似于 Direct3D、Metal 和 Vulkan 等其他图形 API。做出此决定是因为在使用其他图形 API 时发现了使用从 0 到 1 的范围的几个优点。

从WebGL迁移到WebGPU

将模型位置转换到裁剪空间的主要责任在于投影矩阵。调整代码的最简单方法是确保投影矩阵输出结果在 0 到 1 的范围内。对于使用 gl-matrix 等库的用户,有一个简单的解决方案:perspective您可以使用 而不是 函数perspectiveZO其他矩阵操作也有类似的函数可用。

if (webGPU) {
	// Creates a matrix for a symetric perspective-view frustum
  // using left-handed coordinates
  mat4.perspectiveZO(out, Math.PI / 4, ...);
} else {
  // Creates a matrix for a symetric perspective-view frustum
  // based on the default handedness and default near
  // and far clip planes definition.
  mat4.perspective(out, Math.PI / 4, …);
}

但是,有时您可能有一个现有的投影矩阵,并且无法更改其来源。在这种情况下,要将其转换为 0 到 1 的范围,您可以将投影矩阵预乘以另一个校正深度范围的矩阵。

从WebGL迁移到WebGPU

WebGPU 技巧和窍门

现在,让我们讨论一下使用 WebGPU 的一些技巧和窍门。

尽量减少使用的管道数量

您使用的管道越多,状态切换就越多,性能就越低;这可能并不简单,具体取决于您的资产来自哪里。

提前创建管道

创建管道并立即使用可能会有效,但不建议这样做。相反,创建立即返回并开始在不同线程上工作的函数。使用管道时,执行队列需要等待待处理的管道创建完成。这可能会导致严重的性能问题。为避免这种情况,请确保在创建管道和首次使用管道之间留出一些时间。

或者,更好的是,使用create*PipelineAsync变体!当管道准备就绪时,承诺就会解决,不会有任何停滞。

device.createComputePipelineAsync({
 compute: {
   module: shaderModule,
   entryPoint: 'computeMain'
 }
}).then((pipeline) => {
  const commandEncoder = device.createCommandEncoder();
  const passEncoder = commandEncoder.beginComputePass();
  passEncoder.setPipeline(pipeline);
  passEncoder.setBindGroup(0, bindGroup);
  passEncoder.dispatchWorkgroups(128);
  passEncoder.end();
  device.queue.submit([commandEncoder.finish()]);
});

使用 RenderBundles

渲染包是预先录制的、部分且可重复使用的渲染过程。它们可以包含大多数渲染命令(除了设置视口之类的命令),并且可以在以后作为实际渲染过程的一部分进行“重放”。

const renderPass = encoder.beginRenderPass(descriptor);

renderPass.setPipeline(renderPipeline);
renderPass.draw(3);

renderPass.executeBundles([renderBundle]);

renderPass.setPipeline(renderPipeline);
renderPass.draw(3);

renderPass.end();


渲染包可以与常规渲染过程命令一起执行。每次执行包之前和之后,渲染过程状态都会重置为默认值。这样做主要是为了减少绘制的 JavaScript 开销。无论采用哪种方法,GPU 性能都保持不变。

概括

过渡到 WebGPU 不仅仅意味着切换图形 API。这也是迈向 Web 图形未来的一步,它将各种图形 API 的成功功能和实践相结合。这种迁移需要彻底了解技术和理念上的变化,但其好处是巨大的。

RA/SD 衍生者AI训练营。发布者:chris,转载请注明出处:https://www.shxcj.com/archives/6190

(0)
上一篇 2024-09-20 3:10 下午
下一篇 2024-09-20 3:34 下午

相关推荐

发表回复

登录后才能评论
本文授权以下站点有原版访问授权 https://www.shxcj.com https://www.2img.ai https://www.2video.cn