LOADING

加载过慢请开启缓存 浏览器默认开启

TyranitarX

摄像机Camera

OpenGL 2024/10/11

摄像机/观察空间


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

1. 摄像机位置


获取摄像机位置很简单。摄像机位置简单来说就是世界空间中一个指向摄像机位置的向量。我们把摄像机位置设置为上一节中的那个相同的位置:

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

2. 摄像机方向


下一个需要的向量是摄像机的方向,这里指的是摄像机指向哪个方向。现在我们让摄像机指向场景原点:(0, 0, 0)。还记得如果将两个矢量相减,我们就能得到这两个矢量的差吗?用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量。由于我们知道摄像机指向z轴负方向,但我们希望方向向量(Direction Vector)指向摄像机的z轴正方向。如果我们交换相减的顺序,我们就会获得一个指向摄像机正z轴方向的向量:

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

3. 右轴


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

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

4. 上轴


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

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

在叉乘和一些小技巧的帮助下,我们创建了所有构成观察/摄像机空间的向量。对于想学到更多数学原理的读者,提示一下,在线性代数中这个处理叫做格拉姆—施密特正交化(Gram-Schmidt Process)。使用这些摄像机向量我们就可以创建一个LookAt矩阵了,它在创建摄像机的时候非常有用。

Look At


使用矩阵的好处之一是如果你使用3个相互垂直(或非线性)的轴定义了一个坐标空间,你可以用这3个轴外加一个平移向量来创建一个矩阵,并且你可以用这个矩阵乘以任何向量来将其变换到那个坐标空间。这正是LookAt矩阵所做的,现在我们有了3个相互垂直的轴和一个定义摄像机空间的位置坐标,我们可以创建我们自己的LookAt矩阵了:
$$
LookAt = \begin{bmatrix} \color{red}{R_x} & \color{red}{R_y} & \color{red}{R_z} & 0 \ \color{green}{U_x} & \color{green}{U_y} & \color{green}{U_z} & 0 \ \color{blue}{D_x} & \color{blue}{D_y} & \color{blue}{D_z} & 0 \ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} 1 & 0 & 0 & -\color{purple}{P_x} \ 0 & 1 & 0 & -\color{purple}{P_y} \ 0 & 0 & 1 & -\color{purple}{P_z} \ 0 & 0 & 0 & 1 \end{bmatrix}
$$

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));

在这里实现一个Camera类来维护LookAt矩阵

#ifndef CAMERA_H
#define CAMERA_H

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <string>
#include <iostream>
#include <fstream>
#include <sstream>


class Camera
{
	public:
		glm::vec3 cameraUp;
		glm::vec3 cameraPos;
		glm::vec3 cameraFront;
		glm::mat4 lookAt;
		

		Camera(glm::vec3 cameraPos, glm::vec3 cameraFront, glm::vec3 cameraUp) 
		{
			this->cameraPos = cameraPos;
			this->cameraFront = cameraFront;
			this->cameraUp = cameraUp;
			this->lookAt = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
		}

		void changePos(glm::vec3 cameraPos) {
			this->cameraPos = cameraPos;
			this->lookAt = glm::lookAt(cameraPos, cameraPos + this->cameraFront, this->cameraUp);
		}

		void changeFront(glm::vec3 cameraFront) {
			this->cameraFront = cameraFront;
			this->lookAt = glm::lookAt(cameraPos, cameraPos + this->cameraFront, this->cameraUp);
		}
};


#endif // CAMERA_H

自由移动


让摄像机绕着场景转的确很有趣,但是让我们自己移动摄像机会更有趣!首先我们必须设置一个摄像机系统,所以在我们的程序前面定义一些摄像机变量很有用:

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向量。

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

void processInput(GLFWwindow *window)
{
    ...
    float cameraSpeed = 0.05f; // adjust accordingly
    if (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键的任意一个,摄像机的位置都会相应更新。如果我们希望向前或向后移动,我们就把位置向量加上或减去方向向量。如果我们希望向左右移动,我们使用叉乘来创建一个右向量(Right Vector),并沿着它相应移动就可以了。这样就创建了使用摄像机时熟悉的横移(Strafe)效果。

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

现在你就应该能够移动摄像机了,虽然移动速度和系统有关,你可能会需要调整一下cameraSpeed。

移动速度


目前我们的移动速度是个常量。理论上没什么问题,但是实际情况下根据处理器的能力不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用processInput函数。结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。当你发布你的程序的时候,你必须确保它在所有硬件上移动速度都一样。

图形程序和游戏通常会跟踪一个时间差(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;
  ...
}

与前面的部分结合在一起,我们有了一个更流畅点的摄像机系统

欧拉角


欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:
![[Pasted image 20241011112708.png]]
俯仰角是描述我们如何往上或往下看的角,可以在第一张图中看到。第二张图展示了偏航角,偏航角表示我们往左和往右看的程度。滚转角代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用。每个欧拉角都有一个值来表示,把三个角结合起来我们就能够计算3D空间中任何的旋转向量了。

对于我们的摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会讨论滚转角。给定一个俯仰角和偏航角,我们可以把它们转换为一个代表新的方向向量的3D向量。俯仰角和偏航角转换为方向向量的处理需要一些三角学知识,我们先从最基本的情况开始:

如果我们把斜边边长定义为1,我们就能知道邻边的长度是$$cos x/h=cos x/1=cos xcos⁡ x/h=cos⁡ x/1=cos⁡ x$$它的对边是$$sin y/h=sin y/1=sin ysin⁡ y/h=sin⁡ y/1=sin⁡ y$$这样我们获得了能够得到x和y方向长度的通用公式,它们取决于所给的角度。我们使用它来计算方向向量的分量:

这个三角形看起来和前面的三角形很像,所以如果我们想象自己在xz平面上,看向y轴,我们可以基于第一个三角形计算来计算它的长度/y方向的强度(Strength)(我们往上或往下看多少)。从图中我们可以看到对于一个给定俯仰角的y值等于$$ sin θ $$

direction.y = sin(glm::radians(pitch)); // 注意我们先把角度转为弧度

这里我们只更新了y值,仔细观察x和z分量也被影响了。从三角形中我们可以看到它们的值等于:

direction.x = cos(glm::radians(pitch));
direction.z = cos(glm::radians(pitch));

看看我们是否能够为偏航角找到需要的分量:

就像俯仰角的三角形一样,我们可以看到x分量取决于cos(yaw)的值,z值同样取决于偏航角的正弦值。把这个加到前面的值中,会得到基于俯仰角和偏航角的方向向量:

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));
阅读全文

