WebGPU片元着色器如何获取帧缓冲坐标?

2026-05-22 07:261阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1134个文字,预计阅读时间需要5分钟。

WebGPU片元着色器如何获取帧缓冲坐标?

目录 + 1. 技术说明 + 2. 三角形示例:HTML + JavaScript + 3. 色调器解析 + 断点缓冲占位图展示 + 源码 + 1. 技术说明 + 使用最新 Edge/Chrome Canary 浏览器 + 使用 VSCode + LiveServer 插件 + HTTP 服务器为本机提供 5500 端口

目录
  • 1. 技术说明
  • 2. 三角形例子
    • HTML
    • JavaScript
  • 3. 着色器解析
    • 帧缓冲坐标图示
  • 源码


1. 技术说明
  • 使用最新 Edge/Chrome Canary 浏览器
  • 使用 VSCode 插件 LiveServer 的 HTTP 服务器对本机提供 5500 端口的页面服务,即 localhost:5500/index.html
  • 使用 es-module 风格的 JavaScript 实现
2. 三角形例子

先上效果,后面再解析片元着色器:

HTML

html 部分就简单一些

<canvas id="c" width="600" height="600" style="border: 1px solid darkseagreen;"></canvas> <script type="module" src="./main.js"></script>

不出意外的话,你可以看到一个带暗绿色边框的 canvas,长宽均为 600 像素。

JavaScript

JavaScript 代码也比较简单,省略大部分动态代码和有无判断代码:

