图形编程中的纹理,是一个很大的话题,涉及到的知识面非常多,有硬件的,也有软件的,有实时渲染技术,也有标准的实现等非常多可以讨论的。

受制于个人学识浅薄,本文只能浅表性地列举 WebGL 和 WebGPU 中它们创建、数据传递和着色器中大致的用法,格式差异,顺便捞一捞压缩纹理的资料。

1. WebGL 中的纹理

1.1. 创建二维纹理与设置采样参数

创建纹理对象 texture,并将其绑定:

const texture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, texture)

此时这个对象只是一个空的 WebGLTexture,还没有发生数据传递。

WebGL 没有采样器 API,纹理采样参数的设置是通过调用 gl.texParameteri() 方法完成的:

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)

采样参数是 gl.TEXTURE_WRAP_Sgl.TEXTURE_WRAP_Tgl.TEXTURE_MIN_FILTERgl.TEXTURE_MAG_FILTER,这四个采样参数的值分别是 gl.CLAMP_TO_EDGEgl.CLAMP_TO_EDGEgl.NEARESTgl.NEAREST,具体含义就不细说了,我认为这方面的资料还是蛮多的。

1.2. 纹理数据写入与拷贝

首先,是纹理数据的写入。

使用 gl.texImage2D() 方法将内存中的数据写入至纹理中,流向是 CPU → GPU

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)

这个函数有非常多种重载,可以自行查阅 MDN 或 WebGL 有关规范。

上述函数调用传递的 imageImage 类型的,也即 HTMLImageElement;其它的重载可以使用的数据来源还可以是:

  • ArrayBufferViewUint8ArrayUint16ArrayUint32ArrayFloat32Array
  • ImageData
  • HTMLImageElement/HTMLCanvasElement/HTMLVideoElement
  • ImageBitmap

不同数据来源有对应的数据写入方法。

其次,是纹理的拷贝。

WebGL 2.0 使用 gl.blitFramebuffer() 方法,以帧缓冲对象为媒介,拷贝附着在两类附件上的关联纹理对象。

下面为拷贝 renderableFramebuffer 的颜色附件的简单示例代码:

const renderableFramebuffer = gl.createFramebuffer();
const colorFramebuffer = gl.createFramebuffer(); // ... 一系列绑定和设置 ... gl.bindFramebuffer(gl.READ_FRAMEBUFFER, renderableFramebuffer);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, colorFramebuffer); // ... 执行绘制 ... gl.blitFramebuffer(
0, 0, FRAMEBUFFER_SIZE.x, FRAMEBUFFER_SIZE.y,
0, 0, FRAMEBUFFER_SIZE.x, FRAMEBUFFER_SIZE.y,
gl.COLOR_BUFFER_BIT, gl.NEAREST
);

WebGL 2.0 允许将 FBO 额外绑定到可读帧缓冲(gl.READ_FRAMEBUFFER)或绘制帧缓冲(gl.DRAW_FRAMEBUFFER),WebGL 1.0 只能绑定至单个帧缓冲 gl.FRAMEBUFFER.

WebGL 1.0 没那么便利,就只能自己封装比较麻烦一点的做法了,提供如下思路:

  • 把目标纹理附着到一个 FBO 上,利用一个 WebGLProgram 把源纹理通过着色器渲染进 FBO
  • 把源纹理附着到一个 FBO 上,利用 gl.copyTexImage2D()gl.copyTexSubImage2D() 方法拷贝到目标纹理
  • 把源纹理附着到一个 FBO 上或直接绘制到 canvas 上,使用 gl.readPixels() 读取渲染结果,然后使用 gl.texImage2D() 将像素数据写入目标纹理(这个方法看起来很蠢,虽然技术上行得通)

1.3. 着色器中的纹理

如何在片元着色器代码中对纹理进行采样,获取该顶点对应的纹理颜色呢?

很简单,获取顶点着色器发送过来的插值后的片元纹理坐标 v_texCoord,然后对纹理对象进行采样即可。

uniform sampler2D u_textureSampler;
varying vec2 v_texCoord; void main() {
gl_FragColor = texture2D(u_textureSampler, v_texCoord);
}

关于如何通过 uniform 传递纹理到着色器中,还请查阅我之前发过的 Uniform 一文。

1.4. 纹理对象 vs 渲染缓冲对象

很多国内外的文章有介绍这两个东西,它们通常出现在离屏渲染容器 - 帧缓冲对象的关联附件上。

感兴趣 FBO / RBO 主题的可以翻翻我不久之前的文章。

