如何修改WebGPU代码实现类似ShaderToy的SDF效果?
实现WebGPU中的SDF绘制(ShaderToy风格)
问题背景
要开发WebGPU版本的ShaderToy,已将画布裁剪空间设置为[-1,1]范围,现有基础渲染代码但无法实现SDF(有符号距离场)绘制,需要修改代码并获取相关参考内容。
核心修改步骤
1. 传递UV坐标到片元着色器
顶点着色器需要把当前顶点的位置作为UV坐标传递给片元着色器,因为裁剪空间已经是[-1,1],可以直接用顶点位置作为初始UV,后续再适配画布宽高比避免拉伸。
2. 实现SDF函数与颜色计算
片元着色器中接收UV,先修正宽高比,然后实现SDF函数(比如圆形),根据距离值生成颜色:
- 修正UV:将UV按画布宽高比缩放,避免图形拉伸
- SDF函数:计算像素到图形的有符号距离
- 颜色输出:根据距离值绘制轮廓、填充或渐变
3. 适配画布尺寸(可选)
如果需要动态适配画布大小,可以添加uniform传递画布分辨率,以下示例先基于固定画布尺寸做静态适配。
完整修改后的代码
<!DOCTYPE html> <title>SDF-WebGPU</title> <canvas></canvas> <script>'use strict'; const canvas = document.body.firstChild; canvas.style = ` display: block; image-rendering: pixelated; background-color: #ccc; user-select: none; touch-action: none; width: ${ canvas.width = 480 * devicePixelRatio, 480 }px; height: ${ canvas.height = 360 * devicePixelRatio, 360 }px; `; const init = async function(){ const context = canvas.getContext(`webgpu`), format = navigator.gpu.getPreferredCanvasFormat(), adapter = await navigator.gpu.requestAdapter(), device = await adapter.requestDevice(), Q = device.queue, {VERTEX, COPY_DST} = GPUBufferUsage, SPACE_B = new Float32Array([-1,1, 1,1, -1,-1, 1,-1]), B = device.createBuffer({ label: `SPACE`, size: SPACE_B.byteLength, usage: VERTEX | COPY_DST }), P = device.createRenderPipeline({ layout: `auto`, vertex: { module: device.createShaderModule({ code: `@vertex fn vSh(@location(0) p: vec2<f32>) -> struct { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32> } { return { pos: vec4<f32>(p, 0, 1), uv: p }; }` }), entryPoint: `vSh`, buffers: [{ arrayStride: 8, // 2*4 = 2 floats x 4 bytes attributes: [{ shaderLocation: 0, offset: 0, format: `float32x2` }] }], // buffers }, fragment: { module: device.createShaderModule({ code: `@fragment fn fSh(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> { // 修正UV宽高比,适配480x360画布 vec2 st = uv; st.x *= 480.0 / 360.0; // 圆形SDF:计算当前点到中心的距离,半径0.5 float d = length(st) - 0.5; // 基于距离生成颜色,smoothstep实现边缘抗锯齿 float col = smoothstep(0.0, 0.01, d); return vec4<f32>(1.0 - col, 0.2, 0.4, 1.0); }` }), entryPoint: `fSh`, targets: [ {format} ] }, primitive:{ topology: `triangle-strip` } }), // Pipeline frame=()=>{ const C = device.createCommandEncoder(), R = C.beginRenderPass({ colorAttachments:[{ view: context.getCurrentTexture().createView(), loadOp: `clear`, storeOp: `store` }] }); R.setPipeline(P); R.setVertexBuffer(0,B); R.draw(4); R.end(); Q.submit([ C.finish() ]) }; // frame context.configure({ device, format, alphaMode: `opaque` }); Q.writeBuffer(B,0, SPACE_B); frame() }() // init </script>
关键代码说明
- 顶点着色器修改:新增
uv输出变量,将裁剪空间坐标直接传递给片元着色器,作为后续SDF计算的基础坐标。 - UV修正:通过
st.x *= 480.0 / 360.0修正宽高比,避免圆形被拉伸成椭圆。 - SDF实现:
length(st) - 0.5是中心在原点、半径0.5的圆形SDF,返回值为负表示在圆内,正表示在圆外。 - 颜色计算:
smoothstep函数实现边缘抗锯齿,生成平滑的颜色过渡,让图形边缘更柔和。
参考资料
- WGSL基础语法:WebGPU官方着色器语言规范,包含向量操作、函数定义等核心内容,是编写WebGPU着色器的基础。
- SDF核心公式:
- 圆形:
length(p) - r(p为点坐标,r为半径) - 矩形:
length(max(abs(p)-b, 0.0)) + min(max(abs(p.x)-b.x, abs(p.y)-b.y), 0.0)(b为矩形半宽高) - 组合操作:并集
min(d1, d2)、交集max(d1, d2)、差集max(-d1, d2)
- 圆形:
- WebGPU渲染流程:熟悉顶点输入、管线创建、命令提交等流程,确保SDF绘制的渲染链路正确。
内容的提问来源于stack exchange,提问作者Teamur
相关产品推荐
相关产品推荐

