作者运行环境:XCode Version 12.3 (12C33)
需要先自行配置opengl
,glfw
,glad
,glm
的环境。(用homebrew命令行下载,并在项目里配置路径即可。
项目源代码:
链接: https://pan.baidu.com/s/1aMGJGCb8vUvaw3dCBbOkow 密码: go4e
链接: https://pan.baidu.com/s/1itfQ_GsuBPH0ehqCUzt_Ew 密码: c7r9
/// OpenGL Mathematics (GLM) is a header only C++ mathematics library for graphics software based on the OpenGL Shading Language (GLSL) specifications.
#include
#include
#include
#include
#include
#include
#include "shader.h"
#include "camera.h"
#include "parser.h"
using namespace std;
using namespace glm;
#define INF 1.0e10
// 函数声明
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 picking_callback(GLFWwindow *window, int button, int action, int mods);
void key_callback(GLFWwindow *window, int key, int scancode, int action, int mode);
void interactionFunctions();
// 设置窗口大小
const unsigned int SCR_WIDTH = 1024;
const unsigned int SCR_HEIGHT = 768;
// 相机
Camera camera(vec3(0.0f, 0.0f, 30.0f));
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true; // 判断鼠标是否刚进入第一模式, 用于更新光标禁用时鼠标的初始坐标
// 时间参数
float deltaTime = 0.0f; // 也用于相机的调整速度
float lastFrame = 0.0f;
// 兔子参数
mat4 modelBunny = mat4(1.0f); //四阶单位阵
GLuint vertices_size, indices_size; //无符号整型 顶点以及面片size
// !!!: 光照参数
vec3 PointlightPos(15.0f, 15.0f, 15.0f); //三维向量 光照
// 鼠标参数
GLfloat mouseX = SCR_WIDTH / 2.0;
GLfloat mouseY = SCR_HEIGHT / 2.0;
GLuint selectedPointIndice = 0; // max
const int MAXPOINT = 40000;
const int MAXINDEX = 70000;
const char* normalFile = "bunny_normal.ply2";
const char *SPLIT = "--------------------------------------------------------------";
GLfloat vertices[MAXPOINT*6];
GLuint indices[MAXINDEX*3];
bool keys[1024]; // 存取键码,判断按键情况
bool isAttenuation = false; // 判断光照是否衰减
bool isFlashlight = false; // 判断是否打开闪光灯
bool cursorDisabled = true; // 判断光标是否被禁用
void description(){
cout << SPLIT << std::endl;
cout << "Starting GLFW context, OpenGL 3.3\n";
cout << "=: 点光源衰减\n";
cout << "ENTER: 光照开关\n";
cout << "A: 相机向左.\n";
cout << "D: 相机向右.\n";
cout << "W: 相机向前\n";
cout << "S: 相机向后.\n";
cout << "方向键<-: 兔子左移.\n";
cout << "方向键->: 兔子右移.\n";
cout << "J: 兔子向左旋转.\n";
cout << "L: 兔子向右旋转.\n";
cout << "I: 兔子向前旋转.\n";
cout << "K: 兔子向后旋转\n";
cout << "↑或鼠标向上滚动: 兔子放大\n";
cout << "↓或鼠标向下滚动: 兔子缩小\n";
cout << SPLIT << std::endl;
}
//窗口初始化函数
GLFWwindow* init(){
glfwInit();//初始化glfw库
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 窗口创建
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Stanford bunny", NULL, NULL);
//如果窗口创建失败,返回失败信息。
if (window == NULL){
cout << "Failed to create GLFW window" << endl;
glfwTerminate(); //释放所有资源
exit(EXIT_SUCCESS);
}
glfwMakeContextCurrent(window); // 设置当前的窗口上下文
//注册函数,该函数还可以注册其他相关硬件的回调函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//设置按键回调函数
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback); //设置光标回调函数
glfwSetMouseButtonCallback(window, picking_callback); //设置鼠标按键回调函数
glfwSetScrollCallback(window, scroll_callback); //设置滚轮回调函数
// 让GLFW捕获鼠标
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// glad: 加载所有openGL函数指针
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
std::cout << "Failed to initialize GLAD" << std::endl;
exit(EXIT_SUCCESS);
}
// 配置opengl的全局状态: 开启深度测试
glEnable(GL_DEPTH_TEST);
return window;
}
int main(){
// glfw: 初始化并且配置相关参数
// 输出按键说明
description();
//调用窗口初始化函数
GLFWwindow* window = init();
//建立并编译着色器
Shader lightingShader("bunny.vs", "bunny.fs"); //vs文件用来处理顶点 fs文件用来处理像素
Shader lampShader("lamp.vs", "lamp.fs"); //灯着色器
Shader selectShader("select.vs", "select.fs"); //拾取着色器
// 设置顶点数据(和缓冲区)并配置顶点属性
// 导入ply2文件
parseNormal(normalFile, vertices, indices, vertices_size, indices_size);
// parseIndex(normalFile, vertices, indices, vertices_size, indices_size);
/*
//test code:
cout << "测试是否读取正确:" << endl;
for (int i = 0; i < 3; i++)
{
cout << vertices[6*i] << " " << vertices[6*i+1] << " " << vertices[6*i+2] << " " << vertices[6*i+3] << " " << vertices[6*i+4] << " " << vertices[6*i+5] << std::endl;
}
cout << SPLIT << endl;
*/
// 灯光顶点数据
float lightVertices[] = {
-0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f
// -0.2f, -0.2f, -0.2f, 0.2f, -0.2f, -0.2f,
// 0.2f, 0.2f, -0.2f, 0.2f, 0.2f, -0.2f,
// -0.2f, 0.2f, -0.2f, -0.2f, -0.2f, -0.2f,
// -0.2f, -0.2f, 0.2f, 0.2f, -0.2f, 0.2f,
// 0.2f, 0.2f, 0.2f, 0.2f, 0.2f, 0.2f,
// -0.2f, 0.2f, 0.2f, -0.2f, -0.2f, 0.2f,
// -0.2f, 0.2f, 0.2f, -0.2f, 0.2f, -0.2f,
// -0.2f, -0.2f, -0.2f, -0.2f, -0.2f, -0.2f,
// -0.2f, -0.2f, 0.2f, -0.2f, 0.2f, 0.2f,
// 0.2f, 0.2f, 0.2f, 0.2f, 0.2f, -0.2f,
// 0.2f, -0.2f, -0.2f, 0.2f, -0.2f, -0.2f,
// 0.2f, -0.2f, 0.2f, 0.2f, 0.2f, 0.2f,
// -0.2f, -0.2f, -0.2f, 0.2f, -0.2f, -0.2f,
// 0.2f, -0.2f, 0.2f, 0.2f, -0.2f, 0.2f,
// -0.2f, -0.2f, 0.2f, -0.2f, -0.2f, -0.2f,
// -0.2f, 0.2f, -0.2f, 0.2f, 0.2f, -0.2f,
// 0.2f, 0.2f, 0.2f, 0.2f, 0.2f, 0.2f,
// -0.2f, 0.2f, 0.2f, -0.2f, 0.2f, -0.2f
};
// 第一步,配置兔子的VAO(和VBO)
unsigned int bunnyVAO, bunnyVBO, bunnyEBO;
glGenVertexArrays(1, &bunnyVAO); // 创建一个顶点数组(Vertex Array Object)对象
glGenBuffers(1, &bunnyVBO); // 申请显存空间,分配顶点缓冲对象(Vertex Buffer Object)的ID
glGenBuffers(1, &bunnyEBO); // 创建索引缓冲对象(Element Buffer Object)
glBindVertexArray(bunnyVAO); // 绑定VAO对象
glBindBuffer(GL_ARRAY_BUFFER, bunnyVBO); // 绑定VBO
glBufferData(GL_ARRAY_BUFFER, 6*vertices_size*sizeof(GLfloat), vertices, GL_STATIC_DRAW); // 将用户定义的顶点数据传输到绑定的显存缓冲区中
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bunnyEBO); // 绑定EBO
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * indices_size * sizeof(GLuint), indices, GL_STATIC_DRAW); // 将索引存储到EBO当中
/*
glVertexAttribPointer()
第一个参数指定顶点属性位置,与顶点着色器中layout(location=0)对应。
第二个参数指定顶点属性大小。
第三个参数指定数据类型。
第四个参数定义是否希望数据被标准化。
第五个参数是步长(Stride),指定在连续的顶点属性之间的间隔。
第六个参数表示我们的位置数据在缓冲区起始位置的偏移量
*/
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)0); //通知OPENGL怎么解释这些顶点数据
glEnableVertexAttribArray(0); //开启顶点属性
// 普通属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0); // 请注意,这是允许的,对glVertexAttribPointer的调用将VBO注册为当前绑定的顶点缓冲区对象,因此之后我们可以安全地取消绑定
glBindVertexArray(0); // 取消绑定VAO(取消绑定任何缓冲区/数组以防止出现奇怪的错误始终是一件好事),请记住:请勿取消绑定EBO,继续将其与VAO绑定
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //解除绑定VAO后解除EBO绑定。
// 其次,配置灯光的VAO(和VBO)
unsigned int lightVAO, lightVBO;
glGenVertexArrays(1, &lightVAO);
glGenBuffers(1, &lightVBO);
glBindBuffer(GL_ARRAY_BUFFER, lightVBO); //绑定缓存
glBufferData(GL_ARRAY_BUFFER, sizeof(lightVertices), lightVertices, GL_STATIC_DRAW); //灯光顶点数据绑定到缓存中
glBindVertexArray(lightVAO); //绑定灯光VAO对象
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); //通知OPENGL处理顶点数据
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); // 请注意,这是允许的,对glVertexAttribPointer的调用将VBO注册为当前绑定的顶点缓冲区对象,因此之后我们可以安全地取消绑定
glBindVertexArray(0); // 取消绑定VAO(取消绑定任何缓冲区/数组以防止出现奇怪的错误始终是一件好事),请记住:请勿取消绑定EBO,继续将其与VAO绑定
//兔子的初始状态(第二个参数为初始角度,radians设置弧度)
modelBunny = rotate(modelBunny, radians(135.0f), vec3(0.0f, 1.0f, 0.0f));
// 渲染循环
while (!glfwWindowShouldClose(window)){ //glfwWindowShouldClose 检查窗口是否需要关闭
// 每帧的时间逻辑
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// 输入
interactionFunctions();
// !!!: 设置初始模式
if(!cursorDisabled) //如果光标没有禁用
// 显示物体所有面,显示线段,多边形用轮廓显示
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
else
//否则 显示面,多边形采用填充式
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// !!!: 渲染背景颜色
glClearColor(0.8f, 0.8f, 0.8f, 0.8f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除颜色缓冲以及深度缓冲
// 设置格式/绘图对象时一定要激活着色器
lightingShader.use();
lightingShader.setVec3("viewPos", camera.Position);
// 灯光特性
vec3 lightColor;
// !!!: 颜色按时改变
/*
//兔子颜色变换,根据glfwGetTime获取时间
lightColor.x = sin(glfwGetTime() * 2.0f);
lightColor.y = sin(glfwGetTime() * 0.7f);
lightColor.z = sin(glfwGetTime() * 1.3f);
*/
// !!!: 设置光照属性
lightColor.x = 4.0f; // 0.0f
lightColor.y = 4.0f; // 0.0f
lightColor.z = 0.0f;
vec3 diffuseColor = lightColor * vec3(0.3f); // 减少影响
vec3 ambientColor = diffuseColor * vec3(0.2f); // 低影响
// 点光源
lightingShader.setVec3("pointLights.position", PointlightPos);
lightingShader.setVec3("pointLights.ambient", ambientColor);
lightingShader.setVec3("pointLights.diffuse", diffuseColor);
lightingShader.setVec3("pointLights.specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights.constant", 1.0f);
lightingShader.setFloat("pointLights.linear", (isAttenuation ? 0.014 : 0.0f));
lightingShader.setFloat("pointLights.quadratic", (isAttenuation ? 0.0007 : 0.0f));
// 聚光灯
lightingShader.setVec3("spotLight.position", camera.Position);
lightingShader.setVec3("spotLight.direction", camera.Front);
lightingShader.setVec3("spotLight.ambient", 5.0f, 5.0f, 5.0f); //环境光
lightingShader.setVec3("spotLight.diffuse", 5.0f, 5.0f, 5.0f); //漫反射光
lightingShader.setVec3("spotLight.specular", 0.0f, 0.0f, 0.0f); //镜面高光
lightingShader.setFloat("spotLight.constant", (isFlashlight? 1.0f : INF));
lightingShader.setFloat("spotLight.linear", 0.045f);
lightingShader.setFloat("spotLight.quadratic", 0.0075f);
lightingShader.setFloat("spotLight.cutOff", cos(radians(5.0f)));
lightingShader.setFloat("spotLight.outerCutOff", cos(radians(9.0f)));
// 材料特性
lightingShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f); //环境光
// lightingShader.setVec3("material.ambient", 0.5f, 0.5f, 0.5f); // 环境光
lightingShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.2f); //漫反射光
lightingShader.setVec3("material.specular", 1.0f, 1.0f, 1.0f); // 镜面光不会完全影响该物体的材质(高光)
lightingShader.setFloat("material.shininess", 320.0f);
// 查看/投影转换
mat4 projection = perspective(radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 1000.0f);
mat4 view = camera.GetViewMatrix(); //获取相机的视野矩阵
lightingShader.setMat4("projection", projection); //将投影矩阵传入到着色器
lightingShader.setMat4("view", view); //相机视野矩阵传入着色器
// 世界坐标转换
// model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.0f, 1.0f, 0.0f));
lightingShader.setMat4("model", modelBunny); // 兔子模型参数传入到model着色器
// 渲染兔子
glBindVertexArray(bunnyVAO); //绑定兔子的VAO
glDrawElements(GL_TRIANGLES, 3*indices_size, GL_UNSIGNED_INT, 0);//图元绘制函数
glBindVertexArray(0); //取消绑定
// !!!: 画出灯的对象
lampShader.use();
lampShader.setMat4("projection", projection);
lampShader.setMat4("view", view);
mat4 model = mat4(1.0f);
model = translate(model, PointlightPos);
model = scale(model, vec3(0.4f)); // 一个较小的立方体(灯源)
lampShader.setMat4("model", model);
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36); //根据顶点数组中的坐标数据和指定的模式,进行绘制
glBindVertexArray(0);//取消绑定
// !!!: 拾取点的坐标
if (selectedPointIndice <= vertices_size){ //如果点的索引小于点的个数
selectShader.use(); //启用着色器
//创建一个四维向量记录当前点的位置
//n1 n2 n3 分别为该点的xyz坐标
vec4 now(modelBunny * vec4(vertices[selectedPointIndice * 6], vertices[selectedPointIndice * 6 + 1], vertices[selectedPointIndice * 6 + 2], 1.0f));
// 创建一个平移矩阵,第一个参数为目标矩阵,第二个矩阵是平移的方向向量s
model = translate(mat4(1.0f), vec3(now.x, now.y, now.z));
// model是一个小立方体,scale用于创建一个缩放矩阵
model = scale(model, vec3(0.01f, 0.01f, 0.01f));
view = camera.GetViewMatrix(); // 视野矩阵
projection = perspective(radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 1000.0f); // 查看矩阵
// 着色器设置相关矩阵参数
selectShader.setMat4("projection", projection);
selectShader.setMat4("view", view);
selectShader.setMat4("model", model);
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36); //画出一个小立方体框住所选的点。 函数说明:根据顶点数组中的坐标数据和指定的模式(三角形)进行绘制
}
// glfw: 交换缓冲区和轮询IO事件(按下/释放键,鼠标移动等)
glfwSwapBuffers(window); // 交换缓冲区,使用双缓冲(让这个小方块显示在屏幕上);
glfwPollEvents();
}
// optional: 一旦实现了目标取消所有资源分配
glDeleteVertexArrays(1, &bunnyVAO);
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &bunnyVBO);
glDeleteBuffers(1, &lightVBO);
// glfw: 最后一步,释放所有已分配的GLFW资源。
glfwTerminate();
return 0;
}
// 输入函数
void interactionFunctions(){
// 调节旋转/缩放速度
GLfloat bunnySpeed = 30.0f * deltaTime;
// 相机向前
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, bunnySpeed);
// 相机向后
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, bunnySpeed);
// 相机向左
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, bunnySpeed);
// 相机向右
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, bunnySpeed);
// <- 兔子向左移动
if (keys[GLFW_KEY_LEFT])
modelBunny = translate(modelBunny, vec3(bunnySpeed, 0, 0));
// -> 兔子向右移动
if (keys[GLFW_KEY_RIGHT])
modelBunny = translate(modelBunny, vec3(-bunnySpeed, 0, 0));
// J 兔子向左转
if (keys[GLFW_KEY_J])
modelBunny = rotate(modelBunny, radians(bunnySpeed), vec3(0.f, 0.f, 1.f));
// L 兔子向右转
if (keys[GLFW_KEY_L])
modelBunny = glm::rotate(modelBunny, glm::radians(-bunnySpeed), glm::vec3(0.f, 0.f, 1.f));
// I 兔子向前转
if (keys[GLFW_KEY_I])
modelBunny = glm::rotate(modelBunny, glm::radians(-bunnySpeed), glm::vec3(1.f, 0.f, 0.f));
// K 兔子向后转
if (keys[GLFW_KEY_K])
modelBunny = glm::rotate(modelBunny, glm::radians(bunnySpeed), glm::vec3(1.f, 0.f, 0.f));
// ↓键 兔子缩小
if (keys[GLFW_KEY_DOWN])
modelBunny = glm::scale(modelBunny, glm::vec3(1.0f - 0.1f * bunnySpeed, 1.0f - 0.1f * bunnySpeed, 1.0f - 0.1f * bunnySpeed));
// ↑键 兔子放大
if (keys[GLFW_KEY_UP])
modelBunny = glm::scale(modelBunny, glm::vec3(1.0f + 0.1f * bunnySpeed, 1.0f + 0.1f * bunnySpeed, 1.0f + 0.1f * bunnySpeed));
}
//处理所有输入:查询GLFW是否在此帧中按下/释放了相关的按键并做出相应的反应
void key_callback(GLFWwindow *window, int key, int scancode, int action, int mode){
// 如果按下“=”键
if (key == GLFW_KEY_EQUAL && action == GLFW_PRESS){
isAttenuation = !isAttenuation; // 将光照是否衰减取反
cout << "点光源衰减: ";
if(isAttenuation)
cout << "打开.\n";
else
cout << "关闭.\n";
cout << SPLIT << endl;
}
// 按下Enter键,打开闪光灯
if (key == GLFW_KEY_ENTER && action == GLFW_PRESS){
isFlashlight = !isFlashlight;
cout << "闪光灯: ";
if(isFlashlight)
cout << "打开.\n";
else
cout << "关闭.\n";
cout << SPLIT << std::endl;
}
// 按下esc关闭窗口
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS){
glfwSetWindowShouldClose(window, true);
}
// 按下tab键切换模式
if (key == GLFW_KEY_TAB && action == GLFW_PRESS){
//获取当前的输入模式,判断光标是否禁用
if (glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED){
// 如果禁用,则恢复使用光标
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
cursorDisabled = false; //禁用标志
}else{
//如果当前光标不被禁用,则禁用光标(离开拾取模式)
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
cursorDisabled = true;
firstMouse = true; // 记录鼠标返回第一视角模式
}
}
// 判断所有按键的按下与松开状态
if (key >= 0 && key < 1024){
if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
keys[key] = false;
}
}
// glfw: 每当改变窗口大小时,就会执行回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height){
// 确保视口与新的窗口尺寸匹配,请注意,窗口的宽度和高度将大于视口在显示屏上的指定的宽和高
glViewport(0, 0, width, height);
}
// glfw: 任何时候移动鼠标调用该回调函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos){
mouseX = xpos; //鼠标x坐标
mouseY = ypos; //鼠标y坐标
if(cursorDisabled){ // 如果光标禁用
if (firstMouse){ // 如果鼠标第一视角可用更新当前xy坐标
lastX = xpos;
lastY = ypos;
// cout << lastX << " " << lastY << endl;
firstMouse = false;
}
//计算x y坐标的偏移量
float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // 反转,在Y坐标轴上,从下到上
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset); // 相机根据坐标偏移量移动
}
}
// glfw: 任何时候鼠标滚轮滚动时都会调用该函数
// 根据滚轮的滑动,调节相机视图的zoom参数来进行放大缩小
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset){
camera.ProcessMouseScroll(yoffset);
}
// !!!: 拾取点坐标
void picking_callback(GLFWwindow *window, int button, int action, int mods){
// bool test = false;
// 判断光标是否没有被禁用 鼠标左键是否被按下
if (!cursorDisabled && button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_MOUSE_BUTTON_LEFT){
// 鼠标的xy坐标
GLfloat xpos = mouseX;
GLfloat ypos = mouseY;
cout << "选取点的屏幕坐标(窗口的左上角为0,0): (" << xpos << "," << ypos << ")" <<endl;
GLfloat minDistance = pow(10, 20); // 最小距离10^20
GLuint minIndice = 0; // 最小索引
GLfloat minX = 0.0f, minY = 0.0f;
for (int i = 0; i < vertices_size; i++){
/*
将modelBunny的矩阵翻转,然后转置将结果变为一个三阶矩阵 * 点的颜色值 点乘 相机的前坐标 <0 及这个点在正视图上
判断哪些点应该被拾取
*/
if (dot(mat3(transpose(inverse(modelBunny))) *
vec3(vertices[6 * i + 3], vertices[6 * i + 4], vertices[6 * i + 5]), camera.Front) < 0)
{
//std::cout << vertices[6 * i + 3] << vertices[6 * i + 4] << vertices[6 * i + 5] << std::endl;
//std::cout << glm::vec3(vertices[6 * i + 3], vertices[6 * i + 4], vertices[6 * i + 5]).x << glm::vec3(vertices[6 * i + 3], vertices[6 * i + 4], vertices[6 * i + 5]).y<< glm::vec3(vertices[6 * i + 3], vertices[6 * i + 4], vertices[6 * i + 5]).z << std::endl;
vec4 iPos;
mat4 view = camera.GetViewMatrix(); // 相机的视野矩阵
mat4 projection = perspective(radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 1000.0f); //投影矩阵, perspectives是投影矩阵公式 radians是弧度
iPos = modelBunny * vec4(vertices[i * 6], vertices[i * 6 + 1], vertices[i * 6 + 2], 1.0f);
iPos = projection * view * iPos;
// 屏幕点的x坐标
GLfloat pointPosX = SCR_WIDTH / 2 * (iPos.x / iPos.w) + SCR_WIDTH / 2;
// 点的y坐标(特别注意这里的负号)
GLfloat pointPosY = SCR_HEIGHT / 2 * (-iPos.y / iPos.w) + SCR_HEIGHT / 2;
// 判断当前点与所有应该拾取的点之间的距离,并不断的更新minDistance直到所有的点被计算完,得出光标点击的点与离它最近的点之前的距离记录为minDistance
if ((pointPosX - xpos) * (pointPosX - xpos) + (pointPosY - ypos) * (pointPosY - ypos) < minDistance) {
minDistance = (pointPosX - xpos) * (pointPosX - xpos) + (pointPosY - ypos) * (pointPosY - ypos);
// cout << minDistance << " ";
minIndice = i;
minX = pointPosX;
minY = pointPosY;
}
}
}
// 如果minDistance小于20个像素
if (minDistance < 400){
selectedPointIndice = minIndice;
cout << "点的索引号: " << minIndice << endl;
cout << "点的坐标: (" << vertices[minIndice * 6] << "," << vertices[minIndice * 6 + 1] << "," << vertices[minIndice * 6 + 2] << ")" << endl;
cout << "点的屏幕坐标: (" << minX << "," << minY << ")" << endl;
}else{
cout << "附近没有点(光标与最近点之间的距离必须小于20个像素)" << endl;
}
cout << SPLIT << endl;
}
}
// Shader function executes once per fragment
// Outputs color of surface at the current fragment's screen sample position
#version 330 core // 版本号
out vec4 FragColor; // 输出变量
///兔子面元着色器
// 定义材质
struct Material {
vec3 ambient; // 环境光
vec3 diffuse; // 漫反射
vec3 specular; // 镜面反射
float shininess; // 反光度
};
// 定义点光源
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
// 定义聚光灯光源
struct SpotLight {
vec3 position; // 光源顶点位置
vec3 direction; // 母线方向
float cutOff; // 切光角
float outerCutOff;
// 实现衰减的二次项的系数
float constant; // 常量
float linear; // 一次项系数
float quadratic; // 二次项系数
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
in vec3 FragPos;
in vec3 Normal; // 方向量
// 全局共享变量
uniform vec3 viewPos; // 摄像机位置(观察点)
uniform PointLight pointLights; // 点光源
uniform SpotLight spotLight; // 聚光灯光源
uniform Material material; // 材质
// function prototypes
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light , vec3 normal , vec3 fragPos, vec3 viewDir);
void main(){
// 属性(需要标准化)
vec3 norm = normalize(Normal); // 法向
vec3 viewDir = normalize(viewPos - FragPos); // 最终方向向量
// == =====================================================
// Our lighting is set up in 2 phases: a point light and a flashlight
// For each phase, a calculate function is defined that calculates the corresponding color per lamp. In the main() function we take all the calculated colors and sum them up for this fragment's final color.
// == =====================================================
// phase 1: point lights
vec3 result = CalcPointLight(pointLights, norm, FragPos, viewDir);
// phase 2: spot light
result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
// 设置片元颜色,采用RGBA
FragColor = vec4(result, 1.0);
}
// 计算使用点光源时的光照
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir){
vec3 lightDir = normalize(light.position - fragPos);
// 漫反射光照
// 计算光源对当前片段实际的漫发射影响(两个向量之间的角度大于90度,点乘的结果就会变成负数,为此,我们使用max函数返回两个参数之间较大的参数,从而保证漫反射分量不会变成负数)
float diff = max(dot(normal, lightDir), 0.0);
// 镜面反射光照
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// 光线衰减
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// combine results
vec3 ambient = light.ambient * material.ambient;
vec3 diffuse = light.diffuse * diff * material.diffuse;
vec3 specular = light.specular * spec * material.specular;
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
return (ambient + diffuse + specular); //点光源颜色
}
// 计算使用聚光灯的颜色
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir){
vec3 lightDir = normalize(light.position - fragPos);
// 漫反射着色
float diff = max(dot(normal, lightDir), 0.0);
// 镜面光着色
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// 光照衰减
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// spotlight intensity
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
// 综合光照
vec3 ambient = light.ambient * material.diffuse;
vec3 diffuse = light.diffuse * material.diffuse;
vec3 specular = light.specular * spec * material.specular;
ambient *= attenuation * intensity;
diffuse *= attenuation * intensity;
specular *= attenuation * intensity;
return (ambient + diffuse + specular); //聚光灯颜色
}
#version 330 core
// 输入变量也叫顶点属性(Vertex Attribute),能声明的顶点属性是有上限的
// 为了定义顶点数据该如何管理,使用location这一个元数据指定输入变量,这样才可以在CPU上配置顶点属性。
layout (location = 0) in vec3 aPos; // 位置变量属性 = 0
layout (location = 1) in vec3 aNormal;
out vec3 FragPos; // 片元的位置
out vec3 Normal; // 添加法向量
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main(){
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
gl_Position = projection * view * vec4(FragPos, 1.0);
}
#version 330 core
out vec4 FragColor;
void main(){
FragColor = vec4(1.0); // set alle 4 vector values to 1.0
}
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
#ifndef CAMERA_H
#define CAMERA_H
#include
#include
#include
#include
// 定义几种相机的运动
// Used as abstraction to stay away from window-system specific input methods
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;
// An abstract camera class that processes input and calculates the corresponding Euler Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:
// 相机属性
glm::vec3 Position; // 位置
glm::vec3 Front; // 面向的方向(LookAt)
glm::vec3 Up; // 仰角
glm::vec3 Right;
glm::vec3 WorldUp;
// euler Angles
float Yaw;
float Pitch;
// 相机移动选项
float MovementSpeed;
float MouseSensitivity;
float Zoom;
// 以vector数组作为参数的构造函数
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();
}
// 用标量作为参数的构造函数
Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
{
Position = glm::vec3(posX, posY, posZ);
WorldUp = glm::vec3(upX, upY, upZ);
Yaw = yaw;
Pitch = pitch;
updateCameraVectors();
}
// returns the view matrix calculated using Euler Angles and the LookAt Matrix
glm::mat4 GetViewMatrix()
{
return glm::lookAt(Position, Position + Front, Up);
}
// 处理键盘(或其他输入设备的)输入事件
// Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)
void ProcessKeyboard(Camera_Movement direction, float speed)
{
if (direction == FORWARD)
Position += Front * speed;
if (direction == BACKWARD)
Position -= Front * speed;
if (direction == LEFT)
Position -= Right * speed;
if (direction == RIGHT)
Position += Right * speed;
}
// 处理鼠标输入相应事件
// Expects the offset value in both the x and y direction.
void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true)
{
xoffset *= MouseSensitivity;
yoffset *= MouseSensitivity;
Yaw += xoffset;
Pitch += yoffset;
// make sure that when pitch is out of bounds, screen doesn't get flipped
if (constrainPitch)
{
if (Pitch > 89.0f)
Pitch = 89.0f;
if (Pitch < -89.0f)
Pitch = -89.0f;
}
// update Front, Right and Up Vectors using the updated Euler angles
updateCameraVectors();
}
// 处理鼠标滚轮事件
// 只需要接收滚轮的y轴偏移量信息
void ProcessMouseScroll(float yoffset)
{
// +=: 鼠标向上滚动放大
// -=: 鼠标向下滚动放大
Zoom += (float)yoffset;
if (Zoom < 1.0f)
Zoom = 1.0f;
if (Zoom > 45.0f)
Zoom = 45.0f;
}
private:
// calculates the front vector from the Camera's (updated) Euler Angles
void updateCameraVectors()
{
// calculate the new Front vector
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);
// also re-calculate the Right and Up vector
Right = glm::normalize(glm::cross(Front, WorldUp)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
Up = glm::normalize(glm::cross(Right, Front));
}
};
#endif
#ifndef PARSER_H
#define PARSER_H
#include
#include
#include
#include
#include
#include
void parseIndex(const char* fileName, GLfloat* vertices, GLuint* indices, GLuint& vertices_size, GLuint& indices_size);
void parseNormal(const char* fileName, GLfloat* vertices, GLuint* indices, GLuint& vertices_size, GLuint& indices_size);
#endif
#include "parser.h"
// 读取原始的ply2文件,顶点为三个元素
void parseIndex(const char* fileName, GLfloat* vertices, GLuint* indices, GLuint& vertices_size, GLuint& indices_size){
std::FILE* f = std::fopen(fileName, "r");
// 先读取前两行,分别是顶点数和面元个数
std::fscanf(f, "%d%d", &vertices_size, &indices_size);
// cout << vertices_size << endl << indices_size << endl;
for(int i = 0; i < vertices_size; i++){
std::fscanf(f, "%f%f%f", &vertices[3*i], &vertices[3*i+1], &vertices[3*i+2]);
}
for(int i = 0; i < indices_size; i++){
int _;
std::fscanf(f, "%d%d%d%d", &_, &indices[3*i], &indices[3*i+1], &indices[3*i+2]);
}
std::fclose(f);
}
// 读取更新后的ply2_normal文件,顶点为六个元素,后面三个是法向量(?)
void parseNormal(const char* fileName, GLfloat* vertices, GLuint* indices, GLuint& vertices_size, GLuint& indices_size){
std::FILE* f = std::fopen(fileName, "r");
std::fscanf(f, "%d%d", &vertices_size, &indices_size);
// cout << vertices_size << endl << indices_size << endl;
for(int i = 0; i < vertices_size; i++){
std::fscanf(f, "%f%f%f%f%f%f", &vertices[6*i], &vertices[6*i+1], &vertices[6*i+2], \
&vertices[6*i+3], &vertices[6*i+4], &vertices[6*i+5]);
}
for(int i = 0; i < indices_size; i++){
std::fscanf(f, "%d%d%d", &indices[3*i], &indices[3*i+1], &indices[3*i+2]);
}
std::fclose(f);
}
#version 330 core
out vec4 FragColor;
void main(){
FragColor = vec4(1.0); // set alle 4 vector values to 1.0
}
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main(){
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
#ifndef SHADER_H
#define SHADER_H
#include
#include
#include
#include
#include
#include
class Shader
{
public:
unsigned int ID;
// constructor generates the shader on the fly
Shader(const char* vertexPath, const char* fragmentPath, const char* geometryPath = nullptr)
{
// 1. retrieve the vertex/fragment source code from filePath
std::string vertexCode;
std::string fragmentCode;
std::string geometryCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
std::ifstream gShaderFile;
// ensure ifstream objects can throw exceptions:
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
gShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try
{
// open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
// stringstream进行流的输入输出操作(可以进行安全的数据类型转换)
std::stringstream vShaderStream, fShaderStream;
// read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf(); // 实现一个流对象指向的内容用另一个流对象来输出
fShaderStream << fShaderFile.rdbuf();
// 关闭文件
vShaderFile.close();
fShaderFile.close();
// convert stream into string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
// 如果几何着色器路径已经存在,仍然加载
if (geometryPath != nullptr)
{
gShaderFile.open(geometryPath);
std::stringstream gShaderStream;
gShaderStream << gShaderFile.rdbuf();
gShaderFile.close();
geometryCode = gShaderStream.str();
}
}
catch (std::ifstream::failure& e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
// c_str()返回一个指向正规C字符串的指针常量,为了将string类兼容到c语言中
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
// 2. 编译着色器
unsigned int vertex, fragment;
// 顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// 片元着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// 如果提供了集合着色器,则对几何着色器编译
unsigned int geometry = 0;
if (geometryPath != nullptr)
{
const char* gShaderCode = geometryCode.c_str();
geometry = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometry, 1, &gShaderCode, NULL);
glCompileShader(geometry);
checkCompileErrors(geometry, "GEOMETRY");
}
// 3.创建着色器程序
ID = glCreateProgram(); // 指定将链接以创建program的着色器对象
glAttachShader(ID, vertex); // 将着色器对象附加到program对象
glAttachShader(ID, fragment);
if (geometryPath != nullptr)
glAttachShader(ID, geometry);
glLinkProgram(ID); // 链接一个program对象
checkCompileErrors(ID, "PROGRAM");
// 在着色器已经链接到程序之后并且不再必要的时删除
glDeleteShader(vertex);
glDeleteShader(fragment);
if (geometryPath != nullptr)
glDeleteShader(geometry);
}
// 激活着色器
void use()
{
glUseProgram(ID);
}
// utility uniform functions
void setBool(const std::string& name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
// ------------------------------------------------------------------------
void setInt(const std::string& name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void setFloat(const std::string& name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void setVec2(const std::string& name, const glm::vec2& value) const
{
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec2(const std::string& name, float x, float y) const
{
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}
// ------------------------------------------------------------------------
void setVec3(const std::string& name, const glm::vec3& value) const
{
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec3(const std::string& name, float x, float y, float z) const
{
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}
// ------------------------------------------------------------------------
void setVec4(const std::string& name, const glm::vec4& value) const
{
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec4(const std::string& name, float x, float y, float z, float w)
{
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}
// ------------------------------------------------------------------------
void setMat2(const std::string& name, const glm::mat2& mat) const
{
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat3(const std::string& name, const glm::mat3& mat) const
{
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat4(const std::string& name, const glm::mat4& mat) const
{
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
private:
// utility function for checking shader compilation/linking errors.
void checkCompileErrors(GLuint shader, std::string type)
{
GLint success;
GLchar infoLog[1024];
if (type != "PROGRAM")
{
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
else
{
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
}
};
#endif
链接: https://pan.baidu.com/s/1HiOmeDFZx9G7Nskw08BW5Q 密码: a1i9
https://learnopengl-cn.github.io/
非原创,侵删([email protected])