在OpenGL中实现视角切换插值过渡动画

在OpenGL中实现视角切换插值过渡动画可以通过以下步骤来完成:

一、定义视角结构体

首先,需要定义一个结构体来表示视角相关的信息,通常包括观察位置(Eye Position)、观察目标点(Look At Point)和上方向向量(Up Vector)。例如:

struct Camera {
    glm::vec3 eye;
    glm::vec3 center;
    glm::vec3 up;
};

这里假设使用了GLM库(OpenGL Mathematics Library)来处理向量和矩阵运算,glm::vec3 表示三维向量。

二、设置初始视角和目标视角

确定动画开始时的初始视角(initialCamera)和切换到的目标视角(targetCamera)。可以根据具体需求手动设置这些视角的值,比如:

Camera initialCamera = {
    glm::vec3(0.0f, 0.0f, 5.0f),  // 初始观察位置
    glm::vec3(0.0f, 0.0f, 0.0f),  // 初始观察目标点
    glm::vec3(0.0f, 1.0f, 0.0f)   // 初始上方向向量
};

Camera targetCamera = {
    glm::vec3(2.0f, 2.0f, 3.0f),  // 目标观察位置
    glm::vec3(0.0f, 0.0f, 0.0f),  // 目标观察目标点
    glm::vec3(0.0f, 1.0f, 0.0f)   // 目标上方向向量
};

三、选择插值方法

常用的插值方法有线性插值(Linear Interpolation,简称LERP)和球面线性插值(Spherical Linear Interpolation,简称SLERP)。

  1. 线性插值(LERP)
    • 线性插值是一种简单直接的插值方法。对于两个给定的值 ab,以及一个插值因子 t(取值范围在0到1之间),线性插值的公式为:result = a + t * (b - a)
    • 在视角切换的应用中,对于视角的每个分量(如观察位置、观察目标点、上方向向量)都可以分别使用线性插值来计算过渡值。例如,对于观察位置的插值计算如下:
glm::vec3 lerpEyePosition = initialCamera.eye + t * (targetCamera.eye - initialCamera.eye);

其中 t 是随着时间逐渐从0增加到1的插值因子,表示动画的进度。

  1. 球面线性插值(SLERP)
    • 球面线性插值主要用于在两个向量之间进行插值,并且能够保持向量的长度和方向的相对关系,使得插值结果更加自然。它的计算公式相对复杂一些,基于三角函数和向量点积等运算。
    • 对于视角的上方向向量等需要保持方向特性的情况,使用SLERP可能会得到更好的效果。例如,对于上方向向量的插值计算可以使用GLM库提供的 glm::slerp 函数(假设GLM库版本支持该功能):
glm::vec3 slerpUpVector = glm::slerp(initialCamera.up, targetCamera.up, t);

四、更新视角并渲染场景

在渲染循环中,根据选择的插值方法和当前的动画进度(由插值因子 t 表示)来更新视角信息,然后使用更新后的视角来渲染场景。

  1. 更新插值因子
    • 在渲染循环中,需要不断更新插值因子 t,使其随着时间逐渐从0增加到1。可以通过获取当前时间与动画总时长的比例来确定 t 的值。例如:
float totalAnimationTime = 2.0f;  // 设定动画总时长为2秒
float currentTime = static_cast<float>(glfwGetTime());  // 获取当前时间(假设使用GLFW库来获取时间)
float t = currentTime / totalAnimationTime;
if (t > 1.0f) t = 1.0f;  // 确保t不超过1
  1. 更新视角信息
    • 根据选择的插值方法,分别对视角的各个分量进行插值计算,得到更新后的视角结构体。例如,使用线性插值更新视角的示例如下:
Camera currentCamera;
currentCamera.eye = lerpEyePosition;
currentCamera.center = initialCamera.center;  // 观察目标点通常可以保持不变,也可根据需求进行插值计算
currentCamera.up = lerpUpVector;

或者使用球面线性插值更新视角的示例如下:

