接上文立方体,我们介绍了在WebGPU中操作三维物体的方法,学习到了除了在创建Buffer时可以填充数据以外,还可以利用device.queue.writeBuffer 这个API进行数据的写入。今天,让我们继续来操作这个立方体,今天要学习的内容是给立方体贴图。
创建一个图片对象
用 await 代表这个操作必须在 async 函数中,或者在 html 中提前做好 img 标签并加载纹理贴图
const textureUrl = 'texture.webp';
const res = await fetch(textureUrl)
const img = await res.blob()
// const img = document.createElement('img')
// img.src = textureUrl
// await img.decode()
const imageBitmap = await createImageBitmap(img);
创建一个纹理对象
- dimension: 纹理可以是1d、2d、3d的,这里我们使用2d纹理就好。
- size: 与dimension所对应,表示纹理的大小
- format: 'rgba8unorm',这里对format的格式说明一下:r, g, b, a: 表示red, green, blue, alpha。unorm: 表示unsigned normalized,即表示是无符号的,归一化为 0~1范围的值。
- usage: 这里可以认为是固定搭配,至少需要这三种usage。
const cubeTexture = device.createTexture({
dimension: '2d',
size: [imageBitmap.width, imageBitmap.height],
format: 'rgba8unorm',
usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
});
拷贝外部图像数据到纹理对象
- source: GPUImageCopyExternalImage,其是一个对象,需要具有以下属性:只能是 ImageBitmap | HTMLCanvasElement | OffscreenCanvas 对象。
- destination: GPUImageCopyTextureTagged,其中需要具有以下属性:必须具有texture属性,为 GPUTexture
- copySize: 表示复制的纹理区域大小。
device.queue.copyExternalImageToTexture(
{ source: imageBitmap },
{ texture: cubeTexture },
[imageBitmap.width, imageBitmap.height]
);
创建采样器--纹理
现在衣服制造完毕,并且也把快递发送给了GPU,但是这件衣服稍微有一点点的花里胡哨,可能GPU拿到这件新衣不知道应该怎样去穿,所以现在我们需要给GPU送去一封说明书。 而在WebGPU中,说明书却是单独发送的,这有一个好处,这提高了说明书的复用性,如果另一件相似的衣服我们传给GPU,那么它可以继续复用之前的说明书。
const sampler = device.createSampler({
magFilter: 'linear',
minFilter: 'linear',
});
发送(采样器、纹理视图)到渲染通道
来到熟悉的环节,创建 BindGroup 来往渲染通道中发送数据。 要向渲染通道传递纹理和采样器,必须创建一个 “Pipeline布局” 对象。这个布局对象要对纹理对象、采样器对象进行绑定。在 WebGPU 中,将诸如 uniform变量、采样器、纹理对象 等资源统一打组,这个组叫 GPUBindGroup。
const uniformBindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{
binding: 0,
resource: {
buffer: uniformBuffer,
},
},
{
binding: 1,
resource: sampler,
},
{
binding: 2,
resource: cubeTexture.createView(),
},
],
});
GPU绘制
发送完成后,现在来到GPU这边,GPU收到货之后,Shader开始运作了。
@binding(1) @group(0) var mySampler: sampler;
@binding(2) @group(0) var myTexture: texture_2d<f32>;
@fragment
fn main(@location(0) fragUV: vec2<f32>,@location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
return textureSample(myTexture, mySampler, fragUV) * fragPosition;
}
sprites 贴图
// initPipeline 函数 /////////////////////////
//分割图片
const uvOffset = new Float32Array([0, 0, 1 / 3, 1 / 2]);
const uvBuffer = device.createBuffer({
label: 'GPUBuffer store UV offset',
size: 4 * 4, // 4 x uint32
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(uvBuffer, 0, uvOffset);
....
// createBindGroup 修改
....
{
binding: 2,
resource: cubeTexture.createView(),
},
{
binding: 3,
resource: {
buffer: uvBuffer
}
}
.....
return { ..., uvBuffer, uvOffset }
// frame 函数 //////////////////////////////////
let count = 0;
let uvOffset = pipelineObj.uvOffset;
// start loop
function frame() {
count++;
if (count % 30 === 0) {
uvOffset[0] = uvOffset[0] >= 2 / 3 ? 0 : uvOffset[0] + 1 / 3
if (count % 90 === 0)
uvOffset[1] = uvOffset[1] >= 1 / 2 ? 0 : uvOffset[1] + 1 / 2
device.queue.writeBuffer(pipelineObj.uvBuffer, 0, uvOffset)
}
...
// 片源着色器 ///////////////////////////////////////////////////////
@binding(3) @group(0) var<uniform> uvOffset : vec4<f32>;
// main 修改
var uv = fragUV * vec2<f32>(uvOffset[2], uvOffset[3]) + vec2<f32>(uvOffset[0], uvOffset[1]);
return textureSample(myTexture, mySampler, uv) * fragPosition;
canvas 贴图
const canvas2 = document.querySelector('canvas#canvas');
device.queue.copyExternalImageToTexture(
{ source: canvas2 },
{ texture: cubeTexture },
[canvas2.width, canvas2.height]
)
video 贴图
// run 函数 ///////////////////////////
const video = document.createElement('video');
video.loop = true
video.autoplay = true
video.muted = true
video.src = videoUrl
await video.play()
const sampler = device.createSampler({
magFilter: 'linear',
minFilter: 'linear',
});
...
// frame 函数 ///////////////////////////
const videoGroup = device.createBindGroup({
layout: pipelineObj.pipeline.getBindGroupLayout(1),
entries: [
{
binding: 0,
resource: sampler
},
{
binding: 1,
resource: device.importExternalTexture({
source: video
})
}
]
})
draw(..., videoGroup)
//draw 函数 ////////////////////////////
passEncoder.setBindGroup(1, videoGroup)
// 片源着色器 ///////////////////////////////////////////////////////
@group(1) @binding(0) var Sampler: sampler;
@group(1) @binding(1) var Texture: texture_external;
@fragment
fn main(@location(0) fragUV: vec2<f32>,
@location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
return textureSampleBaseClampToEdge(Texture, Sampler, fragUV) * fragPosition;
}