纹理与渲染缓冲,即 WebGLTextureWebGLRenderbuffer,其实最大的区别就是纹理允许再次通过 uniform 的形式传给下一个渲染通道的着色器,进行纹理采样。有资料说这两个是存在性能差异的,但是我认为那点差异还不如认真设计好架构。

  • 如果你使用 MRT(无论是通过扩展还是直接使用 WebGL 2.0)技术,建议优先选择渲染缓冲对象,但是其实用哪个都无所谓;
  • 如果你要使用 WebGL 2.0 的 MSAA,那你得用渲染缓冲;
  • 如果你要把 draw 的结果再次传递给下一个渲染通道,那么你得用纹理对象;
  • 对于读像素,用哪个都无所谓,看你用的是 WebGL 1.0 还是 WebGL 2.0,都有对应的方法。

1.5. 立方体六面纹理

这东西虽然是给立方体的六个面贴图用的“特殊”纹理,但是非常合适做环境贴图,对应的数据传递函数、着色器采样函数都略有不同。

// 注意第一个参数,既然有 6 面,就有六个值,这里是 X 轴正方向的面
gl.texImage2D(
gl.TEXTURE_CUBE_MAP_POSITIVE_X,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
imagePositiveX) // 为立方体纹理创建 Mipmap
gl.generateMipmap(gl.TEXTURE_CUBE_MAP) // 设置采样参数
gl.texParameteri(
gl.TEXTURE_CUBE_MAP,
gl.TEXTURE_MIN_FILTER,
gl.LINEAR_MIPMAP_LINEAR)

在着色器中:

// 顶点着色器
attribute vec4 a_position;
uniform mat4 u_vpMatrix;
varying vec3 v_normal; void main() {
gl_Position = u_vpMatrix * a_position;
// 因为位置是以几何中心为原点的,可以用顶点坐标作为法向量
v_normal = normalize(a_position.xyz);
} // 片元着色器
precision mediump float; // 从顶点着色器传入
varying vec3 v_normal; // 纹理
uniform samplerCube u_texture; void main() {
gl_FragColor = textureCube(u_texture, normalize(v_normal));
}

这方面资料其实也不少,网上搜索可以轻易找到。

1.6. WebGL 2.0 的变化

WebGL 2.0 增加了若干内容,资料可以在 WebGL2Fundamentals 找到,这里简单列举。

  • 在着色器中使用 textureSize() 函数获取纹理大小
  • 在着色器中使用 texelFetch() 直接获取指定坐标的纹素
  • 支持了更多纹理格式
  • 支持了 3D 纹理(而不是立方体六面纹理)
  • 支持纹理数组(每个元素都是一个单独的纹理)
  • 支持长宽大小是非 2 次幂的纹理
  • 支持若干压缩纹理格式
  • 支持深度纹理(WebGL 1.0 要调用扩展才能用)
  • 加入 WebGLSampler 对象的支持
  • ...

除此之外,GLSL 升级到 300 后,原来的 texture2D()textureCube() 纹理采样函数全部改为了 texture() 函数,详见文末参考资料的迁移文章。

1.7. Mipmapping 技术

裁剪空间里的顶点构成的形状,其实是近大远小的,这点没什么问题。对于远处的物体,透视投影变换完成后会比较小,这就没必要对这个“小”的部分使用“大”的部分一样清晰的纹理了。

Mipmap 能解决这个问题,幸运的是,WebGL 只需简单的方法调用就可以创建 Mipmap,无需操心太多。

gl.generateMipmap(gl.TEXTURE_2D)

在参考资料中,你可以在 《WebGL纹理详解之三:纹理尺寸与Mipmapping》一文中见到不错的解释,还可以看到 gl.texImage2D() 的第二个参数 level 的具体用法。

2. WebGPU 中的纹理

WebGPU 将纹理分成 GPUTextureGPUTextureView 两种对象。

2.1. GPUTexture 的创建

调用 device.createTexture() 即可创建一个纹理对象,你可以通过传参指定它的用途、格式、维度等属性。它扮演的更多时候是一个数据容器,也就是纹素的容器。

// 普通贴图
const texture = device.createTexture({
size: [512, 512, 1],
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING
| GPUTextureUsage.COPY_DST
| GPUTextureUsage.RENDER_ATTACHMENT,
}) // 深度纹理
const depthTexture = device.createTexture({
size: [800, 600],
format: 'depth24plus',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
}) // 从 canvas 中获取纹理
const gpuContext = canvas.getContext('webgpu')
const canvasTexture = gpuContext.getCurrentTexture()