坐标系统 Coordinate Systems

OpenGL 2024/10/11

重要的5个坐标系统

  • 局部空间(Local Space,或者称为物体空间(Object Space))
  • 世界空间(World Space)
  • 观察空间(View Space,或者称为视觉空间(Eye Space))
  • 裁剪空间(Clip Space)
  • 屏幕空间(Screen Space)

概述


为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。我们的顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。下面的这张图展示了整个流程以及各个变换过程做了什么:

image

  1. 局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。
  2. 下一步是将局部坐标变换为世界空间坐标,世界空间坐标是处于一个更大的空间范围的。这些坐标相对于世界的全局原点,它们会和其它物体一起相对于世界的原点进行摆放。
  3. 接下来我们将世界坐标变换为观察空间坐标,使得每个坐标都是从摄像机或者说观察者的角度进行观察的。
  4. 坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上。
  5. 最后,我们将裁剪坐标变换为屏幕坐标,我们将使用一个叫做视口变换(Viewport Transform)的过程。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。

局部空间


局部空间是指物体所在的坐标空间,即对象最开始所在的地方。想象你在一个建模软件(比如说Blender)中创建了一个立方体。你创建的立方体的原点有可能位于(0, 0, 0),即便它有可能最后在程序中处于完全不同的位置。甚至有可能你创建的所有模型都以(0, 0, 0)为初始位置(译注:然而它们会最终出现在世界的不同位置)。所以,你的模型的所有顶点都是在局部空间中:它们相对于你的物体来说都是局部的。

我们一直使用的那个箱子的顶点是被设定在-0.5到0.5的坐标范围中,(0, 0)是它的原点。这些都是局部坐标。

世界空间


如果我们将我们所有的物体导入到程序当中,它们有可能会全挤在世界的原点(0, 0, 0)上,这并不是我们想要的结果。我们想为每一个物体定义一个位置,从而能在更大的世界当中放置它们。世界空间中的坐标正如其名:是指顶点相对于(游戏)世界的坐标。如果你希望将物体分散在世界上摆放(特别是非常真实的那样),这就是你希望物体变换到的空间。物体的坐标将会从局部变换到世界空间;该变换是由模型矩阵(Model Matrix)实现的。

模型矩阵是一种变换矩阵,它能通过对物体进行位移、缩放、旋转来将它置于它本应该在的位置或朝向。你可以将它想像为变换一个房子,你需要先将它缩小(它在局部空间中太大了),并将其位移至郊区的一个小镇,然后在y轴上往左旋转一点以搭配附近的房子。你也可以把上一节将箱子到处摆放在场景中用的那个矩阵大致看作一个模型矩阵;我们将箱子的局部坐标变换到场景/世界中的不同位置。

观察空间


观察空间经常被人们称之OpenGL的摄像机(Camera)(所以有时也称为摄像机空间(Camera Space)或视觉空间(Eye Space))。观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。而这通常是由一系列的位移和旋转的组合来完成,平移/旋转场景从而使得特定的对象被变换到摄像机的前方。这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里,它被用来将世界坐标变换到观察空间。在下一节中我们将深入讨论如何创建一个这样的观察矩阵来模拟一个摄像机。

正射投影


正射投影矩阵定义了一个类似立方体的平截头箱,它定义了一个裁剪空间,在这空间之外的顶点都会被裁剪掉。创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在使用正射投影矩阵变换至裁剪空间之后处于这个平截头体内的所有坐标将不会被裁剪掉。它的平截头体看起来像一个容器:
image
上面的平截头体定义了可见的坐标,它由宽、高、近(Near)平面和远(Far)平面所指定。任何出现在近平面之前或远平面之后的坐标都会被裁剪掉。正射平截头体直接将平截头体内部的所有坐标映射为标准化设备坐标,因为每个向量的w分量都没有进行改变;如果w分量等于1.0,透视除法则不会改变这个坐标。

要创建一个正射投影矩阵,我们可以使用GLM的内置函数glm::ortho

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);

前两个参数指定了平截头体的左右坐标,第三和第四参数指定了平截头体的底部和顶部。通过这四个参数我们定义了近平面和远平面的大小,然后第五和第六个参数则定义了近平面和远平面的距离。这个投影矩阵会将处于这些x,y,z值范围内的坐标变换为标准化设备坐标。

正射投影矩阵直接将坐标映射到2D平面中,即你的屏幕,但实际上一个直接的投影矩阵会产生不真实的结果,因为这个投影没有将透视(Perspective)考虑进去。所以我们需要透视投影矩阵来解决这个问题。

透视投影


如果你曾经体验过实际生活给你带来的景象,你就会注意到离你越远的东西看起来更小。这个奇怪的效果称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的那样:

image
正如你看到的那样,由于透视,这两条线在很远的地方看起来会相交。这正是透视投影想要模仿的效果,它是使用透视投影矩阵来完成的。这个投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-w到w的范围之间(任何大于这个范围的坐标都会被裁剪掉)。OpenGL要求所有可见的坐标都落在-1.0到1.0范围内,作为顶点着色器最后的输出,因此,一旦坐标在裁剪空间内之后,透视除法就会被应用到裁剪空间坐标上:
$$
out = \begin{pmatrix} x /w \ y / w \ z / w \end{pmatrix}
$$
顶点坐标的每个分量都会除以它的w分量,距离观察者越远顶点坐标就会越小。这是w分量重要的另一个原因,它能够帮助我们进行透视投影。最后的结果坐标就是处于标准化设备空间中的。如果你对正射投影矩阵和透视投影矩阵是如何计算的很感兴趣(且不会对数学感到恐惧的话)我推荐这篇由Songho写的文章

