3D融合场景demo

1:搭建glm+glfw+assimp环境;可以通过Nuget直接安装;(参考:vs2022配置Assimp环境 - 捞的不谈 - 博客园)

2:导入3D模型,模型来时learn opengl ,期间你需要引入一些直接开发的头文件,比如share.cpp,camera,mesh;要自己写shader 进行蒙皮着色,这里不引入骨骼

3: 3D 模型加载进来,开发根据鼠标移动,或者鼠标控制左右转,或者中MVP绕坐标旋转,这里要注意视角,模式等;

4:有个3D模型我们,可能常用的是在实时采集视频里引入3D,这个时候就涉及多层渲染,引入普通YUV,放在底层,不设置深度缓冲;这个YUV就充当了实时视频的角色;可以加些马赛克,掩藏背景;由于我这个YUV背景是外部环境,所以加了马赛克;

5:渲染时候显示中英文,安装freetype即可添加文字渲染shader;freetype由于Windows上直接安装的版本与vs可能不兼容,需要手动在项目属性里引入下静态lib;

6:给任务加些其他特效,比如脚底冒烟,闪电;冒烟采用随机粒子渲染,闪电采用随机分叉;分别渲染两个不同的层;因为他们其实没有什么相关性;

7:我们常用离屏渲染,以方便既可以上屏,又可以readpixel存图,所以增加了FBO+readpixel逻辑;

8:希望按下按键3D 小人抖动一下,抖动节奏按照比塞尔曲线衰减,抖动就是移动,移动的幅度跟贝塞尔斜率相关;所以通过shader+offset+贝塞尔抖动,让他能在几秒内完成抖动,配合脚底的例子光球一起;

9:鉴于背景用的现实场景,可以加些马赛克或者背景虚化,由于我的YUV和3D小人不是渲染在同一层的,所以背景可以完全虚化;并不需要人像抠图+区域虚化;

 glfwSwapInterval(1);可以控制按照垂直同步刷新帧率fps60

