当前位置: 首页 > news >正文

二、WebGPU阶段间变量(inter-stage variables)

二、WebGPU阶段间变量(inter-stage variables)

在上一篇文章中,我们介绍了一些关于WebGPU的基础知识。在本文中,我们将介绍阶段变量(inter-stage variables)的基础知识。

阶段变量在顶点着色器和片段着色器之间起作用。当顶点着色器输出3个位置时,三角形将栅格化。顶点着色器可以在每个位置输出额外的值,默认情况下,这些值将在3个点之间进行插值。让我们举个小例子。我们将从上一篇文章中的三角形着色器开始。

我们要做的就是改变着色器。

  const module = device.createShaderModule({label: 'our hardcoded rgb triangle shaders',code: `struct OurVertexShaderOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,};@vertex fn vs(@builtin(vertex_index) vertexIndex : u32) -> OurVertexShaderOutput {let pos = array(vec2f( 0.0,  0.5),  // top centervec2f(-0.5, -0.5),  // bottom leftvec2f( 0.5, -0.5)   // bottom right);var color = array<vec4f, 3>(vec4f(1, 0, 0, 1), // redvec4f(0, 1, 0, 1), // greenvec4f(0, 0, 1, 1), // blue);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);vsOutput.color = color[vertexIndex];return vsOutput;}@fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {return fsInput.color;}`,});

首先,我们声明一个结构体。这是一个在顶点着色器和片段着色器之间协调阶段间变量的简单方法。

      struct OurVertexShaderOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,};