Camera currentCamera;
currentCamera.eye = lerpEyePosition;
currentCamera.center = initialCamera.center;
currentCamera.up = slerpUpVector;
  1. 渲染场景
    • 使用更新后的视角信息,通过设置OpenGL的观察矩阵等相关矩阵来渲染场景。例如,使用GLM库来设置观察矩阵的示例如下:
glm::mat4 viewMatrix = glm::lookAt(currentCamera.eye, currentCamera.center, currentCamera.up);
// 将viewMatrix应用到渲染管线中,具体应用方式取决于你的渲染框架和代码结构

通过不断重复上述步骤,在渲染循环中随着时间更新插值因子、更新视角信息并渲染场景,就可以实现OpenGL中视角切换的插值过渡动画。

请注意,上述代码示例只是一个简化的框架,在实际应用中,你可能需要根据具体的OpenGL渲染框架、使用的库以及应用场景等对代码进行进一步的完善和调整。

======================================================

以下是一个简单的 OpenGL 中实现视角切换插值过渡动画的 C++ 代码示例,使用了 GLFW 和 GLM 库:

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

// 定义相机结构体
struct Camera {
    glm::vec3 eye;
    glm::vec3 center;
    glm::vec3 up;
};

// 线性插值函数
glm::vec3 lerp(const glm::vec3& a, const glm::vec3& b, float t) {
    return a * (1.0f - t) + b * t;
}

int main() {
    // 初始化 GLFW
    if (!glfwInit()) {
        std::cerr << "GLFW initialization failed" << std::endl;
        return -1;
    }

    // 创建窗口
    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL View Transition", nullptr, nullptr);
    if (!window) {
        std::cerr << "Window creation failed" << std::endl;
        glfwTerminate();
        return -1;
    }

    // 将窗口设置为当前上下文
    glfwMakeContextCurrent(window);

    // 初始化 GLEW
    if (glewInit()!= GLEW_OK) {
        std::cerr << "GLEW initialization failed" << std::endl;
        return -1;
    }

    // 设置初始视角和目标视角
    Camera initialCamera = {
        glm::vec3(0.0f, 0.0f, 5.0f),
        glm::vec3(0.0f, 0.0f, 0.0f),
        glm::vec3(0.0f, 1.0f, 0.0f)
    };

    Camera targetCamera = {
        glm::vec3(2.0f, 2.0f, 3.0f),
        glm::vec3(0.0f, 0.0f, 0.0f),
        glm::vec3(0.0f, 1.0f, 0.0f)
    };

    // 动画相关变量
    float totalAnimationTime = 2.0f; // 动画总时长
    float currentTime = 0.0f;
    float t = 0.0f;

    // 渲染循环
    while (!glfwWindowShouldClose(window)) {
        // 计算当前时间
        currentTime = static_cast<float>(glfwGetTime());
        // 计算插值因子
        t = currentTime / totalAnimationTime;
        if (t > 1.0f) t = 1.0f;

        // 计算当前视角
        Camera currentCamera;
        currentCamera.eye = lerp(initialCamera.eye, targetCamera.eye, t);
        currentCamera.center = lerp(initialCamera.center, targetCamera.center, t);
        currentCamera.up = lerp(initialCamera.up, targetCamera.up, t);

        // 设置视景体
        glm::mat4 viewMatrix = glm::lookAt(currentCamera.eye, currentCamera.center, currentCamera.up);
        glm::mat4 projectionMatrix = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

        // 清除颜色缓冲和深度缓冲
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 使用当前视角矩阵和投影矩阵进行渲染
        // 这里只是一个简单的示例,你需要根据自己的场景和渲染逻辑进行具体的渲染操作
        // 例如,如果你有一个物体要绘制,可以在这之前设置好模型矩阵,然后进行绘制

        // 交换缓冲区,显示渲染结果
        glfwSwapBuffers(window);

        // 处理输入事件
        glfwPollEvents();
    }

    // 释放资源
    glfwTerminate();
    return 0;
}