上面介绍了三种创建纹理的方式,前两种类似,格式和用途略有不同;最后一个是来自 Canvas 的。

注意一点,有一些纹理格式并不是默认就支持的。如果需要特定格式,有可能还要在请求设备对象时,附上功能列表(requiredFeatures

2.2. 纹理数据写入与拷贝

知道创建纹理对象,还要知道如何往其中写入来自 JavaScript 运行时的图像资源。

首先,介绍纹理数据写入。

有两个手段可以向纹理对象写入数据:

  • 使用 ImageBitmap API(globalThis.createImageBitmap()
  • 使用解码后的 RGBA 数组

对于第一种,使用队列对象的 copyExternalImageToTexture() 方法,配合浏览器自带的 API,在队列时间轴上完成外部数据拷入纹理对象:

const diffuseTexture = device.createTexture({ /* ... */ })

/** 方法一 借助 HTMLImageElement 解码 **/
const img = document.createElement('img')
img.src = require('/assets/diffuse.png')
await img.decode()
const imageBitmap = await createImageBitmap(img)
/** 方法一 **/ /** 方法二 使用 Blob **/
const blob = await fetch(url).then((r) => r.blob())
const imageBitmap = await createImageBitmap(blob)
/** 方法二 **/ device.queue.copyExternalImageToTexture(
{ source: imageBitmap },
{ texture: diffuseTexture },
[imageBitmap.width, imageBitmap.height]
)

上述例子提供了两种思路,第一种借助浏览器的 img 元素,也即 Image 来完成图像的网络请求、解码;第二种借助 Blob API;随后,使用 Image(HTMLImageElement)/Blob 对象创建一个 ImageBitmap,并进入队列中完成数据拷贝。

对于第二种,使用队列对象的 writeTexture() 方法,在队列时间轴上完成外部数据拷入纹理对象:

const imgRGBAUint8Array = await fetchAndParseImageToRGBATypedArray('/assets/diffuse.png')
const arrayBuffer = imgRGBAUint8Array.buffer device.queue.writeTexture(
{
bytePerRow: 4 * 512, // 每行多少字节
rowsPerImage: 512 // 这个图像有多少行
},
arrayBuffer,
{ texture: diffuseTexture },
[512, 512, 1]
)

第二种方法相对来说比较消耗性能,因为需要浏览器 API(例如借助 canvas 绘图再取数据)或其它手段(如 wasm 等)解码图像二进制至 RGBA 数组,不太适合每帧操作。

其次,介绍纹理拷贝。

与 WebGL 需要使用 FBO 或重新渲染不同,WebGPU 原生就在指令编码器上提供了纹理复制操作有关的 API:使用 commandEncoder.copyTextureToTexture() 可以完成纹理之间的拷贝,使用 commandEncoder.copyBufferToTexture()commandEncoder.copyTextureToBuffer() 可以在缓冲对象和纹理对象之间的拷贝(以便读取纹素数据)。

以纹理间的拷贝为例:

commandEncoder.copyTextureToTexture({
texture: mipmapTexture,
mipLevel: 4,
}, {
texture: destTexture,
mipLevel: 5,
}, [512, 512, 1])

这个例子将 Mipmap 纹理的第 4 级拷贝至目标纹理对象的第 5 级,纹理的大小是 512 × 512,需要注意 mipmapTexturedestTextureusage,复制源需要有 GPUTextureUsage.COPY_SRC,复制目标要有 GPUTextureUsage.COPY_DST.

既然发生在指令编码器上,那就意味着操作纹理时,与普通的渲染通道、计算通道是平级的 —— 换句话说,拷贝纹理的行为,必须在渲染通道之前或之后进行。

2.3. 纹理视图

因官方文档在我写这篇文章前,都没有给出纹理视图对象的描述,所以下面的描述是我根据 WebGPU 中关于纹理方面的 API 猜测的。

当 CPU 需要使用纹理时,譬如进行纹理数据的写入,或者纹理对象之间的拷贝,会直接在队列上进行,而且传参给的就是 GPUTexture 本身;而 GPU 需要使用纹理时,例如资源绑定组绑定一个纹理,或者渲染通道的附件需要使用容器时,通常传参给的是 GPUTextureView;所以,我猜测:

  • 纹理对象适用于 CPU 侧操作
  • 纹理视图对象为 GPU 提供操作真正纹理数据的一个窗口

创建纹理视图其实很简单,它通过调用纹理对象本身的 createView() 方法创建:

const view = texture.createView()

// 在渲染通道的颜色附件中
const renderPassDescriptor = {
colorAttachments: [
{
view: canvasTexture.createView(),
// ...
}
]
}

纹理视图对象是可以传递参数对象的,类型是 GPUTextureViewDescriptor,当然这个参数对象是可选的。这个参数对象可以更具体描述纹理视图。

譬如,立方体纹理创建视图时,需要明确指定其维度(dimension)参数等参数:

const cubeTextureView = cubeTexture.createView({
dimension: 'cube',
arrayLayerCount: 6,
})

2.4. 着色器中的纹理与采样器

与 WebGL 使用的阉割版 GLSL 相比,WGSL 提供的类型就多多了。

WebGL 1.0 中的采样参数与 WebGL 2.0 姗姗来迟的 WebGLSampler 类型,在 WebGPU 和 WGSL 中统一为具体的变量类型,即 WebGPU 对应 GPUSampler,WGSL 对应 samplersampler_comparision 类型。

WGSL 中的纹理类型有十几种,纹理类型与纹理视图的 dimension 参数是紧密相关的,参考 WebGPU Spec - TextureView Creation

而纹理相关的函数也跟随着增多了许多,且各有用途,有最常规的纹理采样函数 textureSample,读取单个纹素的 textureLoad 函数,获取纹理尺寸的 textureDimensions(等价于 WebGL 2.0 的 textureSize),向存储型纹理写纹素的 textureStore 等,每个函数又有若干种重载。

最基本的用法,使用二维 f32 纹理对象、采样器、纹理坐标进行采样:

@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>; @stage(fragment)
fn main(@location(0) fragUV: vec2<f32>) -> @location(0) vec4<f32> {
return textureSample(myTexture, mySampler, fragUV);
}

2.5. WebGPU 中的 Mipmapping

鉴于纹理技术本身的复杂性,官方在 GitHub issue 386 中关于自动生成 Mipmap 的 API 有激烈的讨论,目前倾向于不实现,把 Mipmap 的生成实现交给社区。

WebGPU 保留了 Mipmap 的支持,但是没有像 WebGL 一样提供简便的 gl.generateMipmap(gl.TEXTURE_2D) 调用方法一键生成,需要自己对纹理的每一个层生成。

幸运的是,WebGPU 社区的 Toji 大佬编写了一个工具来生成纹理的 Mipmap:web-texture-tool/src/webgpu-mipmap-generator.js,原理就是开辟一个新的指令编码器,使用一条特定的渲染管线离屏计算每一级 mipmap,最终写入一个纹理对象并返回。若源纹理具备渲染附件的用途(GPUTextureUsage.RENDER_ATTACHMENT),那么就在源纹理上生成,否则会使用 commandEncoder.copyTextureToTexture() 方法把工具类内部创建的临时 mipmap 纹理对象拷贝到源纹理对象。

目前只能对 "2d" 类型的纹理起作用,这个类的简单用法如下:

import { WebGPUMipmapGenerator } from 'web-texture-tool/webgpu-mipmap-generator.js'

/* -- 常规创建纹理 -- */
const textureDescriptor = { /**/ }
const srcTexture = device.createTexture(textureDescriptor) /* -- 为纹理创建 mipmap -- */
const mipmapGenerator = new WebGPUMipmapGenerator(device)
mipmapGenerator.generateMipmap(srcTexture, textureDescriptor) // ...

generateMipmap() 方法执行后,将在 2d 纹理的每个 layer 创建完成每一层 Mipmap,顺带一提,这个工具并未完全稳定,请考虑各种风险。

注意一点,这个 textureDescriptormipLevelCount 是有一个 算法 的,它必须小于等于根据纹理维度、纹理尺寸计算的 最大限制值。这里纹理维度是 2d 类型,最大尺寸是 64,那么容易算得最大 mipLevel 是 Math.floor(Math.log2(64)) + 1 = 7.

const textureDescriptor = {
// ...
mipLevelCount: 7, // 创建纹理时,允许人为指定 mipmap 有多少级,但是不超最大限制
size: {
width: 64,
height: 64,
depthOrArrayLayers: 1
},
dimension: "2d"
}

扩展阅读:ThreeJS 关于 WebGPU 这项议程,参考了 Toji 的工具,集成到 WebGPUTextureUtils 类,有关讨论见 ThreeJS pull 20284 WebGPUTextures: Add support for mipmap computation.

3. 纹理压缩编码算法

涉及到压缩纹理格式我更是只能“纸上谈兵”,这一段仅作为个人知识浅表性的记录,道阻且长...

这一小节其实与 WebGL、WebGPU 的接口并无太大关系,纹理压缩算法,或者说压缩纹理格式,是另外的一门技术,WebGL 和 WebGPU 在底层实现上做了支持。

简单的说,压缩纹理格式是一种“时间+空间换空间”的产物,需要提前生成,常见的封装文件格式有 ktx2 等(就好比 h264/5.mp4)。它有效地节约了 GPU 显存,并且解压速度比传统的 Web 图像格式 jpgpng 更快,它本身也比 jpg/png 的文件体积要小一些。

不过很遗憾的是,诸多压缩编码算法在各个软硬件厂商的实现都不太一样,没法像 jpg/png 一样广泛、普遍使用。

为了兼容性,通常会针对不同平台生成不同的压缩纹理备用,也就是所谓的“时间+空间换解压时间+显存空间”。

WebGL 1.0 只能使用 2D 纹理,WebGL 2.0 支持使用 3D 纹理,而且对压缩纹理的使用,是需要借助扩展项来完成的。例如:

const ext = (
gl.getExtension('WEBGL_compressed_texture_s3tc') ||
gl.getExtension('MOZ_WEBGL_compressed_texture_s3tc') ||
gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc')
) const texture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, texture)
gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ext.COMPRESSED_RGBA_S3TC_DXT5_EXT, 512, 512, 0, textureData)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)

