一、第一个opengl程序(填充窗口)
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
//按键回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
// 当用户按下ESC键,我们设置window窗口的WindowShouldClose属性为true
// 关闭应用程序
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
int main()
{
//初始化
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
//创建窗口
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
cout << "Failed to create GLFW window" << endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
//初始化GLEW
glewExperimental = GL_TRUE; //在初始化GLEW之前设置glewExperimental变量的值为GL_TRUE,
//这样做能让GLEW在管理OpenGL的函数指针时更多地使用现代化的技术
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
//视口 渲染前必须告诉OpenGL渲染窗口的尺寸大小,
int width, height;
glfwGetFramebufferSize(window, &width, &height); //这里我们是直接从GLFW中获取的。
//我们从GLFW中获取视口的维度而不设置为800*600是为了让它在高DPI的屏幕上也能正常工作。
glViewport(0, 0, width, height);
// 我们可以在创建窗口之后,开始游戏循环之前注册各种回调函数。
glfwSetKeyCallback(window, key_callback);
//来个循环,卡在这里 做事情
while (!glfwWindowShouldClose(window))
{
glfwPollEvents(); //根据触发调用事件函数 如鼠标 键盘
//来渲染了
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置颜色 (ps:这个时候设置的只是后面的缓冲,当使用swap后,才换到前面来)
glClear(GL_COLOR_BUFFER_BIT); //清空整个颜色缓冲(前和后)
glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色的大缓冲)
//它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上
}
//ps:应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。
//我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。
//最后一定要释放资源
glfwTerminate();
return 0;
}
二、第一个三角形
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
//对于大多数场合,我们只需要配置 顶点 和 片段 着色器就行了。几何着色器是可选的,通常使用它默认的着色器就行了
//顶点输入 OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它
//定义为一个GLfloat数组
//与常见屏幕坐标不同,(0, 0)坐标是这个图像的中心,而不是左上角。你通过glViewport函数转化为屏幕坐标
// 事件函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;
// GLSL(OpenGL着色语言) 用于定义如何渲染图形。
//顶点着色器(Vertex Shader)负责处理每个顶点的计算,通常用于将顶点位置转换为裁剪坐标。
const GLchar* vertexShaderSource = "#version 330 core\n" //源码
"layout (location = 0) in vec3 position;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
"}\0";
//片段着色器(Fragment Shader)负责处理每个像素的计算,通常用于确定最终像素的颜色。在
const GLchar* fragmentShaderSource = "#version 330 core\n" //源码
"out vec4 color;\n"
"void main()\n"
"{\n"
"color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
// The MAIN function, from here we start the application and run the game loop
int main()
{
// Init GLFW
glfwInit();
// Set all the required options for GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);//这一行将刚创建的窗口设置为当前的OpenGL上下文。这意味着所有的OpenGL渲染命令将在这个窗口上执行。
// Set the required callback functions
glfwSetKeyCallback(window, key_callback); //注册事件函数
// 告诉GLEW 使用现代的方法来获取函数指针和OpenGL扩展
glewExperimental = GL_TRUE;
// Initialize GLEW to setup the OpenGL Function pointers
glewInit();
//设置视口
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
// Build and compile our shader program
// Vertex shader 顶点着色器
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); //创建着色器对象
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//把这个着色器源码附加到着色器对象上
glCompileShader(vertexShader); //编译
// Check for compile time errors
GLint success;
GLchar 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;
}
// Fragment shader 片段着色器
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); //同上
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// Check for compile time errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// Link shaders 创建一个着色器对象(这个是最终版),把顶点着色器和片段着色器都放进去,在link起来
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// Check for linking errors
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
//有了最终版着色器,释放掉顶点和着色
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// Set up vertex data (and buffer(s)) and attribute pointers //在3d中,三角型顶点数据
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f, // Left
0.5f, -0.5f, 0.0f, // Right
0.0f, 0.5f, 0.0f // Top
};
GLuint VBO, VAO; //声明 VBO顶点缓冲对象 VAO顶点数组对象
glGenVertexArrays(1, &VAO); //生成 VAO VBO
glGenBuffers(1, &VBO);
// Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s).
// 绑定VAO
glBindVertexArray(VAO);
//绑定VBO 设置顶点缓冲区 复制vertices中的数据到GL_ARRAY_BUFFER 目标的缓冲区对象中供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(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);//启用上一行定义的属性
//解绑VBO
glBindBuffer(GL_ARRAY_BUFFER, 0); // Note that this is allowed, the call to glVertexAttribPointer registered VBO as the currently bound vertex buffer object so afterwards we can safely unbind
//4. 解绑VAO
glBindVertexArray(0); // Unbind VAO (it's always a good thing to unbind any buffer/array to prevent strange bugs)
// Game loop
while (!glfwWindowShouldClose(window))
{
// Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions
glfwPollEvents();
// Render
// Clear the colorbuffer
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Draw our first triangle
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
// Swap the screen buffers
glfwSwapBuffers(window);
}
// Properly de-allocate all resources once they've outlived their purpose
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// Terminate GLFW, clearing any resources allocated by GLFW.
glfwTerminate();
return 0;
}
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
三、四边形 自己改
基于三角形的代码改
//四个顶点坐标
GLfloat 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
};
//绘制
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
四、着色器
GLSL
着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。
着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。如果你不知道什么是uniform也不用担心,我们后面会进行讲解。
#version version_number //开头声明版本
in type in_variable_name; //输入量
in type in_variable_name;
out type out_variable_name; //输出量
uniform type uniform_name; //uniform ??
int main()
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
当我们特别谈论到顶点着色器的时候,每个输入变量也叫顶点属性(Vertex Attribute)。我们能声明的顶点属性是有上限的,它一般由硬件来决定。OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件或许允许更多的顶点属性,你可以查询GL_MAX_VERTEX_ATTRIBS来获取具体的上限:
GLint nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
通常情况下它至少会返回16个,大部分情况下是够用了。
数据类型
GLSL中包含C等其它语言大部分的默认基础数据类型:int
、float
、double
、uint
和bool
。GLSL也有两种容器类型,它们会在这个教程中使用很多,分别是向量(Vector)和矩阵(Matrix),其中矩阵我们会在之后的教程里再讨论。
向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:
vec2 someVec; //创建两个向量分量的容器
vec4 differentVec = someVec.xyxx; //创建有4个向量分量的容器,赋值 someVec的第一个、第二个、第一个、第一个分量
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy; //就是向量加法
//各种赋值
vec2 vect = vec2(0.5f, 0.7f);
vec4 result = vec4(vect, 0.0f, 0.0f);
vec4 otherResult = vec4(result.xyz, 1.0f);
输入和输出
虽然着色器是各自独立的小程序,但是它们都是一个整体的一部分,出于这样的原因,我们希望每个着色器都有输入和输出,这样才能进行数据交流和传递。GLSL定义了in
和out
关键字专门来实现这个目的。每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。但在顶点和片段着色器中会有点不同。
·顶点着色器
·片段着色器
顶点着色器(输入量喜欢搞特殊)
#version 330 core
layout(location = 0) in vec3 position; // position变量的属性位置值为0
out vec4 vertexColor; // 为片段着色器指定一个颜色输出
void main()
{
gl_Position = vec4(position, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f); // 把输出变量设置为暗红色
}
片段着色器
#version 330 core
in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)
out vec4 color; // 片段着色器输出的变量名可以任意命名,类型必须是vec4
void main()
{
color = vertexColor;
}
注意:
1.顶点着色器输入量搞特殊
2.顶点着色器的输出是片段着色器的输入,注意顶点输出量和片段输入量名称一致
3.片段着色器的输出量不许是 vec4 类型
在第一个三角的代码基础上这么改(替换开头的相关部分)
const GLchar* vertexShaderSource = "#version 330 core\n" //源码
"layout (location = 0) in vec3 position;\n"
"out vec4 vertexColor;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position, 1.0);\n"
"vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f);\n"
"}\0";
//片段着色器(Fragment Shader)负责处理每个像素的计算,通常用于确定最终像素的颜色。在
const GLchar* fragmentShaderSource = "#version 330 core\n" //源码
"in vec4 vertexColor;\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
"color = vertexColor;\n"
"}\n\0";
效果如下(暗红色三角)
Uniform
开头源码
//用uniform关键字
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"layout (location = 1) in vec3 color;\n"
"out vec3 ourColor;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position, 1.0);\n"
"ourColor = color;\n"
"}\0";
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"uniform vec4 ourColor;\n"
"void main()\n"
"{\n"
"color = ourColor;\n"
"}\n\0";
添加到画三角之前
// Update the uniform color
GLfloat timeValue = glfwGetTime(); //获取当前时间
GLfloat greenValue = (sin(timeValue) / 2) + 0.5; //饱和度变化函数
GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");// 函数来获取 uniform 变量 ourColor 的位置。
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);//红色分量是0.0,绿色分量是根据 greenValue 计算的值,蓝色分量是0.0,透明度是1.0。
结果(绿黑渐变三角,动态的)
更多花样!
分别设置三个顶点的位置和色彩
GLfloat 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 // 顶部
};
改变顶点着色器(注意用layout标识符区分属性)
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n" //位置变量设置为0
"layout (location = 1) in vec3 color;\n" //位置变量设置为1
"out vec3 ourColor;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position, 1.0);\n" //坐标转换
"ourColor = color;\n"
"}\0";
改变片段着色器
const GLchar* fragmentShaderSource = "#version 330 core\n"
"in vec3 ourColor;\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
"color = vec4(ourColor, 1.0f);\n"
"}\n\0";
属性内存的设置
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3* sizeof(GLfloat)));
glEnableVertexAttribArray(1);
第一位是位置号,第二位是顶点数目,三四默认,六个偏移看图知原因
得到炫彩三角
自定义着色器类
头文件Shader.h
有了Shader函数可以使用自己文件路径里的 .vs 和 .frag 着色器 就这么命名的!!
#ifndef SHADER_H
#define SHADER_H
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <GL/glew.h>
class Shader
{
public:
GLuint Program;
// Constructor generates the shader on the fly
Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
{
// 1. Retrieve the vertex/fragment source code from filePath
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// ensures ifstream objects can throw exceptions:
vShaderFile.exceptions(std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::badbit);
try
{
// Open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// Read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// close file handlers
vShaderFile.close();
fShaderFile.close();
// Convert stream into string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const GLchar* vShaderCode = vertexCode.c_str();
const GLchar * fShaderCode = fragmentCode.c_str();
// 2. Compile shaders
GLuint vertex, fragment;
GLint success;
GLchar infoLog[512];
// Vertex Shader
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// Print compile errors if any
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 Shader
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
// Print compile errors if any
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragment, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// Shader Program
this->Program = glCreateProgram();
glAttachShader(this->Program, vertex);
glAttachShader(this->Program, fragment);
glLinkProgram(this->Program);
// Print linking errors if any
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;
}
// Delete the shaders as they're linked into our program now and no longer necessery
glDeleteShader(vertex);
glDeleteShader(fragment);
}
// Uses the current shader
void Use()
{
glUseProgram(this->Program);
}
};
#endif
main.c
#include <iostream>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// Other includes
#include "Shader.h"
// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;
// The MAIN function, from here we start the application and run the game loop
int main()
{
// Init GLFW
glfwInit();
// Set all the required options for GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
glewExperimental = GL_TRUE;
// Initialize GLEW to setup the OpenGL Function pointers
glewInit();
// Define the viewport dimensions
glViewport(0, 0, WIDTH, HEIGHT);
// Build and compile our shader program ?????? 设置自定义着色器路径
Shader ourShader("D:/Amy_file/opengl_sty/05_colorful_sanjiao/shader/shader.vs", "D:/Amy_file/opengl_sty/05_colorful_sanjiao/shader/shader.frag");
// Set up vertex data (and buffer(s)) and attribute pointers
GLfloat vertices[] = {
// Positions // Colors
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // Bottom Right
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // Bottom Left
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // Top
};
GLuint VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s).
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindVertexArray(0); // Unbind VAO
// Game loop
while (!glfwWindowShouldClose(window))
{
// Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions
glfwPollEvents();
// Render
// Clear the colorbuffer
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Draw the triangle
ourShader.Use();
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
// Swap the screen buffers
glfwSwapBuffers(window);
}
// Properly de-allocate all resources once they've outlived their purpose
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// Terminate GLFW, clearing any resources allocated by GLFW.
glfwTerminate();
return 0;
}
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
重点是使用了如下函数,两个参数分别是 .vs 和 .frag文件的路径
这两个文件里分别放了顶点着色器和片段着色器的代码(我是新建个txt文本,把代码放进去,再改名)
例子代码
shader.vs
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
out vec3 ourColor;
void main()
{
gl_Position = vec4(position, 1.0f);
ourColor = color;
}
shader.frag
#version 330 core
in vec3 ourColor;
out vec4 color;
void main()
{
color = vec4(ourColor, 1.0f);
}
运行成功如下
五、纹理
纹理
纹理坐标如下:
GLfloat texCoords[] = {
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 上中
};
对纹理采样的解释非常宽松,它可以采用几种不同的插值方式。所以我们需要自己告诉OpenGL该怎样对纹理采样。
纹理环绕方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
第一个参数指定了纹理目标;我们使用的是2D纹理,因此纹理目标是GL_TEXTURE_2D。第二个参数需要我们指定设置的选项与应用的纹理轴。我们打算配置的是WRAP
选项,并且指定S
和T
轴。最后一个参数需要我们传递一个环绕方式,在这个例子中OpenGL会给当前激活的纹理设定纹理环绕方式为GL_MIRRORED_REPEAT。
如果我们选择GL_CLAMP_TO_BORDER选项,我们还需要指定一个边缘的颜色。这需要使用glTexParameter函数的fv
后缀形式,用GL_TEXTURE_BORDER_COLOR作为它的选项,并且传递一个float数组作为边缘的颜色值:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
纹理过滤
纹理坐标不依赖于分辨率(Resolution),它可以是任意浮点值,所以OpenGL需要知道怎样将纹理像素(Texture Pixel,也叫Texel,译注1)映射到纹理坐标。当你有一个很大的物体但是纹理的分辨率很低的时候这就变得很重要了。你可能已经猜到了,OpenGL也有对于纹理过滤(Texture Filtering)的选项。纹理过滤有很多个选项,但是现在我们只讨论最重要的两种:GL_NEAREST和GL_LINEAR。
纹理像素不等于纹理坐标,纹理坐标是你给模型顶点设置的那个数组,纹理像素就是像素点。
GL_NEAREST (临近过滤)opengl默认的过滤方式。OpenGL会选择中心点最接近纹理坐标的那个像素。如下
GL_LINEAR(线性过滤)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:
颗粒感与朦胧感(典型日系胶片风),但边缘颗粒不太行 >_<
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
多级渐远纹理
在渲染中切换多级渐远纹理级别(Level)时,OpenGL在两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界。就像普通的纹理过滤一样,切换多级渐远纹理级别时你也可以在两个不同多级渐远纹理级别之间使用NEAREST和LINEAR过滤。为了指定不同多级渐远纹理级别之间的过滤方式,你可以使用下面四个选项中的一个代替原有的过滤方式:
过滤方式 | 描述 |
---|---|
GL_NEAREST_MIPMAP_NEAREST | 使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样 |
GL_LINEAR_MIPMAP_NEAREST | 使用最邻近的多级渐远纹理级别,并使用线性插值进行采样 |
GL_NEAREST_MIPMAP_LINEAR | 在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样 |
GL_LINEAR_MIPMAP_LINEAR | 在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样 |
就像纹理过滤一样,我们可以使用glTexParameteri将过滤方式设置为前面四种提到的方法之一:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
一个常见的错误是,将放大过滤的选项设置为多级渐远纹理过滤选项之一。这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM错误代码。
加载与创建纹理
SOIL库
使用纹理之前首先要有合适匹配图片格式。
SOIL是简易OpenGL图像库(Simple OpenGL Image Library)的缩写,它支持大多数流行的图像格式,使用起来也很简单,你可以从他们的主页下载。像其它库一样,你必须自己生成.lib。
配环境了 nnd
完成后用如下头文件,就能使用相关功能了
#include <SOIL.h>
接下来要使用SOIL加载图片了,使用它的SOIL_load_image函数:
int width, height;
unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB);
五个参数
1.图片名 2、返回的图片宽度 3、返回的高度 4、指定图片的通道(Channel)数量,但是这里我们只需留为
0 5、加载图片方式
我们只关注图片的RGB
值。结果会储存为一个很大的char/byte数组。
生成纹理
与前面的 VAO VBO 同理。纹理也要使用id生成对象,让我们来创建一个:
GLuint texture;
glGenTextures(1, &texture);
第一个参数:生成纹理的数量。 第二个参数:返回的纹理对象。
绑定,让之后任何的纹理指令都可以配置当前绑定的纹理:
glBindTexture(GL_TEXTURE_2D, texture);
现在纹理已经绑定了,我们可以使用前面载入的图片数据生成一个纹理了。纹理可以通过glTexImage2D来生成:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
- 第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
- 第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
- 第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有
RGB
值,因此我们也把纹理储存为RGB
值。 - 第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
- 下个参数应该总是被设为
0
(历史遗留问题)。 - 第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为
char
(byte)数组,我们将会传入对应值。 - 最后一个参数是真正的图像数据。
生成了纹理和相应的多级渐远纹理后,释放图像的内存并解绑纹理对象是一个很好的习惯。
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);
实操
main.cpp
#include <iostream>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// Other Libs
#include <SOIL.h>
// Other includes
#include "Shader.h"
// 回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;
// The MAIN function, from here we start the application and run the game loop
int main()
{
// Init GLFW
glfwInit();
// Set all the required options for GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);
//注册
glfwSetKeyCallback(window, key_callback);
// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
glewExperimental = GL_TRUE;
// Initialize GLEW to setup the OpenGL Function pointers
glewInit();
// Define the viewport dimensions
glViewport(0, 0, WIDTH, HEIGHT);
// Build and compile our shader program
Shader ourShader("D:/Amy_file/opengl_sty/05_colorful_sanjiao/shader/shader.vs", "D:/Amy_file/opengl_sty/05_colorful_sanjiao/shader/shader.frag");
// Set up vertex data (and buffer(s)) and attribute pointers
GLfloat vertices[] = {
// Positions // Colors // Texture Coords
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // Top Right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // Bottom Right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // Bottom Left
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // Top Left
};
GLuint indices[] = { // Note that we start from 0!
0, 1, 3, // First Triangle
1, 2, 3 // Second Triangle
};
GLuint VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// Position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// Color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
// TexCoord attribute
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glBindVertexArray(0); // Unbind VAO
// Load and create a texture
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture); // All upcoming GL_TEXTURE_2D operations now have effect on this texture object
// Set the texture wrapping parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture wrapping to GL_REPEAT (usually basic wrapping method)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// Set texture filtering parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载图片,生成纹理
int width, height;
unsigned char* image = SOIL_load_image("11.png", &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture when done, so we won't accidentily mess up our texture.
// Game loop
while (!glfwWindowShouldClose(window))
{
// Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions
glfwPollEvents();
// Render
// Clear the colorbuffer
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Bind Texture
glBindTexture(GL_TEXTURE_2D, texture);
// Activate shader
ourShader.Use();
// Draw container
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
// Swap the screen buffers
glfwSwapBuffers(window);
}
// Properly de-allocate all resources once they've outlived their purpose
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
// Terminate GLFW, clearing any resources allocated by GLFW.
glfwTerminate();
return 0;
}
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
代码中设置了索引值,并放在了EVO中,索引值的作用如下:
顶点着色器
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;
out vec3 ourColor;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(position.x,-position.y,position.z, 1.0f);
ourColor = color;
TexCoord = texCoord;
}
片段着色器
#version 330 core
in vec3 ourColor;
in vec2 TexCoord;
out vec4 color;
uniform sampler2D ourTexture;
void main()
{
color = texture(ourTexture, TexCoord);
}
结果如下
六、变换
向量
向量加减乘除一个常数:略
向量点乘
向量叉乘
矩阵
矩阵加减一个常数:同向量
矩阵乘
ps:左边矩阵的行数=右边矩阵的列数
缩放
位移
旋转
由于官方源码(没能实现),后面跟这个老哥学
七、旋转+平移+缩放 实现地月系统
说明:大正方体模拟的 “地球”(只有自传),小正方体模拟的 “月球”(有自传+平移+公转)。
改代码(三棱锥倒着公转版)
学下一章时突然发现y轴是朝上的(难怪三棱椎是倒下的)
改了下坐标
八、键盘控制镜头的平移 透视啥的透视投影 观察矩阵 对LookAt的理解
略 看老哥的
需要注意部分:
1、z轴是向屏幕里面的
2、
#version 330 core
layout (location = 0) in vec3 position;
layout(location = 1) in vec3 color; // 颜色变量的顶点属性位置值为 1
out vec3 ourColor;
uniform mat4 transform_1;
uniform mat4 projection_1;
uniform mat4 view_1;
void main()
{
ourColor = color;
gl_Position = projection_1 * view_1 * transform_1 * vec4(position, 1.0f); // 注意矩阵乘法要从右向左读
}
3.片段着色器
#version 330 core
in vec3 ourColor;
out vec4 FragColor;
void main()
{
FragColor = vec4(ourColor,1.0f);
}
4.Camera.h
#include <iostream>
using namespace std;
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "glm\glm.hpp"
#include "glm\gtc\matrix_transform.hpp"
enum Camera_Movement { // 枚举类型
FORWARD, // 向前
BACKWARD, // 向后
LEFT, // 向左
RIGHT, // 向右
UPWARD, // 向上
DOWNWARD // 向下
};
const GLfloat SPEED = 6.0f; // 初始速度
class Camera
{
public:
// 构造函数
Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3 target = glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f)) :movementSpeed(SPEED)
{
this->position = position;
this->camera_Z_axis = target;
this->camera_Y_axis = up;
this->camera_X_axis = glm::normalize(glm::cross(this->camera_Z_axis, this->camera_Y_axis));
this->updateCameraVectors(); // 实时更新
}
// 观察矩阵
glm::mat4 GetViewMatrix()
{
return glm::lookAt(this->position, this->position + this->camera_Z_axis, this->camera_Y_axis);
//参数一:相机位置【在世界坐标系里的位置】
//参数二:目标位置【即摄像机镜头看向的位置,一般设为(0, 0, 0),即看向中心位置】
//参数san:在 camera坐标系 里的正视向量【一般设为(0,1,0)】就是个向上(y方向)的单位向量
}
//根据键盘输入来移动摄像机
void ProcessKeyboard(Camera_Movement direction, float deltaTime)
{
float velocity = this->movementSpeed * deltaTime;
if (direction == FORWARD)
this->position += this->camera_Z_axis * velocity;
if (direction == BACKWARD)
this->position -= this->camera_Z_axis * velocity;
if (direction == LEFT)
this->position -= this->camera_X_axis * velocity;
if (direction == RIGHT)
this->position += this->camera_X_axis * velocity;
if (direction == UPWARD)
this->position += this->camera_Y_axis * velocity;
if (direction == DOWNWARD)
this->position -= this->camera_Y_axis * velocity;
}
glm::vec3 GetPosition() // 下一篇文章使用
{
return this->position;
}
private:
// 摄影机的属性
glm::vec3 position; // 相机当前位置
glm::vec3 camera_Z_axis; // 摄影机的 Z 轴向量
glm::vec3 camera_X_axis; // 摄影机的 X 轴向量
glm::vec3 camera_Y_axis; // 摄影机的 Y 轴向量
GLfloat movementSpeed; // 镜头移动速度
void updateCameraVectors()
{
this->camera_Z_axis = glm::normalize(this->camera_Z_axis);
this->camera_X_axis = glm::normalize(glm::cross(this->camera_Z_axis, this->camera_Y_axis));
this->camera_Y_axis = glm::normalize(glm::cross(this->camera_X_axis, this->camera_Z_axis));
}
};
5.main.cpp
修改了代码 让地球变成四棱锥(好设置坐标)并让他以y轴自转
/* 引入相应的库 */
#include <iostream>
using namespace std;
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// Other Libs
#include "SOIL2/SOIL2.h"
//glm
#include"glm\glm.hpp"
#include"glm\gtc\matrix_transform.hpp"
#include"glm\gtc\type_ptr.hpp"
// Other includes
#include "Shader.h"
#include"Camera.h"
// 深度测试
/* 编写各顶点位置 */ //改成三棱锥
float vertices_1[] = {
// x、y、z 坐标 // color
//-0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, // red 红色面
// 0.0f, -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, // green 绿色面
// 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
// 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
//
//-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // blue 蓝色面
//0.0f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
//0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
// 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, // yellow 黄色面
// 0.0f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
// 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
//改成四棱锥 这样比较正
// x、y、z 坐标 // color
1.0f, -0.5f, -1.0f, 1.0f, 0.0f, 0.0f, // red 红色面 底面
1.0f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f,
-1.0f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f,
-1.0f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f,
-1.0f, -0.5f, -1.0f, 1.0f, 0.0f, 0.0f,
1.0f, -0.5f, -1.0f, 1.0f, 0.0f, 0.0f,
-1.0f, -0.5f, 1.0f, 0.0f, 1.0f, 0.0f, // green 绿色面
1.0f, -0.5f, 1.0f, 0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-1.0f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, // blue 蓝色面
-1.0f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
1.0f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, // yellow 黄色面
1.0f, -0.5f, -1.0f, 1.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f,
-1.0f, -0.5f, -1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -0.5f, -1.0f, 1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
};
const GLint WIDTH = 600, HEIGHT = 600;
//
bool keys[1024]; // 专门存储按过的键
//Camera camera(glm::vec3(1.0f, 1.0f, -5.0f), glm::vec3(-1.0f, -1.0f, 5.0f), glm::vec3(0.0f, 1.0f, 0.0f));
Camera camera(glm::vec3(0.0f, 0.0f, -5.0f), glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3(0.0f, 1.0f, 0.0f));
//参数一:摄像机在世界坐标的位置 参数二:摄像机朝向 参数三:向上的方向
void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode); // 对键盘的响应函数
void Key_Movement(); // 与 Camera 类互动的函数
GLfloat deltaTime = 0.0f;
GLfloat lastTime = 0.0f;
int main()
{
/* 初始化 */
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 告诉系统 版本是为了opengl画图准备的 支持3.0以后的版本 是新的 3.3
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // must for Mac(Windows可不加) 启动兼容
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // 缩放关闭
/* 窗口捕获与处理 */
GLFWwindow* window_1 = glfwCreateWindow(WIDTH, HEIGHT, "NJUPT_Learn OpenGL Earth-moon System", nullptr, nullptr);
int screenWidth_1, screenHeight_1;
glfwGetFramebufferSize(window_1, &screenWidth_1, &screenHeight_1);
cout << "screenWidth_1 = " << screenWidth_1 << ", screenHeight = " << screenHeight_1 << endl; //返回窗口尺寸
glewExperimental = GL_TRUE;
glfwMakeContextCurrent(window_1);
glewInit();
/* 开启深度测试 */
glEnable(GL_DEPTH_TEST);
/* 将我们自己设置的着色器文本传进来 */
Shader ourShader = Shader("D:/Amy_file/opengl_sty/09_camera/shader/shader.vs", "D:/Amy_file/opengl_sty/09_camera/shader/shader.frag");; // juedui路径
/* 设置顶点缓冲对象(VBO) + 设置顶点数组对象(VAO) */
GLuint VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
//绑定VAO VBO
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_1), vertices_1, GL_STATIC_DRAW);
/* 设置链接顶点属性 */
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0); // 通道 0 打开
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1); // 通道 1 打开
// 我们可以在创建窗口之后,开始游戏循环之前注册各种回调函数。
glfwSetKeyCallback(window_1, KeyCallback);
//打印操作说明
cout << "操作说明\n" <<"前进:q \n" << "后退:e \n" <<"上:w\n"<<"下:s\n"<<"左:a\n"<<"右:d\n"<<"退出:esc"<<endl;
/* draw loop 画图循环 */
while (!glfwWindowShouldClose(window_1))
{
GLfloat currentTime = glfwGetTime();
deltaTime = currentTime - lastTime;
lastTime = currentTime;
/* 视口 + 时间 */
glViewport(0, 0, screenWidth_1, screenHeight_1);
glfwPollEvents();
Key_Movement(); // 获取键盘的小动作
/* 渲染 + 清除颜色缓冲 */
glClearColor(0.5f, 0.8f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* 绘制图形 */
ourShader.Use();
glBindVertexArray(VAO);
for (int i = 0; i < 2; i++) //i=0 or 1
{
GLfloat time = glfwGetTime(); // 获取时间
//转换矩阵给个初始化(很关键,不然地球月亮都没有) 定义 transform_1为4x4单位矩阵
glm::mat4 transform_1 = glm::mat4(1.0f); //它表示了一个"无变换"状态。当你将一个矩阵与单位矩阵相乘时,不会对矩阵进行任何变换操作,即矩阵保持不变。
glm::mat4 view_1 = camera.GetViewMatrix(); // 求得观察矩阵
//透视投影矩阵projection
glm::mat4 projection_1 = glm::perspective(glm::radians(45.0f), (float)screenWidth_1 / (float)screenHeight_1, 0.1f, 100.0f);
if (i == 0) // 大立方体
{
transform_1 = glm::translate(transform_1, glm::vec3(0.0f, 0.0f, 0.0f));
float new_size = cos(currentTime) * 0.2f + 0.8f;
transform_1 = glm::scale(transform_1, glm::vec3(new_size, new_size, new_size)); //体积周期变化
transform_1 = glm::rotate(transform_1, time, glm::vec3(0.0f, 1.0f, 0.0f));
}
else // 小立方体
{
transform_1 = glm::translate(transform_1, glm::vec3(1.8f * cos(time), 1.8f * sin(time), 0.0f)); //公转
transform_1 = glm::rotate(transform_1, currentTime, glm::vec3(0.5f, 0.5f, 0.0f)); //zizhuan
transform_1 = glm::scale(transform_1, glm::vec3(0.15f, 0.15f, 0.15f));//体积变为原来的15%
}
int transform_1_Location = glGetUniformLocation(ourShader.Program, "transform_1");//对应着色器中的uniform的变量transform_1,获得七位置
glUniformMatrix4fv(transform_1_Location, 1, GL_FALSE, glm::value_ptr(transform_1)); //将transform_1内容传给着色器中
int projection_1_Location = glGetUniformLocation(ourShader.Program, "projection_1"); //同上
glUniformMatrix4fv(projection_1_Location, 1, GL_FALSE, glm::value_ptr(projection_1));
int view_1_Location = glGetUniformLocation(ourShader.Program, "view_1"); //同上
glUniformMatrix4fv(view_1_Location, 1, GL_FALSE, glm::value_ptr(view_1));
glDrawArrays(GL_TRIANGLES, 0, 18);//画 从0索引开始 画18个点
}
glBindVertexArray(0); // 解绑定 VAO
/* 交换缓冲 */
glfwSwapBuffers(window_1);
}
/* 释放资源 */
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate(); // 结束
return 0;
}
// while循环 一直更新旋转矩阵transform_1 transform_1与顶点着色器中的position相乘完成变换
void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, GL_TRUE);
cout << "按下了关闭键 esc = " << key << endl;
}
if (key >= 0 && key <= 1024)
{
if (action == GLFW_PRESS)
keys[key] = true; // true 代表按下了键
else if (action == GLFW_RELEASE)
keys[key] = false;
}
}
void Key_Movement()
{
if (keys[GLFW_KEY_Q]) // 向前
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_E]) // 向后
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A]) // 向左
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D]) // 向右
camera.ProcessKeyboard(RIGHT, deltaTime);
if (keys[GLFW_KEY_W]) // 向上
camera.ProcessKeyboard(UPWARD, deltaTime);
if (keys[GLFW_KEY_S]) // 向下
camera.ProcessKeyboard(DOWNWARD, deltaTime);
}
运行图