在GLM中可以这样创建一个透视投影矩阵:

glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);

同样,glm::perspective所做的其实就是创建了一个定义了可视空间的大平截头体,任何在这个平截头体以外的东西最后都不会出现在裁剪空间体积内,并且将会受到裁剪。一个透视平截头体可以被看作一个不均匀形状的箱子,在这个箱子内部的每个坐标都会被映射到裁剪空间上的一个点。下面是一张透视平截头体的图片:

image

它的第一个参数定义了fov的值,它表示的是视野(Field of View),并且设置了观察空间的大小。如果想要一个真实的观察效果,它的值通常设置为45.0f,但想要一个毁灭战士(DOOM,经典的系列第一人称射击游戏)风格的结果你可以将其设置一个更大的值。第二个参数设置了宽高比,由视口的宽除以高所得。第三和第四个参数设置了平截头体的平面。我们通常设置近距离为0.1f,而远距离设为100.0f。所有在近平面和远平面内且处于平截头体内的顶点都会被渲染。

当你把透视矩阵的 near 值设置太大时(如10.0f),OpenGL会将靠近摄像机的坐标(在0.0f和10.0f之间)都裁剪掉,这会导致一个你在游戏中很熟悉的视觉效果:在太过靠近一个物体的时候你的视线会直接穿过去。

当使用正射投影时,每一个顶点坐标都会直接映射到裁剪空间中而不经过任何精细的透视除法(它仍然会进行透视除法,只是w分量没有被改变(它保持为1),因此没有起作用)。因为正射投影没有使用透视,远处的物体不会显得更小,所以产生奇怪的视觉效果。由于这个原因,正射投影主要用于二维渲染以及一些建筑或工程的程序,在这些场景中我们更希望顶点不会被透视所干扰。某些如 Blender 等进行三维建模的软件有时在建模时也会使用正射投影,因为它在各个维度下都更准确地描绘了每个物体。下面你能够看到在Blender里面使用两种投影方式的对比:

image

你可以看到,使用透视投影的话,远处的顶点看起来比较小,而在正射投影中每个顶点距离观察者的距离都是一样的。

把它们都组合到一起


通过计算可使一个物体坐标转换为裁剪坐标。最后一步将裁剪空间转换为屏幕空间OpenGL会自动进行透视除法(齐次坐标转欧式坐标)和裁剪。
$$
V_{clip} = M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local}
$$
顶点着色器的输出要求所有的顶点都在裁剪空间内,这正是我们刚才使用变换矩阵所做的。OpenGL然后对裁剪坐标执行透视除法从而将它们变换到标准化设备坐标。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(在我们的例子中是一个800x600的屏幕)。这个过程称为视口变换。

以下是利用glm库构建三个变换矩阵的方案:

// 物体空间到世界空间 模型矩阵
glm::mat4 model; 
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));

//世界空间到观察空间 观察矩阵
glm::mat4 view; // 注意,我们将矩阵向我们要进行移动场景的反方向移动。 
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));

//投影矩阵 透视方案
glm::mat4 projection; 
projection = glm::perspective(glm::radians(45.0f), screenWidth / screenHeight, 0.1f, 100.0f);

顶点着色器

#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); 
	... 
}

同时将矩阵传入顶点着色器中

shader.setMat4("model", model);
shader.setMat4("view", view);
shader.setMat4("projection", projection);
阅读全文

变换 Transformations

OpenGL 2024/10/10

通过矩阵变换 即相机外参数 包括旋转矩阵和平移矩阵 使得物体进行移动。各类变换矩阵定义如下

缩放矩阵

位移矩阵

旋转矩阵

  • 沿x轴旋转
  • 沿y轴旋转
  • 沿z轴旋转聚合矩阵明显的 这样的旋转方式会造成万向节死锁问题,解决方案为使用四元数

译注对四元数的理解会用到非常多的数学知识。如果你想了解四元数与3D旋转之间的关系,可以来阅读我的教程。如果你对万向节死锁的概念仍不是那么清楚,可以来阅读我教程的Bonus章节
现在3Blue1Brown也已经开始了一个四元数的视频系列,他采用球极平面投影(Stereographic Projection)的方式将四元数投影到3D空间,同样有助于理解四元数的概念(仍在更新中):https://www.youtube.com/watch?v=d4EgbgTm0Bg

矩阵相乘即可简单的获取顺序化的变换矩阵。

GLM

相关变换矩阵使用库glm其中有mat4类型的矩阵用于旋转变换

glm::mat4 trans = glm::mat4(1.0f);
//平移
trans = glm::translate(trans, glm::vec3(-0.1f, 0.0, 0.0));
//旋转
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
//缩放
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));

glsl中也有对应mat4的变量 实际上是一个 float[16]

uniform mat4 transform;
[...]
//左乘变换矩阵完成变换操作
gl_Position = transform * vec4(aPos, 1.0);

同样通过uniform变量从C代码中传入

glm::mat4 trans = glm::mat4(1.0f);
int transLocation = glGetUniformLocation(shader.ID, "transform");
glUniformMatrix4fv(transLocation, 1, GL_FALSE, glm::value_ptr(trans));
阅读全文

Transformation Code

OpenGL 2024/10/10

PSSSS 此处代码为键盘控制平移旋转代码

vertexShader.glsl

#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 triColor;
out vec2 texCoord;

uniform mat4 transform;

void main()
{
    gl_Position = transform * vec4(aPos, 1.0);
    triColor = aColor;
    texCoord = aTexCoord;
}

Main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <iostream>
#include <fstream>

#include <thread>
#include <chrono>

#include "shader.h"
#include "texture.h"


