96SEO 2026-08-13 18:46 1
我多次尝试入门 WebGL / WebGPU。结果都是“学而不入”,白白浪费时间。说起来,直到改用 AI 学习——有问必答,还能逐行解释——反而更快了。

卡住我的痛点:
这篇文章假设一个最简单的游戏画面来演示 WebGPU 的用法:人物站在草地上。手里的武器轻轻摆动,剑身外环绕着流动的法术光环,头顶无数粒子往天上飞。人物不做运动是为了把逻辑减到最少,同时又能看到显卡渲染的完整流程。
全程需牢记的三个概念:
下面按“初始化 → 建数据 → 绑参数 → 建管线 → 渲染一帧”的顺序,用 raw/ 源码走一遍。
if {
// 浏览器不支持
return;}
const adapter = await navigator.gpu.requestAdapter;const device = await adapter.requestDevice;const context = canvas.getContext;话说回来,const format = navigator.gpu.getPreferredCanvasFormat;按理说,context.configure({
device。format,alphaMode: "opaque"
});
: 浏览器暴露 WebGPU 的入口,不存在说明浏览器不支持。: 对应一块物理 GPU,回答“这块显卡支持什么”。: 逻辑设备,所有 ///是默认提交队列。: 画布上下文。: 画布颜色纹理的像素格式,常见 "bgra8unorm" 或 "rgba8unorm"。
让浏览器创建一条与画布尺寸匹配的交换链,即向底层图形 API申请同尺寸的颜色纹理放在显存里。
function resize {
const dpr = Math.min;const w = Math.floor;const h = Math.floor;老实说,canvas.width = w;canvas.height = h;context.configure;return { w,h };}
function recreateDepth {
if depthTexture.destroy;depthTexture = device.createTexture({
说到size,format: DEPTH_FORMAT。// "depth24plus"
usage的观点是,GPUTextureUsage.RENDER_ATTACHMENT,});depthView = depthTexture.createView;}
: CSS 尺寸。: 实际物理像素,需要乘以 dpr.If you ignore resize handling:
The depth texture records “每个像素当前最近的表面离相机多远”,用于遮挡。它是显存里的纹理,使用 得到的 供渲染管线使用——后续章节会细讲。
The scene’s grass,character。weapon and aura are generated by as CPU‑side arrays . To render m we first copy m into GPU memory:
const vertexBuffer = device.createBuffer({
从size来看,mesh.vertices.byteLength,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,});device.queue.writeBuffer;const indexBuffer = device.createBuffer({
size的观点是,mesh.indices.byteLength。usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,});device.queue.writeBuffer;
Main.js calls this four times to upload four meshes .
A vertex buffer is just a byte stream;GPU does not know which bytes represent position vs normal vs UV. Two pieces of information are needed:
// 每个顶点占用字节数 // 偏移: // pos.x pos.y pos.z nor.x nor.y nor.z uv.x uv.y // |------12B------|------12B------|---8B---|
Each vertex refore occupies 20 bytes . The interleaved layout is cache‑friendly for GPU.
js
export const VERTEX_STRIDE = 5 * Float32Array.BYTES_PER_ELEMENT;// 20
export const VERTEX_LAYOUT = {
arrayStride: VERTEX_STRIDE。attributes:,};
wgsl
@vertex
fn vs_main(
@location position : vec3f。@location normal : vec3f,@location uv : vec2f,) -> VSOut {…说起来,}
The numbers after @location must match shaderLocation values in VERTEX_LAYOUT. If y don’t line up pipeline creation will fail—this is where many beginners hit a wall.
| 枚举 | 用途 | 本 demo 使用情况 | |||||
|---|---|---|---|---|---|---|---|
| `GPUBufferUsage.VERTEX` | 顶点缓冲 | `GPUBufferUsage.INDEX` | 索引缓冲 | `GPUBufferUsage.UNIFORM` | uniform 常量 | `GPUBufferUsage.STORAGE` | storage 缓冲 | `GPUBufferUsage.COPY_DST` | 允许 writeBuffer/copy 写入 | `GPUBufferUsage.COPY_SRC` | 允许拷出 | `GPUBufferUsage.MAP_READ|WRITE` | CPU 映射读写 |
writeBuffer:真实写入显存
js
device.queue.writeBuffer;The call copies a `Float32Array` from ordinary memory into a GPU‑visible buffer that carries `COPY_DST` flag. The copy is queued on `device.queue`;because rendering commands share same queue y will see updated contents when executed.
uniform buffer:为什么每个物体一个?js
const UNIFORM_SIZE = 256;// 示例大小,留有对齐余量
const groundUB = createUniformBuffer;怎么说呢,const charUB = createUniformBuffer;const weaponUB = createUniformBuffer;Uniforms hold per‑frame constants such as view‑proj matrix、model matrix、时间或颜色。每个物体都有独立的 model 矩阵。
所以 **每个物体一个 uniform buffer** 更直观,也避免了多对象间的数据竞争。 |
| # 使用标志 | 含义 | # 示例场景 | |||||
|---|---|---|---|---|---|---|---|
| `TEXTURE_BINDING` | 供 shader采样 | `RENDER_ATTACHMENT` | 作为颜色/深度附件被渲染 | `STORAGE_BINDING` | compute read/write | `COPY_DST` | 允许写入 | `COPY_SRC` | 允许拷出 | `RENDER_ATTACHMENT` | 深度缓冲专用 |
四、创建 BindGroup:把资源“插”给 shader
BindGroupLayout 与 BindGroup
Shader 中使用
至于layout。"auto" 与 getBindGroupLayoutjs // pipelines/opaque.js – 创建管线时使用自动布局: const opaquePipeline = device.createRenderPipeline({ 再看layout,"auto",vertex:{…},fragment:{…},primitive:{…},depthStencil:{…},}), // main.js – 创建对应 BindGroup: const groundBG = device.createBindGroup({ 至于layout。opaquePipeline.getBindGroupLayout,entries:,});
Setting
entries:一个组里可以有多个槽
多个组则通过不同的
为什么每个物体一个 BindGroup?因为每帧每个物体都有独立的 uniform 内容。为每个对象创建自己的 uniform buffer + BindGroup即可在渲染时切换绑定,从而让 shader “看到”对应对象的数据。BindGroup 本身非常轻量,仅保存对已上传显存资源的引用。
createRenderPipeline 各属性js const module = device.createShaderModule; const opaquePipeline = device.createRenderPipeline({ layout的观点是,"auto",// 自动生成 BindGroupLayout vertex:{ module,entryPoint:"vsmain",buffers:,// 顶点布局 },fragment:{ module。entryPoint:"fs_main",targets:,// 输出颜色格式 },primitive:{ topology:"triangle-list",// 三角形列表 cullMode:"back",// 背面剔除 },depthStencil:{ 再看format,"depth24plus",depthWriteEnabled:true,depthCompare:"less",}}); 再看属性说明,
Shader 模块仅负责编译 WGSL;其实,同一模块可同时包含 VS 和 FS。通过不同
VERTEX_LAYOUT 映射
WGSL 必须严格按照此顺序声明输入:
Thus arrayStride tells how many bytes each vertex occupies;offset tells where each attribute lives inside that stride;shaderLocation links directly to WGSL’s
一张表看清三层对应关系
保持这些对应关系一致,是防止花屏或验证错误的关键。
每帧只更新 uniform,不重传顶点js function frame{ computeMatrices;// 更新 viewProj & model 矩阵 fillOpaqueUniform;// writeBuffers → uniform buffers fillCharUniform;fillWeaponUniform; renderFrame;// 创建并提交命令清单 } requestAnimationFrame; 顶点和索引缓冲自首次上传后保持不变。仅有矩阵和时间等小数据会每帧更新——正是序言中强调的概念:“每帧更新 uniform,接下来创建命令清单”。
命令清单只是“记菜单”js const encoder = device.createCommandEncoder; /* Compute Pass – 粒子模拟 */ const computePass = encoder.beginComputePass;computePass.setPipeline;computePass.setBindGroup;computePass.dispatchWorkgroups);computePass.end; /* Render Pass – 绘制 */ const colorView = context.getCurrentTexture.createView;const renderPass = encoder.beginRenderPass({ colorAttachments:,depthStencilAttachment:{ view : depthView。depthClearValue :1.0,depthLoadOp :"clear",depthStoreOp :"store" } }); /* Opaque batch */ renderPass.setPipeline;老实说,renderPass.setBindGroup;其实,renderPass.setVertexBuffer;不过,renderPass.setIndexBuffer;renderPass.drawIndexed; /* …同理绘制角色和武器 ,*/ /* Aura batch / renderPass.setPipeline;/ ,bind groups & draws …*/ /* Particles batch */ renderPass.setPipeline;renderPass.draw; renderPass.end; device.queue.submit]); All calls merely write commands into ordinary memory. No pixel is processed yet—recording is extremely fast. 从类比来看, ⚙️ createCommandEncoder → 打开一本空白菜单 🖊️ setBindGroup/drawIndexed → 在菜单上写“来一份草地、一份角色” 🚚 submit → 把整张单子交给厨房烹饪。建议记住这个比喻,以免误以为绘制已经发生。
After
GPU 执行细节 – 批次 & 流水线并行
A batch is defined by one continuous block 娱乐ween two
Within a batch:
Different draws can overlap in hardware pipelines but must respect batch boundaries because switching pipelines changes shaders & fixed‑function state which requires completing all previous work first. This explains *** transparent objects are drawn after opaque objects—blending needs already written background colors.
format 链:从画布到管线必须一致js const format = navigator.gpu.getPreferredCanvasFormat;context.configure; // 在 pipeline 中 fragment:{ targets: // 必须和 swapchain 格式匹配 } Three places must agree on same pixel format:
If any step mismatches validation fails instantly.
两个附件的观点是,colorView 与 depthViewjs const colorView = context.getCurrentTexture.createView;
... |
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback