要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右测的向量以及一个指向它上方的向量。细心的读者可能已经注意到我们实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。

这里涉及到OpenGL的坐标系类型,OpenGL是右手坐标系,而DX是左手坐标系,简单讲讲右手坐标系:

在图上画右手坐标系,一条水平的直线表示X轴,从左往右为正方向;一条垂直的直线表示Y轴,从下到上位正方向;一条垂直纸面的轴为Z轴,从纸内到纸外为正方向。

右手坐标系

被称为右手坐标系是因为当你伸出右手,四指的方向指向X轴正方向,弯曲四指指向Y轴正方向,大拇指的方向为Z轴正方向。

摄像机初始位置在原点,朝向是-z轴方向。想要看到我们的物体,可以让摄像机往+z轴方向移动,也可以让整个场景往-z轴方向移动。我们选择让整个场景往-z轴方向移动。关于摄像机的更多东西我们会在下一节讨论

摄像机

(1)摄像机位置

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);

不要忘记正z轴是从屏幕指向你的,如果我们希望摄像机向后移动,我们就沿着z轴的正方向移动。

(2)摄像机方向

Z轴:

摄像机的方向,这里指的是摄像机指向哪个方向。现在我们让摄像机指向场景原点:(0, 0, 0)

方向向量(Direction Vector)并不是最好的名字,因为它实际上指向从它到目标向量的相反方向,我们就会获得一个指向摄像机正z轴方向的向量

glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

X轴:

右向量(Right Vector),它代表摄像机空间的x轴的正方向。为获取右向量我们需要先使用一个小技巧:先定义一个上向量(Up Vector)。接下来把上向量和第二步得到的方向向量进行叉乘。两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection))

Y轴:

有了x轴向量和z轴向量,获取一个指向摄像机的正y轴向量就相对简单了:我们把右向量和方向向量进行叉乘:

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

LOOKAT

当我们讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。这正是LookAt矩阵所做的,现在我们有了3个相互垂直的轴和一个定义摄像机空间的位置坐标,我们可以创建我们自己的LookAt矩阵了。GLM已经提供了这些支持。我们要做的只是定义一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量(我们计算右向量使用的那个上向量)。接着GLM就会创建一个LookAt矩阵,我们可以把它当作我们的观察矩阵:

glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f,  3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f,  0.0f);

LookAt函数现在成了:

view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

我们首先将摄像机位置设置为之前定义的cameraPos。方向是当前的位置加上指向向量(与方向向量相反)。这样能保证无论我们怎么移动,摄像机都会注视着目标方向。让我们摆弄一下这些向量,在按下某些按钮时更新cameraPos向量。

(3)由键盘控制移动(改变comerapos)

(1)移动速度

如果定义移动速度是个常量。但结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。当你发布你的程序的时候,你必须确保它在所有硬件上移动速度都一样。因此设置一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。

我们跟踪两个全局变量来计算出deltaTime值:

float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间

在每一帧中我们计算出新的deltaTime以备后用。

float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;

现在我们有了deltaTime,在计算速度的时候可以将其考虑进去了:

void processInput(GLFWwindow *window)
{float cameraSpeed = 2.5f * deltaTime;...
}

(2)processInput函数

为GLFW的键盘输入定义过一个processInput函数了,我们来新添加几个需要检查的按键命令:

void processInput(GLFWwindow *window)
{...float cameraSpeed = 0.05f; // adjust accordinglyif (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)cameraPos += cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)cameraPos -= cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}

当我们按下WASD键的任意一个,摄像机的位置都会相应更新。如果我们希望向前或向后移动(向后就是向空间坐标系Z轴正向移动),我们就把位置向量加上或减去指向向量。如果我们希望向左右移动,我们使用叉乘来创建一个右向量(Right Vector),并沿着它相应移动就可以了。这样就创建了使用摄像机时熟悉的横移(Strafe)效果。

注意,我们对右向量进行了标准化。如果我们没对这个向量进行标准化,最后的叉乘结果会根据cameraFront变量返回大小不同的向量。如果我们不对向量进行标准化,我们就得根据摄像机的朝向不同加速或减速移动了,但如果进行了标准化移动就是匀速的。

(3)捕捉键盘响应

1、声明函数

​
void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode);​

2、绑定

glfwSetKeyCallback(window, KeyCallback);

3、捕捉键盘操作,并调用

glfwPollEvents();  //把所有事件系统都取过来:键盘/鼠标等操作
DoMovement();  //获取完操作之后的额外参数

4、书写函数

