正确做法是在 mvp 变换链末端插入动态 y 轴缩放(如 glm::scale(mat4(1.0f), vec3(1,1+stretchfactor,1))),stretchfactor 由下落速度线性计算并限幅 0.3~0.4,触地时归零插值;动态模糊需在片元着色器中基于顶点投影运动向量定向采样,禁用 gl_fragcoord.y 直接偏移,并与拉伸共用同一时间驱动逻辑以避免错位。

如何用 glScalef 或 glm::scale 实现纵向视野拉伸
跳落过程中拉伸视野本质是临时修改投影矩阵的 Y 轴缩放,让画面在视觉上“被压扁”再“弹回”,不是改摄像机位置或 FOV。直接调 gluPerspective 或重算 glm::perspective 会破坏远近裁剪和深度精度,容易导致 Z-fighting 或模型突然消失。
正确做法是在 MVP 变换链末端(即 ViewProjection 矩阵之后)插入一个动态缩放:用 glm::scale(mat4(1.0f), vec3(1.0f, 1.0f + stretchFactor, 1.0f)),其中 stretchFactor 随下落速度线性增长(例如 clamp(velocity.y * 0.05f, 0.0f, 0.3f))。注意必须在顶点着色器中应用该缩放,不能只作用于 UI 层——否则 3D 模型不形变,只有 HUD 被拉,失去冲击感。
-
stretchFactor建议上限设为 0.3~0.4,超过人眼明显察觉失真,且易引发边缘像素撕裂 - 务必在角色触地瞬间将
stretchFactor归零,并用简单插值(如mix(current, 0.0f, 0.2f))避免跳变 - 若用 OpenGL 固定管线,
glScalef(1.0f, 1.0f + s, 1.0f)必须放在glMatrixMode(GL_PROJECTION)后、glLoadIdentity()前,否则会被后续投影矩阵覆盖
怎样在 Shader 中叠加基于速度的动态模糊(非后处理全屏模糊)
全屏运动模糊开销大、难控制方向,且对静止 UI 过度模糊。更稳的做法是:仅对世界空间中向下运动的片段,在片元着色器里沿屏幕 Y 轴做定向采样。核心是传入当前帧与上一帧的顶点位置差(即 vec2 motionVec),再用它驱动纹理采样偏移。
关键不是模糊强度,而是模糊方向必须严格匹配角色下落方向。若角色斜向跳落,模糊应倾斜;纯垂直下落时才用纯 Y 向。因此需在顶点着色器中计算 screenMotion = (proj * view * model * vec4(pos, 1.0)).xy - prevScreenPos.xy,再传给片元着色器。
- 不要用帧间深度图差值来算运动向量——跳跃时角色 Z 深度变化剧烈,会导致模糊方向错乱
- 模糊采样步长建议固定为 3~5 次,权重用线性衰减(如
weight = 1.0 - float(i)/SAMPLE_COUNT),避免高斯模糊引入额外 uniform 或 texture lookup - 必须加开关:仅当
abs(velocity.y) > 3.0f且角色处于空中状态(!isGrounded)时启用,否则走路也会糊
为什么 gl_FragCoord.y 不能直接当模糊偏移依据
有人尝试用 gl_FragCoord.y 算屏幕位置,再乘速度值做偏移,结果整个画面从上到下模糊强度一致,完全脱离物理逻辑。实际下落冲击的模糊是局部的:脚部区域最糊,头部区域几乎不糊。这是因为人眼焦点在落点附近,且身体各部位相对速度不同。
真正有效的偏移量必须来自世界空间速度在屏幕空间的投影,而非屏幕坐标本身。例如:取顶点法线朝下的顶点(如脚底骨骼点),将其速度向量经 MVP 变换后归一化,再映射为 vec2 偏移,最后按顶点距离摄像机远近做衰减(1.0 / (1.0 + depth * 0.1))。
- 切勿在片元着色器里重新计算世界速度——缺少顶点插值保证,同一三角面内模糊方向会跳变
- 若用骨骼动画,优先从脚部 bone 的 world transform 差分获取速度,比用根节点更准
- 移动端需警惕:多次纹理采样 + 复杂坐标变换易触发 shader fallback,建议预计算好最大偏移量并限制采样次数 ≤ 4
触地瞬间如何避免拉伸与模糊残留导致画面抽搐
残留问题通常不是代码没关开关,而是状态同步延迟:物理系统判定落地的帧,渲染系统还在用上一帧的速度值计算拉伸和模糊。尤其当使用固定 timestep(如 60Hz physics + vsync 30Hz render)时,这个偏差可达两帧。
- 落地检测必须输出“硬标志”(
justLanded = true),并在下一帧渲染前清空所有动态效果变量(stretchFactor = 0.0f,motionVec = vec2(0.0)) - 不要依赖
velocity.y == 0.0f判定落地——浮点误差会让它永远不精确等于零,改用velocity.y > -0.1f && isGrounded - 若用 Unity 或 Unreal 的 C++ 插件,确保
OnCollisionEnter或HitResult回调后立即写入渲染标记,而不是等下一帧Update
最常被忽略的是:拉伸缩放和动态模糊必须共用同一套时间驱动逻辑,不能一个用物理帧 Delta,另一个用渲染帧 Delta。否则哪怕只差 1ms,画面就会出现半帧拉伸+全帧模糊的错位感。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











