在OpenGL中實現視角切換插值過渡動畫

MarsCactus發表於2024-11-10

在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 才能正常執行該程式碼。

相關文章