由于没有录屏,截图瞬时无法截图到震动和闪电效果;看几个随机截图的静态状态吧:(原创禁止转载

output_002

output_000

 

output_001

 

 

下边是完整代码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "shader.h"
#include "model.h"
#include "freetype_text.h"

#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstdlib>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

const unsigned int SCR_WIDTH = 1280;
const unsigned int SCR_HEIGHT = 720;

const int YUV_W = 800;
const int YUV_H = 600;
const char* YUV_FILE = "camera_800x600_306632.yuv";

const int FBO_W = 1280;
const int FBO_H = 720;

float lastFrame = 0.0f;
int fps = 0;
float fpsTimer = 0.0f;
int frameCount = 0;

bool needSaveFrame = false;

bool isShaking = false;
float shakeTimer = 0.0f;
const float SHAKE_DURATION = 2.5f;
const float SHAKE_AMPLITUDE = 0.5f;

float bezierShakeEnvelope(float t)
{
    float u = 1.0f - t;
    return u * u * 1.0f + 2.0f * u * t * 0.3f + t * t * 0.0f;
}

GLuint fbo = 0;
GLuint fboColorTex = 0;
GLuint fboDepthRbo = 0;

// [新增] 粒子系统
const int MAX_PARTICLES = 300;
struct Particle {
    glm::vec3 pos;
    glm::vec3 vel;
    float life;
    float maxLife;
};
std::vector<Particle> particles;
GLuint particleVAO = 0;
GLuint particleVBO = 0;

// [新增] 分形闪电
struct LightningBolt {
    std::vector<glm::vec3> vertices;
};
LightningBolt bolt;
float boltLife = 0.0f;
const float BOLT_DURATION = 0.12f;
GLuint lightningVAO = 0;
GLuint lightningVBO = 0;

float quadVertices[] = {
    -1.0f,  1.0f,  0.0f, 1.0f,
    -1.0f, -1.0f,  0.0f, 0.0f,
     1.0f, -1.0f,  1.0f, 0.0f,
    -1.0f,  1.0f,  0.0f, 1.0f,
     1.0f, -1.0f,  1.0f, 0.0f,
     1.0f,  1.0f,  1.0f, 1.0f
};

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

void initFBO()
{
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    glGenTextures(1, &fboColorTex);
    glBindTexture(GL_TEXTURE_2D, fboColorTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FBO_W, FBO_H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboColorTex, 0);

    glGenRenderbuffers(1, &fboDepthRbo);
    glBindRenderbuffer(GL_RENDERBUFFER, fboDepthRbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, FBO_W, FBO_H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboDepthRbo);

    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "ERROR: FBO incomplete!" << std::endl;

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void RGBAToNV12(unsigned char* rgba, int w, int h, unsigned char* yuv)
{
    unsigned char* yPlane = yuv;
    unsigned char* uvPlane = yuv + w * h;

    std::vector<int> yBuf(w * h);
    std::vector<int> uBuf(w * h);
    std::vector<int> vBuf(w * h);

    for (int y = 0; y < h; y++)
    {
        for (int x = 0; x < w; x++)
        {
            int srcIdx = ((h - 1 - y) * w + x) * 4;
            int r = rgba[srcIdx];
            int g = rgba[srcIdx + 1];
            int b = rgba[srcIdx + 2];

            int dstIdx = y * w + x;
            yBuf[dstIdx] = (int)(0.299f * r + 0.587f * g + 0.114f * b);
            uBuf[dstIdx] = (int)(-0.169f * r - 0.331f * g + 0.499f * b + 128.0f);
            vBuf[dstIdx] = (int)(0.499f * r - 0.419f * g - 0.081f * b + 128.0f);
        }
    }

    for (int i = 0; i < w * h; i++)
    {
        int yv = yBuf[i];
        yPlane[i] = (unsigned char)(yv < 0 ? 0 : (yv > 255 ? 255 : yv));
    }

    int uvW = w / 2;
    int uvH = h / 2;
    for (int y = 0; y < uvH; y++)
    {
        for (int x = 0; x < uvW; x++)
        {
            int y00 = (y * 2) * w + (x * 2);
            int y01 = (y * 2) * w + (x * 2 + 1);
            int y10 = (y * 2 + 1) * w + (x * 2);
            int y11 = (y * 2 + 1) * w + (x * 2 + 1);

            int uAvg = (uBuf[y00] + uBuf[y01] + uBuf[y10] + uBuf[y11]) / 4;
            int vAvg = (vBuf[y00] + vBuf[y01] + vBuf[y10] + vBuf[y11]) / 4;

            uAvg = uAvg < 0 ? 0 : (uAvg > 255 ? 255 : uAvg);
            vAvg = vAvg < 0 ? 0 : (vAvg > 255 ? 255 : vAvg);

            int uvIdx = (y * uvW + x) * 2;
            uvPlane[uvIdx] = (unsigned char)uAvg;
            uvPlane[uvIdx + 1] = (unsigned char)vAvg;
        }
    }
}

// [新增] 初始化粒子 VAO/VBO
void initParticles()
{
    glGenVertexArrays(1, &particleVAO);
    glGenBuffers(1, &particleVBO);
    glBindVertexArray(particleVAO);
    glBindBuffer(GL_ARRAY_BUFFER, particleVBO);
    glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 5 * sizeof(float), NULL, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(4 * sizeof(float)));
    glBindVertexArray(0);
}

// [新增] 更新粒子
void updateParticles(float dt, glm::vec3 emitPos)
{
    static float emitTimer = 0.0f;
    emitTimer += dt;

    // [修改] 降低发射频率,光球更大更持久
    while (emitTimer > 0.06f && (int)particles.size() < MAX_PARTICLES)
    {
        emitTimer -= 0.06f;
        Particle p;

        // 脚底圆形区域发射
        float angle = (rand() % 360) * 3.14159f / 180.0f;
        float radius = (rand() % 100) / 400.0f;
        p.pos = emitPos + glm::vec3(cos(angle) * radius, 0.0f, sin(angle) * radius);

        // [修改] 向上飘散,带初始螺旋速度
        float upSpeed = 0.4f + (rand() % 100) / 300.0f;
        p.vel = glm::vec3(cos(angle) * 0.5f, upSpeed, sin(angle) * 0.5f);

        p.life = 1.0f;
        p.maxLife = 2.0f + (rand() % 100) / 100.0f;
        particles.push_back(p);
    }

    for (auto& p : particles)
    {
        p.pos += p.vel * dt;

        // [新增] 螺旋上升:水平速度每帧旋转
        float rotSpeed = 3.0f * dt;
        float cx = cos(rotSpeed);
        float sz = sin(rotSpeed);
        float vx = p.vel.x * cx - p.vel.z * sz;
        float vz = p.vel.x * sz + p.vel.z * cx;
        p.vel.x = vx;
        p.vel.z = vz;

        // [新增] 越往上升越慢(灵气阻力感)
        p.vel *= (1.0f - dt * 0.2f);

        p.life -= dt * 0.3f;
    }

    particles.erase(std::remove_if(particles.begin(), particles.end(),
        [](const Particle& p) { return p.life <= 0.0f; }), particles.end());

    std::vector<float> data;
    data.reserve(particles.size() * 5);
    for (auto& p : particles)
    {
        data.push_back(p.pos.x);
        data.push_back(p.pos.y);
        data.push_back(p.pos.z);
        float lifeRatio = glm::clamp(p.life / p.maxLife, 0.0f, 1.0f);
        data.push_back(lifeRatio);

        // [修改] 光球大小:出生小 -> 中间膨胀大 -> 消失小
        float size = 0.3f + sin(lifeRatio * 3.14159f) * 0.7f;
        data.push_back(size);
    }

    if (!data.empty())
    {
        glBindBuffer(GL_ARRAY_BUFFER, particleVBO);
        glBufferSubData(GL_ARRAY_BUFFER, 0, data.size() * sizeof(float), data.data());
    }
}

// [新增] 渲染粒子
void renderParticles(Shader& shader, const glm::mat4& view, const glm::mat4& projection, int count)
{
    if (count <= 0) return;
    glEnable(GL_PROGRAM_POINT_SIZE);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE); // 加法混合,越叠越亮
    glDepthMask(GL_FALSE); // 粒子不写入深度,避免遮挡问题

    shader.use();
    shader.setMat4("view", view);
    shader.setMat4("projection", projection);
    glBindVertexArray(particleVAO);
    glDrawArrays(GL_POINTS, 0, count);

    glDepthMask(GL_TRUE);
    glDisable(GL_BLEND);
    glDisable(GL_PROGRAM_POINT_SIZE);
}
void initLightning()
{
    glGenVertexArrays(1, &lightningVAO);
    glGenBuffers(1, &lightningVBO);
    glBindVertexArray(lightningVAO);
    glBindBuffer(GL_ARRAY_BUFFER, lightningVBO);
    glBufferData(GL_ARRAY_BUFFER, 10000 * sizeof(glm::vec3), NULL, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glBindVertexArray(0);
}

void buildLightningRecursive(glm::vec3 a, glm::vec3 b, int depth, float offsetScale)
{
    if (depth <= 0) {
        bolt.vertices.push_back(a);
        bolt.vertices.push_back(b);
        return;
    }

    glm::vec3 mid = (a + b) * 0.5f;
    glm::vec3 dir = b - a;

    glm::vec3 randVec(
        (rand() % 1000 - 500) / 500.0f,
        (rand() % 1000 - 500) / 500.0f,
        (rand() % 1000 - 500) / 500.0f
    );
    glm::vec3 perp = glm::normalize(glm::cross(dir, randVec));
    if (glm::length(perp) < 0.01f) perp = glm::vec3(1, 0, 0);

    float offset = ((rand() % 1000) / 1000.0f - 0.5f) * 2.0f * offsetScale;
    mid += perp * offset;

    if (depth > 2 && (rand() % 100) < 45) {
        glm::vec3 branchDir = glm::normalize(glm::vec3(
            (rand() % 1000 - 500) / 500.0f,
            (rand() % 1000 - 500) / 1000.0f,
            (rand() % 1000 - 500) / 500.0f
        ));
        float branchLen = glm::length(dir) * (0.3f + (rand() % 500) / 1000.0f);
        glm::vec3 branchEnd = mid + branchDir * branchLen;
        buildLightningRecursive(mid, branchEnd, depth - 2, offsetScale * 0.5f);
    }

    buildLightningRecursive(a, mid, depth - 1, offsetScale * 0.5f);
    buildLightningRecursive(mid, b, depth - 1, offsetScale * 0.5f);
}

void spawnLightning(glm::vec3 start, glm::vec3 end)
{
    bolt.vertices.clear();
    buildLightningRecursive(start, end, 6, 1.2f);
    boltLife = BOLT_DURATION;

    if (!bolt.vertices.empty()) {
        glBindBuffer(GL_ARRAY_BUFFER, lightningVBO);
        glBufferSubData(GL_ARRAY_BUFFER, 0,
            bolt.vertices.size() * sizeof(glm::vec3),
            bolt.vertices.data());
    }
}

void renderLightning(Shader& shader, const glm::mat4& view, const glm::mat4& projection, float dt)
{
    if (boltLife <= 0.0f) return;
    boltLife -= dt;

    float intensity = 1.0f;
    if (boltLife < BOLT_DURATION * 0.5f) {
        intensity = boltLife / (BOLT_DURATION * 0.5f);
    }

    glDisable(GL_DEPTH_TEST);
    glLineWidth(3.0f);

    shader.use();
    shader.setMat4("view", view);
    shader.setMat4("projection", projection);
    shader.setVec3("boltColor", glm::vec3(0.9f, 0.95f, 1.0f));
    shader.setFloat("intensity", intensity);

    glBindVertexArray(lightningVAO);
    glDrawArrays(GL_LINES, 0, (GLsizei)bolt.vertices.size());

    glEnable(GL_DEPTH_TEST);
}

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Model + Smoke + Shake", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    srand((unsigned int)glfwGetTime());

    initFBO();
    initParticles();
    initLightning(); // [新增]闪电

    size_t ySize = YUV_W * YUV_H;
    size_t uvSize = YUV_W * YUV_H / 2;
    size_t totalSize = ySize + uvSize;
    std::vector<unsigned char> yuvData(totalSize);

    std::ifstream file(YUV_FILE, std::ios::binary);
    if (!file)
    {
        std::cout << "ERROR: Failed to open YUV file: " << YUV_FILE << std::endl;
        return -1;
    }
    file.read((char*)yuvData.data(), totalSize);
    file.close();

    GLuint yTex, uvTex;
    glGenTextures(1, &yTex);
    glBindTexture(GL_TEXTURE_2D, yTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, YUV_W, YUV_H, 0, GL_RED, GL_UNSIGNED_BYTE, yuvData.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glGenTextures(1, &uvTex);
    glBindTexture(GL_TEXTURE_2D, uvTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, YUV_W / 2, YUV_H / 2, 0, GL_RG, GL_UNSIGNED_BYTE, yuvData.data() + ySize);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    GLuint quadVAO, quadVBO;
    glGenVertexArrays(1, &quadVAO);
    glGenBuffers(1, &quadVBO);
    glBindVertexArray(quadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
    glBindVertexArray(0);

    Shader yuvShader("yuv_background.vert", "yuv_background.frag");
    Shader modelShader("model_loading.vert", "model_loading.frag");
    Shader particleShader("particle.vert", "particle.frag"); // [新增]
    Shader lightningShader("lightning.vert", "lightning.frag"); // [新增]

    FreeTypeText textRenderer;
    if (!textRenderer.init("C:/Windows/Fonts/msyh.ttc", FBO_W, FBO_H, "text.vert", "text.frag"))
    {
        std::cout << "Failed to init FreeType text renderer" << std::endl;
        return -1;
    }

    Model ourModel("nanosuit/nanosuit.obj");

    std::vector<unsigned char> rgbaPixels(FBO_W * FBO_H * 4);
    std::vector<unsigned char> yuvOutput(FBO_W * FBO_H * 3 / 2);

    lastFrame = static_cast<float>(glfwGetTime());

    while (!glfwWindowShouldClose(window))
    {
        float currentFrame = static_cast<float>(glfwGetTime());
        float deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;
        if (deltaTime > 0.1f) deltaTime = 0.016f;

        frameCount++;
        fpsTimer += deltaTime;
        if (fpsTimer >= 1.0f)
        {
            fps = frameCount;
            frameCount = 0;
            fpsTimer = 0.0f;
        }

        processInput(window);

        // ============================================
        // 阶段1:离屏渲染到 FBO
        // ============================================
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glViewport(0, 0, FBO_W, FBO_H);
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // ---- YUV 背景 ----
        glDisable(GL_DEPTH_TEST);
        yuvShader.use();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, yTex);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, uvTex);
        yuvShader.setInt("yTexture", 0);
        yuvShader.setInt("uvTexture", 1);
        glBindVertexArray(quadVAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        // ---- 模型 + 震动 ----
        glEnable(GL_DEPTH_TEST);
        modelShader.use();

        glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)FBO_W / (float)FBO_H, 0.1f, 100.0f);
        modelShader.setMat4("projection", projection);

        glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
        modelShader.setMat4("view", view);

        glm::vec3 shakeOffset(0.0f);
        if (isShaking)
        {
            shakeTimer += deltaTime;
            if (shakeTimer >= SHAKE_DURATION)
            {
                isShaking = false;
                shakeTimer = 0.0f;
                std::cout << "Shake finished!" << std::endl;
            }
            else
            {
                float t = shakeTimer / SHAKE_DURATION;
                float envelope = bezierShakeEnvelope(t);
                float freqX = 80.0f + (1.0f - t) * 30.0f;
                float freqY = 60.0f + (1.0f - t) * 20.0f;
                shakeOffset.x = sin(shakeTimer * freqX) * SHAKE_AMPLITUDE * envelope;
                shakeOffset.y = cos(shakeTimer * freqY) * SHAKE_AMPLITUDE * envelope * 0.6f;
            }
        }

        float rotAngle = currentFrame * 30.0f;
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(0.0f, -1.0f, 0.0f) + shakeOffset);
        model = glm::rotate(model, glm::radians(rotAngle), glm::vec3(0.0f, 1.0f, 0.0f));
        model = glm::scale(model, glm::vec3(0.2f, 0.2f, 0.2f));
        modelShader.setMat4("model", model);

        ourModel.Draw(modelShader);
        // [新增] ---- 分形闪电 ----
        renderLightning(lightningShader, view, projection, deltaTime);
        // [新增] ---- 粒子冒烟/光球(模型脚底下)----
        glm::vec3 emitPos = glm::vec3(0.0f, -1.0f, 0.0f) + shakeOffset;
        updateParticles(deltaTime, emitPos);
        renderParticles(particleShader, view, projection, (int)particles.size());

        // ---- 文字 ----
        glDisable(GL_DEPTH_TEST);
        std::wstringstream wss;
        wss << L"帧率: " << fps << L" | 空格震动+闪电| P保存YUV|粒子冒烟";
        textRenderer.renderText(wss.str(), 25.0f, FBO_H - 50.0f, 0.5f, glm::vec3(0.0f, 1.0f, 0.0f));
        glEnable(GL_DEPTH_TEST);

        // ============================================
        // 阶段2:FBO 复制到屏幕
        // ============================================
        glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glBlitFramebuffer(0, 0, FBO_W, FBO_H, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_COLOR_BUFFER_BIT, GL_LINEAR);

        // ============================================
        // 阶段3:从 FBO 保存 YUV
        // ============================================
        if (needSaveFrame)
        {
            glBindFramebuffer(GL_FRAMEBUFFER, fbo);
            glReadPixels(0, 0, FBO_W, FBO_H, GL_RGBA, GL_UNSIGNED_BYTE, rgbaPixels.data());
            RGBAToNV12(rgbaPixels.data(), FBO_W, FBO_H, yuvOutput.data());

            std::ofstream outFile("output.yuv", std::ios::binary);
            if (outFile)
            {
                outFile.write((char*)yuvOutput.data(), yuvOutput.size());
                outFile.close();
                std::cout << "Saved output.yuv (" << FBO_W << "x" << FBO_H << ")" << std::endl;
            }
            needSaveFrame = false;
        }

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(1, &quadVAO);
    glDeleteBuffers(1, &quadVBO);
    glDeleteTextures(1, &yTex);
    glDeleteTextures(1, &uvTex);
    glDeleteTextures(1, &fboColorTex);
    glDeleteRenderbuffers(1, &fboDepthRbo);
    glDeleteFramebuffers(1, &fbo);
    glDeleteVertexArrays(1, &particleVAO);
    glDeleteBuffers(1, &particleVBO);
    glDeleteVertexArrays(1, &lightningVAO); // [新增]
    glDeleteBuffers(1, &lightningVBO);      // [新增]

    glfwTerminate();
    return 0;
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    static bool spacePressedLast = false;
    bool spacePressed = glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS;
    if (spacePressed && !spacePressedLast && !isShaking)
    {
        isShaking = true;
        shakeTimer = 0.0f;
        
        // [新增] 同时劈一道闪电
        glm::vec3 boltStart(
            (rand() % 100 - 50) / 30.0f,
            4.0f,
            (rand() % 100 - 50) / 30.0f
        );
        glm::vec3 boltEnd(0.0f, -0.5f, 0.0f);
        spawnLightning(boltStart, boltEnd);

        std::cout << "Shake + Lightning triggered!" << std::endl;
    }
    spacePressedLast = spacePressed;

    static bool pPressedLast = false;
    bool pPressed = glfwGetKey(window, GLFW_KEY_P) == GLFW_PRESS;
    if (pPressed && !pPressedLast)
    {
        needSaveFrame = true;
        std::cout << "Save triggered!" << std::endl;
    }
    pPressedLast = pPressed;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

 文字渲染:

#ifndef FREETYPE_TEXT_H
#define FREETYPE_TEXT_H

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <ft2build.h>
#include FT_FREETYPE_H

#include <map>
#include <string>
#include <iostream>

#include "shader.h"

struct Character {
    GLuint textureID;
    glm::ivec2 size;
    glm::ivec2 bearing;
    GLuint advance;
};

class FreeTypeText
{
public:
    std::map<wchar_t, Character> characters; // [修改] key 改成 wchar_t
    FT_Library ft = nullptr;                 // [新增] 保留库实例,用于动态加载
    FT_Face face = nullptr;                  // [新增] 保留字体面
    GLuint VAO = 0, VBO = 0;
    Shader* textShader = nullptr;

    bool init(const char* fontPath, GLuint screenWidth, GLuint screenHeight, const char* vertPath, const char* fragPath)
    {
        textShader = new Shader(vertPath, fragPath);

        if (FT_Init_FreeType(&ft))
        {
            std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
            return false;
        }

        if (FT_New_Face(ft, fontPath, 0, &face))
        {
            std::cout << "ERROR::FREETYPE: Failed to load font: " << fontPath << std::endl;
            return false;
        }

        FT_Set_Pixel_Sizes(face, 0, 48);

        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

        // [修改] 只预加载常用 ASCII,中文字按需加载
        for (unsigned char c = 0; c < 128; c++)
        {
            loadCharacter((wchar_t)c);
        }

        // 配置 VAO/VBO
        glGenVertexArrays(1, &VAO);
        glGenBuffers(1, &VBO);
        glBindVertexArray(VAO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);

       
        // [修改] 恢复原版:正交投影原点在左下角(Y轴向上)
        glm::mat4 projection = glm::ortho(0.0f, (float)screenWidth, 0.0f, (float)screenHeight);
        textShader->use();
        textShader->setMat4("projection", projection);

        return true;
    }

