opengl-homework/main.cpp
2025-05-21 12:14:57 +08:00

492 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// main.cpp
#include <GL/glew.h>
#include <GL/glut.h>
#include <iostream>
#include <vector>
#include <cmath> // For sin, cos
// GLM Headers
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/constants.hpp> // 包含 glm::pi
#include "shader_utils.hpp" // 我们创建的着色器加载工具
// 窗口尺寸
int windowWidth = 1024;
int windowHeight = 768;
// 着色器程序ID
GLuint programID;
// GLuint pickingProgramID; // (可选,如果实现拾取)
// Uniform ID
GLuint MatrixID_MVP, MatrixID_M, MatrixID_V;
GLuint LightPosID, LightColorID, LightPowerID, ViewPosID;
GLuint TextureSamplerID;
// VAO 和 VBO
GLuint CubeVAO, CubeVBO_vertices, CubeVBO_normals, CubeVBO_uvs;
// GLuint LightVAO; // 用于表示光源的球体 (使用 GLUT 绘制可能不需要单独VAO)
// 纹理
GLuint checkerboardTextureID;
// 相机参数
glm::vec3 cameraPosition = glm::vec3(4.0f, 3.0f, 3.0f);
float cameraHorizontalAngle = glm::pi<float>(); // 初始水平角 (pi)
float cameraVerticalAngle = 0.0f; // 初始垂直角
float initialFoV = 45.0f;
float cameraSpeed = 3.0f; // 单位/秒
float mouseSpeed = 0.002f;
// 鼠标状态
int lastMouseX = windowWidth / 2;
int lastMouseY = windowHeight / 2;
bool mouseLeftDown = false;
// 光源属性
glm::vec3 lightPos = glm::vec3(2.0f, 2.0f, 2.0f);
glm::vec3 lightColor = glm::vec3(1.0f, 1.0f, 1.0f);
float lightPower = 50.0f; // 启用光照强度
// 立方体旋转
float cubeRotationAngle = 0.0f;
// 函数声明
void initGL();
void display();
void reshape(int, int);
void keyboard(unsigned char, int, int);
void mouseButton(int button, int state, int x, int y);
void mouseMotion(int x, int y);
void idle();
void createCheckerboardTexture();
void setupCubeBuffers();
// 立方体顶点数据 (位置, 法线, UV)
// 36个顶点因为每个面有不同法线或UV不能共享顶点
const GLfloat cube_vertices[] = {
// Back face
-0.5f, -0.5f, -0.5f, // Bottom-left
0.5f, 0.5f, -0.5f, // top-right
0.5f, -0.5f, -0.5f, // bottom-right
0.5f, 0.5f, -0.5f, // top-right
-0.5f, -0.5f, -0.5f, // bottom-left
-0.5f, 0.5f, -0.5f, // top-left
// Front face
-0.5f, -0.5f, 0.5f, // bottom-left
0.5f, -0.5f, 0.5f, // bottom-right
0.5f, 0.5f, 0.5f, // top-right
0.5f, 0.5f, 0.5f, // top-right
-0.5f, 0.5f, 0.5f, // top-left
-0.5f, -0.5f, 0.5f, // bottom-left
// Left face
-0.5f, 0.5f, 0.5f, // top-right
-0.5f, 0.5f, -0.5f, // top-left
-0.5f, -0.5f, -0.5f, // bottom-left
-0.5f, -0.5f, -0.5f, // bottom-left
-0.5f, -0.5f, 0.5f, // bottom-right
-0.5f, 0.5f, 0.5f, // top-right
// Right face
0.5f, 0.5f, 0.5f, // top-left
0.5f, -0.5f, -0.5f, // bottom-right
0.5f, 0.5f, -0.5f, // top-right
0.5f, -0.5f, -0.5f, // bottom-right
0.5f, 0.5f, 0.5f, // top-left
0.5f, -0.5f, 0.5f, // bottom-left
// Bottom face
-0.5f, -0.5f, -0.5f, // top-right
0.5f, -0.5f, -0.5f, // top-left
0.5f, -0.5f, 0.5f, // bottom-left
0.5f, -0.5f, 0.5f, // bottom-left
-0.5f, -0.5f, 0.5f, // bottom-right
-0.5f, -0.5f, -0.5f, // top-right
// Top face
-0.5f, 0.5f, -0.5f, // top-left
-0.5f, 0.5f, 0.5f, // bottom-left
0.5f, 0.5f, 0.5f, // bottom-right
0.5f, 0.5f, 0.5f, // bottom-right
0.5f, 0.5f, -0.5f, // top-right
-0.5f, 0.5f, -0.5f, // top-left
};
const GLfloat cube_normals[] = {
// Back face
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
// Front face
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
// Left face
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
// Right face
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
// Bottom face
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f,
// Top face
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
};
const GLfloat cube_uvs[] = {
// Back face
0.0f, 0.0f,
1.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f,
// Front face
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
// Left face
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
// Right face
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
// Bottom face
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f,
// Top face
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
};
void createCheckerboardTexture() {
const int texWidth = 64;
const int texHeight = 64;
GLubyte checkerboard[texHeight][texWidth][3]; // RGB
for (int i = 0; i < texHeight; i++) {
for (int j = 0; j < texWidth; j++) {
// 创建更明显的棋盘格图案
int c = (((i / 8) % 2) == ((j / 8) % 2)) ? 255 : 100;
checkerboard[i][j][0] = (GLubyte)c;
checkerboard[i][j][1] = (GLubyte)c;
checkerboard[i][j][2] = (GLubyte)c;
}
}
glGenTextures(1, &checkerboardTextureID);
glBindTexture(GL_TEXTURE_2D, checkerboardTextureID);
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_MAG_FILTER, GL_NEAREST); // 使用NEAREST以便清晰看到像素块
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); // 使用mipmap
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, checkerboard);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
void setupCubeBuffers() {
glGenVertexArrays(1, &CubeVAO);
glBindVertexArray(CubeVAO);
// 顶点位置
glGenBuffers(1, &CubeVBO_vertices);
glBindBuffer(GL_ARRAY_BUFFER, CubeVBO_vertices);
glBufferData(GL_ARRAY_BUFFER, sizeof(cube_vertices), cube_vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(0);
// 法线
glGenBuffers(1, &CubeVBO_normals);
glBindBuffer(GL_ARRAY_BUFFER, CubeVBO_normals);
glBufferData(GL_ARRAY_BUFFER, sizeof(cube_normals), cube_normals, GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(1);
// UV坐标
glGenBuffers(1, &CubeVBO_uvs);
glBindBuffer(GL_ARRAY_BUFFER, CubeVBO_uvs);
glBufferData(GL_ARRAY_BUFFER, sizeof(cube_uvs), cube_uvs, GL_STATIC_DRAW);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(2);
glBindVertexArray(0);
}
void initGL() {
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
std::cerr << "Failed to initialize GLEW" << std::endl;
exit(-1);
}
std::cout << "Using GLEW Version: " << glewGetString(GLEW_VERSION) << std::endl;
std::cout << "OpenGL Renderer: " << glGetString(GL_RENDERER) << std::endl;
std::cout << "OpenGL Version: " << glGetString(GL_VERSION) << std::endl;
std::cout << "GLSL Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
glClearColor(0.1f, 0.1f, 0.2f, 0.0f);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// glEnable(GL_CULL_FACE); // 如果需要,可以开启背面剔除
programID = LoadShaders("shaders/phong.vert", "shaders/phong.frag");
if (programID == 0) {
std::cerr << "Failed to load shaders. Exiting." << std::endl;
// getchar(); // 暂停查看错误
exit(-1);
}
MatrixID_MVP = glGetUniformLocation(programID, "MVP");
MatrixID_M = glGetUniformLocation(programID, "M");
MatrixID_V = glGetUniformLocation(programID, "V");
LightPosID = glGetUniformLocation(programID, "lightPos_worldspace");
LightColorID = glGetUniformLocation(programID, "lightColor");
LightPowerID = glGetUniformLocation(programID, "lightPower");
ViewPosID = glGetUniformLocation(programID, "viewPos_worldspace");
TextureSamplerID = glGetUniformLocation(programID, "myTextureSampler");
createCheckerboardTexture();
setupCubeBuffers();
glutSetCursor(GLUT_CURSOR_NONE);
glutWarpPointer(windowWidth / 2, windowHeight / 2);
// 确保在第一次鼠标移动前 lastMouseX/Y 已被正确设置
lastMouseX = windowWidth / 2;
lastMouseY = windowHeight / 2;
}
void display() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(programID);
glm::vec3 direction(
cos(cameraVerticalAngle) * sin(cameraHorizontalAngle),
sin(cameraVerticalAngle),
cos(cameraVerticalAngle) * cos(cameraHorizontalAngle)
);
glm::vec3 right = glm::vec3(
sin(cameraHorizontalAngle - glm::pi<float>()/2.0f),
0,
cos(cameraHorizontalAngle - glm::pi<float>()/2.0f)
);
glm::vec3 up = glm::cross(right, direction);
glm::mat4 ProjectionMatrix = glm::perspective(glm::radians(initialFoV), (float)windowWidth / (float)windowHeight, 0.1f, 100.0f);
glm::mat4 ViewMatrix = glm::lookAt(
cameraPosition,
cameraPosition + direction,
up
);
glm::mat4 ModelMatrixCube = glm::mat4(1.0);
ModelMatrixCube = glm::translate(ModelMatrixCube, glm::vec3(0.0f, 0.0f, 0.0f));
ModelMatrixCube = glm::rotate(ModelMatrixCube, cubeRotationAngle, glm::vec3(0.0f, 1.0f, 0.0f));
ModelMatrixCube = glm::scale(ModelMatrixCube, glm::vec3(1.0f, 1.0f, 1.0f));
glm::mat4 MVP_Cube = ProjectionMatrix * ViewMatrix * ModelMatrixCube;
glUniformMatrix4fv(MatrixID_MVP, 1, GL_FALSE, &MVP_Cube[0][0]);
glUniformMatrix4fv(MatrixID_M, 1, GL_FALSE, &ModelMatrixCube[0][0]);
glUniformMatrix4fv(MatrixID_V, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniform3fv(LightPosID, 1, &lightPos[0]);
glUniform3fv(LightColorID, 1, &lightColor[0]);
glUniform1f(LightPowerID, lightPower); // 传递光照强度
glUniform3fv(ViewPosID, 1, &cameraPosition[0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, checkerboardTextureID);
glUniform1i(TextureSamplerID, 0);
glBindVertexArray(CubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
// 绘制光源 (使用 GLUT 绘制一个白色小球)
glUseProgram(0);
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(glm::value_ptr(ProjectionMatrix));
glMatrixMode(GL_MODELVIEW);
glm::mat4 MV_Light = ViewMatrix * glm::translate(glm::mat4(1.0f), lightPos) * glm::scale(glm::mat4(1.0f), glm::vec3(0.1f));
glLoadMatrixf(glm::value_ptr(MV_Light));
glColor3f(lightColor.r, lightColor.g, lightColor.b);
glutSolidSphere(0.1, 10, 10);
glutSwapBuffers();
}
void reshape(int w, int h) {
windowWidth = w;
windowHeight = h;
if (h == 0) h = 1;
glViewport(0, 0, w, h);
glutPostRedisplay();
}
void keyboard(unsigned char key, int x, int y) {
float deltaTime = 0.016f; // 假设大约60FPS, 更精确的delta time计算会更好
glm::vec3 direction(
cos(cameraVerticalAngle) * sin(cameraHorizontalAngle),
sin(cameraVerticalAngle),
cos(cameraVerticalAngle) * cos(cameraHorizontalAngle)
);
glm::vec3 right = glm::vec3(
sin(cameraHorizontalAngle - glm::pi<float>()/2.0f),
0,
cos(cameraHorizontalAngle - glm::pi<float>()/2.0f)
);
switch (key) {
case 'w': case 'W': cameraPosition += direction * deltaTime * cameraSpeed; break;
case 's': case 'S': cameraPosition -= direction * deltaTime * cameraSpeed; break;
case 'a': case 'A': cameraPosition -= right * deltaTime * cameraSpeed; break;
case 'd': case 'D': cameraPosition += right * deltaTime * cameraSpeed; break;
case ' ': cameraPosition.y += deltaTime * cameraSpeed; break; // 空格键向上
case 'c': case 'C': cameraPosition.y -= deltaTime * cameraSpeed; break; // C键向下
case 27: // ESC 键
glDeleteBuffers(1, &CubeVBO_vertices);
glDeleteBuffers(1, &CubeVBO_normals);
glDeleteBuffers(1, &CubeVBO_uvs);
glDeleteVertexArrays(1, &CubeVAO);
glDeleteProgram(programID);
glDeleteTextures(1, &checkerboardTextureID);
exit(0);
break;
}
glutPostRedisplay();
}
void mouseButton(int button, int state, int x, int y) {
if (button == GLUT_LEFT_BUTTON) {
if (state == GLUT_DOWN) {
mouseLeftDown = true;
lastMouseX = x;
lastMouseY = y;
} else if (state == GLUT_UP) {
mouseLeftDown = false;
}
}
}
void mouseMotion(int x, int y) {
// 仅当鼠标在窗口内时处理,避免跳动
if (x < 0 || x >= windowWidth || y < 0 || y >= windowHeight) {
// 当鼠标移出窗口时,可以选择不更新视角或者将鼠标重置回中心
// 如果选择重置,可能需要更复杂的逻辑来避免在窗口边缘的抖动
// mouseLeftDown = false; // 取消注释此行可以在鼠标移出时停止拖动视角
return;
}
if (mouseLeftDown) {
float deltaX = float(x - lastMouseX);
float deltaY = float(y - lastMouseY);
cameraHorizontalAngle += mouseSpeed * deltaX;
cameraVerticalAngle += mouseSpeed * deltaY;
// 限制垂直角度
cameraVerticalAngle = glm::clamp(cameraVerticalAngle, -glm::pi<float>()/2.0f + 0.01f, glm::pi<float>()/2.0f - 0.01f);
lastMouseX = x;
lastMouseY = y;
// FPS游戏风格的鼠标控制 (可选):
// if (x != windowWidth/2 || y != windowHeight/2) {
// glutWarpPointer(windowWidth/2, windowHeight/2);
// lastMouseX = windowWidth/2;
// lastMouseY = windowHeight/2;
// }
glutPostRedisplay();
}
}
void idle() {
cubeRotationAngle += 0.005f;
if (cubeRotationAngle > 2.0f * glm::pi<float>()) {
cubeRotationAngle -= 2.0f * glm::pi<float>();
}
glutPostRedisplay();
}
int main(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(windowWidth, windowHeight);
glutInitWindowPosition(100, 100);
glutCreateWindow("OpenGL Course Project - 3D Scene");
initGL();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMouseFunc(mouseButton);
glutMotionFunc(mouseMotion);
// glutPassiveMotionFunc(mouseMotion); // 如果希望鼠标不按下也触发 warp (如果使用)
glutIdleFunc(idle);
glutMainLoop();
return 0;
}