在上述代码中,首先定义了一个Camera结构体来表示相机的位置、目标点和上方向向量。然后,使用线性插值函数lerp来计算在初始视角和目标视角之间的过渡视角。在渲染循环中,根据当前时间计算插值因子t,并使用插值因子更新当前视角。最后,设置视景体并进行渲染操作。

请注意,这只是一个简单的示例,实际应用中你可能需要根据自己的场景和需求进行更多的设置和扩展,比如添加物体的绘制、处理输入事件等。同时,确保你已经正确地配置了 OpenGL 的环境和相关库的链接。

========================================================

以下是一个简单的 OpenGL 中实现视角切换插值过渡动画的 C++ 代码示例,使用了 GLFW 和 GLM 库:

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

// 定义相机结构体
struct Camera {
    glm::vec3 eye;
    glm::vec3 center;
    glm::vec3 up;
};

// 线性插值函数
glm::vec3 lerp(const glm::vec3& a, const glm::vec3& b, float t) {
    return a * (1.0f - t) + b * t;
}

int main() {
    // 初始化 GLFW
    if (!glfwInit()) {
        std::cerr << "GLFW initialization failed" << std::endl;
        return -1;
    }

    // 创建窗口
    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL View Transition", nullptr, nullptr);
    if (!window) {
        std::cerr << "Window creation failed" << std::endl;
        glfwTerminate();
        return -1;
    }

    // 将窗口设置为当前上下文
    glfwMakeContextCurrent(window);

    // 初始化 GLEW
    if (glewInit()!= GLEW_OK) {
        std::cerr << "GLEW initialization failed" << std::endl;
        return -1;
    }

    // 设置初始视角和目标视角
    Camera initialCamera = {
        glm::vec3(0.0f, 0.0f, 5.0f),
        glm::vec3(0.0f, 0.0f, 0.0f),
        glm::vec3(0.0f, 1.0f, 0.0f)
    };

    Camera targetCamera = {
        glm::vec3(2.0f, 2.0f, 3.0f),
        glm::vec3(0.0f, 0.0f, 0.0f),
        glm::vec3(0.0f, 1.0f, 0.0f)
    };

    // 动画相关变量
    float totalAnimationTime = 2.0f; // 动画总时长
    float currentTime = 0.0f;
    float t = 0.0f;

    // 渲染循环
    while (!glfwWindowShouldClose(window)) {
        // 计算当前时间
        currentTime = static_cast<float>(glfwGetTime());
        // 计算插值因子
        t = currentTime / totalAnimationTime;
        if (t > 1.0f) t = 1.0f;

        // 计算当前视角
        Camera currentCamera;
        currentCamera.eye = lerp(initialCamera.eye, targetCamera.eye, t);
        currentCamera.center = lerp(initialCamera.center, targetCamera.center, t);
        currentCamera.up = lerp(initialCamera.up, targetCamera.up, t);

        // 设置视景体
        glm::mat4 viewMatrix = glm::lookAt(currentCamera.eye, currentCamera.center, currentCamera.up);
        glm::mat4 projectionMatrix = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

        // 清除颜色缓冲和深度缓冲
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 使用当前视角矩阵和投影矩阵进行渲染
        // 这里只是一个简单的示例,你需要根据自己的场景和渲染逻辑进行具体的渲染操作
        // 例如,如果你有一个物体要绘制,可以在这之前设置好模型矩阵,然后进行绘制

        // 交换缓冲区,显示渲染结果
        glfwSwapBuffers(window);

        // 处理输入事件
        glfwPollEvents();
    }

    // 释放资源
    glfwTerminate();
    return 0;
}

在上述代码中,首先定义了一个Camera结构体来表示相机的位置、目标点和上方向向量。然后,使用线性插值函数lerp来计算在初始视角和目标视角之间的过渡视角。在渲染循环中,根据当前时间计算插值因子t,并使用插值因子更新当前视角。最后,设置视景体并进行渲染操作。