using std::cout;
using std::endl;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window, int rateLocation, float rate, int transLocation, float transMatrix[]);


//硬编码的顶点着色器 包括位置旋转等操作
const char* vertexShaderSource = "./shaders/VertexShader.glsl";
//硬编码的片段着色器 主要是渲染颜色
const char* fragmentShaderSource = "./shaders/FragmentShader.glsl";


// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{

    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_DECORATED, GL_FALSE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // ready for render

    float vertices[] = {
        //     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
             0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
             0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
            -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
            -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
    };

    unsigned int indices[] = {
       0, 1, 3, // first triangle
       1, 2, 3  // second triangle
    };

    //定义并生成一个VAO 存储VBO的链表结构
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);

    //绑定VAO
    glBindVertexArray(VAO);
    // 定义并生成一个顶点缓冲对象 通过无符号整数引用
    unsigned int VBO;
    glGenBuffers(1, &VBO);
    // 绑定新生成的顶点缓冲对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 顶点数据复制到缓冲中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 索引缓冲
    unsigned int EBO;
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // 编辑顶点属性
    // 1. 设置顶点属性指针
    //链接顶点属性 (向顶点着色器指定输入
    // 1、顶点着色器中定义的location
    // 2、顶点缓冲的长度
    // 3、顶点数据的类型
    // 4、数据是否标准化(即映射到标准化设备坐标中)
    // 5、连续顶点属性组之间的间隔
    // 6、初始顶点在缓冲中距离地址最开始的偏移量
    // 0. 复制顶点数组到缓冲中供OpenGL使用
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3* sizeof(float)));
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6* sizeof(float)));
    glEnableVertexAttribArray(2);


    Shader shader = Shader(vertexShaderSource, fragmentShaderSource);
    Texture texture1 = Texture("123.png", GL_RGB);
    Texture texture2 = Texture("awesomeface.png",GL_RGBA);

    shader.use();
    shader.setInt("ourTexture", 0);
    shader.setInt("secondTexture", 1);
    // 定义第二个图片的初始透明度
    int rateLocation = glGetUniformLocation(shader.ID, "rate");
    glUniform1f(rateLocation, 0.2f);
    // 定义初始的位移矩阵为单位矩阵
    glm::mat4 trans = glm::mat4(1.0f);
    int transLocation = glGetUniformLocation(shader.ID, "transform");
    glUniformMatrix4fv(transLocation, 1, GL_FALSE, glm::value_ptr(trans));
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----       

        float ratevalue;
        glGetUniformfv(shader.ID, rateLocation, &ratevalue);
        float transMatrix[16];
        glGetUniformfv(shader.ID, transLocation, transMatrix);
        processInput(window, rateLocation, ratevalue, transLocation, transMatrix);
        // the rendering shits        
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        // render a verticle
        //float timeValue = glfwGetTime();
        //float colorValue = (sin(timeValue) / 2.0f) + 0.5f;
        //int vertexColorLocation = glGetUniformLocation(shaderProgram, "triColor");


        //glUniform4f(vertexColorLocation, 0.0f, 0.0f, colorValue, 1.0f);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1.ID);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2.ID);
        glBindVertexArray(VAO);

        // opengl绘制图元方式
        /*glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);*/
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glm::mat4 trans = glm::make_mat4(transMatrix);
        trans = glm::translate(trans, glm::vec3(0.0, -0.5, 0.0));
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        //glUseProgram(shaderProgram[1]);
        //glBindVertexArray(VAO);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window,int rateLocation, float rate , int transLocation,float transMatrix[])
{
    glm::mat4 trans = glm::make_mat4(transMatrix);
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
    else if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS) {
        rate = rate + 0.1 > 1.0 ? 1.0 : rate + 0.1;
        trans = glm::translate(trans, glm::vec3(0.0, 0.1, 0.0));
        glUniform1f(rateLocation, rate);
    }
    else if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS) {
        rate = rate - 0.1 < 0.0 ? 0.0 : rate - 0.1;
        trans = glm::translate(trans, glm::vec3(0.0, -0.1, 0.0));
        glUniform1f(rateLocation, rate - 0.1);
    }
    else if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) {
        rate = rate - 0.1 < 0.0 ? 0.0 : rate - 0.1;
        trans = glm::translate(trans, glm::vec3(-0.1f, 0.0, 0.0));
        glUniform1f(rateLocation, rate - 0.1);
    }
    else if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS) {
        rate = rate - 0.1 < 0.0 ? 0.0 : rate - 0.1;
        trans = glm::translate(trans, glm::vec3(0.1f, 0.0, 0.0));
        glUniform1f(rateLocation, rate - 0.1);
    }
    glUniformMatrix4fv(transLocation, 1, GL_FALSE, glm::value_ptr(trans));
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}
阅读全文

Coordinate Systems Code

OpenGL 2024/10/10

Main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <iostream>
#include <fstream>

#include <thread>
#include <chrono>

#include "shader.h"
#include "texture.h"


using std::cout;
using std::endl;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window, int rateLocation, float rate, int transLocation, float transMatrix[]);


//硬编码的顶点着色器 包括位置旋转等操作
const char* vertexShaderSource = "./shaders/VertexShader.glsl";
//硬编码的片段着色器 主要是渲染颜色
const char* fragmentShaderSource = "./shaders/FragmentShader.glsl";


// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{

    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_DECORATED, GL_FALSE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // ready for render

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

    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };

    glm::vec3 cubePositions[] = {
        glm::vec3(0.0f,  0.0f,  0.0f),
        glm::vec3(2.0f,  5.0f, -15.0f),
        glm::vec3(-1.5f, -2.2f, -2.5f),
        glm::vec3(-3.8f, -2.0f, -12.3f),
        glm::vec3(2.4f, -0.4f, -3.5f),
        glm::vec3(-1.7f,  3.0f, -7.5f),
        glm::vec3(1.3f, -2.0f, -2.5f),
        glm::vec3(1.5f,  2.0f, -2.5f),
        glm::vec3(1.5f,  0.2f, -1.5f),
        glm::vec3(-1.3f,  1.0f, -1.5f)
    };
    //定义并生成一个VAO 存储VBO的链表结构
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);

    //绑定VAO
    glBindVertexArray(VAO);
    // 定义并生成一个顶点缓冲对象 通过无符号整数引用
    unsigned int VBO;
    glGenBuffers(1, &VBO);
    // 绑定新生成的顶点缓冲对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 顶点数据复制到缓冲中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 编辑顶点属性
    // 1. 设置顶点属性指针
    //链接顶点属性 (向顶点着色器指定输入
    // 1、顶点着色器中定义的location
    // 2、顶点缓冲的长度
    // 3、顶点数据的类型
    // 4、数据是否标准化(即映射到标准化设备坐标中)
    // 5、连续顶点属性组之间的间隔
    // 6、初始顶点在缓冲中距离地址最开始的偏移量
    // 0. 复制顶点数组到缓冲中供OpenGL使用
    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);


    Shader shader = Shader(vertexShaderSource, fragmentShaderSource);
    Texture texture1 = Texture("123.png", GL_RGB);
    Texture texture2 = Texture("awesomeface.png",GL_RGBA);

    shader.use();
    shader.setInt("ourTexture", 0);
    shader.setInt("secondTexture", 1);
    // 定义第二个图片的初始透明度
    int rateLocation = glGetUniformLocation(shader.ID, "rate");
    glUniform1f(rateLocation, 0.2f);
    // 定义初始的位移矩阵为单位矩阵
    glm::mat4 trans = glm::mat4(1.0f);
    int transLocation = glGetUniformLocation(shader.ID, "transform");
    glUniformMatrix4fv(transLocation, 1, GL_FALSE, glm::value_ptr(trans));

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        //Z缓冲 保证透视正常
        glEnable(GL_DEPTH_TEST);
        // input
        // -----
        //模型矩阵 转换到世界空间

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

        //观察矩阵 将物体向反方向移动
        glm::mat4 view = glm::mat4(1.0f);
        // 注意,我们将矩阵向我们要进行移动场景的反方向移动。
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -4.0f));
        // 透视投影矩阵
        glm::mat4 projection;
        projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

        shader.setMat4("view", view);
        shader.setMat4("projection", projection);

        float ratevalue;
        glGetUniformfv(shader.ID, rateLocation, &ratevalue);

        float transMatrix[16];
        glGetUniformfv(shader.ID, transLocation, transMatrix); 

        // render a verticle
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1.ID);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2.ID);

        glBindVertexArray(VAO);

        for (unsigned int i = 0; i < 10; i++)
        {
            glm::mat4 model = glm::mat4(1.0f);
            model = glm::translate(model, cubePositions[i]);
            float angle = 20.0f;
            if(i % 3 == 0)
                model = glm::rotate(model, (float)glfwGetTime()*glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
            else
                model = glm::rotate(model, 0.0f, glm::vec3(1.0f, 0.3f, 0.5f));
            shader.setMat4("model", model);

            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

        processInput(window, rateLocation, ratevalue, transLocation, transMatrix);
        // opengl绘制图元方式
        /*glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);*/
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window,int rateLocation, float rate , int transLocation,float transMatrix[])
{
    glm::mat4 trans = glm::make_mat4(transMatrix);
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
    else if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS) {
        rate = rate + 0.1 > 1.0 ? 1.0 : rate + 0.1;
        trans = glm::translate(trans, glm::vec3(0.0, 0.1, 0.0));
        glUniform1f(rateLocation, rate);
    }
    else if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS) {
        rate = rate - 0.1 < 0.0 ? 0.0 : rate - 0.1;
        trans = glm::translate(trans, glm::vec3(0.0, -0.1, 0.0));
        glUniform1f(rateLocation, rate - 0.1);
    }
    else if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) {
        rate = rate - 0.1 < 0.0 ? 0.0 : rate - 0.1;
        trans = glm::translate(trans, glm::vec3(-0.1f, 0.0, 0.0));
        glUniform1f(rateLocation, rate - 0.1);
    }
    else if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS) {
        rate = rate - 0.1 < 0.0 ? 0.0 : rate - 0.1;
        trans = glm::translate(trans, glm::vec3(0.1f, 0.0, 0.0));
        glUniform1f(rateLocation, rate - 0.1);
    }
    glUniformMatrix4fv(transLocation, 1, GL_FALSE, glm::value_ptr(trans));
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

VertexShader.glsl

#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 texCoord;

uniform mat4 transform;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = transform * projection * view * model * vec4(aPos, 1.0);
    texCoord = aTexCoord;
}
阅读全文

你好,三角形Hello Triangle

OpenGL 2024/10/10
首先 OpenGL是一个开发者与显卡供应商之间的协议,因为代码无法在GPU上直接运行,因此需要转换为机器码。OpenGL类似windows的DirectX Vulkan,实际上图形渲染代码在不同GPU设备上可运行的机器码不同。因此某些程序第一次运行时需要一次编译着色器,将编译好的内容通过协议接口获取保存在本地,便于之后运行。

fig.1 opengl渲染流程
image

Primitive


OpenGL需要用户将三维图像渲染表示的类型(点/线/三角面片etc.).GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP) 这些表示方案叫做图元。

流程中需要至少定义顶点着色器和片段着色器,因为现如今GPU设备中没有对这两部的默认定义。

顶点输入


在绘制图形之前,对于一个2D的三角形。我们需要三个顶点确认三角形形状。通过==顶点着色器==之后,顶点坐标会落座在标准化设备坐标系中。OpenGL仅当3D坐标在3个轴(x、y和z)上-1.0到1.0的范围内时才处理它。所有在这个范围内的坐标叫做==标准化设备坐标==(Normalized Device Coordinates),此范围内的坐标最终显示在屏幕上(在这个范围以外的坐标则不会显示)。

