diff --git a/.gitignore b/.gitignore index 92c472b2b..0d0d7b2d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,42 @@ -# ignores following folders -bin/ +``` +# Build artifacts +*.o +*.obj +*.exe +*.dll +*.so +*.a +*.out + +# CMake build directory build/ -out/ + +# Dependencies +venv/ +.venv/ +__pycache__/ +*.egg-info/ + +# Logs and temp files +*.log +*.tmp +*.swp + +# Environment +.env +.env.local +*.env.* + +# Editors +.vscode/ +.idea/ + +# Coverage reports +coverage/ +htmlcov/ +.coverage + +# OS specific +.DS_Store +Thumbs.db +``` \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cffe24db..0ab8df972 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,6 +186,7 @@ set(GUEST_ARTICLES 8.guest/2022/6.physically_based_bloom 8.guest/2022/7.area_lights/1.area_light 8.guest/2022/7.area_lights/2.multiple_area_lights + 8.guest/2024/1.fps_demo ) configure_file(configuration/root_directory.h.in configuration/root_directory.h) diff --git a/src/8.guest/2024/1.fps_demo/README.md b/src/8.guest/2024/1.fps_demo/README.md new file mode 100644 index 000000000..77d3fb9f3 --- /dev/null +++ b/src/8.guest/2024/1.fps_demo/README.md @@ -0,0 +1,117 @@ +# FPS Demo - First Person Shooter Game Basic Setup + +这是一个基于 OpenGL 的 FPS(第一人称射击)游戏基础设置演示。 + +## 功能特性 + +- **FPS 相机控制**: + - WASD 键移动(前后左右) + - 鼠标控制视角 + - 鼠标滚轮缩放 + +- **基本光照**: + - 环境光 + - 漫反射光照 + - 镜面高光 + +- **3D 渲染**: + - 带纹理的旋转立方体 + - 深度测试 + - 法线贴图支持 + +## 文件结构 + +``` +1.fps_demo/ +├── fps_demo.cpp # 主程序源代码 +├── fps_demo.vs # 顶点着色器 +└── fps_demo.fs # 片段着色器 +``` + +## 构建说明 + +### Linux (Ubuntu/Debian) + +1. 安装依赖: +```bash +sudo apt-get install libglfw3-dev libglm-dev libassimp-dev libglew-dev libfreetype-dev libgl1-mesa-dev +``` + +2. 创建构建目录并编译: +```bash +cd /path/to/LearnOpenGL +mkdir build && cd build +cmake .. +make +``` + +3. 运行程序: +```bash +cd ../bin/8.guest/2024 +./8.guest__2024__1.fps_demo +``` + +### Windows + +1. 确保已安装 Visual Studio 和 CMake +2. 使用 CMake GUI 或命令行生成项目文件 +3. 编译并运行 + +### macOS + +```bash +brew install cmake assimp glm glfw freetype +mkdir build && cd build +cmake -G Xcode .. +# 或使用 make +cmake .. +make +``` + +## 操作说明 + +| 按键 | 功能 | +|------|------| +| W | 向前移动 | +| S | 向后移动 | +| A | 向左移动 | +| D | 向右移动 | +| 鼠标移动 | 控制视角 | +| 鼠标滚轮 | 缩放视野 | +| ESC | 退出程序 | + +## 技术细节 + +### 相机系统 +使用 LearnOpenGL 的 Camera 类,提供: +- 欧拉角计算(Yaw, Pitch) +- 观察矩阵生成 +- 键盘和鼠标输入处理 + +### 着色器 +- **顶点着色器**: 处理顶点位置、纹理坐标和法线,应用 MVP 变换 +- **片段着色器**: 实现 Phong 光照模型(环境光 + 漫反射 + 镜面光) + +### 顶点数据 +每个顶点包含: +- 位置 (3 float) +- 纹理坐标 (2 float) +- 法线 (3 float) + +## 扩展建议 + +要完善 FPS 游戏,可以考虑添加: +1. 玩家碰撞检测 +2. 武器系统和射击机制 +3. 敌人 AI +4. 地图/关卡系统 +5. 音效系统(irrKlang) +6. 动画系统(Assimp) +7. UI/HUD 界面 +8. 物理引擎集成 + +## 参考资料 + +- [LearnOpenGL](https://learnopengl.com) +- [GLFW Documentation](https://www.glfw.org/documentation.html) +- [GLM Documentation](https://glm.g-truc.net/) diff --git a/src/8.guest/2024/1.fps_demo/fps_demo.cpp b/src/8.guest/2024/1.fps_demo/fps_demo.cpp new file mode 100644 index 000000000..01df1df9c --- /dev/null +++ b/src/8.guest/2024/1.fps_demo/fps_demo.cpp @@ -0,0 +1,284 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +// Function declarations +void framebuffer_size_callback(GLFWwindow* window, int width, int height); +void mouse_callback(GLFWwindow* window, double xpos, double ypos); +void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); +void processInput(GLFWwindow *window); + +// Settings +const unsigned int SCR_WIDTH = 1280; +const unsigned int SCR_HEIGHT = 720; + +// Camera +Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); +float lastX = SCR_WIDTH / 2.0f; +float lastY = SCR_HEIGHT / 2.0f; +bool firstMouse = true; + +// Timing +float deltaTime = 0.0f; +float lastFrame = 0.0f; + +int main() +{ + // GLFW: Initialize and configure + glfwInit(); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + +#ifdef __APPLE__ + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); +#endif + + // GLFW: Window creation + GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "FPS Demo", NULL, NULL); + if (window == NULL) + { + std::cout << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + return -1; + } + glfwMakeContextCurrent(window); + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + glfwSetCursorPosCallback(window, mouse_callback); + glfwSetScrollCallback(window, scroll_callback); + + // Capture mouse for FPS controls + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + // GLAD: Load all OpenGL function pointers + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + std::cout << "Failed to initialize GLAD" << std::endl; + return -1; + } + + // Configure global OpenGL state + glEnable(GL_DEPTH_TEST); + + // Build and compile shader program + Shader ourShader("fps_demo.vs", "fps_demo.fs"); + + // Set up vertex data (and buffer(s)) and configure vertex attributes + float vertices[] = { + // Positions // Texture Coords // Normals + -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, -1.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, -1.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, + + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, + + -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, + + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, + + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, + + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f + }; + + unsigned int VBO, VAO; + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + + glBindVertexArray(VAO); + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Position attribute + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + // Texture coord attribute + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + // Normal attribute + glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float))); + glEnableVertexAttribArray(2); + + // Load and create texture + unsigned int texture1; + glGenTextures(1, &texture1); + glBindTexture(GL_TEXTURE_2D, texture1); + + // Set texture wrapping parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + // Set texture filtering parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Load image, create texture and generate mipmaps + int width, height, nrChannels; + stbi_set_flip_vertically_on_load(true); + unsigned char *data = stbi_load(FileSystem::getPath("resources/textures/container.jpg").c_str(), &width, &height, &nrChannels, 0); + if (data) + { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + } + else + { + std::cout << "Failed to load texture" << std::endl; + } + stbi_image_free(data); + + // Tell OpenGL which texture unit the sampler belongs to + ourShader.use(); + ourShader.setInt("texture1", 0); + + // Light direction (from top-right-front) + glm::vec3 lightDir(1.0f, 1.0f, 1.0f); + ourShader.setVec3("lightDir", lightDir); + + // Render loop + while (!glfwWindowShouldClose(window)) + { + // Per-frame time logic + float currentFrame = static_cast(glfwGetTime()); + deltaTime = currentFrame - lastFrame; + lastFrame = currentFrame; + + // Input handling + processInput(window); + + // Render + glClearColor(0.1f, 0.1f, 0.15f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Bind texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture1); + + // Activate shader + ourShader.use(); + + // Pass projection matrix to shader + glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), + (float)SCR_WIDTH / (float)SCR_HEIGHT, + 0.1f, 100.0f); + ourShader.setMat4("projection", projection); + + // Camera/view transformation + glm::mat4 view = camera.GetViewMatrix(); + ourShader.setMat4("view", view); + + // Calculate view direction for lighting + glm::vec3 viewDir = camera.Position - glm::vec3(0.0f, 0.0f, 0.0f); + ourShader.setVec3("viewDir", viewDir); + + // Model transformation (rotate cube slowly) + glm::mat4 model = glm::mat4(1.0f); + model = glm::rotate(model, glm::radians(currentFrame * 20.0f), glm::vec3(0.5f, 1.0f, 0.3f)); + ourShader.setMat4("model", model); + + // Draw the cube + glBindVertexArray(VAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + + // Swap buffers and poll IO events + glfwSwapBuffers(window); + glfwPollEvents(); + } + + // Clean up resources + glDeleteVertexArrays(1, &VAO); + glDeleteBuffers(1, &VBO); + + // Terminate GLFW + glfwTerminate(); + return 0; +} + +// Process all input: query GLFW whether relevant keys are pressed/released +void processInput(GLFWwindow *window) +{ + if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) + glfwSetWindowShouldClose(window, true); + + // FPS camera controls: WASD for movement + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) + camera.ProcessKeyboard(FORWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) + camera.ProcessKeyboard(BACKWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) + camera.ProcessKeyboard(LEFT, deltaTime); + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) + camera.ProcessKeyboard(RIGHT, deltaTime); +} + +// GLFW: Whenever the window size changed, this callback function executes +void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); +} + +// GLFW: Whenever the mouse moves, this callback is called +void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) +{ + float xpos = static_cast(xposIn); + float ypos = static_cast(yposIn); + + if (firstMouse) + { + lastX = xpos; + lastY = ypos; + firstMouse = false; + } + + float xoffset = xpos - lastX; + float yoffset = lastY - ypos; // Reversed since y-coordinates go from bottom to top + + lastX = xpos; + lastY = ypos; + + camera.ProcessMouseMovement(xoffset, yoffset); +} + +// GLFW: Whenever the mouse scroll wheel scrolls, this callback is called +void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) +{ + camera.ProcessMouseScroll(static_cast(yoffset)); +} diff --git a/src/8.guest/2024/1.fps_demo/fps_demo.fs b/src/8.guest/2024/1.fps_demo/fps_demo.fs new file mode 100644 index 000000000..da5ec58cd --- /dev/null +++ b/src/8.guest/2024/1.fps_demo/fps_demo.fs @@ -0,0 +1,33 @@ +#version 330 core +out vec4 FragColor; + +in vec2 TexCoords; +in vec3 Normal; +in vec3 FragPos; + +uniform sampler2D texture1; +uniform vec3 lightDir; +uniform vec3 viewDir; + +void main() +{ + // ambient + float ambientStrength = 0.2; + vec3 ambient = ambientStrength * vec3(texture(texture1, TexCoords)); + + // diffuse + vec3 norm = normalize(Normal); + vec3 lightDirection = normalize(-lightDir); + float diff = max(dot(norm, lightDirection), 0.0); + vec3 diffuse = diff * vec3(texture(texture1, TexCoords)); + + // specular + float specularStrength = 0.5; + vec3 viewDirection = normalize(viewDir); + vec3 reflectDir = reflect(-lightDirection, norm); + float spec = pow(max(dot(viewDirection, reflectDir), 0.0), 32); + vec3 specular = specularStrength * vec3(1.0) * spec; + + vec3 result = ambient + diffuse + specular; + FragColor = vec4(result, 1.0); +} diff --git a/src/8.guest/2024/1.fps_demo/fps_demo.vs b/src/8.guest/2024/1.fps_demo/fps_demo.vs new file mode 100644 index 000000000..25545f735 --- /dev/null +++ b/src/8.guest/2024/1.fps_demo/fps_demo.vs @@ -0,0 +1,21 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoord; +layout (location = 2) in vec3 aNormal; + +out vec2 TexCoords; +out vec3 Normal; +out vec3 FragPos; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + FragPos = vec3(model * vec4(aPos, 1.0)); + Normal = mat3(transpose(inverse(model))) * aNormal; + TexCoords = aTexCoord; + + gl_Position = projection * view * vec4(FragPos, 1.0); +}