使用Three.js时WebGL Canvas尺寸为何是CSS样式尺寸的两倍?
嘿,这个问题我之前在项目里踩过坑!核心原因其实是**设备像素比(Device Pixel Ratio, DPR)**在搞事情,下面给你拆解清楚:
1. 先搞懂Canvas尺寸的双重逻辑
Three.js里的Canvas元素有两个关键尺寸:
- CSS样式宽高:这是你在resize函数里设置的、Canvas在页面布局中占据的可视尺寸,比如
window.innerWidth和window.innerHeight。 - 实际像素宽高:这是Canvas内部用于渲染的像素缓冲区大小,Three.js默认会把这个值设置为「CSS宽高 × 设备DPR」。
为啥要这么做?因为现在很多设备(比如Retina屏)的DPR是2,意味着屏幕上1个CSS像素对应2×2个物理像素。如果Canvas的实际像素尺寸和CSS一致,渲染出来的画面会因为像素不足而模糊,Three.js自动适配DPR就是为了让渲染结果在高分辨率屏幕上更清晰。
2. 为什么结合Effects Composer和自定义着色器会出bug?
问题出在「尺寸不匹配」上:
- Effects Composer内部的渲染目标(
WebGLRenderTarget)默认会继承Renderer的DPR设置,所以它的像素尺寸也是CSS宽高×DPR。 - 如果你写自定义着色器的时候,没有考虑DPR的影响——比如直接用CSS尺寸计算纹理坐标、或者传递给着色器的
resolutionuniform是CSS尺寸而非实际像素尺寸——就会导致采样错位、画面拉伸、或者部分效果失效。
举个例子:如果你的着色器里用uv = gl_FragCoord.xy / vec2(window.innerWidth, window.innerHeight),但实际渲染的画布像素是两倍大,那么uv坐标就会被压缩,纹理显示就会出问题。
3. 解决思路
针对这个问题,有几个常用的修复方向:
临时调试:强制关闭DPR适配
初始化Renderer时手动设置像素比为1,这样Canvas实际尺寸就和CSS一致,方便快速验证问题:const renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio(1); // 强制禁用高清渲染缺点是高分辨率屏上画面会模糊,只适合调试用。
让自定义着色器适配DPR
给着色器传递正确的resolutionuniform(即Canvas的实际像素尺寸),可以通过renderer.getDrawingBufferSize()获取:// 在resize函数中更新uniform const size = renderer.getDrawingBufferSize(new THREE.Vector2()); yourMaterial.uniforms.resolution.value.set(size.width, size.height);然后在片元着色器里用这个resolution计算正确的uv:
uniform vec2 resolution; void main() { vec2 uv = gl_FragCoord.xy / resolution; // 后续采样逻辑... }对齐Effects Composer的渲染目标尺寸
创建Effects Composer时,显式指定渲染目标的像素比,确保和Renderer一致:const renderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, { pixelRatio: renderer.getPixelRatio() } ); const composer = new THREE.EffectComposer(renderer, renderTarget);这样Composer内部的渲染流程就会和Renderer的尺寸逻辑对齐,避免不匹配的问题。
总结
Canvas实际尺寸是CSS两倍是Three.js的默认高清渲染策略,本身不是bug;你遇到的问题是因为自定义着色器或Effects Composer的配置没有和DPR逻辑对齐,只要调整好尺寸适配就能解决啦!
内容的提问来源于stack exchange,提问作者Mischawaka