由于我们希望渲染一个三角形,我们一共要指定三个顶点,每个顶点都有一个3D位置。我们会将它们以标准化设备坐标的形式(OpenGL的可见区域)定义为一个float数组。

	float verticles[] = {
		-0.5f, -0.5f, 0.0f, 
		 0.5f, -0.5f, 0.0f, 
		 0.0f, 0.5f, 0.0f
	}

其中Z轴作为第三维深度坐标,表示点与你的距离

之后OpenGL通过视口变换将==顶点着色器==转换好的==标准化设备坐标==转换为==屏幕空间坐标==,所得的平面空间坐标输入到==片段着色器中==。

顶点着色器是作为现代OpenGL处理图形渲染管线的第一个阶段。他会在GPU上创建内存用于储存我们需要使用的顶点数据,还需要配置OpenGL如何解释这些内存,并且指定如何将其发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。

我们通过==顶点缓冲对象==(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。

以下代码示例了如何创建顶点缓冲对象并加入缓冲列表中:

float vertices[] = {
-0.5f, -0.5f, 0.0f,
 0.5f, -0.5f, 0.0f,
 0.0f,  0.5f, 0.0f
};
// 定义并生成一个顶点缓冲对象
unsigned int VBO;
glGenBuffers(1, &VBO);
// 绑定新生成的顶点缓冲对象
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 顶点数据复制到缓冲中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

其中glBufferData的第四个参数指定了我们希望显卡如何管理给定的数据。它有三种格式:

  • GL_STATIC_DRAW :数据不会或几乎不会改变。
  • GL_DYNAMIC_DRAW:数据会被改变很多。
  • GL_STREAM_DRAW :数据每次绘制时都会改变。

顶点着色器


顶点着色器(Vertex Shader)是几个可编程着色器中的一个。如果我们打算做渲染的话,现代OpenGL需要我们至少设置一个顶点和一个片段着色器。我们会简要介绍一下着色器以及配置两个非常简单的着色器来绘制我们第一个三角形。

着色器利用===着色器语言GLSL(OpenGL Shading Language)===进行编写。如下为实例(并非C代码

#version 330 core 
layout (location = 0) in vec3 aPos; 
void main() 
{ 
	gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 
}

编译着色器


由于并非C代码 因此我们需要运行时动态编译它

// 编译着色器
// 着色器对象同样使用无符号整数的id进行引用 此处创建
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);

//将我们在上面写好的着色器代码附加到着色器对象上并编译
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
//编译着色器
glCompileShader(vertexShader);

你可能会希望检测在调用glCompileShader后编译是否成功了,如果没成功的话,你还会希望知道错误是什么,这样你才能修复它们。检测编译时错误可以通过以下代码来实现:

int  success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);

首先我们定义一个整型变量来表示是否成功编译,还定义了一个储存错误消息(如果有的话)的容器。然后我们用glGetShaderiv检查是否编译成功。如果编译失败,我们会用glGetShaderInfoLog获取错误消息,然后打印它。

if(!success)
{
    glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}

片段着色器


片段着色器(Fragment Shader)是第二个也是最后一个我们打算创建的用于渲染三角形的着色器。片段着色器所做的是计算像素最后的颜色输出。

#version 330 core 
out vec4 FragColor; 
void main() 
{ 
	FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); 
}

片段着色器只需要一个输出变量,这个变量是一个4分量向量,它表示的是最终的输出颜色,我们应该自己将其计算出来。声明输出变量可以使用out关键字,这里我们命名为FragColor。下面,我们将一个Alpha值为1.0(1.0代表完全不透明)的橘黄色的vec4赋值给颜色输出。

编译片段着色器的过程与顶点着色器类似,只不过我们使用GL_FRAGMENT_SHADER常量作为着色器类型:

unsigned int fragmentShader; 
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader);

着色器程序


着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们===链接===(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。

当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。创建着色器程序比较简单,代码如下:

//创建着色器程序
unsigned int shaderProgram;
shaderProgram = glCreateProgram();

//添加并链接着色器
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

char programLog[512];
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
    glGetProgramInfoLog(shaderProgram, 512, NULL, programLog);
    std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << programLog << std::endl;
}

//激活程序对象
glUseProgram(shaderProgram);

//着色器对象已经没用了 删除
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

流程为:
创建着色器程序->添加并连接之前创建好的着色器->激活程序对象->删除不需要的着色器

链接顶点属性


顶点着色器允许我们指定任何以顶点属性为形式的输入。这使其具有很强的灵活性的同时,它还的确意味着我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。所以,我们必须在渲染前指定OpenGL该如何解释顶点数据。

我们的顶点缓冲数据会被解析为下面这样子:

![[Pasted image 20241009112036.png]]

  • 位置数据被储存为32位(4字节)浮点值。
  • 每个位置包含3个这样的值。
  • 在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)。
  • 数据中第一个值在缓冲开始的位置。

利用OpenGL绘制一个物体的大致流程如下:

// 0. 复制顶点数组到缓冲中供OpenGL使用 
glBindBuffer(GL_ARRAY_BUFFER, VBO); 
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 
// 1. 设置顶点属性指针 
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); 
// 2. 当我们渲染一个物体时要使用着色器程序 
glUseProgram(shaderProgram); 
// 3. 绘制物体 
someOpenGLFunctionThatDrawsOurTriangle();

每当我们绘制一个物体的时候都必须重复这一过程。这看起来可能不多,但是如果有超过5个顶点属性,上百个不同物体呢(这其实并不罕见)。绑定正确的缓冲对象,为每个物体配置所有顶点属性很快就变成一件麻烦事。有没有一些方法可以使我们把所有这些状态配置储存在一个对象中,并且可以通过绑定这个对象来恢复状态呢?

顶点数组对象


