492 lines
15 KiB
C++
492 lines
15 KiB
C++
// 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;
|
||
}
|