这个示例代码展示了在 WebGL 1.0 通过 compressedTexImage2D() 方法使用了一个 S3TC_DXT5 压缩编码的纹理数据 textureData.

具体的 WebGL 1/2 压缩扩展和用法参考 MDN - compressedTexImage[23]D()

对于 WebGPU,它支持三类压缩格式:

  • texture-compression-bc
  • texture-compression-etc2
  • texture-compression-astc

请求设备对象时传入 requiredFeatures 即可请求所需压缩纹理格式:

// 以 astc 格式为例 -- 需要在适配器上判断是否支持此格式

const requiredFeatures = []
if (gpuAdapter.features.has('texture-compression-astc')) {
requiredFeatures.push('texture-compression-astc')
}
const device = await adapter.requestDevice({
requiredFeatures
})

当适配器支持时即可请求。这样,astc 族压缩纹理格式就全部可用了:

const compressedTextureASTC = device.createTexture({
// ...
format: "astc-10x6-unorm-srgb"
})

三大类型的压缩纹理格式支持列表参考 WebGPU Spec - Feature Index: 24.4, 24.5, 24.6

幸运的是,Toji 的库 toji/web-texture-tool 也为纹理的加载写了两种 Loader,用于 WebGL 和 WebGPU 中纹理数据的生成,支持压缩格式。