    // [新增] 按需加载单个字符
    bool loadCharacter(wchar_t c)
    {
        if (characters.find(c) != characters.end())
            return true;

        if (FT_Load_Char(face, c, FT_LOAD_RENDER))
        {
            std::wcout << L"ERROR::FREETYPE: Failed to load Glyph: " << c << std::endl;
            return false;
        }

        GLuint texture;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(
            GL_TEXTURE_2D,
            0,
            GL_RED,
            face->glyph->bitmap.width,
            face->glyph->bitmap.rows,
            0,
            GL_RED,
            GL_UNSIGNED_BYTE,
            face->glyph->bitmap.buffer
        );

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        Character character = {
            texture,
            glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
            glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
            static_cast<GLuint>(face->glyph->advance.x)
        };
        characters.insert(std::pair<wchar_t, Character>(c, character));
        return true;
    }

    // [修改] 接收宽字符串
    void renderText(std::wstring text, float x, float y, float scale, glm::vec3 color)
    {
        textShader->use();
        textShader->setVec3("textColor", color);
        glActiveTexture(GL_TEXTURE0);
        glBindVertexArray(VAO);

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        for (wchar_t c : text)
        {
            // [新增] 按需加载
            if (characters.find(c) == characters.end())
                loadCharacter(c);

            Character ch = characters[c];

            float xpos = x + ch.bearing.x * scale;
            float ypos = y - (ch.size.y - ch.bearing.y) * scale; // [恢复] 原版计算

            float w = ch.size.x * scale;
            float h = ch.size.y * scale;

            // [恢复] 原版 UV(LearnOpenGL 标准做法)
            float vertices[6][4] = {
                { xpos,     ypos + h,   0.0f, 0.0f },
                { xpos,     ypos,       0.0f, 1.0f },
                { xpos + w, ypos,       1.0f, 1.0f },

                { xpos,     ypos + h,   0.0f, 0.0f },
                { xpos + w, ypos,       1.0f, 1.0f },
                { xpos + w, ypos + h,   1.0f, 0.0f }
            };

            glBindTexture(GL_TEXTURE_2D, ch.textureID);
            glBindBuffer(GL_ARRAY_BUFFER, VBO);
            glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glDrawArrays(GL_TRIANGLES, 0, 6);

            x += (ch.advance >> 6) * scale;
        }

        glBindVertexArray(0);
        glBindTexture(GL_TEXTURE_2D, 0);
        glDisable(GL_BLEND);
    }
};

