Phong模型
简单光照明模拟物体表面对光的反射作用。
光源为点光源。
反射作用分为
环境光(Ambient Light)
漫反射(Diffuse Reflection)
镜面反射(Specular Reflection)
环境光(Ambient Light)
在没有光源的地方,景物没有受到光源的直接照射,但其表面仍有一定的亮度,使它们可见。这是因为光线在场景中经过复杂的传播之后,形成了弥漫于整个空间的光线,称之为环境光。
环境光是指光源间接对物体的影响
光在物体和环境之间多次反射,最终达到平衡
同一环境下的环境光光强分布均匀
近似表示为:Ie=Ia∙Ka(其中,Ia为环境光强度,Ka为物体对环境光的反射系数)
使用光的颜色乘以一个很小的常量因子,再乘以物体的颜色,然后将最后结果作为片段的颜色:
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result,1.0)
}
漫反射
点光源是位于空间某个位置的一个点,向周围所有的方向上辐射等光强的光,记其光强为Ip.
在点光源的照射下,物体表面的不同部分亮度不同,亮度的大小依赖于它的朝向以及它与点光源之间的距离。
理想漫反射
在一个粗糙的、无光泽的表面呈现为漫反射。当光线照射到这样的表面时,光线沿各个方向都作相同的反射,所以从任何角度去看这种表面都有相同的亮度。
漫反射的特点:光源来自一个方向,反射光均匀地射向各个方向,与视点无关。
由Lambert余弦定律,漫反射光强为Id=IpKd×cosθ
其中:Ip是入射光的强度; Kd是不物体有关的漫反射系数,0< Kd <1;
θ是L和N的夹角 0≤θ≤π/2 ; L是P点指向光源的方向矢量
N是物体表面在P点的法矢量
其中:0≤θ≤π/2,当θ = 0时,物体表面正好垂直于光线方向,这时获得的光照强度最大;当θ = 90时物体表面与光线方向平行,此时光线照射不到物体,光的强度最弱;当θ>90后,物体的表面转向到光线的背面,此时物体对应的表面接受不到光照。
将L和N觃格化为单位矢量则cosθ=(L∙N)
漫反射光强为Id=IpKd× (L∙N)
多个点光源
漫反射的光强为:
法向量
法向量是一个垂直于顶点表面的(单位)向量。法向量通过顶点属性里指定经过模型和视变换后需要重新计算。使用叉乘对所有的立方体顶点计算法向量,但由于3D立方体不是一个复杂的形状,所以我们可以简单地把法向量数据手工添加到顶点数据中。
float vertices[] = {
-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, 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, 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, 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, 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
};
和顶点位置属性一样,我们需要将法向量数据发送到GPU,并且使用glVertexAttribPointer告诉OpenGL数据的解析方式。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
将顶点属性传递到着色器后,需要在着色器中开始光照计算。有两种方法执行向量L和N的计算。一种方式是在世界坐标系中计算,另一种是在相机坐标系中计算,两种方法都可以实现。
以世界坐标系中计算L和N为例进行说明,在相机坐标系中也有类似操作。在世界坐标系中,计算L时,光源lightPos是在世界坐标系中指定的位置,直接使用即可。顶点位置需要变换到世界坐标系中,利用Model矩阵即可,使用式子:FragPos=vec3(model∗vec4(position,1.0));(变换后顶点位置)
在计算N时需要注意,我们不能直接利用Model * normal来获取变换后的法向量,应该使用式子
Normal=mat3(transpose(inverse(model)))∗normal(变换后法向量)
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
out vec3 FragPos;
out vec3 Normal;
void main()
{
gl_position = projection * view * model * vec4(aPos,1.0);
FragPos = vec3(model * vec4(aPos,1.0));
Normal = aNormal;
}
在片元着色器中,计算漫反射光成分的代码为:
#version core 330
in vec3 FragPos;
in vec3 Normal;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main()
{
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm,lightDir),0.0);
vec3 diffuse = diff *lightColor;
vec3 result = (ambient + diffuse) * objectColor;
FragColor=vec4(result,1.0);
}
这里使用max(dot(lightDir,normal),0)主要是为了防止当光线和法向量夹角大于90°后,取值为负,因此使用max保证漫反射光照系数在[0,1.0]范围内。
镜面反射
镜面反射遵循反射定律,反射光位于表面法矢的两侧。
对于理想的高光泽度反射面,反射角等于入射角的时候,光线正好会被反射。这时,如果观察者正好处在P点的镜面反射上,就会看到一个比周围亮得多的一个高光点。
非理想的反射面,只要其表面是光滑的,在点光源的照射下,也会产生一块特别亮的区域,称为高光点。
尽管这时镜面反射光的强度会随α角的增加而急剧地减少,但观察者还是可以在α角很小的情况下接受这种改变了方向的一部分镜面反射光。
镜面反射的光强可表示为
其中,Ip是入射光强度;Ks是与物体有关的镜面反射系数;α为视点方向V与镜面反射方向R之间的夹角,且V,R均规格化为单位向量cos α =R ∙ V
n为反射指数,反映物体表面的光泽程度,数目越大物体表面越光滑
多个点光源(m个点光源)
计算镜面光成分过程为:
在片元着色器中添加一个uniform,把相应的摄像机位置传给片元着色器。
uniform vec3 CameraPos;
// 镜面反射成分 此时需要光线方向为由光源指出
float specularStrength = 0.5f;
vec3 reflectDir = normalize(reflect(-lightDir, normal));
vec3 viewDir = normalize(viewPos - FragPos);
float specularAmount = pow(max(dot(reflectVec,CameraVec),0),32); // 32为镜面高光系数
vec3 specular = specularStrength * specFactor * lightColor * objectColor;
需要注意的是,利用reflect函数计算光的出射方向时,要求入射方向指向物体表面位置,因此这里反转了lightDir.
pow(max(dot(viewDir, reflectDir), 0.0), 32)中先计算视线方向与反射方向的点乘(并确保它不是负值),然后取它的32次幂。这个32是高光的反光度。一个物体的反光度越高,反射能力越强,散射得越少,高光点就会越小。
Phong光照明模型的综合表述:由物体表面上一点P反射到规点的光强I为环境光的反射光强Ie、理想漫反射光强Id和镜面反射光强Is的总和。I=IaKa+IpKd(L∙N)+IpKs(R∙V)n
将上述三种光成分叠加后,成为最终物体的颜色,片元着色器实现为:
vec3 result = ambient + diffuse + specular
color = vec4(result , 1.0f);
vertexSource.txt
#version 330 core
layout (location = 0) in vec3 aPos;
layout(location = 3) in vec3 aNormal;
uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
out vec3 FragPos;
out vec3 Normal;
void main()
{
gl_Position = projMat * viewMat * modelMat * vec4(aPos, 1.0);
FragPos= (modelMat * vec4(aPos.xyz,1.0)).xyz;
Normal = mat3(transpose(inverse(modelMat)))* aNormal;
}
fragmentSource.txt
#version 330 core
in vec3 FragPos;
in vec3 Normal;
uniform vec3 objColor;
uniform vec3 ambientColor;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform vec3 CameraPos;
out vec4 FragColor;
void main()
{
vec3 lightDir = normalize(lightPos-FragPos);
vec3 reflectVec = reflect(-lightDir,Normal);
vec3 CameraVec = normalize(CameraPos-FragPos);
float specularAmount = pow(max(dot(reflectVec,CameraVec),0),32);
vec3 specular = specularAmount*lightColor;
vec3 diffuse =max( dot(lightDir,Normal),0)*lightColor;
FragColor = vec4((diffuse + ambientColor + specular) * objColor,1.0);
}
main.cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include"Shader.h"
#include"Camera.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT =600;
#pragma region Camera Declare
Camera camera(glm::vec3(0, 0, 3.0f), glm::radians(-15.0f), glm::radians(180.0f), glm::vec3(0, 1.0f, 0));
#pragma endregion
#pragma region Input Declare
float lastX;
float lastY;
bool firstMouse = true;
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, true);
}
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
{
camera.speedZ = 0.1f;
}
else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
{
camera.speedZ = -0.1f;
}
else
{
camera.speedZ = 0;
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, true);
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
{
camera.speedX = 0.1f;
}
else if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
{
camera.speedX = -0.1f;
}
else
{
camera.speedX = 0;
}
}
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, true);
}
if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
{
camera.speedY = -0.1f;
}
else if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
{
camera.speedY = 0.1f;
}
else
{
camera.speedY = 0;
}
}
// 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 mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse == true)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float deltaX, deltaY;
deltaX = xpos - lastX;
deltaY = ypos - lastY;
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(deltaX, deltaY);
}
#pragma endregion
unsigned int LoadImageToGPU(const char*filename, GLint internalformat, GLenum format, int textureSlot)
{
unsigned int texBuffer;
glGenTextures(1, &texBuffer);
glActiveTexture(GL_TEXTURE0 + textureSlot);
glBindTexture(GL_TEXTURE_2D, texBuffer);
int width, height, nrChannels;
stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis.
unsigned char *data = stbi_load(filename, &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
return texBuffer;
}
int main()
{
#pragma region Open a window
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
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);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouse_callback);
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;
}
// configure global opengl state
// -----------------------------
glEnable(GL_DEPTH_TEST);
#pragma endregion
#pragma region Model Data
GLfloat vertices[] = {
-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, 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, 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, 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, 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
};
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)
};
#pragma endregion
#pragma region Init Shader Pragram
Shader ourShader("VertexSource.vert", "fragmentSource.frag");
#pragma endregion
#pragma region Init and Load Models to VAO,VBO
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(3);
#pragma endregion
#pragma region Init and Load Texture
unsigned int texBufferA;
texBufferA = LoadImageToGPU("container.jpg",GL_RGB,GL_RGB,0);
unsigned int texBufferB;
texBufferB = LoadImageToGPU("awesomeface.png",GL_RGBA, GL_RGBA, 1);
#pragma endregion
// tell opengl for each sampler to which texture unit it belongs to (only has to be done once)
// -------------------------------------------------------------------------------------------
#pragma region Prepare MVP matrices
glm::mat4 modelMat;
glm::mat4 viewMat;
glm::mat4 projMat;
projMat = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
#pragma endregion
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// input
processInput(window);
// clear srceen
glClearColor(0, 0, 0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // also clear the depth buffer now!
viewMat = camera.GetViewMatrix();
for (unsigned int i = 0; i < 10; i++)
{
//set Model Matrix
modelMat = glm::translate(glm::mat4(1.0f), cubePositions[i]);
//set View and Project Matrices here
//set Material->shader program
ourShader.use();
//set Material->textures
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texBufferA);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texBufferB);
//set material->uniforms
/* glUniform1i(glGetUniformLocation(ourShader.ID, "ourTexture"), 0);
glUniform1i(glGetUniformLocation(ourShader.ID, "ourFace"), 1);*/
unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "modelMat");
unsigned int viewLoc = glGetUniformLocation(ourShader.ID, "viewMat");
unsigned int projectLoc = glGetUniformLocation(ourShader.ID, "projMat");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(modelMat));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(projectLoc, 1, GL_FALSE, glm::value_ptr(projMat));
glUniform3f(glGetUniformLocation(ourShader.ID, "objColor"), 1.0f, 0.5f, 0.31f);
glUniform3f(glGetUniformLocation(ourShader.ID, "ambientColor"), 0.2f, 0.1f, 0.0f);
glUniform3f(glGetUniformLocation(ourShader.ID, "lightPos"), 10.0f, 10.0f,-5.0f);
glUniform3f(glGetUniformLocation(ourShader.ID, "lightColor"), 0.5f, 0.3f, 0.3f);
glUniform3f(glGetUniformLocation(ourShader.ID, "CameraPos"), camera.Position.x, camera.Position.y, camera.Position.z);
// set Model
glBindVertexArray(VAO);
//Drawcall
glDrawArrays(GL_TRIANGLES, 0, 36);
}
//Clean up,prepare for next render loop
glfwSwapBuffers(window);
glfwPollEvents();
camera.UpdataCameraPos();
}
// optional: de-allocate all resources once they've outlived their purpose:
// ------------------------------------------------------------------------
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// glfw: terminate, clearing all previously allocated GLFW resources.
// ------------------------------------------------------------------
glfwTerminate();
return 0;
}