请注意,这只是一个简单的示例,实际应用中你可能需要根据自己的场景和需求进行更多的设置和扩展,比如添加物体的绘制、处理输入事件等。同时,确保你已经正确地配置了 OpenGL 的环境和相关库的链接。

========================================================================

以下是使用不同语言实现的 OpenGL 视角切换插值过渡动画的代码示例:

Python (使用 PyOpenGL 库)

import glfw
import OpenGL.GL as gl
import numpy as np
from OpenGL.GL.shaders import compileShader, compileProgram
import glm

# 初始化 GLFW
if not glfw.init():
    raise Exception("GLFW initialization failed")

# 创建窗口
window = glfw.create_window(800, 600, "OpenGL Perspective Transition", None, None)
if not window:
    glfw.terminate()
    raise Exception("Window creation failed")

# 将窗口设为当前上下文
glfw.make_context_current(window)

# 初始化 GLEW
glewExperimental = True
if glewInit()!= GLEW_OK:
    raise Exception("GLEW initialization failed")

# 顶点着色器源码
vertex_shader_source = """
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}
"""

# 片段着色器源码
fragment_shader_source = """
#version 330 core
out vec4 FragColor;
void main()
{
    FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
"""

# 编译着色器
vertex_shader = compileShader(vertex_shader_source, gl.GL_VERTEX_SHADER)
fragment_shader = compileShader(fragment_shader_source, gl.GL_FRAGMENT_SHADER)
shader_program = compileProgram(vertex_shader, fragment_shader)

# 设置顶点数据
vertices = np.array([
    -0.5, -0.5, 0.0,
     0.5, -0.5, 0.0,
     0.0,  0.5, 0.0
], dtype=np.float32)

# 创建顶点缓冲区对象(VBO)和顶点数组对象(VAO)
vbo = gl.glGenBuffers(1)
vao = gl.glGenVertexArrays(1)

gl.glBindVertexArray(vao)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
gl.glBufferData(gl.GL_ARRAY_BUFFER, vertices.nbytes, vertices, gl.GL_STATIC_DRAW)

gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 3 * 4, None)
gl.glEnableVertexAttribArray(0)

gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
gl.glBindVertexArray(0)

# 定义初始视角和目标视角
initial_eye = glm.vec3(0.0, 0.0, 3.0)
target_eye = glm.vec3(2.0, 2.0, 5.0)
initial_target = glm.vec3(0.0, 0.0, 0.0)
target_target = glm.vec3(0.0, 0.0, 1.0)
initial_up = glm.vec3(0.0, 1.0, 0.0)
target_up = glm.vec3(0.0, 1.0, 0.5)

# 动画时长和时间变量
animation_duration = 3.0  # 秒
current_time = 0.0

def update_view_matrix():
    global current_time
    if current_time < animation_duration:
        # 计算插值因子
        t = current_time / animation_duration
        # 插值计算观察位置、目标点和上方向向量
        eye = glm.lerp(initial_eye, target_eye, t)
        target = glm.lerp(initial_target, target_target, t)
        up = glm.slerp(initial_up, target_up, t)
    else:
        eye = target_eye
        target = target_target
        up = target_up

    # 构建观察矩阵
    view_matrix = glm.lookAt(eye, target, up)
    return view_matrix