#endif

  

#version 330 core
in vec2 TexCoords;
out vec4 color;

uniform sampler2D text;
uniform vec3 textColor;

void main()
{
    vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
    color = vec4(textColor, 1.0) * sampled;
}



#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
out vec2 TexCoords;

uniform mat4 projection;

void main()
{
    gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
    TexCoords = vertex.zw;
}

//文字的shader

  背景YUV渲染:

#version 330 core
in vec2 TexCoord;
out vec4 FragColor;

uniform sampler2D yTexture;
uniform sampler2D uvTexture;

float hash(vec2 p)
{
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}

void main()
{
    float y = texture(yTexture, TexCoord).r;
    float u = texture(uvTexture, TexCoord).r - 0.5;
    float v = texture(uvTexture, TexCoord).g - 0.5;

    float r = y + 1.402 * v;
    float g = y - 0.344136 * u - 0.714136 * v;
    float b = y + 1.772 * u;

    vec3 baseColor = vec3(r, g, b);

    vec2 blocks = vec2(40.0, 30.0);
    vec2 cellId = floor(TexCoord * blocks);
    vec2 inside = fract(TexCoord * blocks);

    float rnd = hash(cellId);
    float rnd2 = hash(cellId + vec2(13.37, 73.31));
    float rnd3 = hash(cellId + vec2(51.17, 37.73));

    float angle = rnd * 6.28318;
    float c = cos(angle);
    float s = sin(angle);
    vec2 rot = vec2(
        c * (inside.x - 0.5) - s * (inside.y - 0.5),
        s * (inside.x - 0.5) + c * (inside.y - 0.5)
    ) + 0.5;

    vec2 sampleCoord = (cellId + rot) / blocks;
    sampleCoord += (vec2(rnd2, rnd3) - 0.5) * 0.025;

    float y2 = texture(yTexture, sampleCoord).r;
    float u2 = texture(uvTexture, sampleCoord).r - 0.5;
    float v2 = texture(uvTexture, sampleCoord).g - 0.5;

    float r2 = y2 + 1.402 * v2;
    float g2 = y2 - 0.344136 * u2 - 0.714136 * v2;
    float b2 = y2 + 1.772 * u2;

    vec3 color = vec3(r2, g2, b2);

    float lum = dot(color, vec3(0.299, 0.587, 0.114));
    color *= 0.85 + lum * 0.3;

    vec2 edgeDist = abs(inside - 0.5);
    float edge = max(edgeDist.x, edgeDist.y);
    float border = smoothstep(0.47, 0.5, edge);

    float edgeGlow = border * (0.4 + rnd * 0.6);
    vec3 glow = vec3(0.9, 0.95, 1.0) * edgeGlow * 0.5;

    vec2 hlPos = vec2(rnd2, rnd3);
    float highlight = exp(-length((inside - hlPos) * blocks) * 2.5);
    highlight *= smoothstep(0.2, 0.6, lum);
    vec3 hlColor = vec3(1.0, 0.98, 0.95) * highlight * 1.5;

    float starChance = step(0.94, rnd);
    float star = exp(-length(inside - vec2(0.5)) * blocks.x * 0.25);
    star = pow(star, 4.0) * starChance;
    vec3 starColor = vec3(1.0, 0.97, 0.9) * star * 3.0;

    color +=  hlColor + starColor;

    float vig = 1.0 - length(TexCoord - 0.5) * 0.4;
    color *= vig;

    FragColor = vec4(color, 1.0);
}
//上边这个增加了闪闪发光的马赛克的frag

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(aPos, 0.0, 1.0);
TexCoord = aTexCoord;
}

 

模型就按照assimp加载的后的解析顺序渲染即可

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

out vec2 TexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    TexCoords = aTexCoords;
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

#version 330 core
in vec2 TexCoords;

out vec4 FragColor;

uniform sampler2D texture_diffuse1;

void main()
{
    FragColor = texture(texture_diffuse1, TexCoords);
}

闪电和粒子光球

闪电
#version 330 core
out vec4 FragColor;

uniform vec3 boltColor;
uniform float intensity;

void main()
{
    FragColor = vec4(boltColor * intensity, 1.0);
}
#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * vec4(aPos, 1.0);
}
粒子光球:
#version 330 core
in float vLife;
out vec4 FragColor;