纹理压缩算法(格式)简单记忆规则:

  • ETC1/2 - Android
  • DXT/S3TC - Windows
  • PVRTC - Apple
  • ASTC - Will Be The Future

详细的资料在文末的参考资料里了。

4. 总结

关于 Mipmap、级联纹理、压缩格式等进阶知识,我觉得已经超出了这两个 API 比对的范围,况且个人理解尚不深,就不关公面前舞大刀了。

这篇与上篇相隔时间较长,我在学习的过程中补充了很多欠缺的知识,为了严谨和准确性也查阅了不少的例子、啃了不少的源码。

简而言之,WebGPU 把 WebGL 1/2 两代的纹理接口进行了科学统一,并且出厂自带压缩纹理格式的支持(当然,还是看具体平台的,需要按需选取)。

其中最让我感兴趣的就是 WebGPU 对纹理的二级细化,提供 GPUTextureGPUTextureView 两级 API,发文时还未见到官方规范解释这两个 API,猜测前者专注于数据的 IO,后者则提供纹理数据的一层视图(根据参数具象化纹理数据的某一方面)。

很遗憾,发文时我还没深入了解过存储型纹理,以后介绍 GPGPU 时再说吧。

参考资料

WebGL 与 WebGPU比对[6] - 纹理的更多相关文章

  1. WebGL 与 WebGPU 比对[1] 前奏

    目录 1 为什么是 WebGPU 而不是 WebGL 3.0 显卡驱动 图形 API 的简单年表 WebGL 能运行在各个浏览器的原因 WebGPU 的名称由来 2 与 WebGL 比较编码风格 Op ...

  2. WebGL 与 WebGPU比对[5] - 渲染计算的过程

    目录 1. WebGL 1.1. 使用 WebGLProgram 表示一个计算过程 1.2. WebGL 没有通道 API 2. WebGPU 2.1. 使用 Pipeline 组装管线中各个阶段 2 ...

  3. WebGL 与 WebGPU比对[4] - Uniform

    目录 1. WebGL 1.0 Uniform 1.1. 用 WebGLUniformLocation 寻址 1.2. 矩阵赋值用 uniformMatrix[234]fv 1.3. 标量与向量用 u ...

  4. WebGL编程指南案例解析之纹理叠加

    var vShader = ` attribute vec4 a_Position; attribute vec2 a_TexCoord; varying vec2 v_TexCoord; void ...

  5. WebGL 颜色与纹理

    1.纹理坐标 纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹理颜色.WebGL系统中的纹理坐标系统是二维的,如图所示.为了将纹理坐标和广泛使用的x.y坐标区分开来,WebGL使用s和t ...

  6. WebGPU | 相关知识概述

    首先看下WebGPU的目标: 同时支持实时屏幕渲染和离屏渲染. 使通用计算能够在 GPU 上高效执行. 支持针对各种原生 GPU API 的实现:Microsoft 的 D3D12.Apple 的 M ...

  7. WebGPU 中消失的 VAO

    1 VAO 是 OpenGL 技术中提出来的 参考: 外链 其中有一段文字记录了 VAO 是什么: A Vertex Array Object (VAO) is an object which con ...

  8. WebGPU 中消失的 FBO 和 RBO

    目录 1 WebGL 中的 FBO 与 RBO 1.1 帧缓冲对象(FramebufferObject) 1.2 颜色附件与深度模板附件的真正载体 1.3 FBO/RBO/WebGLTexture 相 ...

  9. WebGPU 计算管线、计算着色器(通用计算)入门案例:2D 物理模拟

    目录 1. WebGL 2. WebGPU 2.1. 适配器(Adapter)和设备(Device) 2.2. 着色器(Shaders) 2.3. 管线(Pipeline) 2.4. 并行(Paral ...

随机推荐

  1. 微信小程序--给数组的每个对象添加动画(数据驱动)

    思路:用数据驱动事件,用数组的方式去对循环数组的每个对象进行操作 js代码: data:{ selectCategory: [{ name: '生产模式', content: [{ txt: '原厂' ...

  2. JVM学习十一 - (复习)性能调优

    在高性能硬件上部署程序,目前主要有两种方式: 通过 64 位 JDK 来使用大内存: 使用若干个 32 位虚拟机建立逻辑集群来利用硬件资源. 使用 64 位 JDK 管理大内存 堆内存变大后,虽然垃圾 ...

  3. Java使用DOM方式读写XML

    一.DOM读取 import ***; public class ReadXML { public static void main(String[] args) { try { //DOM Docu ...

  4. IOS开发之----常用函数和常数--秀清

    介绍一下Objective-c常用的函数,常数变量 算术函数 [算术函数] 函数名 说明 int rand() 随机数生成.(例)srand(time(nil)); //随机数初期化int val = ...

  5. k8s之Pod基础概念

    1. 资源限制 Pod是kubernetes中最小的资源管理组件,Pod也是最小化运行容器化应用的资源对象.一个Pod代表着集群中运行的一个进程.kubernetes中其他大多数组件都是围绕着Pod来 ...

  6. 06 前端之Bootstrap框架

    目录 前端之Bootstrap框架 一.简介 二.引入方式 本地引入(最完整的) CDN引入 三.布局容器 四.栅格系统 五.列偏移 六.表格与表单 6.1表格 6.2表单form 七.按钮 预定义样 ...

  7. Error from server error dialing backend remote error tls internal error

    # kubectl exec -it mysql-master-8cfb64ff9-ct4dx -n prophet -- /bin/bash Error from server: error dia ...

  8. PHP7.x环境下安装redis扩展

    注:以下介绍的安装方式为PHP的安装路径为/usr/local/php,如果你的服务器上PHP的安装目录不一致请按实际情况处理. 首先下载PHP7的redis扩展 wget https://githu ...

  9. demo_2_27

    #define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>#include <string.h> int count_bit_one ...

  10. CobaltStrike逆向学习系列(8):Beacon 结果回传流程分析

    这是[信安成长计划]的第 8 篇文章 关注微信公众号[信安成长计划] 0x00 目录 0x01 Beacon 接收与处理 0x02 结果回传 Beacon 在接受完命令并执行后,会将数据加密回传给 T ...