===顶点数组对象===(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中

一个顶点数组对象会储存以下内容:

  • glEnableVertexAttribArrayglDisableVertexAttribArray的调用。
  • 通过glVertexAttribPointer设置的顶点属性配置。
  • 通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
    image
//创建一个VAO
unsigned int VAO;
glGenVertexArray(1,&VAO);
//在使用VBO之前绑定一次VAO
glBindVertexArray(VAO);

[...]

glUseProgram(shaderProgram); 
//绘制之前绑定要绘制的VAO
glBindVertexArray(VAO); 
someOpenGLFunctionThatDrawsOurTriangle();

元素缓冲对象


简单来说 元素缓冲对象(Element Buffer Object)实际上应该叫做索引缓冲对象。是如果有多个图形共用某个顶点时,对定义的VBO中顶点集合的索引定义。用于节省内存占用。

example:

float vertices[] = { 
	0.5f, 0.5f, 0.0f, // 右上角 
	0.5f, -0.5f, 0.0f, // 右下角 
	-0.5f, -0.5f, 0.0f, // 左下角 
	-0.5f, 0.5f, 0.0f // 左上角 
}; 
unsigned int indices[] = { 
	// 注意索引从0开始! 
	// 此例的索引(0,1,2,3)就是顶点数组vertices的下标, 
	// 这样可以由下标代表顶点组合成矩形 
	0, 1, 3, // 第一个三角形 
	1, 2, 3 // 第二个三角形 };

unsigned int EBO;
glGenBuffers(1, &EBO);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
阅读全文

Texture Code

OpenGL 2024/10/10

PSSSS 此处代码为最后一个作业按键调整透明度的代码

纹理类

#ifndef TEXTURE_H
#define TEXTURE_H

#include <glad/glad.h>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

using std::string;
using std::ifstream;
using std::stringstream;
using std::cout;
using std::endl;

class Texture
{
    public:
        unsigned int ID;
        int width, height, nrChannels;

        Texture(const char* filePath,int picType)
        {    
            glGenTextures(1, &ID);
            glBindTexture(GL_TEXTURE_2D, ID);
            // 为当前绑定的纹理对象设置环绕方式
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            // 纹理过滤方式
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            // 加载并生成纹理
            stbi_set_flip_vertically_on_load(true);
            unsigned char* data = stbi_load(filePath, &width, &height, &nrChannels, 0);
            if (data)
            {
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, picType, GL_UNSIGNED_BYTE, data);
                glGenerateMipmap(GL_TEXTURE_2D);
            }
            else
            {
                cout << "Failed to load texture" << endl;
            }
            stbi_image_free(data);
        }
};

#endif

Main.cpp

    #include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>
#include <fstream>

#include "shader.h"
#include "texture.h"


using std::cout;
using std::endl;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window,int rateLocation,float rate);


//硬编码的顶点着色器 包括位置旋转等操作
const char* vertexShaderSource = "./shaders/VertexShader.glsl";
//硬编码的片段着色器 主要是渲染颜色
const char* fragmentShaderSource = "./shaders/FragmentShader.glsl";


// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{

    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_DECORATED, GL_FALSE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // ready for render

    float vertices[] = {
        //     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
             0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
             0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
            -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
            -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
    };

    unsigned int indices[] = {
       0, 1, 3, // first triangle
       1, 2, 3  // second triangle
    };

    //定义并生成一个VAO 存储VBO的链表结构
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);

    //绑定VAO
    glBindVertexArray(VAO);
    // 定义并生成一个顶点缓冲对象 通过无符号整数引用
    unsigned int VBO;
    glGenBuffers(1, &VBO);
    // 绑定新生成的顶点缓冲对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 顶点数据复制到缓冲中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 索引缓冲
    unsigned int EBO;
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // 编辑顶点属性
    // 1. 设置顶点属性指针
    //链接顶点属性 (向顶点着色器指定输入
    // 1、顶点着色器中定义的location
    // 2、顶点缓冲的长度
    // 3、顶点数据的类型
    // 4、数据是否标准化(即映射到标准化设备坐标中)
    // 5、连续顶点属性组之间的间隔
    // 6、初始顶点在缓冲中距离地址最开始的偏移量
    // 0. 复制顶点数组到缓冲中供OpenGL使用
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3* sizeof(float)));
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6* sizeof(float)));
    glEnableVertexAttribArray(2);


    Shader shader = Shader(vertexShaderSource, fragmentShaderSource);
    Texture texture1 = Texture("123.png", GL_RGB);
    Texture texture2 = Texture("awesomeface.png",GL_RGBA);

    shader.use();
    shader.setInt("ourTexture", 0);
    // or set it via the texture class
    shader.setInt("secondTexture", 1);
    int rateLocation = glGetUniformLocation(shader.ID, "rate");
    glUniform1f(rateLocation, 0.2f);
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----        
        float ratevalue;
        glGetUniformfv(shader.ID, rateLocation, &ratevalue);        
        processInput(window, rateLocation, ratevalue);
        // the rendering shits        
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        // render a verticle
        //float timeValue = glfwGetTime();
        //float colorValue = (sin(timeValue) / 2.0f) + 0.5f;
        //int vertexColorLocation = glGetUniformLocation(shaderProgram, "triColor");


        //glUniform4f(vertexColorLocation, 0.0f, 0.0f, colorValue, 1.0f);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1.ID);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2.ID);
        glBindVertexArray(VAO);
        // opengl绘制图元方式
        /*glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);*/
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        //glUseProgram(shaderProgram[1]);
        //glBindVertexArray(VAO);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window,int rateLocation, float rate)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
    else if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS) {
        rate = rate + 0.1 > 1.0 ? 1.0 : rate + 0.1;
        glUniform1f(rateLocation, rate);
    }
    else if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS) {
        rate = rate - 0.1 < 0.0 ? 0.0 : rate - 0.1;
        glUniform1f(rateLocation, rate - 0.1);
    }

}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

vertexShader.glsl

#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 triColor;
out vec2 texCoord;
void main()
{
    gl_Position = vec4(aPos.x ,aPos.y,aPos.z, 1.0);
    triColor = aColor;
    texCoord = aTexCoord;
}

fragmentShader.glsl