void main()
{
    vec2 coord = gl_PointCoord - vec2(0.5);
    float dist = length(coord);
    if (dist > 0.5) discard;
    
    float glow = exp(-dist * dist * 8.0);
    
    vec3 coreColor = vec3(0.1, 0.0, 0.0);
    vec3 edgeColor = vec3(0.8, 0.9, 0.0);
    vec3 color = mix(coreColor, edgeColor, 1.0 - vLife);
    
    color *= 1.5 + glow * 2.0;
    
    float alpha = glow * vLife * 0.8;
    
    FragColor = vec4(color, alpha);
}
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in float aLife;
layout (location = 2) in float aSize;

uniform mat4 view;
uniform mat4 projection;

out float vLife;

void main()
{
    gl_Position = projection * view * vec4(aPos, 1.0);
    gl_PointSize = aSize * 30.0; 
    vLife = aLife;
}

辅助:相机,mesh,glad

#ifndef OPENGL_CAMERA_H
#define OPENGL_CAMERA_H

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <vector>

// 相机移动方向枚举
enum Camera_Movement {
    FORWARD,
    BACKWARD,
    LEFT,
    RIGHT
};

// 默认相机参数
const float YAW         = -90.0f;
const float PITCH       =  0.0f;
const float SPEED       =  2.5f;
const float SENSITIVITY =  0.1f;
const float ZOOM        =  45.0f;


class Camera
{
public:
    glm::vec3 Position;
    glm::vec3 Front;
    glm::vec3 Up;
    glm::vec3 Right;
    glm::vec3 WorldUp;

    float Yaw;
    float Pitch;

    float MovementSpeed;
    float MouseSensitivity;
    float Zoom;

    // 构造函数
    Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH)
        : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
    {
        Position = position;
        WorldUp = up;
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();
    }

    // 获取View矩阵
    glm::mat4 GetViewMatrix()
    {
        return glm::lookAt(Position, Position + Front, Up);
    }

    // 处理键盘输入
    void ProcessKeyboard(Camera_Movement direction, float deltaTime)
    {
        float velocity = MovementSpeed * deltaTime;
        if (direction == FORWARD)
            Position += Front * velocity;
        if (direction == BACKWARD)
            Position -= Front * velocity;
        if (direction == LEFT)
            Position -= Right * velocity;
        if (direction == RIGHT)
            Position += Right * velocity;
    }

    // 处理鼠标移动
    void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true)
    {
        xoffset *= MouseSensitivity;
        yoffset *= MouseSensitivity;

        Yaw   += xoffset;
        Pitch += yoffset;

        if (constrainPitch)
        {
            if (Pitch > 89.0f)
                Pitch = 89.0f;
            if (Pitch < -89.0f)
                Pitch = -89.0f;
        }
        updateCameraVectors();
    }

    // 鼠标滚轮缩放
    void ProcessMouseScroll(float yoffset)
    {
        Zoom -= (float)yoffset;
        if (Zoom < 1.0f)
            Zoom = 1.0f;
        if (Zoom > 45.0f)
            Zoom = 45.0f;
    }

private:
    void updateCameraVectors()
    {
        glm::vec3 front;
        front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));
        front.y = sin(glm::radians(Pitch));
        front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));
        Front = glm::normalize(front);

        Right = glm::normalize(glm::cross(Front, WorldUp));
        Up    = glm::normalize(glm::cross(Right, Front));
    }
};
#endif

mesh里骨骼最终没有使用因为暂时渲染静态,没有骨骼的文件

#ifndef OPENGL_MESH_H
#define OPENGL_MESH_H

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include "shader.h"

#include <string>
#include <vector>

// [新增] 每个顶点最多受 4 根骨骼影响
#define MAX_BONE_INFLUENCE 4

// [修改] Vertex 结构体增加骨骼绑定数据
struct Vertex {
    glm::vec3 Position;
    glm::vec3 Normal;
    glm::vec2 TexCoords;
    glm::vec3 Tangent;
    glm::vec3 Bitangent;
    // [新增]
    int m_BoneIDs[MAX_BONE_INFLUENCE];
    // [新增]
    float m_Weights[MAX_BONE_INFLUENCE];
};

struct Texture {
    unsigned int id;
    std::string type;
    std::string path;
};

class Mesh {
public:
    std::vector<Vertex> vertices;
    std::vector<unsigned int> indices;
    std::vector<Texture> textures;
    unsigned int VAO;

    Mesh(std::vector<Vertex> vertices, std::vector<unsigned int> indices, std::vector<Texture> textures)
    {
        this->vertices = vertices;
        this->indices = indices;
        this->textures = textures;

        setupMesh();
    }

    void Draw(Shader& shader)
    {
        unsigned int diffuseNr = 1;
        unsigned int specularNr = 1;
        unsigned int normalNr = 1;
        unsigned int heightNr = 1;
        for (unsigned int i = 0; i < textures.size(); i++)
        {
            glActiveTexture(GL_TEXTURE0 + i);

            std::string number;
            std::string name = textures[i].type;
            if (name == "texture_diffuse")
                number = std::to_string(diffuseNr++);
            else if (name == "texture_specular")
                number = std::to_string(specularNr++);
            else if (name == "texture_normal")
                number = std::to_string(normalNr++);
            else if (name == "texture_height")
                number = std::to_string(heightNr++);

            glUniform1i(glGetUniformLocation(shader.ID, (name + number).c_str()), i);
            glBindTexture(GL_TEXTURE_2D, textures[i].id);
        }

        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);

        glActiveTexture(GL_TEXTURE0);
    }

private:
    unsigned int VBO, EBO;

    void setupMesh()
    {
        glGenVertexArrays(1, &VAO);
        glGenBuffers(1, &VBO);
        glGenBuffers(1, &EBO);

        glBindVertexArray(VAO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

        // 顶点位置
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
        // 法线
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
        // uv纹理坐标
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
        // tangent切线
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Tangent));
        // bitangent副切线
        glEnableVertexAttribArray(4);
        glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Bitangent));
        // [新增] 骨骼ID (整数属性,必须用 glVertexAttribIPointer)
        glEnableVertexAttribArray(5);
        glVertexAttribIPointer(5, 4, GL_INT, sizeof(Vertex), (void*)offsetof(Vertex, m_BoneIDs));
        // [新增] 骨骼权重
        glEnableVertexAttribArray(6);
        glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, m_Weights));

        glBindVertexArray(0);
    }
};
#endif