def render():
    global current_time
    while not glfw.window_should_close(window):
        # 处理输入
        process_input(window)

        # 清除颜色缓冲和深度缓冲
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)

        # 使用着色器程序
        gl.glUseProgram(shader_program)

        # 更新观察矩阵
        view_matrix = update_view_matrix()
        # 获取 uniform 变量的位置
        view_location = gl.glGetUniformLocation(shader_program, "view")
        projection_location = gl.glGetUniformLocation(shader_program, "projection")
        model_location = gl.glGetUniformLocation(shader_program, "model")

        # 传递矩阵到着色器
        gl.glUniformMatrix4fv(view_location, 1, gl.GL_FALSE, glm.value_ptr(view_matrix))
        projection_matrix = glm.perspective(glm.radians(45.0), 800 / 600, 0.1, 100.0)
        gl.glUniformMatrix4fv(projection_location, 1, gl.GL_FALSE, glm.value_ptr(projection_matrix))
        model_matrix = glm.mat4(1.0)
        gl.glUniformMatrix4fv(model_location, 1, gl.GL_FALSE, glm.value_ptr(model_matrix))

        # 绘制三角形
        gl.glBindVertexArray(vao)
        gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3)
        gl.glBindVertexArray(0)

        # 交换缓冲区,显示渲染结果
        glfw.swap_buffers(window)

        # 更新时间
        current_time += glfw.get_time() - current_time
        glfw.poll_events()

    glfw.terminate()

def process_input(window):
    if glfw.get_key(window, glfw.KEY_ESCAPE) == glfw.PRESS:
        glfw.set_window_should_close(window, True)

if __name__ == "__main__":
    render()

以下是使用不同语言实现的 OpenGL 视角切换插值过渡动画的代码示例:

  • Java(使用 LWJGL 库)
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.system.MemoryStack;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.NULL;

public class OpenGLViewTransition {
    // 窗口的宽度和高度
    private static final int WIDTH = 800;
    private static final int HEIGHT = 600;

    // 初始视角和目标视角
    private static float[] initialEye = {0.0f, 0.0f, 3.0f};
    private static float[] targetEye = {2.0f, 2.0f, 5.0f};
    // 初始和目标观察目标点
    private static float[] initialCenter = {0.0f, 0.0f, 0.0f};
    private static float[] targetCenter = {1.0f, 1.0f, 0.0f};
    // 初始和目标上方向向量
    private static float[] initialUp = {0.0f, 1.0f, 0.0f};
    private static float[] targetUp = {0.5f, 0.5f, 1.0f};

    // 插值因子
    private static float t = 0.0f;
    // 动画是否完成的标志
    private static boolean animationFinished = false;

    public static void main(String[] args) {
        // 设置错误回调
        GLFWErrorCallback.createPrint(System.err).set();

        // 初始化 GLFW
        if (!glfwInit()) {
            throw new IllegalStateException("GLFW initialization failed");
        }

        // 创建窗口
        long window = glfwCreateWindow(WIDTH, HEIGHT, "OpenGL View Transition", NULL, NULL);
        if (window == NULL) {
            throw new RuntimeException("Failed to create the GLFW window");
        }

        // 设置窗口的当前上下文
        glfwMakeContextCurrent(window);
        // 启用垂直同步
        glfwSwapInterval(1);

        // 初始化 GLEW
        GL.createCapabilities();

        // 设置视口
        glViewport(0, 0, WIDTH, HEIGHT);

        // 主循环
        while (!glfwWindowShouldClose(window)) {
            // 处理输入
            processInput(window);

            // 如果动画未完成,进行插值计算并更新视角
            if (!animationFinished) {
                t += 0.01f; // 增加插值因子
                if (t >= 1.0f) {
                    t = 1.0f;
                    animationFinished = true;
                }

                // 计算插值后的观察位置
                float[] interpolatedEye = new float[3];
                for (int i = 0; i < 3; i++) {
                    interpolatedEye[i] = initialEye[i] + t * (targetEye[i] - initialEye[i]);
                }

                // 计算插值后的观察目标点
                float[] interpolatedCenter = new float[3];
                for (int i = 0; i < 3; i++) {
                    interpolatedCenter[i] = initialCenter[i] + t * (targetCenter[i] - initialCenter[i]);
                }

                // 计算插值后的上方向向量
                float[] interpolatedUp = new float[3];
                for (int i = 0; i < 3; i++) {
                    interpolatedUp[i] = initialUp[i] + t * (targetUp[i] - initialUp[i]);
                }

                // 设置视角
                setView(interpolatedEye, interpolatedCenter, interpolatedUp);
            }

            // 清除颜色缓冲
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // 绘制场景(这里只是一个简单的三角形示例)
            drawScene();

            // 交换缓冲区,显示渲染结果
            glfwSwapBuffers(window);

            // 检查并调用事件回调
            glfwPollEvents();
        }

        // 释放资源
        glfwFreeCallbacks(window);
        glfwDestroyWindow(window);

        // 终止 GLFW
        glfwTerminate();
    }