#version 460 core
in vec3 triColor;
in vec2 texCoord;

out vec4 FragColor;

uniform sampler2D ourTexture;
uniform sampler2D secondTexture;
uniform float rate;
void main()
{
    FragColor = mix(texture(ourTexture,texCoord),texture(secondTexture,vec2(texCoord.x,texCoord.y)), rate);
}
阅读全文

Shaders Code

OpenGL 2024/10/10

— Main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>
#include <fstream>

#include "shader.h"

using std::cout;
using std::endl;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);


//硬编码的顶点着色器 包括位置旋转等操作
const char* vertexShaderSource = "VertexShader.glsl";
//硬编码的片段着色器 主要是渲染颜色
const char* fragmentShaderSource = "FragmentShader.glsl";


// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{

    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_DECORATED, GL_FALSE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // ready for render

    float vertices[] = {
        // 位置              // 颜色 
        0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f, // 右下 
        -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下 
        0.0f, 0.5f, 0.0f,   0.0f, 0.0f, 1.0f // 顶部 
    };


    //定义并生成一个VAO 存储VBO的链表结构
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);

    //绑定VAO
    glBindVertexArray(VAO);
    // 定义并生成一个顶点缓冲对象 通过无符号整数引用
    unsigned int VBO;
    glGenBuffers(1, &VBO);
    // 绑定新生成的顶点缓冲对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 顶点数据复制到缓冲中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 编辑顶点属性
    // 1. 设置顶点属性指针
    //链接顶点属性 (向顶点着色器指定输入
    // 1、顶点着色器中定义的location
    // 2、顶点缓冲的长度
    // 3、顶点数据的类型
    // 4、数据是否标准化(即映射到标准化设备坐标中)
    // 5、连续顶点属性组之间的间隔
    // 6、初始顶点在缓冲中距离地址最开始的偏移量
    // 0. 复制顶点数组到缓冲中供OpenGL使用
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
    glEnableVertexAttribArray(1);

    Shader shader = Shader(vertexShaderSource, fragmentShaderSource);
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);
        // the rendering shits        
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        // render a verticle
        //float timeValue = glfwGetTime();
        //float colorValue = (sin(timeValue) / 2.0f) + 0.5f;
        //int vertexColorLocation = glGetUniformLocation(shaderProgram, "triColor");
        shader.use();
        //glUniform4f(vertexColorLocation, 0.0f, 0.0f, colorValue, 1.0f);
        glBindVertexArray(VAO);
        // opengl绘制图元方式
        /*glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);*/
        glDrawArrays(GL_TRIANGLES, 0, 3);

        //glUseProgram(shaderProgram[1]);
        //glBindVertexArray(VAO);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
    else if (glfwGetKey(window, GLFW_KEY_BACKSPACE) == GLFW_PRESS) {
        std::cout << "press backspace" << std::endl;
    } 

}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

— VertexShader.glsl

#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

out vec3 triColor;

void main()
{
    gl_Position = vec4(aPos.x ,aPos.y,aPos.z, 1.0);
    triColor = aColor;
}

— FragmentShader.glsl

#version 460 core
in vec3 triColor;
out vec4 FragColor;

void main()
{
    FragColor = vec4(triColor,1.0);
}
阅读全文

Hello Triangle Code

OpenGL 2024/10/9
#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void render_verticle(GLFWwindow* window);
//硬编码的顶点着色器 包括位置旋转等操作
const char* vertexShaderSource = "#version 330 core \n"
                                 "layout(location = 0) in vec3 aPos;\n"
                                 "void main()\n"
                                 "{"
                                 "    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
                                 "}\n";
//硬编码的片段着色器 主要是渲染颜色
const char* fragmentShaderSource = "#version 330 core \n"
                                   "out vec4 FragColor;\n"
                                   "void main()\n"
                                   "{\n"
                                   "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
                                   "}";
// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_DECORATED, GL_FALSE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);
        // the rendering shits        
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        // render a verticle
        render_verticle(window);
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
    else if (glfwGetKey(window, GLFW_KEY_BACKSPACE) == GLFW_PRESS) {
        std::cout << "press backspace" << std::endl;
    } 

}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

void render_verticle(GLFWwindow* window) 
{
    float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
    };
    //定义并生成一个VAO 存储VBO的链表结构
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);

    //绑定VAO
    glBindVertexArray(VAO);
    // 定义并生成一个顶点缓冲对象 通过无符号整数引用
    unsigned int VBO;
    glGenBuffers(1, &VBO);
    // 绑定新生成的顶点缓冲对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 顶点数据复制到缓冲中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 编辑顶点属性
    // 1. 设置顶点属性指针
    //链接顶点属性 (向顶点着色器指定输入
    // 1、顶点着色器中定义的location
    // 2、顶点缓冲的长度
    // 3、顶点数据的类型
    // 4、数据是否标准化(即映射到标准化设备坐标中)
    // 5、连续顶点属性组之间的间隔
    // 6、初始顶点在缓冲中距离地址最开始的偏移量
    // 0. 复制顶点数组到缓冲中供OpenGL使用
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);


    // 编译着色器
    // 着色器对象同样使用无符号整数的id进行引用 此处创建
    unsigned int vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);

    //将我们在上面写好的着色器代码附加到着色器对象上并编译
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    //编译着色器
    glCompileShader(vertexShader);
    //判断着色器编译是否成功 失败报错
    int  success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    //片段着色器 
    unsigned int fragmentShader;
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    //片段着色器编译检查

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //创建着色器程序
    unsigned int shaderProgram;
    shaderProgram = glCreateProgram();

    //添加并链接着色器
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    char programLog[512];
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, programLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << programLog << std::endl;
    }


    //着色器对象已经没用了 删除
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    //激活程序对象
    glUseProgram(shaderProgram);
    /*glBindVertexArray(VAO);*/
    glDrawArrays(GL_TRIANGLES, 0, 3);
}
阅读全文
1 ... 2
avatar
TyranitarX

事已至此,先睡觉吧