如何在WebGL中实现类似Sketchfab的全屏背景图(非CSS方式)
哈哈,这个问题我刚好折腾过!其实不用把背景放在zfar位置那么麻烦,直接在屏幕空间画个全屏四边形,再处理纹理的适配就能实现和CSS background-size: cover一模一样的效果,和Sketchfab的思路差不多。给你一步步拆解:
核心思路:屏幕空间全屏四边形 + 纹理适配
不用纠结相机的zfar计算,背景本来就是贴在屏幕上的,直接用**标准化设备坐标(NDC)**画一个铺满屏幕的四边形,再通过着色器计算纹理的缩放比例,实现居中裁剪的cover效果。
步骤1:创建全屏四边形
直接用NDC坐标(范围是(-1,-1)到(1,1))定义四边形的顶点,这样不需要MVP矩阵,顶点直接输出到屏幕就能铺满整个画布。顶点数据要包含位置和UV坐标:
// 顶点数据:[x, y, u, v] const fullscreenQuadVertices = new Float32Array([ -1.0, -1.0, 0.0, 0.0, // 左下 1.0, -1.0, 1.0, 0.0, // 右下 -1.0, 1.0, 0.0, 1.0, // 左上 1.0, 1.0, 1.0, 1.0 // 右上 ]);
把这些数据传入WebGL缓冲区,设置好顶点属性(位置和UV)即可。
步骤2:实现Cover纹理适配(关键!)
在片元着色器里计算纹理和屏幕的宽高比,调整UV坐标让纹理居中铺满屏幕,超出部分自动裁剪:
片元着色器代码
precision mediump float; varying vec2 v_uv; // 从顶点着色器传入的原始UV(0-1范围) uniform sampler2D u_bgTexture; uniform vec2 u_textureSize; // 纹理的宽高 uniform vec2 u_screenSize; // 画布的宽高 void main() { // 计算纹理和屏幕的宽高比 float texRatio = u_textureSize.x / u_textureSize.y; float screenRatio = u_screenSize.x / u_screenSize.y; // 计算缩放因子:让纹理的短边适配屏幕,长边超出 vec2 scale = vec2(1.0); if (texRatio > screenRatio) { // 纹理比屏幕宽 → 按屏幕高度缩放,宽度超出裁剪 scale.x = texRatio / screenRatio; } else { // 屏幕比纹理宽 → 按屏幕宽度缩放,高度超出裁剪 scale.y = screenRatio / texRatio; } // 调整UV坐标,让纹理居中 vec2 adjustedUv = (v_uv - 0.5) * scale + 0.5; // 采样纹理输出颜色 gl_FragColor = texture2D(u_bgTexture, adjustedUv); }
顶点着色器代码
attribute vec2 a_position; attribute vec2 a_uv; varying vec2 v_uv; void main() { v_uv = a_uv; gl_Position = vec4(a_position, 0.0, 1.0); // 直接输出NDC坐标,w=1确保在屏幕上 }
步骤3:绘制顺序与深度处理
- 先绘制背景:绘制前关闭深度测试(
gl.disable(gl.DEPTH_TEST)),这样背景会直接覆盖整个屏幕,不会被后续的3D模型遮挡。 - 绘制完背景后,再开启深度测试(
gl.enable(gl.DEPTH_TEST)),绘制你的3D模型即可。
绘制流程示例
// 绘制背景 gl.disable(gl.DEPTH_TEST); // 设置uniform:纹理尺寸、屏幕尺寸 gl.uniform2f(textureSizeLoc, bgTexture.width, bgTexture.height); gl.uniform2f(screenSizeLoc, canvas.width, canvas.height); // 绑定背景纹理 gl.bindTexture(gl.TEXTURE_2D, bgTexture); // 绘制全屏四边形(用TRIANGLE_STRIP只需要4个顶点) gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // 绘制3D模型 gl.enable(gl.DEPTH_TEST); // ... 你的3D模型绘制代码 ...
为什么不用zfar位置的平面?
用相机zfar位置的平面需要计算平面尺寸(比如width = 2 * zfar * tan(fov/2),height = width / aspectRatio),但这种方法需要绑定相机的MVP矩阵,而且本质上还是在3D空间绘制,不如直接在屏幕空间画四边形高效——毕竟背景不需要和3D场景有空间交互,直接贴屏才是最优解。
这样实现出来的效果就和Sketchfab的背景完全一致,完美复刻background-size: cover的行为,而且纯WebGL实现,不需要依赖CSS。
内容的提问来源于stack exchange,提问作者user2331095
相关产品推荐
相关产品推荐