    private static void processInput(long window) {
        if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
            glfwSetWindowShouldClose(window, true);
        }
    }

    private static void setView(float[] eye, float[] center, float[] up) {
        try (MemoryStack stack = stackPush()) {
            FloatBuffer eyeBuffer = BufferUtils.createFloatBuffer(eye.length);
            eyeBuffer.put(eye).flip();
            FloatBuffer centerBuffer = BufferUtils.createFloatBuffer(center.length);
            centerBuffer.put(center).flip();
            FloatBuffer upBuffer = BufferUtils.createFloatBuffer(up.length);
            upBuffer.put(up).flip();

            // 设置观察矩阵
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            gluLookAt(eyeBuffer.get(0), eyeBuffer.get(1), eyeBuffer.get(2),
                    centerBuffer.get(0), centerBuffer.get(1), centerBuffer.get(2),
                    upBuffer.get(0), upBuffer.get(1), upBuffer.get(2));
        }
    }

    private static void drawScene() {
        glBegin(GL_TRIANGLES);
        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3f(-0.5f, -0.5f, 0.0f);
        glColor3f(0.0f, 1.0f, 0.0f);
        glVertex3f(0.5f, -0.5f, 0.0f);
        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(0.0f, 0.5f, 0.0f);
        glEnd();
    }
}
  • Python(使用 PyOpenGL 和 GLFW 库)
import glfw
import OpenGL.GL as gl
import numpy as np
from OpenGL.GL.shaders import compileProgram, compileShader
import glm

# 窗口的宽度和高度
WIDTH = 800
HEIGHT = 600

# 初始视角和目标视角
initial_eye = glm.vec3(0.0, 0.0, 3.0)
target_eye = glm.vec3(2.0, 2.0, 5.0)
# 初始和目标观察目标点
initial_center = glm.vec3(0.0, 0.0, 0.0)
target_center = glm.vec3(1.0, 1.0, 0.0)
# 初始和目标上方向向量
initial_up = glm.vec3(0.0, 1.0, 0.0)
target_up = glm.vec3(0.5, 0.5, 1.0)

# 插值因子
t = 0.0
# 动画是否完成的标志
animation_finished = False

