做前端有小两年了,对自己使用的渲染技术却还是门外汉,这怎么说得过去,毕竟自己当初还是因为这神奇的技术才入了这个坑的呢。
我竟然都懒得去百度百科复制粘贴了。OpenGL其实是一套通用的API标准,它准确地指定了每个函数的结果/输出是什么以及应该如何执行。但其本身不是库也没有实现,具体实现是各大显卡厂商的显卡驱动程序。
首先推荐个学习网站:LearnOpenGL
虽然是英文版,英语不好的通过网页的翻译工具并不影响阅读
你完全可以跟着网上的教程去学习包括环境的搭建,值得一提的是,你要区分这几个关键词:glad、glew、glfw、freeGLUT。因为OpenGL的实现都是驱动程序,对于开发者而言,我们需要获取OpenGL的一些可操作对象,然而这些不同平台会有差异,因此就有了glew/glad,它们是OpenGL的库,在封装的同时并且跨平台的,使开发者不用担心平台的差异。glad和glew并没有太大差异,可把glad当作是glew的升级版,在一些教程里的接口基本没什么差异。
我们最终是希望所有的图形操作是展示在屏幕上的,这就需要OpenGL的上下文了。这就有了glfw/freeGLUT, 可把glfw看作是freeGLUT的升级版,其实相当于是OpenGL渲染的载体,并且也是跨平台的。不得不感叹一句,我们都是站在巨人的肩上的。
进入正题,前面推荐的网站上使用OpenGL库是glad,这里我使用的是glew(主要是因为cocos底层用的是glew),这两个没什么太大区别,对于常规操作基本没什么差异。
这里我使用的是mac,所以环境搭建会有点区别,不是mac的可以跳过。首先,我需要下载并编译glew+glfw这两个库,对于这个我就遇到了点麻烦。mac其实有个叫Homebrew的工具,使用很方便,直接brew+install+libName 即可安装想装的库。但是前提得安装了Homebrew,安装命令:
ruby -e “$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)”
但如果你也出现访问禁止的错误,你需要把这个域名加到你本地hosts里,像这样:
199.232.68.133 raw.githubusercontent.com
其实就是github的域名,然后终端执行:
brew install glfw
brew install glew
它们安装在你本地的/usr/local/Cellar目录
然后就是xcode(vs的配置也差不多)环境配置了,检查/usr/local/include 和 /usr/local/lib 这两个目录有没有你安装的库头文件和库,默认是会引用你Cellar目录你安装的内容,如果没有需要手动复制过去。
然后在xcode的search path 添加搜索路径即可,像这样:
但这里,环境配置部分就结束了,可以愉快的撸代码了。
这并不是一件容易的事,你需要创建窗口、知道图形渲染过程和着色器使用,这些你都可以到前面的网站上找到,我并不想缀述。如果你通过学习能看懂以下代码,说明你已经入门了。
// Include standard headers
#include
// Include GLEW
#include
//glm库
#include
#include
#include
// Include GLFW
#include
GLFWwindow* window;
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define SCR_WIDTH 800
#define SCR_HEIGHT 600
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)
};
// camera
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);
bool firstMouse = true;
float yaw = -90.0f; // yaw is initialized to -90.0 degrees since a yaw of 0.0 results in a direction vector pointing to the right so we initially rotate a bit to the left.
float pitch = 0.0f;
float lastX = 800.0f / 2.0;
float lastY = 600.0 / 2.0;
float fov = 45.0f;
// timing
float deltaTime = 0.0f; // time between current frame and last frame
float lastFrame = 0.0f;
GLfloat vertices3d[] = {
//x y z r g b tx ty
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
// 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
//-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
//0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
// -0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
//-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
//-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
//0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
//0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
// 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
//-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
//0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
// -0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f
};
//每两个三角形组成一个面
const int indices3d[] = { // note that we start from 0!
0, 1, 3, // first triangle
1, 2, 3, // second triangle
4, 5, 7, // first triangle
5, 6, 7, // second triangle
8, 9, 11, // first triangle
9, 10, 11, // second triangle
12, 13, 15, // first triangle
13, 14, 15, // second triangle
16, 17, 19, // first triangle
17, 18, 19, // second triangle
20, 21, 23, // first triangle
21, 22, 23, // second triangle
};
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 1) in vec3 aColor;\n"
"layout (location = 2) in vec2 aTexCoord;\n"
"out vec3 ourColor;\n"
"out vec2 TexCoord;\n"
//"uniform mat4 transform;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main()\n"
"{\n"
" gl_Position = projection * view * model * vec4(aPos, 1.0);\n"
" ourColor = aColor;\n"
" TexCoord = vec2(aTexCoord.x, aTexCoord.y);\n"
"}\n\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 ourColor;\n"
"in vec2 TexCoord;\n"
"uniform sampler2D texture1;\n"
"void main()\n"
"{\n"
" FragColor = texture(texture1,TexCoord);\n"
"}\n\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);
float cameraSpeed = 2.5 * deltaTime;
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;
}
bool bInWinRect(double xpos,double ypos)
{
if(xpos < 0 || xpos > SCR_WIDTH )
{
return false;
}
if(ypos < 0 || ypos > SCR_HEIGHT)
{
return false;
}
return true;
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if(!bInWinRect(xpos,ypos))
{
return;
}
if(firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
// std::cout<<"xpos->"<"<
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
float sensitivity = 0.05;
xoffset *= sensitivity;
yoffset *= sensitivity;
yaw += xoffset;
pitch += yoffset;
if(pitch > 89.0f)
pitch = 89.0f;
if(pitch < -89.0f)
pitch = -89.0f;
glm::vec3 direction;
direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
direction.y = sin(glm::radians(pitch));
direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
cameraFront = glm::normalize(direction);
}
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 scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
if(fov >= 1.0f && fov <= 90.0f)
fov -= yoffset;
else if(fov <= 1.0f)
fov = 1.0f;
else if(fov > 90.0f)
fov = 90.0f;
}
int main( void )
{
// Initialise GLFW
if( !glfwInit() )
{
fprintf( stderr, "Failed to initialize GLFW\n" );
getchar();
return -1;
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Open a window and create its OpenGL context
window = glfwCreateWindow( SCR_WIDTH, SCR_HEIGHT, "LEARN GL", NULL, NULL);
if( window == NULL ){
fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
getchar();
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// glfwSetWindowSize(window,SCR_WIDTH,SCR_HEIGHT);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
//glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
// Initialize GLEW
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
getchar();
glfwTerminate();
return -1;
}
glEnable(GL_DEPTH_TEST);
// Ensure we can capture the escape key being pressed below
// Dark blue background
glClearColor(0.1f, 0.1f, 0.5f, 0.0f);
//创建着色器
//vertex shader
int 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;
}
// fragment shader
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// check for shader compile 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
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
glLinkProgram(shaderProgram);
//创建顶点属性及顶点缓冲区
GLuint vertexArrayID;
glGenVertexArrays(1, &vertexArrayID);
glBindVertexArray(vertexArrayID);
// This will identify our vertex buffer
GLuint vertexBuffer;
// Generate 1 buffer, put the resulting identifier in vertexbuffer
glGenBuffers(1, &vertexBuffer);
// The following commands will talk about our 'vertexbuffer' buffer
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
// Give our vertices to OpenGL.
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices3d), vertices3d, GL_STATIC_DRAW);
//元素缓冲区对象
GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices3d), indices3d, GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// texture coord attribute
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
//解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//加载图片
int width, height, nrChannels;
stbi_set_flip_vertically_on_load(true);
unsigned char *data = stbi_load("/Users/hfy/Dev/Test/ShaderTest/ShaderTest/test.jpg", &width, &height, &nrChannels, 0);
//纹理设置
unsigned int texture1;
glGenTextures(1, &texture1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
//激活纹理单元,不同系统默认纹理单元可能有差异,本测试系统默认是关闭状态
glUseProgram(shaderProgram);
glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 1);
while(!glfwWindowShouldClose(window))
{
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
processInput(window);
// Clear the screen. It's not mentioned before Tutorial 02, but it can cause flickering, so it's there nonetheless.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // also clear the depth buffer now!
// Draw nothing, see you in tutorial 2 !
glActiveTexture(GL_TEXTURE0);//GL_TEXTURE0本测试系统并不对应0,而是1
glBindTexture(GL_TEXTURE_2D, texture1);
// get matrix's uniform location and set matrix
glUseProgram(shaderProgram);
//视锥
glm::mat4 projection = glm::perspective(glm::radians(fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
unsigned int projectionLoc = glGetUniformLocation(shaderProgram, "projection");
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
//glm::mat4 view = glm::mat4(1.0f);
//视图
glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
unsigned int viewLoc = glGetUniformLocation(shaderProgram, "view");
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
// note: currently we set the projection matrix each frame, but since the projection matrix rarely changes it's often best practice to set it outside the main loop only once.
glBindVertexArray(vertexArrayID);
for (unsigned int i = 0; i < 10; i++)
{
// calculate the model matrix for each object and pass it to shader before drawing
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * (i + 1) * (float)glfwGetTime();
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
unsigned int modelLoc = glGetUniformLocation(shaderProgram, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
// glDrawArrays(GL_TRIANGLES, 0, 36);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}
// Swap buffers
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &vertexArrayID);
glDeleteBuffers(1, &vertexBuffer);
glDeleteBuffers(1, &EBO);
// Close OpenGL window and terminate GLFW
glfwTerminate();
return 0;
}
运行效果类似这样:
这几乎涵盖了入门篇的所有内容,代码虽然有点长,但比较直观,像着色器和摄像机等等是可以封装成类的,这里方便学习全都放一个文件里了。值得一提的是,这里需要另外附加两个库,一个是图片加载器的库,一个是glm,即:gl的数学库,里面大部分是一些矩阵转换运算的函数,你都可以去前面的推荐网站找到下载地址以及使用方式。