model

#ifndef MODEL_H
#define MODEL_H

#include <glad/glad.h> 

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include "stb_image.h"
#include "gl_mesh.h"

#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
#include <map>
#include <cassert> 


#define MAX_BONE_INFLUENCE 4

unsigned int TextureFromFile(const char* path, const std::string& directory, bool gamma = false);

// [����] Assimp ����ת glm ����
inline glm::mat4 aiMatrix4x4ToGlm(const aiMatrix4x4& from)
{
    glm::mat4 to;
    to[0][0] = from.a1; to[1][0] = from.b1; to[2][0] = from.c1; to[3][0] = from.d1;
    to[0][1] = from.a2; to[1][1] = from.b2; to[2][1] = from.c2; to[3][1] = from.d2;
    to[0][2] = from.a3; to[1][2] = from.b3; to[2][2] = from.c3; to[3][2] = from.d3;
    to[0][3] = from.a4; to[1][3] = from.b4; to[2][3] = from.c4; to[3][3] = from.d4;
    return to;
}

// [����] ������Ϣ��ID + ��ģ�Ϳռ䵽�����ռ��ƫ�ƾ���
struct BoneInfo
{
    int id;
    glm::mat4 offset;
};

// [����] �����ڵ㣬����㼶��ϵ�ͱ任
struct BoneNode
{
    std::string name;
    glm::mat4 originalTransform;   // ģ���ļ����ԭʼ���ر任
    glm::mat4 localTransform;      // ��ǰ���ر任���ɱ������޸ģ�
    std::vector<BoneNode> children;
};

class Model
{
public:
    std::vector<Texture> textures_loaded;
    std::vector<Mesh>    meshes;
    std::string directory;
    bool gammaCorrection;

    // [����] �����������
    std::map<std::string, BoneInfo> m_BoneInfoMap;
    int m_BoneCounter = 0;
    BoneNode m_RootNode;
    std::vector<glm::mat4> m_FinalBoneMatrices;

    Model(std::string const& path, bool gamma = false) : gammaCorrection(gamma)
    {
        loadModel(path);
        m_FinalBoneMatrices.resize(100, glm::mat4(1.0f)); // [����] Ԥ����100����������
    }

    void Draw(Shader& shader)
    {
        for (unsigned int i = 0; i < meshes.size(); i++)
            meshes[i].Draw(shader);
    }

    // [����]
    auto& GetBoneInfoMap() { return m_BoneInfoMap; }
    // [����]
    int& GetBoneCount() { return m_BoneCounter; }

    // [����] �������ƣ�����������������ת�Ƕȣ�pitch=x, yaw=y, roll=z������λ����
    void SetBoneRotation(const std::string& boneName, float pitch, float yaw, float roll)
    {
        BoneNode* node = FindBoneNode(&m_RootNode, boneName);
        if (!node) return;

        // ����ԭʼƽ�ƣ��滻��ת
        glm::vec3 pos = glm::vec3(node->originalTransform[3]);

        glm::mat4 rotX = glm::rotate(glm::mat4(1.0f), glm::radians(pitch), glm::vec3(1, 0, 0));
        glm::mat4 rotY = glm::rotate(glm::mat4(1.0f), glm::radians(yaw), glm::vec3(0, 1, 0));
        glm::mat4 rotZ = glm::rotate(glm::mat4(1.0f), glm::radians(roll), glm::vec3(0, 0, 1));

        node->localTransform = glm::translate(glm::mat4(1.0f), pos) * rotY * rotX * rotZ;
    }

    // [����] �Ӹ��ڵ㿪ʼ�ݹ�������й��������ձ任����
    void UpdateSkeleton()
    {
        UpdateSkeletonNode(&m_RootNode, glm::mat4(1.0f));
    }

    // [����] ��ȡ���չ����������飨������ɫ����
    const std::vector<glm::mat4>& GetFinalBoneMatrices() const { return m_FinalBoneMatrices; }

private:
    void loadModel(std::string const& path)
    {
        Assimp::Importer importer;
        const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);
        if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
        {
            std::cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << std::endl;
            return;
        }
        directory = path.substr(0, path.find_last_of('/'));