def main():
    # 初始化 GLFW
    if not glfw.init():
        return

    # 创建窗口
    window = glfw.create_window(WIDTH, HEIGHT, "OpenGL View Transition", None, None)
    if not window:
        glfw.terminate()
        return

    # 设置窗口的当前上下文
    glfw.make_context_current(window)

    # 设置视口
    gl.glViewport(0, 0, WIDTH, HEIGHT)

    # 编译顶点着色器和片段着色器
    vertex_shader_source = """
    #version 330
    layout (location = 0) in vec3 position;
    uniform mat4 view;
    uniform mat4 projection;
    void main()
    {
        gl_Position = projection * view * vec4(position, 1.0);
    }
    """
    fragment_shader_source = """
    #version 330
    out vec4 fragColor;
    void main()
    {
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
    """
    shader_program = compileProgram(
        compileShader(vertex_shader_source, gl.GL_VERTEX_SHADER),
        compileShader(fragment_shader_source, gl.GL_FRAGMENT_SHADER)
    )

    # 设置三角形的顶点数据
    vertices = np.array([
        -0.5, -0.5, 0.0,
        0.5, -0.5, 0.0,
        0.0, 0.5, 0.0
    ], dtype=np.float32)

    # 创建顶点缓冲对象(VBO)和顶点数组对象(VAO)
    vbo = gl.glGenBuffers(1)
    vao = gl.glGenVertexArrays(1)
    gl.glBindVertexArray(vao)
    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
    gl.glBufferData(gl.GL_ARRAY_BUFFER, vertices.nbytes, vertices, gl.GL_STATIC_DRAW)
    gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, None)
    gl.glEnableVertexAttribArray(0)

    # 主循环
    while not glfw.window_should_close(window):
        # 处理输入
        process_input(window)

        # 如果动画未完成,进行插值计算并更新视角
        if not animation_finished:
            t += 0.01
            if t >= 1.0:
                t = 1.0
                animation_finished = True

            # 计算插值后的观察位置
            interpolated_eye = glm.vec3(
                initial_eye.x + t * (target_eye.x - initial_eye.x),
                initial_eye.y + t * (target_eye.y - initial_eye.y),
                initial_eye.z + t * (target_eye.z - initial_eye.z)
            )

            # 计算插值后的观察目标点
            interpolated_center = glm.vec3(
                initial_center.x + t * (target_center.x - initial_center.x),
                initial_center.y + t * (target_center.y - initial_center.y),
                initial_center.z + t * (target_center.z - initial_center.z)
            )

            # 计算插值后的上方向向量
            interpolated_up = glm.vec3(
                initial_up.x + t * (target_up.x - initial_up.x),
                initial_up.y + t * (target_up.y - initial_up.y),
                initial_up.z + t * (target_up.z - initial_up.z)
            )

            # 计算观察矩阵和投影矩阵
            view_matrix = glm.lookAt(interpolated_eye, interpolated_center, interpolated_up)
            projection_matrix = glm.perspective(glm.radians(45.0), WIDTH / HEIGHT, 0.1, 100.0)

            # 使用 shader_program
            gl.glUseProgram(shader_program)
            # 上传观察矩阵和投影矩阵到 shader
            view_location = gl.glGetUniformLocation(shader_program, "view")
            projection_location = gl.glGetUniformLocation(shader_program, "projection")
            gl.glUniformMatrix4fv(view_location, 1, gl.GL_FALSE, glm.value_ptr(view_matrix))
            gl.glUniformMatrix4fv(projection_location, 1, gl.GL_FALSE, glm.value_ptr(projection_matrix))

        # 清除颜色缓冲和深度缓冲
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)

        # 绘制三角形
        gl.glBindVertexArray(vao)
        gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3)

        # 交换缓冲区,显示渲染结果
        glfw.swap_buffers(window)

        # 检查并调用事件回调
        glfw.poll_events()

    # 释放资源
    gl.glDeleteVertexArrays(1, [vao])
    gl.glDeleteBuffers(1, [vbo])
    gl.glDeleteProgram(shader_program)

    # 终止 GLFW
    glfw.terminate()

def process_input(window):
    if glfw.get_key(window, glfw.KEY_ESCAPE) == glfw.PRESS:
        glfw.set_window_should_close(window, True)

if __name__ == "__main__":
    main()

以下是一个简单的 WebGL 视角切换插值过渡动画的代码示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>WebGL View Transition</title>
</head>