void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode)
{if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {glfwSetWindowShouldClose(window, GL_TRUE);  //设定关闭窗口}if (key >= 0 && key < 1024) {if (action == GLFW_PRESS){keys[key] = true;  //键盘按下去了,就设置为 true,即为1}else if (action == GLFW_RELEASE){keys[key] = false;  //键盘松开,设为 false}}
}
void ProcessKeyboard(Camera_Movement direction, GLfloat deltaTime){//乘以 deltaTime:消除电脑性能,控制时间一样长GLfloat velocity = this->movementSpeed * deltaTime;//处理键盘//向前:加上向前if (direction == FORWARD) {this->position += this->front * velocity;}//向后:减去向前if (direction == BACKWARD) {this->position -= this->front * velocity;}if (direction == LEFT) {this->position -= this->right * velocity;}if (direction == RIGHT) {this->position += this->right * velocity;}}

加入鼠标(修改cameraFront)

(1)欧拉角

欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:

俯仰角是描述我们如何往上或往下看的角,可以在第一张图中看到。第二张图展示了偏航角,偏航角表示我们往左和往右看的程度。滚转角代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用。每个欧拉角都有一个值来表示,把三个角结合起来我们就能够计算3D空间中任何的旋转向量了。

对于我们的摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会讨论滚转角。

direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 译注:direction代表摄像机的前轴(Front),这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

这样我们就有了一个可以把俯仰角和偏航角转化为用来自由旋转视角的摄像机的3维方向向量了。具体原理参考这里

(2)隐藏光标,并屏蔽第一次鼠标移动

1、隐藏光标

首先我们要告诉GLFW,它应该隐藏光标,并捕捉(Capture)它。捕捉光标表示的是,如果焦点在你的程序上(译注:即表示你正在操作这个程序,Windows中拥有焦点的程序标题栏通常是有颜色的那个,而失去焦点的程序标题栏则是灰色的),光标应该停留在窗口中(除非程序失去焦点或者退出)。我们可以用一个简单地配置调用来完成:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

在调用这个函数之后,无论我们怎么去移动鼠标,光标都不会显示了,它也不会离开窗口(按esc可退出)。

2、屏蔽鼠标第一次移动

我们可以简单的使用一个bool变量检验我们是否是第一次获取鼠标输入,如果是,那么我们先把鼠标的初始位置更新为xpos和ypos值,这样就能解决这个问题;接下来的鼠标移动就会使用刚进入的鼠标位置坐标来计算偏移量了:

if(firstMouse) // 这个bool变量初始时是设定为true的
{lastX = xpos;lastY = ypos;firstMouse = false;
}

(3)鼠标输入

偏航角和俯仰角是通过鼠标(或手柄)移动获得的,水平的移动影响偏航角,竖直的移动影响俯仰角。它的原理就是,储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置与上一帧的位置相差多少。如果水平/竖直差别越大那么俯仰角或偏航角就改变越大,也就是摄像机需要移动更多的距离。为了计算俯仰角和偏航角,我们需要让GLFW监听鼠标移动事件。(和键盘输入相似)我们会用一个回调函数来完成,函数的原型如下:

void mouse_callback(GLFWwindow* window, double xpos, double ypos);

这里的xpos和ypos代表当前鼠标的位置。当我们用GLFW注册了回调函数之后,鼠标一移动mouse_callback函数就会被调用:

glfwSetCursorPosCallback(window, mouse_callback);

在处理FPS风格摄像机的鼠标输入的时候,我们必须在最终获取方向向量之前做下面这几步:

  1. 计算鼠标距上一帧的偏移量。
  2. 把偏移量添加到摄像机的俯仰角和偏航角中。
  3. 对偏航角和俯仰角进行最大和最小值的限制。
  4. 计算方向向量。

第一步:

是计算鼠标自上一帧的偏移量。我们必须先在程序中储存上一帧的鼠标位置,我们把它的初始值设置为屏幕的中心(屏幕的尺寸是800x600):

float lastX = 400, lastY = 300;

在鼠标的回调函数中我们计算当前帧和上一帧鼠标位置的偏移量:

float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // 注意这里是相反的,因为y坐标是从底部往顶部依次增大的
lastX = xpos;
lastY = ypos;float sensitivity = 0.05f;
xoffset *= sensitivity;
yoffset *= sensitivity;

注意我们把偏移量乘以了sensitivity(灵敏度)值。如果我们忽略这个值,鼠标移动就会太大了;你可以自己实验一下,找到适合自己的灵敏度值。

第二步:

我们把偏移量加到全局变量pitch和yaw上:

yaw   += xoffset;
pitch += yoffset;

第三步

我们需要给摄像机添加一些限制,这样摄像机就不会发生奇怪的移动了(这样也会避免一些奇怪的问题)。对于俯仰角,要让用户不能看向高于89度的地方(在90度时视角会发生逆转,所以我们把89度作为极限),同样也不允许小于-89度。这样能够保证用户只能看到天空或脚下,但是不能超越这个限制。我们可以在值超过限制的时候将其改为极限值来实现:

if(pitch > 89.0f)pitch =  89.0f;
if(pitch < -89.0f)pitch = -89.0f;

注意我们没有给偏航角设置限制,这是因为我们不希望限制用户的水平旋转。当然,给偏航角设置限制也很容易,如果你愿意可以自己实现。

第四步

就是通过俯仰角和偏航角来计算以得到真正的方向向量:

glm::vec3 front;
front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
front.y = sin(glm::radians(pitch));
front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
cameraFront = glm::normalize(front);

计算出来的方向向量就会包含根据鼠标移动计算出来的所有旋转了。由于cameraFront向量已经包含在GLM的lookAt函数中,我们这就没什么问题了。

最后的代码应该是这样的:

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if(firstMouse)//屏蔽第一次鼠标运动{lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos; lastX = xpos;lastY = ypos;float sensitivity = 0.05;xoffset *= sensitivity;yoffset *= sensitivity;//计算偏移量yaw   += xoffset;//修改欧拉角pitch += yoffset;if(pitch > 89.0f)//规定范围pitch = 89.0f;if(pitch < -89.0f)pitch = -89.0f;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));cameraFront = glm::normalize(front);
}

5、声明函数

//监听鼠标移动事件:xPos 和 yPos 代表当前鼠标位置
void MouseCallback(GLFWwindow *window, double xPos, double yPos);

6、绑定

glfwSetCursorPosCallback(window, MouseCallback);

7、书写函数

void MouseCallback(GLFWwindow *window, double xPos, double yPos)
{if (firstMouse) {  //只有第一次才把鼠标的初始位置更新为 xPos 和 yPos 值lastX = xPos;lastY = yPos;firstMouse = false;}GLfloat xOffset = xPos - lastX;  //当前位置 - 上一个xGLfloat yOffset = lastY - yPos;  //注意这里是相反的,因为 y 坐标是从底部往顶部依次增大的lastX = xPos;lastY = yPos;camera.ProcessMouseMovement(xOffset, yOffset);
}
void ProcessMouseMovement(GLfloat xOffset, GLfloat yOffset, GLboolean constrainPith = true){xOffset *= this->mouseSensitivity;yOffset *= this->mouseSensitivity;this->yaw += xOffset;this->pitch += yOffset;if (constrainPith) {  //保证用户只能看到天空或脚下,但是不能超越这个限制//设置界限 < 90.0f,超过 90 度就会失效,视角发生逆转,因为直角是 90 度if (this->pitch > 89.0f) {this->pitch = 89.0f;}if (this->pitch < -89.0f) {this->pitch = -89.0f;}}this->updateCameraVectors();}

鼠标控制缩放

实现一个缩放(Zoom)接口。在之前的教程中我们说视野(Field of View)或fov定义了我们可以看到场景中多大的范围。当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉。我们会使用鼠标的滚轮来放大。

(1)定义鼠标滚轮的回调函数:

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{if(fov >= 1.0f && fov <= 45.0f)fov -= yoffset;if(fov <= 1.0f)fov = 1.0f;if(fov >= 45.0f)fov = 45.0f;
}

当滚动鼠标滚轮的时候,yoffset值代表我们竖直滚动的大小。当scroll_callback函数被调用后,我们改变全局变量fov变量的内容。因为45.0f是默认的视野值,我们将会把缩放级别(Zoom Level)限制在1.0f45.0f

(2)把透视投影矩阵上传到GPU,但现在使用fov变量作为它的视野:

glm::mat4 projection = glm::perspective(glm::radians((float)fov), static_cast<GLfloat>(screenWidth) / static_cast<GLfloat>(screenHeight), 0.1f, 1000.0f);

(3)记注册鼠标滚轮的回调函数:

glfwSetScrollCallback(window, scroll_callback);

哈,如果你一步步做下来,会发现问题,鼠标滑轮控制大小好像不管用耶,为什么呢,其实犯了一个很低级的错误(我也是摆弄好久搞不明白。。。。。。一度怀疑人生),问题就在

(2)把透视投影矩阵上传到GPU,但现在使用fov变量作为它的视野:

glm::mat4 projection = glm::perspective(glm::radians((float)fov), static_cast<GLfloat>(screenWidth) / static_cast<GLfloat>(screenHeight), 0.1f, 1000.0f);

这一步,它的位置不对,应该放在主函数

//画图
    while (!glfwWindowShouldClose(window))
    {

的里面,不是放这个循环的外面。我们知道它是控制画这一过程的,随着时间、用户的操作,它随机响应各种变化,然后画出响应的画面。如果放在外面,就像相当于,projection一直是定值,它没有被修改。因为它在循环外就执行一次

主函数的修改

(1)由于顶点改变基本的渲染修改

定义所有顶点的工作非常复杂繁琐,我这里直接把定义好的36个顶点都列出来(6个面 * 每个面2个三角形 * 每个三角形3个顶点),每个面顶点都包含了纹理坐标。

float vertices[] = {-0.5f, -0.5f, -0.5f,  1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f,  1.0f, 0.0f, 0.0f,0.5f,  0.5f, -0.5f,  1.0f, 0.0f, 0.0f,0.5f,  0.5f, -0.5f,  1.0f, 0.0f, 0.0f,-0.5f,  0.5f, -0.5f, 1.0f, 0.0f,0.0f,-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,-0.5f, -0.5f,  0.5f, 0.0f, 1.0f, 0.0f,0.5f, -0.5f,  0.5f,  0.0f, 1.0f, 0.0f,0.5f,  0.5f,  0.5f,  0.0f, 1.0f, 0.0f,0.5f,  0.5f,  0.5f,  0.0f, 1.0f, 0.0f,-0.5f,  0.5f,  0.5f, 0.0f, 1.0f, 0.0f,-0.5f, -0.5f,  0.5f, 0.0f, 1.0f, 0.0f,-0.5f,  0.5f,  0.5f, 0.0f, 0.0f, 1.0f,-0.5f,  0.5f, -0.5f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f,  0.5f, 0.0f, 0.0f, 1.0f,-0.5f,  0.5f,  0.5f, 0.0f, 0.0f, 1.0f,0.5f,  0.5f,  0.5f,  1.0f, 1.0f, 0.0f,0.5f,  0.5f, -0.5f,  1.0f, 1.0f, 0.0f,0.5f, -0.5f, -0.5f,  1.0f, 1.0f, 0.0f,0.5f, -0.5f, -0.5f,  1.0f, 1.0f, 0.0f,0.5f, -0.5f,  0.5f,  1.0f, 1.0f, 0.0f,0.5f,  0.5f,  0.5f,  1.0f, 1.0f, 0.0f,-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,0.5f, -0.5f, -0.5f,  1.0f, 0.0f, 1.0f,0.5f, -0.5f,  0.5f,  1.0f, 0.0f, 1.0f,0.5f, -0.5f,  0.5f,  1.0f, 0.0f, 1.0f,-0.5f, -0.5f,  0.5f, 1.0f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,-0.5f,  0.5f, -0.5f, 0.0f, 1.0f, 1.0f,0.5f,  0.5f, -0.5f,  0.0f, 1.0f, 1.0f,0.5f,  0.5f,  0.5f,  0.0f, 1.0f, 1.0f,0.5f,  0.5f,  0.5f,  0.0f, 1.0f, 1.0f,-0.5f,  0.5f,  0.5f, 0.0f, 1.0f, 1.0f,-0.5f,  0.5f, -0.5f, 0.0f, 1.0f, 1.0f
};

修改顶点属性的设置

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

修改顶点着色器中纹理坐标的位置

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
out vec3 Color;
//uniform mat4 transform;
uniform mat4 model;  //模型变化
uniform mat4 view;  //相机坐标系
uniform mat4 projection;  //投影变换
void main(){//gl_Position = transform * vec4(position, 1.0f);//从右向左乘:先乘 model,最后乘 projectiongl_Position = projection * view * model * vec4(position, 1.0f);Color = color;
}

修改片段着色器

#version 330 core
in vec3 Color;
out vec4 color;void main(){color = vec4(Color, 1.0f);
}

再显示的方式就是我们之前讲过的glDrawArrays(GL_TRIANGLES, 0, 36);

(2)深度测试,为显示立方体

OpenGL内部本就保存了一份顶点的深度信息,这个信息的名字叫z缓存(z-buffer),也叫深度缓存。默认情况下,深度检测是关闭的,我们需要在某个位置把它打开。打开的方法是调用glEnable(GL_DEPTH_TEST);

然后,在清除屏幕的时候,也需要把深度缓存的数据清除掉!

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

(3)定义矩阵

1、定义模型变换矩阵:

glm::mat4 model;
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));

模型会根据运行时间旋转一定的角度,看起来有动画的效果。

2、定义观察变换矩阵:

glm::mat4 view;
view = camera.GetViewMatrix();  //获得相机矩阵

3、定义投影变换矩阵:

glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

glm::radians定义了FOV,角度为45度。第二个参数定义了屏幕宽高比(aspect ratio),这个值会影响显示到窗口中的物体是原样显示还是被拉伸。0.1f是近裁剪面,100.0f是远裁剪面。

(4)矩阵转给着色器

1、修改顶点着色器

将这些矩阵应用到顶点着色器中,顶点着色器需要3个变量来接收这些矩阵然后使用:

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4 (aPos, 1.0f);TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

主循环中也要每次给这三个变量赋值:

shader.setMat4("model", glm::value_ptr(model));
shader.setMat4("view", glm::value_ptr(view));
shader.setMat4("projection", glm::value_ptr(projection));

2、将矩阵传入着色器

 GLuint modelLoc = glGetUniformLocation(ourShader.Program, "model");  //到 vs 找到那个 model 变量
GLuint viewLoc = glGetUniformLocation(ourShader.Program, "view");  //到 vs 找到那个 view 变量
GLuint projectionLoc = glGetUniformLocation(ourShader.Program, "projection");  //到 vs 找到那个 projection 变量
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));

源码在

1、core.vs

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
out vec3 Color;
//uniform mat4 transform;
uniform mat4 model;  //模型变化
uniform mat4 view;  //相机坐标系
uniform mat4 projection;  //投影变换
void main(){//gl_Position = transform * vec4(position, 1.0f);//从右向左乘:先乘 model,最后乘 projectiongl_Position = projection * view * model * vec4(position, 1.0f);Color = color;
}

2、core.frag

#version 330 core
in vec3 Color;
out vec4 color;void main(){color = vec4(Color, 1.0f);
}

3、Shader.h

#pragma once//#ifndef shader_hpp
//#define shader_hpp
//#endif /* shader_hpp */
#include<string>
#include<fstream>  //可以打开文件
#include<sstream>
#include<iostream>
#include<GL/glew.h>class Shader {GLuint vertex, fragment;
public:GLuint Program;Shader(const GLchar * vertexPath, const GLchar * fragmentPath){std::string vertexCode;std::string fragmentCode;std::ifstream vShaderFile;std::ifstream fShaderFile;vShaderFile.exceptions(std::ifstream::badbit);fShaderFile.exceptions(std::ifstream::badbit);try {vShaderFile.open(vertexPath);fShaderFile.open(fragmentPath);std::stringstream vShaderStream, fShaderStream;vShaderStream << vShaderFile.rdbuf();fShaderStream << fShaderFile.rdbuf();//文件关闭顺序,先 v 再 fvShaderFile.close();fShaderFile.close();vertexCode = vShaderStream.str();fragmentCode = fShaderStream.str();}catch (std::ifstream::failure a) {std::cout <<"ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ"<< std::endl;}//类型转换const GLchar *vShaderCode = vertexCode.c_str();const GLchar *fShaderCode = fragmentCode.c_str();//import and compile the shadervertex = glCreateShader(GL_VERTEX_SHADER);  //不用重新定义glShaderSource(vertex, 1, &vShaderCode, NULL);glCompileShader(vertex);  //编译GLint success;GLchar infoLog[512];glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);  //编译是否完成的位置if (!success) {glGetShaderInfoLog(vertex, 512, NULL, infoLog);std::cout <<"ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"<< infoLog << std::endl;}//边缘调色器fragment = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragment, 1, &fShaderCode, NULL);glCompileShader(fragment);  //编译glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);  //编译是否完成的位置if (!success) {glGetShaderInfoLog(fragment, 512, NULL, infoLog);std::cout <<"ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"<< infoLog << std::endl;}//create the program and link the programthis->Program = glCreateProgram();  //创建着色器程序glAttachShader(this->Program, vertex);glAttachShader(this->Program, fragment);glLinkProgram(this->Program);  //链接glValidateProgram(this->Program);  //可省略glGetProgramiv(this->Program, GL_LINK_STATUS, &success);if (!success) {glGetProgramInfoLog(this->Program, 512, NULL, infoLog);  //获取链接情况std::cout <<"ERROR::SHADER::PROGRAM::LINKING_FAILED\n" <<infoLog << std::endl;}}~Shader() {glDetachShader(this->Program, vertex);glDetachShader(this->Program, fragment);glDeleteShader(vertex);glDeleteShader(fragment);glDeleteProgram(this->Program);}void Use() {glUseProgram(this->Program);}
};

4、main

#include <iostream>//GLEW
#define GLEW_STATIC
#include <GL/glew.h>//GLFW
#include <GLFW/glfw3.h>//Shader
#include "Shader.h"// SOIL2
//Linux 用的是 \, 但是 / 都可以用//glm
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>  //需要什么变换,就导入什么文件,具体可以去官网看
#include <glm/gtc/type_ptr.hpp>#include "Camera.h"  //当前引用,所以用""const GLint WIDTH = 800, HEIGHT = 600;  //新建窗口//键盘回应
void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode);
//监听鼠标移动事件:xPos 和 yPos 代表当前鼠标位置
void MouseCallback(GLFWwindow *window, double xPos, double yPos);
//键盘移动
void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);void DoMovement();//初始化一个相机
Camera camera(glm::vec3(0.0f, 0.0f, 2.0f));
//设置初始量
GLfloat lastX = WIDTH / 2.0;
GLfloat lastY = HEIGHT / 2.0;
bool firstMouse = true;
double fov = 45.0;
//鼠标缩放
//double fov = 45.0;bool keys[1024];  //存放获取的所有键盘操作,先存下来再进行操作GLfloat deltaTime = 0.0f;  //两帧之间的间隔时间
GLfloat lastTime = 0.0f;  //上一帧绘制的时间int main()
{glfwInit();//OpenGL 版本glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//窗口设置glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  //用的是新版的 OpenGL 3.3glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // must for MacglfwWindowHint(GLFW_RESIZABLE, GL_FALSE);  //改为 GL_TRUE,改变窗口,纵横比会变GLFWwindow *window = glfwCreateWindow(WIDTH, HEIGHT, "Learn OpenGL B17070306", nullptr,nullptr);  //窗口名字改成自己的学号if (nullptr == window){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}// next two lines are for mac retina displayint screenWidth, screenHeight;glfwGetFramebufferSize(window, &screenWidth, &screenHeight);  //获取窗口大小glfwMakeContextCurrent(window);  //可以新建很多 window// Set the required callback function//KeyCallback 是响应键盘消息的回调函数glfwSetKeyCallback(window, KeyCallback);//mouse_callback 是响应鼠标消息的回调函数,鼠标一移动 MouseCallback 函数就会被调用glfwSetCursorPosCallback(window, MouseCallback);//注册鼠标滚轮的回调函数glfwSetScrollCallback(window, ScrollCallback);glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);  //不允许光标出现//glewExperimental = GL_TRUE;  //在 OpenGL 4.3 以上要加这一句:让 glewInit() 可以顺利完成所有的初始化if (GLEW_OK != glewInit()){std::cout << "Failed to initialise GLEW" << std::endl;return -1;}glViewport(0, 0, screenWidth, screenHeight);  //从(0,0)开始画点,直到 WIDTH 和 HEIGHTglEnable(GL_DEPTH_TEST);  //深度测试glDepthFunc(GL_LESS);  //深度信息小于当期信息,就把进行测试/*//启动透明度混合,固定不能改,alpha 线性混合:设置当前为 α ,其他就为 1- αglEnable(GL_BLEND);//表示把渲染的图像融合到目标区域。也就是说源的每一个像素的alpha都等于自己的alpha,//目标的每一个像素的alpha等于1减去该位置源像素的alpha。因此不论叠加多少次,亮度是不变的。glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);*///vs 是顶点调色器,frag 是边缘调色器Shader ourShader = Shader("core.vs", "core.frag");  //文件相对路径//now the verte information comes belowfloat vertices[] = {-0.5f, -0.5f, -0.5f,  1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f,  1.0f, 0.0f, 0.0f,0.5f,  0.5f, -0.5f,  1.0f, 0.0f, 0.0f,0.5f,  0.5f, -0.5f,  1.0f, 0.0f, 0.0f,-0.5f,  0.5f, -0.5f, 1.0f, 0.0f,0.0f,-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,-0.5f, -0.5f,  0.5f, 0.0f, 1.0f, 0.0f,0.5f, -0.5f,  0.5f,  0.0f, 1.0f, 0.0f,0.5f,  0.5f,  0.5f,  0.0f, 1.0f, 0.0f,0.5f,  0.5f,  0.5f,  0.0f, 1.0f, 0.0f,-0.5f,  0.5f,  0.5f, 0.0f, 1.0f, 0.0f,-0.5f, -0.5f,  0.5f, 0.0f, 1.0f, 0.0f,-0.5f,  0.5f,  0.5f, 0.0f, 0.0f, 1.0f,-0.5f,  0.5f, -0.5f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f,  0.5f, 0.0f, 0.0f, 1.0f,-0.5f,  0.5f,  0.5f, 0.0f, 0.0f, 1.0f,0.5f,  0.5f,  0.5f,  1.0f, 1.0f, 0.0f,0.5f,  0.5f, -0.5f,  1.0f, 1.0f, 0.0f,0.5f, -0.5f, -0.5f,  1.0f, 1.0f, 0.0f,0.5f, -0.5f, -0.5f,  1.0f, 1.0f, 0.0f,0.5f, -0.5f,  0.5f,  1.0f, 1.0f, 0.0f,0.5f,  0.5f,  0.5f,  1.0f, 1.0f, 0.0f,-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,0.5f, -0.5f, -0.5f,  1.0f, 0.0f, 1.0f,0.5f, -0.5f,  0.5f,  1.0f, 0.0f, 1.0f,0.5f, -0.5f,  0.5f,  1.0f, 0.0f, 1.0f,-0.5f, -0.5f,  0.5f, 1.0f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,-0.5f,  0.5f, -0.5f, 0.0f, 1.0f, 1.0f,0.5f,  0.5f, -0.5f,  0.0f, 1.0f, 1.0f,0.5f,  0.5f,  0.5f,  0.0f, 1.0f, 1.0f,0.5f,  0.5f,  0.5f,  0.0f, 1.0f, 1.0f,-0.5f,  0.5f,  0.5f, 0.0f, 1.0f, 1.0f,-0.5f,  0.5f, -0.5f, 0.0f, 1.0f, 1.0f};//the date should be transfered to the memory on the Graphics Card,传到显存GLuint VAO, VBO;  //VAO:Vertex Array Object   VBO:Vertex Buffer Object传数据glGenVertexArrays(1, &VAO);  //创建 VAOglGenBuffers(1, &VBO);glBindVertexArray(VAO);  //设当前直线glBindBuffer(GL_ARRAY_BUFFER, VBO);  //VAO 和 VBO 成对出现// transfer the data:传数据glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);  //静态访问,几乎不修改//set the attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,6 * sizeof(GLfloat), (GLvoid *)0);  //0:对应调色器里 location 的值;3:对应 vec3 三个量;GL_FLOAT:浮点型;GL_FALSE:;5*sizeof(GLfloat):对应 Buffer 里传的数据;(GLvoid*)0:从第 0 个位置开始glEnableVertexAttribArray(0);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,6 * sizeof(GLfloat), (GLvoid *)(3 * sizeof(GLfloat)));  //0:对应调色器里 location 的值;3:对应 vec3 三个量;GL_FLOAT:浮点型;GL_FALSE:;5*sizeof(GLfloat):对应 Buffer 里传的数据;(GLvoid*)0:从第 0 个位置开始glEnableVertexAttribArray(1);glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);glm::mat4 view = glm::mat4(1.0f);  //初始化 4 * 4 单位矩阵//计算透视投影矩阵,设置几个参数就OK了//将视锥体的空间投影成正方体/将长方体变成立方体//第一个:正交投影:FoV/Field of View 视域。默认焦距是 1 。//第二个:长宽比//第三个、第四个:视平台里的上下底的距离,人的视觉空间。设置:近:0.1f。远:1000.0f。//画图while (!glfwWindowShouldClose(window)){GLfloat currentFrame = glfwGetTime();  //屏幕刚画出来的时间deltaTime = currentFrame - lastTime;  //更新两帧之间的间隔时间lastTime = currentFrame;  //更新上一帧绘制的时间glfwPollEvents();  //把所有事件系统都取过来:键盘/鼠标等操作DoMovement();  //获取完操作之后的额外参数glClearColor(0.2f, 0.3f, 0.3f, 1.0f);  //窗口背景颜色,RGB,最后一个是透明度glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除颜色缓冲,否则会保留之前的移动轨迹颜色//Bind the shaderourShader.Use();glm::mat4 model = glm::mat4(1.0f);  //modelmodel = glm::rotate(model, glm::radians(20.0f) * static_cast<GLfloat>(glfwGetTime()), glm::vec3(1.0f, 1.0f, 1.0f));view = camera.GetViewMatrix();  //获得相机矩阵//glm::mat4 projection = glm::perspective(glm::radians((float)fov),
(float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);//glm 从 0.9.9 版本起,默认会将矩阵类型初始化为一个零矩阵(所有元素均为 0)//glm::mat4 transform = glm::mat4(1.0f);  //初始化 4 * 4 单位矩阵//旋转//GLM 希望它的角度是弧度制,radians 将角度转化为弧度制//glfwGetTime():让图形一直变换,做一个类型转换,用 static_cast<GLfloat>,设为 GLfloat 型//glm::vec3(1.0f, 1.0f, 1.0f),分别绕 x 轴、y 轴、z 轴进行旋转,如果都为 1.0f,就是绕和向量 (1,1,1) 转//transform = glm::rotate(transform, glm::radians(20.0f) * static_cast<GLfloat>(glfwGetTime()), glm::vec3(1.0f, 1.0f, 1.0f));//缩放,x、y、z 都缩放到原来的 0.5 倍//transform = glm::scale(transform, glm::vec3(0.5f, 0.5f, 0.5f));//平移//transform = //将矩阵传入着色器GLuint modelLoc = glGetUniformLocation(ourShader.Program, "model");  //到 vs 找到那个 model 变量GLuint viewLoc = glGetUniformLocation(ourShader.Program, "view");  //到 vs 找到那个 view 变量GLuint projectionLoc = glGetUniformLocation(ourShader.Program, "projection");  //到 vs 找到那个 projection 变量//GLuint transLoc = glGetUniformLocation(ourShader.Program, "transform");  //到 vs 找到那个 transform 变量//Matrix4fv:4维矩阵,fv:浮点类型//transLoc:变量 uniform 的位置//1:代表只传入一个矩阵//GL_FALSE:不对矩阵进行置换,即不交换矩阵的行和列。GLM 的默认布局就是列主序,所以并不需要置换矩阵//最后:直接给出 transform 矩阵数组,这里我们要把矩阵转换成数组的格式传递。//glUniformMatrix4fv(transLoc, 1, GL_FALSE, glm::value_ptr(transform));  //glUniformMatrix4fv:四个坐标  glUniform4fv:三个坐标//Matrix4fv:4维矩阵,fv:浮点类型//modelLoc:变量 uniform 的位置//1:代表只传入一个矩阵//GL_FALSE:不对矩阵进行置换,即不交换矩阵的行和列。GLM 的默认布局就是列主序,所以并不需要置换矩阵//最后:直接给出 model 矩阵数组,这里我们要把矩阵转换成数组的格式传递。glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));//Draw the triangleglBindVertexArray(VAO);  //使用 VAO,直接绑定glDrawArrays(GL_TRIANGLES, 0, 36);  //画三角形,总共有 36 个顶点//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);glBindVertexArray(0);glfwSwapBuffers(window);  //调用双面进行画,显示一个,另一个在画,画面更流畅}glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);//lDeleteBuffers(1, &EBO);glfwTerminate();return 0;
}void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode)
{if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {glfwSetWindowShouldClose(window, GL_TRUE);  //设定关闭窗口}if (key >= 0 && key < 1024) {if (action == GLFW_PRESS){keys[key] = true;  //键盘按下去了,就设置为 true,即为1}else if (action == GLFW_RELEASE){keys[key] = false;  //键盘松开,设为 false}}
}void MouseCallback(GLFWwindow *window, double xPos, double yPos)
{if (firstMouse) {  //只有第一次才把鼠标的初始位置更新为 xPos 和 yPos 值lastX = xPos;lastY = yPos;firstMouse = false;}GLfloat xOffset = xPos - lastX;  //当前位置 - 上一个xGLfloat yOffset = lastY - yPos;  //注意这里是相反的,因为 y 坐标是从底部往顶部依次增大的lastX = xPos;lastY = yPos;camera.ProcessMouseMovement(xOffset, yOffset);
}void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{if (fov >= 1.0f && fov <= 45.0f)fov -= yoffset;if (fov <= 1.0f)fov = 1.0f;if (fov >= 45.0f)fov = 45.0f;
}void DoMovement()
{if (keys[GLFW_KEY_W] || keys[GLFW_KEY_UP]) {  //W 或者 ↑camera.ProcessKeyboard(FORWARD, deltaTime);}if (keys[GLFW_KEY_S] || keys[GLFW_KEY_DOWN]) {camera.ProcessKeyboard(BACKWARD, deltaTime);}if (keys[GLFW_KEY_A] || keys[GLFW_KEY_LEFT]) {camera.ProcessKeyboard(LEFT, deltaTime);}if (keys[GLFW_KEY_D] || keys[GLFW_KEY_RIGHT]) {camera.ProcessKeyboard(RIGHT, deltaTime);}
}

5、Camera.h


//
//  Camera.h
//  Course 3
//
//  Created by rui huang on 10/18/17.
//  Copyright © 2017 rui huang. All rights reserved.
//#pragma once#include <vector>#define GLEW_STATIC
#include <GL/glew.h>#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>enum Camera_Movement
{FORWARD,BACKWARD,LEFT,RIGHT
};const GLfloat YAW = -90.0f;//上下
const GLfloat PITCH = 0.0f;//左右
const GLfloat SPEED = 6.0f;
const GLfloat SENSITIVITY = 0.25f;
const GLfloat ZOOM = 45.0f;//缩放// An abstract camera class that processes input and calculates the corresponding Eular Angles, Vectors and Matrices for OpenGL
class Camera
{
public:// Constructor with vectors//设置摄像机位置、上向量、俯仰角Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), GLfloat yaw = YAW, GLfloat pitch = PITCH) :front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM){this->position = position;  //相机的起点this->worldUp = up;  //向前的量this->yaw = yaw;  //仰角:飞机上下动this->pitch = pitch;  //飞机左右动this->updateCameraVectors();  //更新:建立坐标系}// Constructor with scalar valuesCamera(GLfloat posX, GLfloat posY, GLfloat posZ, GLfloat upX, GLfloat upY, GLfloat upZ, GLfloat yaw = YAW, GLfloat pitch = PITCH) :front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM){this->position = glm::vec3(posX, posY, posZ);this->worldUp = glm::vec3(upX, upY, upZ);this->yaw = yaw;this->pitch = pitch;this->updateCameraVectors();}void ProcessKeyboard(Camera_Movement direction, GLfloat deltaTime){//乘以 deltaTime:消除电脑性能,控制时间一样长GLfloat velocity = this->movementSpeed * deltaTime;//处理键盘//向前:加上向前if (direction == FORWARD) {this->position += this->front * velocity;}//向后:减去向前if (direction == BACKWARD) {this->position -= this->front * velocity;}if (direction == LEFT) {this->position -= this->right * velocity;}if (direction == RIGHT) {this->position += this->right * velocity;}}void ProcessMouseMovement(GLfloat xOffset, GLfloat yOffset, GLboolean constrainPith = true){xOffset *= this->mouseSensitivity;yOffset *= this->mouseSensitivity;this->yaw += xOffset;this->pitch += yOffset;if (constrainPith) {  //保证用户只能看到天空或脚下,但是不能超越这个限制//设置界限 < 90.0f,超过 90 度就会失效,视角发生逆转,因为直角是 90 度if (this->pitch > 89.0f) {this->pitch = 89.0f;}if (this->pitch < -89.0f) {this->pitch = -89.0f;}}this->updateCameraVectors();}glm::mat4 GetViewMatrix(){//lookAt:观察矩阵:摄像机位置;目标位置;上向量return glm::lookAt(this->position, this->position + this->front, this->up);}GLfloat GetZoom(){return this->zoom;}void ProcessMouseScroll(GLfloat yOffset){}
private://私用变量glm::vec3 position;glm::vec3 front;glm::vec3 up;glm::vec3 right;glm::vec3 worldUp;GLfloat yaw;GLfloat pitch;GLfloat movementSpeed;GLfloat mouseSensitivity;GLfloat zoom;void updateCameraVectors(){glm::vec3 front;//通过俯仰角和偏航角来计算以得到真正的方向向量//极坐标系下的:x、y、z轴front.x = cos(glm::radians(this->pitch)) * cos(glm::radians(this->yaw));  //x 轴向前front.y = sin(glm::radians(this->pitch));  //y 轴向上front.z = cos(glm::radians(this->pitch)) * sin(glm::radians(this->yaw)); //z 轴向右this->front = glm::normalize(front);  //向前this->right = glm::normalize(glm::cross(this->front, this->worldUp));  //向右:向前 × 向上,normalize单位化this->up = glm::normalize(glm::cross(this->right, this->front));  //向上:向右 × 向前}
};

参考链接:

https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera

https://www.jianshu.com/p/bc09f44e0856

https://blog.csdn.net/Wonz5130/article/details/83514217

计算机图形学|南邮——画由键盘鼠、标控制的正方体相关推荐

  1. OpenGL南邮计算机图形学实验报告四——用键盘控制物体

    OpenGL南邮计算机图形学实验报告四--用键盘控制物体 计算机图形学的新题目要求 OpenGL配置参考: 南邮老前辈wonz哥的OpenGL配置(Shader.h始终不用改).SOIL2 环境配置. ...

  2. 平移计算机图形学代码注释,求代码注释:计算机图形学的OpenGL画四面体。高手来吧。争取每句都注释下。谢谢...

    求代码注释:计算机图形学的OpenGL画四面体.高手来吧.争取每句都注释下.谢谢 答案:3  信息版本:手机版 解决时间 2018-12-20 05:00 已解决 2018-12-20 01:58 求 ...

  3. 图形学画直线c语言,002计算机图形学之直线画线算法

    002计算机图形学之直线画线算法 我们知道直线方程的斜截式是如下的样子: y = kx +b 在显示器上显示直线的话,如果使用如上的方程,每描一个点 需要进行一次浮点乘法,一次浮点加法,和取整操作. ...

  4. 东北大学和南邮的计算机,考研南邮跟东北大学通信与信息系统

    chaishuangmin 签约达人 07-16 TA获得超过8936个赞 我觉得东南大学的无线电很强的,绝对不可能和南邮在一个档次上,应该是强N倍.当然不是说南邮不好,而是说在和东南大学相比较的话, ...

  5. 学计算机去南邮还是南信,南信大和南邮相比怎么样 南信大怎么样

    南信大和南邮相比怎么样,南京邮电大学简称南邮,南京信息工程大学简称南信大,都是南京的高校,那么南信大和南邮相比怎么样呢?南信大怎么样呢? 南信大和南邮相比怎么样 南信大,坐落于南京江北新区,全称为南京 ...

  6. 图形学画直线c语言,计算机图形学:3种画直线算法(转)

    //--------------------------------------------------------------------- //绘制直线的DDA算法基本函数 //--------- ...

  7. 安大计算机与南邮,安徽大学计算机研究生和南京邮电大学研究生哪个好,安大是211,南邮不是,主要从就业角度考虑,哪个...

    # 5楼 15月前 [b]计算机考研院校排名[/b][p=28, null, left][font=微软雅黑][size=16px]1.北京大学(分数排名) 330[/size][/font][/p] ...

  8. 合集 | 南邮实验报告目录

    南邮实验报告目录 南邮各种学科编程类实验程序 数据结构 南邮数据结构实验1.1:顺序表的相关操作 南邮数据结构实验1.2:带表头结点单链表的相关操作 南邮数据结构实验1.3:带表头结点单链表的逆置 南 ...

  9. OpenGL南邮计算机图形学实验报告二——两个纹理的渐变变换和移动

    OpenGL南邮计算机图形学实验报告二--两个纹理的渐变变换和移动 计算机图形学的新题目要求 OpenGL配置参考: 南邮老前辈wonz哥的OpenGL配置(Shader.h始终不用改).SOIL2 ...

最新文章

  1. YII2中分页组件的使用
  2. Redis在Windows上编译
  3. 软件工程结对开发团队成员以及题目介绍
  4. 中国水泥工业节能减排行业盈利前景与十四五专项规划报告2022年
  5. flask mysql 版本_Flask mysql
  6. camunda 流程执行追踪_九思OA项目管理解决方案:规范企业项目流程,掌握项目进展...
  7. java发送请求_Java发送Http请求
  8. Solidity语言基础 和 Etherum ERC20合约基础
  9. jgit - java实现git操作
  10. pycharm 设置环境变量
  11. U盘文件格式简单介绍
  12. 如何爬取ajax网页之爬取雪球网文章
  13. 解压ubi文件_IoT(八)ubi文件系统挂载解包
  14. mac tortoisesvn客户端_tortoisesvn mac版下载
  15. socket写超时c语言,设置socket超时时间
  16. 微信小程序图片懒加载(自定义组件)
  17. 华硕FX63VM笔记本bios如何设置U盘启动
  18. CocosCreater 接入手Q (QQ小游戏)、小米快游戏 接入指南、脱坑指南
  19. Oauth2.0 QQ微信微博实现第三方登陆
  20. 只用200行Go代码写一个自己的区块链!

热门文章

  1. 记Ubuntu18.04安装后网卡搜不到wifi的问题的解决过程(Device c822驱动问题)
  2. 建立WEB两大经典!《VML极道教程》+FlashVml(闪耀之星)3.0中/英文版联合发布!
  3. 微信身份证 CTID CARD
  4. 共阴数码管与共阳数码管工作原理有什么不同
  5. android的各个版本和logo
  6. distpicker省市县三级联动简单使用方法
  7. 关于利用傅里叶级数拟合三角波和矩形波matlab
  8. Vue之Axios跨域问题解决方案
  9. 解决ios输入框无法自动弹起的问题
  10. html在qq怎么全屏显示,QQ怎么设置全屏资料背景