好久没写过GL了,正好来发发教程
要求
给出兔兔的顶点坐标与三角面索引,需要实现:
- 绘制模型,冯氏光照
- 模型及视角移动
- 鼠标点选高亮某三角面
总体思路 & 坑点
For 可以自己实现OpenGL编写的朋友
-
导入模型 :给出的数据为顶点坐标,没有法线信息,需要求解法线,在我的实现中对每一三角面根据三点坐标求解了法线,没有进行法线插值,这会使得兔兔表面不够圆润,并且需要 NumFace * 3 大小的VBO,较浪费空间。按道理可以对每一点求解法线,使用该点与邻接点的向量进行加权平均,具体可以参考链接 Weighted Vertex Normals
-
光照模型:没什么特别的,Blinn-Phong或者Phong的Shader
-
模型及视角移动:也没什么特别的,取下帧间鼠标Δ值和键盘按键变model view矩阵就行了
-
点选高亮:这个还蛮有趣的,想了个办法,应该不是最优解,用一个drawcall绘制一张每个面颜色都是该面索引值 / 总面数的RT,然后readBack一下鼠标位置的颜色,拿到高亮面的顶点,这里我直接再加了一个drawcall画这三角,应该是多余了。
实现细节
For 不怎么熟悉OpenGL的朋友
配置OpenGL环境
- 使用GLFW初始化窗口
GLFW is a lightweight utility library for use with OpenGL. GLFW stands for Graphics Library Framework. It provides programmers with the ability to create and manage windows and OpenGL contexts, as well as handle joystick, keyboard and mouse input.
一般使用GLFW作为跨平台的窗口工具,在本例中就作为创建OpenGL绘制窗口,处理鼠标键盘事件的API。
- 使用GLAD链接OpenGL API
可以简单认为,虽然各个平台都支持OpenGL绘制,但一般都仅仅是提供了按OpenGL标准所实现的二进制链接库,如WIndows下默认静态链接库会有opengl32.lib,但仍然需要一个第三方库去在运行时加载这一dll,提供一个符合标准的c++头文件,并且将二进制库中的实现加载到对应的函数API上。
void Application::Run()
{
//Initialize GLFW Window
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
window = glfwCreateWindow(800, 600, "Bunny", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
}
glfwMakeContextCurrent(window);
//Initialize GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
}
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
Init(); //Read Data From File & Initialize VAOs FBOs
while (!glfwWindowShouldClose(window))
{
Render(); //Main Render Function
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
}
处理输入
- 将所有点与索引输入,按面顺序排序,每个点有7个float的长度:
- 位置:vec3
- 法线:vec3
- 面编号(归一化):float
void Application::Init() {
//Read From File
std::ifstream fin("bunny_iH.ply2");
if (!fin) {
assert(false);
}
numVertices = 0;
fin >> numVertices;
fin >> numFaces;
vertices = new float[(size_t)numVertices * 3];
sortedVertices = new float[(size_t)numFaces * 3 * 7];
for (int i = 0; i < numVertices; i++) {
fin >> vertices[i * 3] >> vertices[i * 3 + 1] >> vertices[i * 3 + 2];
}
for (int i = 0; i < numFaces; i++) {
int a, b, c, d;
fin >> a >> b >> c >> d; // a always equals to 3
int v1 = i * 3;
int v2 = i * 3 + 1;
int v3 = i * 3 + 2;
// position : vec3 [v*7, v*7+2]
// normal : vec3 [v*7+3, v*7+5]
// faceIndex : float [v*7+6, v*7+6]
sortedVertices[v1 * 7] = vertices[b * 3];
sortedVertices[v1 * 7 + 1] = vertices[b * 3 + 1];
sortedVertices[v1 * 7 + 2] = vertices[b * 3 + 2];
sortedVertices[v2 * 7] = vertices[c * 3];
sortedVertices[v2 * 7 + 1] = vertices[c * 3 + 1];
sortedVertices[v2 * 7 + 2] = vertices[c * 3 + 2];
sortedVertices[v3 * 7] = vertices[d * 3];
sortedVertices[v3 * 7 + 1] = vertices[d * 3 + 1];
sortedVertices[v3 * 7 + 2] = vertices[d * 3 + 2];
float* normal = new float[3];
calcNormal(&sortedVertices[v1 * 7], &sortedVertices[v2 * 7], &sortedVertices[v3 * 7], normal);
sortedVertices[v1 * 7 + 3] = normal[0];
sortedVertices[v1 * 7 + 4] = normal[1];
sortedVertices[v1 * 7 + 5]