<body>
    <canvas id="glCanvas" width="500" height="500"></canvas>
    <script type="text/javascript">
        // 获取 canvas 元素和 WebGL 上下文
        const canvas = document.getElementById('glCanvas');
        const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

        // 顶点着色器代码
        const vertexShaderSource = `
            attribute vec3 aPosition;
            uniform mat4 uProjectionMatrix;
            uniform mat4 uModelViewMatrix;
            void main() {
                gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
            }
        `;

        // 片段着色器代码
        const fragmentShaderSource = `
            precision mediump float;
            void main() {
                gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
            }
        `;

        // 创建着色器程序
        function createShaderProgram(gl, vertexShaderSource, fragmentShaderSource) {
            const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
            const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

            const shaderProgram = gl.createProgram();
            gl.attachShader(shaderProgram, vertexShader);
            gl.attachShader(shaderProgram, fragmentShader);
            gl.linkProgram(shaderProgram);

            if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
                console.error('Error linking shader program:', gl.getProgramInfoLog(shaderProgram));
                gl.deleteProgram(shaderProgram);
                return null;
            }

            return shaderProgram;
        }

        // 创建着色器
        function createShader(gl, type, source) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);

            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error('Error compiling shader:', gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }

            return shader;
        }

        // 初始化 WebGL
        function initWebGL() {
            const shaderProgram = createShaderProgram(gl, vertexShaderSource, fragmentShaderSource);

            if (!shaderProgram) {
                console.error('Failed to create shader program');
                return;
            }

            // 设置顶点数据
            const vertices = new Float32Array([
                -0.5, -0.5, 0.0,
                0.5, -0.5, 0.0,
                0.0, 0.5, 0.0
            ]);

            const buffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
            gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

            const aPosition = gl.getAttribLocation(shaderProgram, 'aPosition');
            gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(aPosition);

            return shaderProgram;
        }

        // 计算插值
        function interpolate(current, target, t) {
            return [
                current[0] + (target[0] - current[0]) * t,
                current[1] + (target[1] - current[1]) * t,
                current[2] + (target[2] - current[2]) * t
            ];
        }

        let currentView = [0, 0, 5]; // 当前视角位置
        let targetView = [2, 2, 10]; // 目标视角位置
        let t = 0; // 插值参数
        let shaderProgram;

        function drawScene() {
            if (!shaderProgram) {
                shaderProgram = initWebGL();
            }

            // 更新插值参数
            t += 0.01;
            if (t >= 1) {
                t = 1;
            }

            // 计算当前视角位置的插值
            const interpolatedView = interpolate(currentView, targetView, t);

            // 创建投影矩阵和模型视图矩阵
            const projectionMatrix = mat4.create();
            mat4.perspective(projectionMatrix, Math.PI / 4, canvas.width / canvas.height, 0.1, 100.0);

            const modelViewMatrix = mat4.create();
            mat4.lookAt(modelViewMatrix, interpolatedView, [0, 0, 0], [0, 1, 0]);

            // 将矩阵传递给着色器
            const uProjectionMatrix = gl.getUniformLocation(shaderProgram, 'uProjectionMatrix');
            const uModelViewMatrix = gl.getUniformLocation(shaderProgram, 'uModelViewMatrix');
            gl.uniformMatrix4fv(uProjectionMatrix, false, projectionMatrix);
            gl.uniformMatrix4fv(uModelViewMatrix, false, modelViewMatrix);

            // 清除画布并绘制三角形
            gl.clearColor(0.0, 0.0, 0.0, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
            gl.drawArrays(gl.TRIANGLES, 0, 3);

            // 如果插值未完成,继续请求动画帧
            if (t < 1) {
                requestAnimationFrame(drawScene);
            }
        }

        // 开始动画
        requestAnimationFrame(drawScene);
    </script>
</body>

</html>

在上述代码中:

  1. 首先获取了 canvas 元素和 WebGL 上下文。
  2. 定义了顶点着色器和片段着色器的代码,顶点着色器负责计算顶点的位置,片段着色器负责设置像素的颜色。
  3. 创建了着色器程序,并初始化了顶点数据。
  4. interpolate 函数用于计算两个视角位置之间的插值。
  5. drawScene 函数中,不断更新插值参数 t,计算当前视角位置的插值,并创建投影矩阵和模型视图矩阵。然后将矩阵传递给着色器,清除画布并绘制三角形。如果插值未完成,继续请求动画帧以实现动画效果。

请注意,上述代码仅是一个简单的示例,实际应用中可能需要根据具体需求进行更多的优化和扩展。同时,确保浏览器支持 WebGL 才能正常运行该代码。

posted @ 2024-11-10 13:38  MarsCactus  阅读(20)  评论(0编辑  收藏  举报