WebGPU学习(7)---渲染到纹理

渲染到纹理

渲染到纹理又称为离屏渲染,这次我们将在纹理上绘制一个红色三角形并将纹理显示在屏幕上。在线代码

1. 准备纹理和采样器

首先,准备将作为渲染目标的纹理。

  const renderTargetTexture = g_device.createTexture({
    size: [512, 512, 1],
    usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
    format: 'rgba8unorm',
  });
  const renderTargetTextureView = renderTargetTexture.createView();

我们来使用 g_device.createTexture 函数创建纹理。这次,纹理的大小设置为 512x512。至于纹理的使用,我们指定两件事:将其用作渲染目标和将其用作纹理。要将其用作渲染目标,请指定 GPUTextureUsage.RENDER_ATTACHMENT;要将其用作纹理,请指定 GPUTextureUsage.TEXTURE_BINDING。此外,“rgba8unorm”被指定为纹理格式。

另外,为了使用纹理作为渲染目标,我们需要纹理的视图,因此我们通过renderTargetTexture.createView()方法创建一个。

接下来,准备引用纹理时所需的采样器。

  const sampler = g_device.createSampler({
    magFilter: 'linear',
    minFilter: 'linear',
  });

2. 准备WGSL代码以在屏幕上显示纹理

显示三角形的WGSL代码在《在WebGPU上绘制三角形》中介绍过,所以这边就省略了。
以下是用于在屏幕上显示纹理的 WGSL 代码。

const vertWGSL2 = `

struct VertexOutput {
  @builtin(position) position: vec4,
  @location(0) texCoord: vec2,
}

@vertex
fn main(
  @builtin(vertex_index) VertexIndex : u32
) -> VertexOutput {
  var output : VertexOutput;
  let x = f32((VertexIndex & 1) << 2);
  let y = f32((VertexIndex & 2) << 1);
  output.texCoord.x = x * 0.5;
  output.texCoord.y = y * 0.5;
  output.position = vec4(x - 1.0, y - 1.0, 0, 1);
  return output;
}
`;

const fragWGSL2 = `

@group(0) @binding(0) var texture: texture_2d;
@group(0) @binding(1) var textureSampler: sampler;

struct FragmentInput {
  @location(0) texCoord: vec2,
}

@fragment
fn main(input: FragmentInput) -> @location(0) vec4 {
	return textureSample(texture, textureSampler, input.texCoord);
}
`;

对于片元着色器非常简单,因为代码只引用纹理。

但是需要解释下在顶点着色器中所做的事情。这里有一些技巧代码,可以在着色器中即兴创作,并使用 UV 绘制一个覆盖屏幕的大三角形,而无需任何顶点数据。
WebGPU学习(7)---渲染到纹理_第1张图片

3. 创建渲染管道

准备一个渲染管线。绘制三角形的RenderPipeline与上次几乎相同,但有一个变化。在fragment.targets[0].format部分指定format:'rgba8unorm'以匹配纹理的格式而不是presentationFormat。

对于绘制纹理时新创建的RenderPipeline,指定之前准备的新WGSL代码。

  // 绘制三角形时的RenderPipeline
  const pipeline = g_device.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: g_device.createShaderModule({
        code: vertWGSL,
      }),
      entryPoint: 'main',
    },
    fragment: {
      module: g_device.createShaderModule({
        code: fragWGSL,
      }),
      entryPoint: 'main',
      targets: [
        {
          format: 'rgba8unorm', // 仅更改此处。匹配纹理格式
        },
      ],
    },
    primitive: {
      topology: 'triangle-list',
    },
  });
  // 绘制纹理时的RenderPipeline
  const pipeline2 = g_device.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: g_device.createShaderModule({
        code: vertWGSL2,
      }),
      entryPoint: 'main',
    },
    fragment: {
      module: g_device.createShaderModule({
        code: fragWGSL2,
      }),
      entryPoint: 'main',
      targets: [
        {
          format: presentationFormat,
        },
      ],
    },
    primitive: {
      topology: 'triangle-list',
    },
  });

4. 创建绑定组

创建一个 BindGroup 来引用纹理。这部分指定了之前创建的纹理视图和纹理采样器,其方式与普通纹理映射相同。

  const bindGroup = g_device.createBindGroup({
    layout: pipeline2.getBindGroupLayout(0),
    entries: [
      {
        binding: 0,
        resource: renderTargetTextureView,
      },
      {
        binding: 1,
        resource: sampler,
      },
    ],
  });

5. 创建命令

接下来,创建渲染命令。它分为两个pass,第一遍(将三角形绘制到纹理)和第二遍(将纹理显示在屏幕上)。

每个都必须创建一个单独的 CommandEncoder。此时,在GPURenderPassDescriptor的colorAttachments中,指定了每一个pass使用的渲染目标。在第一个pass中,指定了本次提前创建的纹理视图renderTargetTextureView;在第二个pass中,指定了context.getCurrentTexture().createView()创建的屏幕纹理视图。

  // first pass
const commandEncoder = g_device.createCommandEncoder();

  const renderPassDescriptor: GPURenderPassDescriptor = {
    colorAttachments: [
      {
        view: renderTargetTextureView,

        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();

// second pass
  const commandEncoder2 = g_device.createCommandEncoder();
  
  const renderPassDescriptor2: GPURenderPassDescriptor = {
    colorAttachments: [
      {
        view: context.getCurrentTexture().createView(),

        clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },

        loadOp: 'clear',

        storeOp: 'store',
      },
    ],
  };

  const passEncoder = commandEncoder2.beginRenderPass(renderPassDescriptor2);
  passEncoder.setPipeline(pipeline2);
  passEncoder.setBindGroup(0, bindGroup);
  passEncoder.draw(3, 1, 0, 0);
  passEncoder.end();
  
  g_device.queue.submit([commandEncoder.finish(), commandEncoder2.finish()]);

第二个Pass指定之前创建的 RenderPipeline 和 BindGroup。
最后,我们组合两个 CommandEncoder 并将它们传递给 g_device.queue.submit 函数。

总结

我们可以从屏幕上看到三角形被绘制到纹理上。

在WebGL中,需要生成一个FBO(Framebuffer对象)来设置渲染目标,并且FBO中的绑定操作也很复杂。WebGPU 上的纹理渲染给人的印象是编码变得更加直观。
WebGPU学习(7)---渲染到纹理_第2张图片

你可能感兴趣的:(webgpu,webgpu,图形学)