然后我们声明顶点着色器来返回这种类型的结构

      @vertex fn vs(@builtin(vertex_index) vertexIndex : u32) -> OurVertexShaderOutput {

我们创建一个包含3种颜色的数组。

  var color = array<vec4f, 3>(vec4f(1, 0, 0, 1), // redvec4f(0, 1, 0, 1), // greenvec4f(0, 0, 1, 1), // blue);

然后不是返回一个vec4f来获取位置,而是我们声明一个结构的实例,填充它,然后返回它

      var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);vsOutput.color = color[vertexIndex];return vsOutput;

在片段着色器中,我们声明它将这些结构之一作为函数的参数

   @fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {return fsInput.color;}

然后返回颜色。
如果我们运行它,我们会看到,每次GPU调用我们的片段着色器时,它都会传递在所有3个点之间插值的颜色。

以下为当前代码及运行结果:

HTML:

<!--* @Description: * @Author: tianyw* @Date: 2022-11-11 12:50:23* @LastEditTime: 2023-09-17 16:33:32* @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>001hello-triangle</title><style>html,body {margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;text-align: center;flex-direction: column;justify-content: center;}div,canvas {height: 100%;width: 100%;}</style>
</head><body><div id="003color-triangle"><canvas id="gpucanvas"></canvas></div><script type="module" src="./003color-triangle.ts"></script></body></html>

TS:

/** @Description:* @Author: tianyw* @Date: 2023-04-08 20:03:35* @LastEditTime: 2023-09-17 21:06:44* @LastEditors: tianyw*/
export type SampleInit = (params: {canvas: HTMLCanvasElement;
}) => void | Promise<void>;import shaderWGSL from "./shaders/shader.wgsl?raw";
const init: SampleInit = async ({ canvas }) => {const adapter = await navigator.gpu?.requestAdapter();if (!adapter) return;const device = await adapter?.requestDevice();if (!device) {console.error("need a browser that supports WebGPU");return;}const context = canvas.getContext("webgpu");if (!context) return;const devicePixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * devicePixelRatio;canvas.height = canvas.clientHeight * devicePixelRatio;const presentationFormat = navigator.gpu.getPreferredCanvasFormat();context.configure({device,format: presentationFormat,alphaMode: "premultiplied"});const shaderModule = device.createShaderModule({label: "our hardcoded rgb triangle shaders",code: shaderWGSL});const renderPipeline = device.createRenderPipeline({label: "hardcoded rgb triangle pipeline",layout: "auto",vertex: {module: shaderModule,entryPoint: "vs"},fragment: {module:shaderModule,entryPoint: "fs",targets: [{format: presentationFormat}]},primitive: {// topology: "line-list"// topology: "line-strip"//  topology: "point-list"topology: "triangle-list"// topology: "triangle-strip"}});function frame() {const renderCommandEncoder = device.createCommandEncoder({label: "render vert frag"});if (!context) return;const textureView = context.getCurrentTexture().createView();const renderPassDescriptor: GPURenderPassDescriptor = {colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: "clear",storeOp: "store"}]};const renderPass =renderCommandEncoder.beginRenderPass(renderPassDescriptor);renderPass.setPipeline(renderPipeline);renderPass.draw(3, 1, 0, 0);renderPass.end();const renderBuffer = renderCommandEncoder.finish();device.queue.submit([renderBuffer]);requestAnimationFrame(frame);}requestAnimationFrame(frame);
};const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader:

struct OurVertexShaderOutput {@builtin(position) position: vec4f,@location(0) color: vec4f
}@vertex 
fn vs(@builtin(vertex_index) vertexIndex: u32) -> OurVertexShaderOutput {let pos = array<vec2f, 3>(vec2f(0.0, 0.5), // top centervec2f(-0.5, -0.5), // bottom leftvec2f(0.5, -0.5)  // bottom right);var color = array<vec4f,3>(vec4f(1, 0, 0, 1), // redvec4f(0, 1, 0, 1), // greenvec4f(0, 0, 1, 1) // blue);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);vsOutput.color = color[vertexIndex];return vsOutput;
}@fragment
fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {return fsInput.color;
}

在这里插入图片描述

在这里插入图片描述

阶段间变量最常用于跨三角形插值纹理坐标,我们将在纹理文章中介绍。另一个常见的用法是插值法线穿过三角形,这将在第一篇文章中介绍照明。

阶段变量按位置连接

重要的一点是,就像WebGPU中几乎所有的东西一样,顶点着色器和片段着色器之间的连接是通过索引的。对于阶段间变量,它们通过位置索引连接。
为了了解我的意思,让我们只更改片段着色器,在location(0)处采用vec4f参数,而不是结构体。

     @fragment fn fs(@location(0) color: vec4f) -> @location(0) vec4f {return color;}

下面两个片段着色器的代码是同等效果的,依然可以渲染出渐变色的三角形。

在这里插入图片描述

@builtin(position)

我们的原始着色器在顶点和片段着色器中使用相同的结构,有一个名为position的字段,但它没有位置。它被声明为@builtin(position)。

      struct OurVertexShaderOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,};

该字段不是阶段间变量。相反,它是内置的。碰巧@builtin(position)在顶点着色器和片段着色器中有不同的含义。

在顶点着色器中,@builtin(position)是GPU在片段着色器中绘制三角形/线/点所需的输出。

在片段着色器中,@builtin(position)是一个输入,是片段着色器当前被要求计算颜色的像素的像素坐标。

像素坐标由像素的边缘指定。提供给片段着色器的值是像素中心的坐标。

如果我们要绘制的纹理大小为3x2像素,这些就是坐标。

在这里插入图片描述

我们可以改变我们的着色器来使用这个位置。例如,让我们画一个棋盘。

const module = device.createShaderModule({label: 'our hardcoded checkerboard triangle shaders',code: `struct OurVertexShaderOutput {@builtin(position) position: vec4f,};@vertex fn vs(@builtin(vertex_index) vertexIndex : u32) -> OurVertexShaderOutput {let pos = array(vec2f( 0.0,  0.5),  // top centervec2f(-0.5, -0.5),  // bottom leftvec2f( 0.5, -0.5)   // bottom right);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);return vsOutput;}@fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {let red = vec4f(1, 0, 0, 1);let cyan = vec4f(0, 1, 1, 1);let grid = vec2u(fsInput.position.xy) / 8;let checker = (grid.x + grid.y) % 2 == 1;return select(red, cyan, checker);}`,});

position 被声明为@builtin(position),它会将xy坐标转换为vec2u,后者是两个无符号整数。然后将它们除以8,得到每8个像素增加一次的计数。然后,它将x和y网格坐标相加,计算模块2,并将结果与模块1进行比较。这将给我们一个布尔值,每隔一个整数就为true或false。最后,它使用WGSL函数select 给定2个值,根据布尔条件选择其中一个。在JavaScript中,select是这样写的

// If condition is false return `a`, otherwise return `b`
select = (a, b, condition) => condition ? b : a;

代码及运行结果:

HTML:

<!--* @Description: * @Author: tianyw* @Date: 2022-11-11 12:50:23* @LastEditTime: 2023-09-17 16:33:32* @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>001hello-triangle</title><style>html,body {margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;text-align: center;flex-direction: column;justify-content: center;}div,canvas {height: 100%;width: 100%;}</style>
</head><body><div id="004color-grid-triangle"><canvas id="gpucanvas"></canvas></div><script type="module" src="./004color-grid-triangle.ts"></script></body></html>

TS:

/** @Description:* @Author: tianyw* @Date: 2023-04-08 20:03:35* @LastEditTime: 2023-09-17 21:06:44* @LastEditors: tianyw*/
export type SampleInit = (params: {canvas: HTMLCanvasElement;
}) => void | Promise<void>;import shaderWGSL from "./shaders/shader.wgsl?raw";
const init: SampleInit = async ({ canvas }) => {const adapter = await navigator.gpu?.requestAdapter();if (!adapter) return;const device = await adapter?.requestDevice();if (!device) {console.error("need a browser that supports WebGPU");return;}const context = canvas.getContext("webgpu");if (!context) return;const devicePixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * devicePixelRatio;canvas.height = canvas.clientHeight * devicePixelRatio;const presentationFormat = navigator.gpu.getPreferredCanvasFormat();context.configure({device,format: presentationFormat,alphaMode: "premultiplied"});const shaderModule = device.createShaderModule({label: "our hardcoded rgb triangle shaders",code: shaderWGSL});const renderPipeline = device.createRenderPipeline({label: "hardcoded rgb triangle pipeline",layout: "auto",vertex: {module: shaderModule,entryPoint: "vs"},fragment: {module:shaderModule,entryPoint: "fs",targets: [{format: presentationFormat}]},primitive: {// topology: "line-list"// topology: "line-strip"//  topology: "point-list"topology: "triangle-list"// topology: "triangle-strip"}});function frame() {const renderCommandEncoder = device.createCommandEncoder({label: "render vert frag"});if (!context) return;const textureView = context.getCurrentTexture().createView();const renderPassDescriptor: GPURenderPassDescriptor = {colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: "clear",storeOp: "store"}]};const renderPass =renderCommandEncoder.beginRenderPass(renderPassDescriptor);renderPass.setPipeline(renderPipeline);renderPass.draw(3, 1, 0, 0);renderPass.end();const renderBuffer = renderCommandEncoder.finish();device.queue.submit([renderBuffer]);requestAnimationFrame(frame);}requestAnimationFrame(frame);
};const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader:

struct OurVertexShaderOutput {@builtin(position) position: vec4f
}@vertex 
fn vs(@builtin(vertex_index) vertexIndex: u32) -> OurVertexShaderOutput {let pos = array<vec2f, 3>(vec2f(0.0, 0.5), // top centervec2f(-0.5, -0.5), // bottom leftvec2f(0.5, -0.5)  // bottom right);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);return vsOutput;
}@fragment
fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {let red = vec4f(1,0,0,1);let cyan = vec4f(0, 1, 1, 1);let grid = vec2u(fsInput.position.xy) / 8;let checker = (grid.x + grid.y) % 2 == 1;return select(red, cyan, checker);
}

在这里插入图片描述

在这里插入图片描述

即使你在片段着色器中不使用@builtin(position),它的存在也很方便,因为它意味着我们可以在顶点着色器和片段着色器中使用同一个结构体。需要注意的是,顶点着色器和片段着色器中的position结构体字段是完全不相关的。它们是完全不同的变量。

如上所述,对于阶段间变量,重要的是@location(?)。因此,为顶点着色器的输出和片段着色器的输入声明不同的结构体是很常见的。

为了让这个更清楚,在我们的例子中,顶点着色器和片段着色器在同一个字符串中只是为了方便。我们也可以将它们分成单独的模块。

const vsModule = device.createShaderModule({label: 'hardcoded triangle',code: `struct OurVertexShaderOutput {@builtin(position) position: vec4f,};@vertex fn vs(@builtin(vertex_index) vertexIndex : u32) -> OurVertexShaderOutput {let pos = array(vec2f( 0.0,  0.5),  // top centervec2f(-0.5, -0.5),  // bottom leftvec2f( 0.5, -0.5)   // bottom right);var vsOutput: OurVertexShaderOutput;vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);return vsOutput;}`,});const fsModule = device.createShaderModule({label: 'checkerboard',code: `@fragment fn fs(@builtin(position) pixelPosition: vec4f) -> @location(0) vec4f {let red = vec4f(1, 0, 0, 1);let cyan = vec4f(0, 1, 1, 1);let grid = vec2u(pixelPosition.xy) / 8;let checker = (grid.x + grid.y) % 2 == 1;return select(red, cyan, checker);}`,});

我们必须更新创建的管道才能使用它们

  const pipeline = device.createRenderPipeline({label: 'hardcoded checkerboard triangle pipeline',layout: 'auto',vertex: {module: vsModule,entryPoint: 'vs',},fragment: {module: fsModule,entryPoint: 'fs',targets: [{ format: presentationFormat }],},});

这里 demo 只更改了 fragment 的代码,效果等同:

在这里插入图片描述

关键是,在大多数WebGPU示例中,两个着色器使用相同的字符串只是为了方便。实际上,首先WebGPU解析WGSL以确保其语法正确。然后,WebGPU查看你指定的入口点。从那里开始,它会查看入口点引用的部分,而不是该入口点的其他部分。它很有用,因为如果两个或多个着色器共享绑定、结构、常量或函数,就不需要两次输入结构、绑定和分组位置等内容。但是,从WebGPU的角度来看,就好像您为每个入口点复制了所有它们一样。

注意:使用@builtin(position)生成棋盘并不常见。棋盘或其他图案更常用纹理来实现。实际上,如果调整窗口的大小,就会出现问题。因为棋盘是基于画布的像素坐标,所以它是相对于画布的,而不是相对于三角形的。

插值设置

我们在上面看到,阶段间变量,顶点着色器的输出,在传递给片段着色器时进行插值。有两组设置可以改变插值的发生方式。将它们设置为默认值以外的任何值并不常见,但有一些用例将在其他文章中介绍。

插值类型:

  • perspective:值以正确的透视方式(默认)插值。
  • linear:值以线性的、非透视的正确方式插值。
  • falt:值不进行插值。插值采样不用于平面插值

插值采样(Interpolation sampling):

  • center:在像素的中心执行插值(默认)
  • centroid:在当前基元内的碎片覆盖的所有样本内的一点执行插值。这个值对于原始类型中的所有样本都是相同的。
  • sample:对每个样本进行插值。应用这个属性时,每个样本都会调用一次片段着色器。

将它们指定为属性。例如:

@location(2) @interpolate(linear, center) myVariableFoo: vec4f;@location(3) @interpolate(flat) myVariableBar: vec4f;

请注意,如果阶段间变量是整数类型,则必须将其插值设置为平坦 flat。

如果将插值类型设置为flat,则传递给片段着色器的值就是该三角形中第一个顶点的 变量的值。

在下一篇文章中,我们将介绍uniform作为传递数据到着色器的另一种方法。

http://www.lryc.cn/news/187369.html

相关文章:

  • 【Linux】31个普通信号
  • Mac电脑交互式原型设计 Axure RP 8汉化最新 for mac
  • 在线免费无时长限制录屏工具 - 录猎在线版
  • c语言文件操作详解:fgetc,fputc,fgets,fputs,fscanf,,fprintf,fread,fwrite的使用和区别
  • Harmony装饰器
  • 如何加快Chrome谷歌浏览器下载速度?
  • 使用kubectl连接远程Kubernetes(k8s)集群
  • Kubernetes革命:云原生时代的应用编排和自动化
  • mysql.mongoDb,neo4j数据库对比
  • unity使用UniStorm 5.1.0.unitypackage增加天气
  • Flink实现kafka到kafka、kafka到doris的精准一次消费
  • Outlook屏蔽Jira AI提醒
  • 毛玻璃 has 选择器卡片悬停效果
  • [hive]解决group by 字段超过系统规定64个
  • 生成老年人的声音sox
  • DC2DC电源设计注意事项--1,Feedback
  • 计算机视觉处理的开源框架
  • 最新AI智能创作系统源码AI绘画系统/支持GPT联网提问/支持Prompt应用
  • 2019架构真题案例(四十八)
  • zabbix自定义监控内容和自动发现
  • 导引服务机器人 通用技术条件
  • 今日头条文章采集ChatGPT3.5/4.0驱动浏览器改写文章软件说明文档
  • Mac系统清理工具BuhoCleaner
  • SpringBoot集成WebSocket讲解
  • GNOME 45 动态三层缓存补丁更新
  • [论文笔记]Poly-encoder
  • vs2022中配置PCL1.13.1(附带提供属性表.props文件)
  • 基于共生生物优化的BP神经网络(分类应用) - 附代码
  • GIN框架路由的实现原理
  • Android Studio版本升级后的问题 gradle降级、jdk升级