上一篇文章里的立方体由于没有开启深度测试而出现奇怪的样子,本文将开启深度测试并使用纹理来进行绘制。
1.开启深度测试
在 WebGL 中,我们可以通过gl.enable(gl.DEPTH_TEST)
命令来简单地启用深度测试。但是,由于 WebGPU 是低级 API,因此还需要更多设置。
首先,需要对 RenderPipeline 进行一个depthStencil
设置。
// create a render pipelineconst pipeline = g_device.createRenderPipeline({layout: 'auto',vertex: {module: g_device.createShaderModule({code: vertWGSL,}),entryPoint: 'main',buffers: [...],},fragment: {module: g_device.createShaderModule({code: fragWGSL,}),entryPoint: 'main',targets: [{format: presentationFormat,},],},primitive: {topology: 'triangle-list',},depthStencil: { // <---- 更新的内容depthWriteEnabled: true,depthCompare: 'less',format: 'depth24plus',},});
depthWriteEnabled
指定是否写入深度缓冲区。
depthCompare
指定如何比较深度测试。
format
指定深度缓冲区格式。
使用 WebGPU 的时候,即使是深度测试也需要明确创建必要的深度缓冲区,并且它必须与画布的分辨率大小相同。
// create a depth textureconst depthTexture = g_device.createTexture({size: [canvas.width, canvas.height],format: 'depth24plus',usage: GPUTextureUsage.RENDER_ATTACHMENT,});
然后将创建的depthTexture
设置为 RenderPassDescriptor 的depthStencilAttachment
属性。
const renderPassDescriptor: GPURenderPassDescriptor = {colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: 'clear',storeOp: 'store',},],depthStencilAttachment: { // <---- 这次的补充view: depthTexture.createView(),depthClearValue: 1.0,depthLoadOp: 'clear',depthStoreOp: 'store',},};
对于depthLoadOp
,指定clear
以清除深度缓冲区作为初始操作。
对于depthClearValue
,指定清除值。
对于depthStoreOp
,指定store
以保持缓冲区不变。
经过上面的修改,现在深度测试已启用,能够正确显示图形。
2.使用纹理
这一次,让我们为立方体贴上纹理。
2.1创建纹理
let texture: GPUTexture;{const img = document.createElement('img');img.crossOrigin = 'Anonymous';img.src = 'https://storage.googleapis.com/emadurandal-3d-public.appspot.com/images/pexels-james-wheeler-1552212.jpg';await img.decode();const imageBitmap = await createImageBitmap(img);texture = g_device.createTexture({size: [imageBitmap.width, imageBitmap.height, 1],format: 'rgba8unorm',usage:GPUTextureUsage.TEXTURE_BINDING |GPUTextureUsage.COPY_DST |GPUTextureUsage.RENDER_ATTACHMENT,});g_device.queue.copyExternalImageToTexture({ source: imageBitmap },{ texture: texture },[imageBitmap.width, imageBitmap.height]);}
WebGPU 纹理是使用逻辑设备g_device
的createTexture
函数创建的。由于它是Jpeg图片,因此格式指定为rgba8unorm
。
对于usage
,要指定TEXTURE_BINDING
(绑定为纹理)、COPY_DST
(将像素数据复制到 GPUTexture)或RENDER_ATTACHMENT
(用作渲染通道的颜色附件)。
ImageBitmap 使用createImageBitmap
函数从 HTMLImageElement 创建,像素数据使用copyExternalImageToTexture
方法从 ImageBitmap 复制到 GPUTexture。可能大家对createImageBitmap
函数看起来很陌生,它其实是一个从 HTMLImageElement 创建 ImageBitmap 的异步函数,并且已经受到当今现代浏览器的支持。
2.2.创建采样器
WebGL(特别是WebGL1)在创建纹理时就指定缩放纹理时的过滤方法等采样信息。但是在 WebGPU 中,采样信息与纹理是分开的,采样器必须单独创建。
const sampler = g_device.createSampler({magFilter: 'linear',minFilter: 'linear',});
2.3.注册GPUBindGroup
在 GPUBindGroup 中注册纹理和采样器。
const uniformBindGroup = g_device.createBindGroup({layout: pipeline.getBindGroupLayout(0),entries: [{binding: 0,resource: {buffer: uniformBuffer,},},{ // <---- 补充的内容binding: 1,resource: texture.createView(),},{ // <---- 补充的内容binding: 2,resource: sampler,},],});
2.4.在片段着色器中使用纹理
要在片段着色器中使用纹理,我们要使用textureSample
函数。与 GLSL 的纹理函数不同,textureSample
函数还以采样器作为参数。
@group(0) @binding(1) var myTexture: texture_2d<f32>;
@group(0) @binding(2) var mySampler: sampler;@fragment
fn main(@location(0) fragUV: vec2<f32>,
) -> @location(0) vec4<f32> {return textureSample(myTexture, mySampler, fragUV);
}
下面是相应的顶点着色器。
struct Uniforms {projectionMatrix : mat4x4<f32>,viewMatrix : mat4x4<f32>,worldMatrix : mat4x4<f32>,
}
@binding(0) @group(0) var<uniform> uniforms : Uniforms;struct VertexOutput {@builtin(position) Position : vec4<f32>,@location(0) fragUV : vec2<f32>,
}@vertex
fn main(@location(0) position: vec4<f32>,@location(1) color: vec4<f32>,@location(2) uv: vec2<f32>
) -> VertexOutput {var output : VertexOutput;output.Position = uniforms.projectionMatrix * uniforms.viewMatrix * uniforms.worldMatrix * position;output.fragUV = uv;return output;
总结
我们现在采用了一些设置来开启深度测试,也将纹理应用于立方体。