关于webgl:在-WebGPU-的片元着色器中访问帧缓冲坐标

45次阅读

共计 2897 个字符,预计需要花费 8 分钟才能阅读完成。

在片元着色器中拜访帧缓冲坐标

1. 技术阐明

  • 应用最新 Edge/Chrome Canary 浏览器
  • 应用 VSCode 插件 LiveServer 的 HTTP 服务器对本机提供 5500 端口的页面服务,即 http://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 标准中有阐明,它的坐标轴、原点和坐标值域是这样的:


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

而后就是最初的那个多步逻辑分支了,也很简略:

  • 第一个 if 对应图中的 <span style=”color: white; background-color: rgb(255, 0, 128)”> 粉色 </span>
  • 第二个 if 对应图中的 <span style=”background-color: rgb(0, 128, 255); color: white”> 蓝色 </span>
  • 第三个 if 对应图中三角形区域内、蓝色、粉色像素外的 圆区域 ,半径是 0.4(映射后的半径),它应用帧缓冲坐标作为色彩值(除以帧缓冲的长宽 600 映射到了 [0, 1]),加了 0.5 的透明度
  • 最初其它的片元应用语句 discard 抛弃,即不渲染

OK,明天就学到这里。

源码

腾讯微云

正文完
 0