        processNode(scene->mRootNode, scene);
        ReadNodeHierarchy(m_RootNode, scene->mRootNode); // [����] ��ȡ�����㼶
    }

    void processNode(aiNode* node, const aiScene* scene)
    {
        for (unsigned int i = 0; i < node->mNumMeshes; i++)
        {
            aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
            meshes.push_back(processMesh(mesh, scene));
        }
        for (unsigned int i = 0; i < node->mNumChildren; i++)
        {
            processNode(node->mChildren[i], scene);
        }
    }

    // [����] ���������ù���Ȩ��
    void SetVertexBoneData(Vertex& vertex, int boneID, float weight)
    {
        for (int i = 0; i < MAX_BONE_INFLUENCE; ++i)
        {
            if (vertex.m_BoneIDs[i] < 0)
            {
                vertex.m_Weights[i] = weight;
                vertex.m_BoneIDs[i] = boneID;
                break;
            }
        }
    }

    // [����] �� Assimp ��������ȡ����Ȩ����Ϣ
    void ExtractBoneWeightForVertices(std::vector<Vertex>& vertices, aiMesh* mesh, const aiScene* scene)
    {
        for (int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
        {
            int boneID = -1;
            std::string boneName = mesh->mBones[boneIndex]->mName.C_Str();
            if (m_BoneInfoMap.find(boneName) == m_BoneInfoMap.end())
            {
                BoneInfo newBoneInfo;
                newBoneInfo.id = m_BoneCounter;
                newBoneInfo.offset = aiMatrix4x4ToGlm(mesh->mBones[boneIndex]->mOffsetMatrix);
                m_BoneInfoMap[boneName] = newBoneInfo;
                boneID = m_BoneCounter;
                m_BoneCounter++;
            }
            else
            {
                boneID = m_BoneInfoMap[boneName].id;
            }
            assert(boneID != -1);
            auto weights = mesh->mBones[boneIndex]->mWeights;
            int numWeights = mesh->mBones[boneIndex]->mNumWeights;

            for (int weightIndex = 0; weightIndex < numWeights; ++weightIndex)
            {
                int vertexId = weights[weightIndex].mVertexId;
                float weight = weights[weightIndex].mWeight;
                assert(vertexId <= (int)vertices.size());
                SetVertexBoneData(vertices[vertexId], boneID, weight);
            }
        }
    }

    Mesh processMesh(aiMesh* mesh, const aiScene* scene)
    {
        std::vector<Vertex> vertices;
        std::vector<unsigned int> indices;
        std::vector<Texture> textures;

        for (unsigned int i = 0; i < mesh->mNumVertices; i++)
        {
            Vertex vertex;
            glm::vec3 vector;
            vector.x = mesh->mVertices[i].x;
            vector.y = mesh->mVertices[i].y;
            vector.z = mesh->mVertices[i].z;
            vertex.Position = vector;

            if (mesh->HasNormals())
            {
                vector.x = mesh->mNormals[i].x;
                vector.y = mesh->mNormals[i].y;
                vector.z = mesh->mNormals[i].z;
                vertex.Normal = vector;
            }

            if (mesh->mTextureCoords[0])
            {
                glm::vec2 vec;
                vec.x = mesh->mTextureCoords[0][i].x;
                vec.y = mesh->mTextureCoords[0][i].y;
                vertex.TexCoords = vec;
                vector.x = mesh->mTangents[i].x;
                vector.y = mesh->mTangents[i].y;
                vector.z = mesh->mTangents[i].z;
                vertex.Tangent = vector;
                vector.x = mesh->mBitangents[i].x;
                vector.y = mesh->mBitangents[i].y;
                vector.z = mesh->mBitangents[i].z;
                vertex.Bitangent = vector;
            }
            else
                vertex.TexCoords = glm::vec2(0.0f, 0.0f);

            // [����] ��ʼ����������
            for (int j = 0; j < MAX_BONE_INFLUENCE; j++)
            {
                vertex.m_BoneIDs[j] = -1;
                vertex.m_Weights[j] = 0.0f;
            }

            vertices.push_back(vertex);
        }

        // [����] ��ȡ����Ȩ��
        ExtractBoneWeightForVertices(vertices, mesh, scene);

        for (unsigned int i = 0; i < mesh->mNumFaces; i++)
        {
            aiFace face = mesh->mFaces[i];
            for (unsigned int j = 0; j < face.mNumIndices; j++)
                indices.push_back(face.mIndices[j]);
        }

        aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
        std::vector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
        textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
        std::vector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
        textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
        std::vector<Texture> normalMaps = loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal");
        textures.insert(textures.end(), normalMaps.begin(), normalMaps.end());
        std::vector<Texture> heightMaps = loadMaterialTextures(material, aiTextureType_AMBIENT, "texture_height");
        textures.insert(textures.end(), heightMaps.begin(), heightMaps.end());

        return Mesh(vertices, indices, textures);
    }

    std::vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, std::string typeName)
    {
        std::vector<Texture> textures;
        for (unsigned int i = 0; i < mat->GetTextureCount(type); i++)
        {
            aiString str;
            mat->GetTexture(type, i, &str);
            bool skip = false;
            for (unsigned int j = 0; j < textures_loaded.size(); j++)
            {
                if (std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0)
                {
                    textures.push_back(textures_loaded[j]);
                    skip = true;
                    break;
                }
            }
            if (!skip)
            {
                Texture texture;
                texture.id = TextureFromFile(str.C_Str(), this->directory);
                texture.type = typeName;
                texture.path = str.C_Str();
                textures.push_back(texture);
                textures_loaded.push_back(texture);
            }
        }
        return textures;
    }

    // [����] �ݹ��ȡ Assimp �ڵ�㼶������ BoneNode ��
    void ReadNodeHierarchy(BoneNode& dest, const aiNode* src)
    {
        dest.name = src->mName.C_Str();
        dest.originalTransform = aiMatrix4x4ToGlm(src->mTransformation);
        dest.localTransform = dest.originalTransform;
        dest.children.resize(src->mNumChildren);
        for (unsigned int i = 0; i < src->mNumChildren; i++)
        {
            ReadNodeHierarchy(dest.children[i], src->mChildren[i]);
        }
    }

    // [����] �����Ʋ��ҹ����ڵ�
    BoneNode* FindBoneNode(BoneNode* node, const std::string& name)
    {
        if (node->name == name) return node;
        for (auto& child : node->children)
        {
            BoneNode* found = FindBoneNode(&child, name);
            if (found) return found;
        }
        return nullptr;
    }

    // [����] �ݹ����������ձ任���󣺸��任 �� ���ر任 �� Offset
    void UpdateSkeletonNode(BoneNode* node, const glm::mat4& parentTransform)
    {
        glm::mat4 globalTransform = parentTransform * node->localTransform;
        if (m_BoneInfoMap.find(node->name) != m_BoneInfoMap.end())
        {
            int boneId = m_BoneInfoMap[node->name].id;
            m_FinalBoneMatrices[boneId] = globalTransform * m_BoneInfoMap[node->name].offset;
        }
        for (auto& child : node->children)
        {
            UpdateSkeletonNode(&child, globalTransform);
        }
    }
};

unsigned int TextureFromFile(const char* path, const std::string& directory, bool gamma)
{
    std::string filename = std::string(path);
    filename = directory + '/' + filename;

    unsigned int textureID;
    glGenTextures(1, &textureID);

    int width, height, nrComponents;
    unsigned char* data = stbi_load(filename.c_str(), &width, &height, &nrComponents, 0);
    if (data)
    {
        GLenum format;
        if (nrComponents == 1)
            format = GL_RED;
        else if (nrComponents == 3)
            format = GL_RGB;
        else if (nrComponents == 4)
            format = GL_RGBA;

        glBindTexture(GL_TEXTURE_2D, textureID);
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        stbi_image_free(data);
    }
    else
    {
        std::cout << "Texture failed to load at path: " << path << std::endl;
        stbi_image_free(data);
    }

    return textureID;
}
#endif

nanosuit的人物模型,我是从learnopengl拿来的;以及stb_img_write.h,stb_image.h等都可以从网上找到;

posted on 2026-08-20 11:37  邗影  阅读(2)  评论(0)    收藏  举报

导航