const canvas = document.getElementById('c') const shaderText = `/* 着色器代码,后面会给 */` const init = async () => { const adapter = await navigation.gpu.requestAdapter() const device = await adapter.requestDevice() const context = canvas.getContext('webgpu') const presentationFormat = context.getPreferredFormat(adapter) context.configure({ device, format: presentationFormat, size: [ 600, 600 ], // canvas 的画图尺寸 }) const pipeline = device.createRenderPipeline({ vertex: { module: device.createShaderModule({ code: shaderText }), entryPoint: 'vertexMain' }, fragment: { module: device.createShaderModule({ code: shaderText }), entryPoint: 'fragmentMain', targets: [{ format: presentationFormat }], }, primitive: { topology: 'triangle-list' }, }) const render = () => { /* 每帧创建编码器并“录制”编码过程,最终提交给设备 */ const commandEncoder = device.createCommandEncoder() const textureView = context.getCurrentTexture().createView() const renderPassDescriptor = { colorAttachments: [ { view: textureView, clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, loadOp: 'clear', storeOp: 'store', }, ], } const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor) passEncoder.setPipeline(pipeline) passEncoder.draw(3, 1, 0, 0) passEncoder.end() device.queue.submit([commandEncoder.finish()]) requestAnimationFrame(render) } requestAnimationFrame(render) } // async function init init()

我保留了完整的 rAF 帧动画结构。

为了方便说明内置在片元着色器中的帧缓冲坐标变量,我将三角形顶点值写死在顶点着色器中,见下文。

3. 着色器解析

着色器代码:

const shaderText = /* wgsl */` @stage(vertex) fn vertexMain( @builtin(vertex_index) VertexIndex: u32 ) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(0.0, 0.5), vec2<f32>(-0.5, -0.5), vec2<f32>(0.5, -0.5) ); return vec4<f32>(pos[VertexIndex], 0.0, 1.0); } @stage(fragment) fn fragmentMain( @builtin(position) FrameBufferCoord: vec4<f32> ) -> @location(0) vec4<f32> { var color = vec4<f32>(1.0, 0.5, 0.0, .5); let x: f32 = (FrameBufferCoord.x - 300.0) / 300.0; let y: f32 = (-FrameBufferCoord.y + 300.0) / 300.0; let r: f32 = sqrt(x * x + y * y); if (x > -0.1 && x < 0.1) { return vec4<f32>(1.0, 0.0, 0.5, 1.0); } else if (y > -0.1 && y < 0.1) { return vec4<f32>(0.0, 0.5, 1.0, 1.0); } else if (r < 0.4) { return vec4<f32>(FrameBufferCoord.rgb / 600.0, 0.5); } else { discard; } } `

WGSL 褒贬不一,就不说它的语法如何了。

主要是看片元着色器的输入,@builtin(position) FrameBufferCoord: vec4<f32>,它向每一个片元着色器传入了当前片元的帧缓冲坐标,类型是 vec4<f32>

WebGPU片元着色器如何获取帧缓冲坐标?


帧缓冲坐标图示

帧缓冲在 WebGPU 规范中有说明,它的坐标轴、原点和坐标值域是这样的:


我对帧缓冲坐标进行了缩放、平移,也就是计算了 xy,将原点移动到 canvas 中央,然后把坐标区间从 [0, 600] 映射到 [-1, 1]

然后就是最后的那个多步逻辑分支了,也很简单:

  • 第一个 if 对应图中的 粉色
  • 第二个 if 对应图中的 蓝色
  • 第三个 if 对应图中三角形区域内、蓝色、粉色像素外的 圆区域,半径是 0.4(映射后的半径),它使用帧缓冲坐标作为颜色值(除以帧缓冲的长宽 600 映射到了 [0, 1]),加了 0.5 的透明度
  • 最后其它的片元使用语句 discard 丢弃,即不渲染

OK,今天就学到这里。

源码

微云

本文共计1134个文字,预计阅读时间需要5分钟。

WebGPU片元着色器如何获取帧缓冲坐标?

目录 + 1. 技术说明 + 2. 三角形示例:HTML + JavaScript + 3. 色调器解析 + 断点缓冲占位图展示 + 源码 + 1. 技术说明 + 使用最新 Edge/Chrome Canary 浏览器 + 使用 VSCode + LiveServer 插件 + HTTP 服务器为本机提供 5500 端口

目录
  • 1. 技术说明
  • 2. 三角形例子
    • HTML
    • JavaScript
  • 3. 着色器解析
    • 帧缓冲坐标图示
  • 源码


1. 技术说明
  • 使用最新 Edge/Chrome Canary 浏览器
  • 使用 VSCode 插件 LiveServer 的 HTTP 服务器对本机提供 5500 端口的页面服务,即 localhost:5500/index.html
  • 使用 es-module 风格的 JavaScript 实现
2. 三角形例子

先上效果,后面再解析片元着色器:

HTML

html 部分就简单一些

<canvas id="c" width="600" height="600" style="border: 1px solid darkseagreen;"></canvas> <script type="module" src="./main.js"></script>

不出意外的话,你可以看到一个带暗绿色边框的 canvas,长宽均为 600 像素。

JavaScript

JavaScript 代码也比较简单,省略大部分动态代码和有无判断代码:

const canvas = document.getElementById('c') const shaderText = `/* 着色器代码,后面会给 */` const init = async () => { const adapter = await navigation.gpu.requestAdapter() const device = await adapter.requestDevice() const context = canvas.getContext('webgpu') const presentationFormat = context.getPreferredFormat(adapter) context.configure({ device, format: presentationFormat, size: [ 600, 600 ], // canvas 的画图尺寸 }) const pipeline = device.createRenderPipeline({ vertex: { module: device.createShaderModule({ code: shaderText }), entryPoint: 'vertexMain' }, fragment: { module: device.createShaderModule({ code: shaderText }), entryPoint: 'fragmentMain', targets: [{ format: presentationFormat }], }, primitive: { topology: 'triangle-list' }, }) const render = () => { /* 每帧创建编码器并“录制”编码过程,最终提交给设备 */ const commandEncoder = device.createCommandEncoder() const textureView = context.getCurrentTexture().createView() const renderPassDescriptor = { colorAttachments: [ { view: textureView, clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, loadOp: 'clear', storeOp: 'store', }, ], } const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor) passEncoder.setPipeline(pipeline) passEncoder.draw(3, 1, 0, 0) passEncoder.end() device.queue.submit([commandEncoder.finish()]) requestAnimationFrame(render) } requestAnimationFrame(render) } // async function init init()

我保留了完整的 rAF 帧动画结构。

为了方便说明内置在片元着色器中的帧缓冲坐标变量,我将三角形顶点值写死在顶点着色器中,见下文。

3. 着色器解析

着色器代码:

const shaderText = /* wgsl */` @stage(vertex) fn vertexMain( @builtin(vertex_index) VertexIndex: u32 ) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(0.0, 0.5), vec2<f32>(-0.5, -0.5), vec2<f32>(0.5, -0.5) ); return vec4<f32>(pos[VertexIndex], 0.0, 1.0); } @stage(fragment) fn fragmentMain( @builtin(position) FrameBufferCoord: vec4<f32> ) -> @location(0) vec4<f32> { var color = vec4<f32>(1.0, 0.5, 0.0, .5); let x: f32 = (FrameBufferCoord.x - 300.0) / 300.0; let y: f32 = (-FrameBufferCoord.y + 300.0) / 300.0; let r: f32 = sqrt(x * x + y * y); if (x > -0.1 && x < 0.1) { return vec4<f32>(1.0, 0.0, 0.5, 1.0); } else if (y > -0.1 && y < 0.1) { return vec4<f32>(0.0, 0.5, 1.0, 1.0); } else if (r < 0.4) { return vec4<f32>(FrameBufferCoord.rgb / 600.0, 0.5); } else { discard; } } `

WGSL 褒贬不一,就不说它的语法如何了。

主要是看片元着色器的输入,@builtin(position) FrameBufferCoord: vec4<f32>,它向每一个片元着色器传入了当前片元的帧缓冲坐标,类型是 vec4<f32>

WebGPU片元着色器如何获取帧缓冲坐标?


帧缓冲坐标图示

帧缓冲在 WebGPU 规范中有说明,它的坐标轴、原点和坐标值域是这样的:


我对帧缓冲坐标进行了缩放、平移,也就是计算了 xy,将原点移动到 canvas 中央,然后把坐标区间从 [0, 600] 映射到 [-1, 1]

然后就是最后的那个多步逻辑分支了,也很简单:

  • 第一个 if 对应图中的 粉色
  • 第二个 if 对应图中的 蓝色
  • 第三个 if 对应图中三角形区域内、蓝色、粉色像素外的 圆区域,半径是 0.4(映射后的半径),它使用帧缓冲坐标作为颜色值(除以帧缓冲的长宽 600 映射到了 [0, 1]),加了 0.5 的透明度
  • 最后其它的片元使用语句 discard 丢弃,即不渲染

OK,今天就学到这里。

源码

微云