GLM中lerp实现线性插值

针对用户对 glm::lerp 函数的查询,该函数是GLM(OpenGL Mathematics)库中用于实现线性插值的核心工具,在图形学中用于在两点、两向量或两颜色值之间进行平滑过渡。

glm::lerp 函数详解

lerpLinear intERPolation 的缩写。其数学定义非常简单:对于两个点 AB,以及一个介于 [0.0, 1.0] 之间的插值因子 t,插值结果 P 为:
P = A + t * (B - A)

这等价于:P = (1 - t) * A + t * B

t=0 时,结果为 A;当 t=1 时,结果为 B;当 t 在中间时,结果在 AB 的连线上按比例分布。

在GLM库中,lerp 函数是一个模板函数,可以处理各种标量和向量类型。其典型函数签名(以 vec3 为例)为:

cpp 复制代码
// 核心函数签名
template<typename T, qualifier Q>
GLM_FUNC_DECL vec<3, T, Q> lerp(
    vec<3, T, Q> const& x, // 起始值
    vec<3, T, Q> const& y, // 结束值
    T const& t             // 插值因子,通常在 [0, 1] 区间
);

// 另一个重载,允许t为向量,用于各分量独立插值
template<typename T, qualifier Q>
GLM_FUNC_DECL vec<3, T, Q> lerp(
    vec<3, T, Q> const& x,
    vec<3, T, Q> const& y,
    vec<3, T, Q> const& t
);

参数说明与基本用法

参数 类型 说明
x genType 插值的起始值。可以是标量(如 floatdouble)或向量(如 vec2vec3vec4)。
y genType 插值的结束值。必须与 x 的类型相同。
t TgenType 插值因子 。当 xy 是向量时,t 可以是标量(所有分量使用同一因子)或与 xy 同维度的向量(各分量独立插值)。其值通常被限制在 [0, 1],但超出此范围将产生外插结果。

以下代码展示了 glm::lerp 的基本用法:

cpp 复制代码
#include <glm/glm.hpp>
#include <iostream>

int main() {
    // 1. 标量插值
    float startVal = 10.0f;
    float endVal = 20.0f;
    float t = 0.3f;
    float interpolatedScalar = glm::lerp(startVal, endVal, t);
    std::cout << "标量插值结果: " << interpolatedScalar << std::endl; // 输出:13.0

    // 2. 三维向量插值(t为标量)
    glm::vec3 startPos(0.0f, 0.0f, 0.0f);
    glm::vec3 endPos(10.0f, 5.0f, 0.0f);
    t = 0.5f;
    glm::vec3 midPoint = glm::lerp(startPos, endPos, t);
    std::cout << "向量中点 (t=0.5): (" << midPoint.x << ", " << midPoint.y << ", " << midPoint.z << ")" << std::endl; // 输出:(5, 2.5, 0)

    // 3. 三维向量插值(t为向量,各分量独立)
    glm::vec3 tVec(0.0f, 0.5f, 1.0f); // x分量不插值,y分量取中点,z分量完全到终点
    glm::vec3 customLerp = glm::lerp(startPos, endPos, tVec);
    std::cout << "独立分量插值: (" << customLerp.x << ", " << customLerp.y << ", " << customLerp.z << ")" << std::endl; // 输出:(0, 2.5, 0)

    return 0;
}

在OpenGL图形开发中的应用实例

glm::lerp 在图形流水线中应用广泛,以下是一些典型场景。

应用1:颜色渐变与过渡

在UI渲染、粒子效果或场景色调变化中,经常需要在两种颜色之间平滑过渡。

cpp 复制代码
#include <glm/glm.hpp>
#include <vector>

// 假设在渲染循环中更新颜色
void updateColorTransition(float deltaTime) {
    glm::vec3 colorStart(1.0f, 0.0f, 0.0f); // 红色
    glm::vec3 colorEnd(0.0f, 0.0f, 1.0f);   // 蓝色

    // 假设有一个在0到1之间循环的进度值
    static float progress = 0.0f;
    progress += deltaTime * 0.5f; // 每2秒循环一次
    if (progress > 1.0f) progress = 0.0f;

    // 使用lerp计算当前帧的颜色
    glm::vec3 currentColor = glm::lerp(colorStart, colorEnd, progress);

    // 将 currentColor (RGB) 传递给着色器或用于直接设置OpenGL状态
    // glUniform3f(colorLoc, currentColor.r, currentColor.g, currentColor.b);
}

应用2:相机平滑移动(缓动效果)

在实现2D或3D相机跟随、菜单动画时,直接设置位置会产生生硬感,使用 lerp 可以实现平滑的缓动(Easing)效果。

cpp 复制代码
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

class SmoothCamera {
public:
    glm::vec3 cameraPosition;
    glm::vec3 targetPosition;

