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



下边是完整代码:
#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等都可以从网上找到;
浙公网安备 33010602011771号