窗口调整大小时2D光照出现拉伸,如何基于宽高比修正光照半径?
2D光照拉伸问题修复方案
核心原因
你当前计算光照距离的坐标处于**归一化设备坐标(NDC)**空间,该空间下x、y轴的取值范围均为[-1,1],但实际窗口宽高比不为1:1时,两个轴相同单位对应的实际像素长度不同,直接计算欧氏距离就会出现拉伸/挤压变形。
修复方案
方法1:着色器端校正距离计算(推荐,改动最小)
在计算距离前,将坐标差值按宽高比校正到等比例空间后再计算长度,修改calculateLight函数即可:
vec4 calculateLight(Light light) { // 先计算片段到光源的轴向差值 vec2 fragToLight = fragmentPosition.xy - light.position.xy; // 按宽高比校正x轴差值,也可选择y轴乘宽高比,效果一致 float ar = resolution.x / resolution.y; fragToLight.x /= ar; // 校正后再计算距离 float lightDistance = length(fragToLight); if (lightDistance >= light.radius) { return vec4(0, 0, 0, 1); } return light.intensity * (1 - lightDistance / light.radius) * light.colour; }
方法2:CPU端预处理光照半径
如果你不想在着色器每次计算宽高比,也可以在CPU端将光照半径按NDC空间的比例换算后再传给着色器,适合固定宽高比的场景。
代码优化建议
- 修正DrawLight调用位置:你当前把
Renderer2D::DrawLight放在遍历实体的for循环内部,会导致每收集一个光源就触发一次绘制,严重浪费性能。请将该行代码移到for循环外部,等所有光源收集完成后再调用。 - 修正环境光叠加逻辑:你现在把
u_ambientColour的加法放在循环内部,每遍历一盏灯就叠加一次环境光,会导致环境光亮度随光源数量变化。请将环境光的加法挪到循环外面,只叠加一次:
void main() { vec4 texColor = v_Color; if (numLights == 0) { color = texColor * (vec4(1.0) + u_ambientColour); return; } vec4 netLightColour = vec4(0, 0, 0, 1); for(int i = 0; i < numLights; ++i) { netLightColour += calculateLight(allLights[i]); } // 环境光仅叠加一次 netLightColour += u_ambientColour; color = texColor * netLightColour; }
- 半径预转换:如果你的光照半径是世界空间单位,建议在CPU端直接将其转换为NDC空间的对应值再传给着色器,避免着色器端额外换算。
内容的提问来源于stack exchange,提问作者tomatto
相关产品推荐
相关产品推荐