    void update(float deltaTime) {
        // 定义一个平滑系数,值越小,跟随越慢越平滑(通常在0.01-0.1之间)
        float smoothFactor = 0.05f;
        // 使用lerp让相机位置向目标位置平滑过渡
        cameraPosition = glm::lerp(cameraPosition, targetPosition, smoothFactor);

        // 使用更新后的相机位置构建视图矩阵
        glm::mat4 viewMatrix = glm::lookAt(cameraPosition,
                                            glm::vec3(0.0f, 0.0f, 0.0f), // 看向原点
                                            glm::vec3(0.0f, 1.0f, 0.0f)); // 上向量
        // ... 将viewMatrix传递给着色器
    }
};

此方法通过每一帧将当前位置向目标位置移动一小段比例的距离,实现了平滑的阻尼效果,避免了卡顿。

应用3:关键帧动画的顶点插值

在骨骼动画或变形动画中,模型顶点位置在多个关键帧之间定义,需要在非关键帧时刻通过插值计算出顶点位置。

cpp 复制代码
// 简化的顶点关键帧插值示例
struct Keyframe {
    float time;
    std::vector<glm::vec3> vertexPositions;
};

glm::vec3 interpolateVertex(const Keyframe& frameA, const Keyframe& frameB, float currentTime, int vertexIndex) {
    // 确保时间在关键帧范围内
    float frameDuration = frameB.time - frameA.time;
    if (frameDuration <= 0.0f) return frameA.vertexPositions[vertexIndex];

    // 计算当前时间在关键帧A和B之间的归一化比例因子 t
    float t = (currentTime - frameA.time) / frameDuration;
    t = glm::clamp(t, 0.0f, 1.0f); // 确保t在[0,1]区间

    // 对特定顶点的位置进行线性插值
    return glm::lerp(frameA.vertexPositions[vertexIndex],
                     frameB.vertexPositions[vertexIndex],
                     t);
}

应用4:在片元着色器中进行纹理混合或光照插值

虽然GLM是CPU端数学库,但其lerp函数的概念与GLSL(OpenGL着色器语言)中的mix函数完全等价。在编写着色器时,mix函数是lerp的GLSL实现。

glsl 复制代码
// GLSL 片元着色器示例:混合两个纹理
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform float blendFactor; // 从CPU传入,相当于t

in vec2 TexCoord;
out vec4 FragColor;

void main() {
    vec4 color1 = texture(texture1, TexCoord);
    vec4 color2 = texture(texture2, TexCoord);
    // 使用 mix (GLSL中的lerp) 混合颜色
    FragColor = mix(color1, color2, blendFactor);
}

在CPU端使用GLM计算blendFactor的逻辑与上述例子一致。

高级用法与注意事项

  1. 外插t 参数可以不局限于 [0, 1]。当 t < 0t > 1 时,lerp 会产生外插值,即结果点将位于 AB 的延长线上。这可以用于预测位置或创建特殊效果。

    cpp 复制代码
    glm::vec3 extrapolatedPos = glm::lerp(startPos, endPos, 1.5f); // 超过终点50%的位置
  2. 与四元数球面插值 slerp 的区别 :对于旋转(用四元数表示),线性插值 lerp 会导致旋转速度不均匀。GLM提供了 glm::slerpglm::mix(用于四元数时内部是slerp)来进行球面线性插值,它能保证角速度恒定,是旋转插值的正确选择。

    cpp 复制代码
    glm::quat startRot = glm::angleAxis(glm::radians(0.0f), glm::vec3(0,1,0));
    glm::quat endRot = glm::angleAxis(glm::radians(90.0f), glm::vec3(0,1,0));
    glm::quat smoothRot = glm::slerp(startRot, endRot, t); // 正确的旋转插值
  3. 性能与精度lerp 函数在GLM中通常经过优化,对于向量化类型(如 vec4)可能利用SIMD指令。它是一个非常基础且高效的操作。

总结glm::lerp 是一个功能简单但用途极其广泛的函数,它是在两个数值或向量间进行线性混合的标准方法。在OpenGL图形程序开发中,它被广泛应用于动画、过渡、混合以及任何需要平滑变化值的场景。正确理解其参数(特别是 t 的含义和范围)是灵活运用的关键。


参考来源

相关推荐
我不是懒洋洋2 小时前
预处理详解
c语言·开发语言·c++·windows·microsoft·青少年编程·visual studio
计算机安禾2 小时前
【数据结构与算法】第14篇:队列(一):循环队列(顺序存储
c语言·开发语言·数据结构·c++·算法·visual studio
IT从业者张某某2 小时前
基于EGE19.01完成恐龙跳跃游戏-V00-C++使用EGE19.01这个轮子
c++·游戏
-许平安-3 小时前
MCP项目笔记六(PluginsLoader)
c++·笔记·raii·plugin system
呜喵王阿尔萨斯3 小时前
argc & argv
c语言·c++
Vect__3 小时前
std::bind和lambda的使用
c++
她叫我大水龙3 小时前
MSYS2的C/C++,python2,python3编译环境安装脚本
c语言·c++
宵时待雨4 小时前
C++笔记归纳17:哈希
数据结构·c++·笔记·算法·哈希算法
charlie1145141914 小时前
嵌入式C++教程实战之Linux下的单片机编程:从零搭建 STM32 开发工具链(2) —— HAL 库获取、启动文件坑位与目录搭建
linux·开发语言·c++·stm32·单片机·学习·嵌入式