新建OpenGL项目
由于固定管线不会写(...),老师也更鼓励写核心管线——所以还是丢掉参考的固定管线框架,从新项目开始吧!
环境是Win10 with VS2022.
配个库
新建空项目,在项目属性/VC++的 包含目录 和 库目录 中加入所需的库文件。这里主要用到的是GLFW库、GLAD库和管理矩阵的GLM库。
在项目属性/链接器/输入中加入glfw3.lib
和opengl32.lib
。
别忘了添加glad.c
到项目。
因为之后还想贴材质,把stb_image
库也加进来:新建一个cpp文件加入:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
再来点轮子
应省尽省,借用下learnopengl的shader类。 快乐的复制黏贴之前,先明确一下需求: 1) 可以用鼠标对魔方整体旋转任意角度 2) 实现用鼠标放大缩小效果 3) 实现层次的转动(比如点击一层则旋转一层),包括水平层的转动和垂直层的转动 4) 可以选择灯光 5) 可以选择 2-6 阶魔方的显示
learnopengl的这个camera类是FPS类型的camera,但是在我们这个项目中的旋转需要使用TrackBall映射,摄像机的位置视角其实是不变的。所以camera类就先不要了—— mouse_callback()
和scroll_callback()
我们之后在下一篇重写~
其他的内容基本一致————
初始化glfw->
创建窗口->
设置callback函数->
开启鼠标监听->
初始化glad->
启用深度检测->
绑定shader->
准备顶点数据、vaovbo->
读取材质->
渲染循环
其中鼠标滚轮的缩放功能可以直接通过glm::perspective转换为投影矩阵:
//IN RENDER LOOP
// pass projection matrix to shader (note that in this case it could change every frame)
glm::mat4 projection = glm::perspective(glm::radians(cameraZoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
ourShader.setMat4("projection", projection);
...
// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
cameraZoom -= (float)yoffset;
if (cameraZoom < 1.0f) cameraZoom = 1.0f;
if (cameraZoom > 45.0f) cameraZoom = 45.0f;
}
手敲一个顶点数据与贴图坐标,我们就拥有了一个中心为(0,0,0),边长为1的cube!(并可以通过滚轮放大缩小)
rubikcube类
魔方更重要的是如何记录其结构,在这里来抄抄固定管线模板给的框架。 三阶魔方可以抽象为由9个layer和27个cube构成的结构: 拓展到n阶,我们的魔方应该含有n3个layer和n^3个cube。 为了给自己省点脑子,这里layer中储存的cube数组我改为了二维nn的数组,朝着旋转轴方向看,从左下角开始按顺序索引。 依法炮制一下n阶rubikcube的构造函数。因为使用了glm库,向量和矩阵的赋值上可以剩不少事~
/************************************************************************/
/* Constructor of RubikCube class
/************************************************************************/
RubikCube::RubikCube(int _n):n(_n)
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
for (int k = 0; k < n; ++k)
{
magicCube[i][j][k].pos = glm::vec3(cubeSize / 2 + i * cubeSize,
cubeSize / 2 + j * cubeSize, cubeSize / 2 + k * cubeSize);
magicCube[i][j][k].pos -= glm::vec3(cubeSize * n / 2);
magicCube[i][j][k].matrix = glm::translate(magicCube[i][j][k].matrix, glm::vec3(0.0f, 0.0f, 0.0f));
}
}
}
//Construct layers
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
layers[0][i].cubes[j][k] = glm::vec3(i, j, n - 1 - k);
layers[1][i].cubes[j][k] = glm::vec3(j, i, n - 1 - k);
layers[2][i].cubes[j][k] = glm::vec3(j, k, i);
}
}
layers[0][i].axis = glm::vec3(1.0, 0.0, 0.0);
layers[1][i].axis = glm::vec3(0.0, 1.0, 0.0);
layers[2][i].axis = glm::vec3(0.0, 0.0, 1.0);
}
}
然后把cube们都依次绘制一下(先暂时不管内外和各面是否可见),就得到我们魔方的雏形啦。
// render rubikcubes
glBindVertexArray(VAO);
for (int i = 0; i < myCube.n; ++i)
{
for (int j = 0; j < myCube.n; j++) {
for (int k = 0; k < myCube.n; k++) {
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, myCube.magicCube[i][j][k].pos);
ourShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
}