1、文本渲染原理
1.1經典文本渲染:位圖字體

早期的文本渲染,是將須要的字符集放到一個大紋理中,這個紋理稱爲「位圖字體」,渲染某個字符時,經過查找座標,找到該字符對應的區域並渲染出來,再啓動混合,讓字符紋理的背景保持透明,很是容易理解。php
這種方式的缺點也比較明顯:
1)字符集很是小,不支持拓展,由於已經生成了字符表);
2)已經預光柵化了,因此分辨率已經固化了,修改分辨率須要從新編譯一張字符紋理表(「紋理表」是筆者自創的稱呼)css
1.2 現代文本渲染

上面的圖很清楚的說明了一個字符的定義規則,注意,有些字符在基準線之上,有少數字符在基準線之下,好比g p j等。
關於字符更詳細的定義,參考:https://www.supremo.co.uk/typeterms/前端
基於經典文本渲染的瓶頸,如今有更好的文本渲染方式,基於FreeType處理。
FreeType有如下優勢:
1)跨平臺
2)能加載TrueType字體,TrueType不是基於像素定義的,而是經過數學公式(曲線)來定義,相似矢量圖像,因此方便渲染不一樣大小的字形,有更好的適配能力react
2、基於freetype渲染文本


關閉混合,能夠看到每一個字符佔的區域大小是不同的
2.1 先看看着shader的實現
頂點着色器:很簡單,有個小技巧,一個vec4 xy存字符position,zw存紋理,節省內存ios
#version 330 core layout (location = 0) in vec4 vertex; out vec2 TexCoords; uniform mat4 projection; void main () { gl_Position = projection * vec4(vertex.xy, 0.0, 1.0); // z座標=0,在原點,w座標爲1,無縮放 TexCoords = vertex.zw; }
片斷着色器: sampled取出r值,字形原始數據沒有顏色,r通道存放的是alpha值,r值與設置的color作混合,便可獲得帶顏色的文字。xcode
注意下面代碼中註釋的部分,若是不開啓混合,須要判斷alpha通道的值爲0時,進行discard操做,不然按照當前的邏輯字符會渲染成一個矩形圖案。開啓混合模式,當前窗口的顏色緩衝會把字符的背景中鏤空的部分覆蓋掉dom
#version 330 core in vec2 TexCoords; out vec4 color; uniform sampler2D text; uniform vec3 textColor; void main() { vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); // if (sampled.w == 0) { // discard; // } color = vec4(textColor, 1.0) * sampled; }
2.1 工程中引入須要的頭文件
#include <ft2build.h> #include FT_FREETYPE_H
2.2 啓用混合模式,某則渲染出來是方形圖案(不啓用混合,經過其餘方式也能夠實現)
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2.3 freetype庫初始化
// FreeType FT_Library ft; // All functions return a value different than 0 whenever an error occurred if (FT_Init_FreeType(&ft)) { std::cout << "ERROR::freetype: Could not init FreeType Library" << std::endl; } // Load font as face FT_Face face; if (FT_New_Face(ft, "fonts/Antonio-Bold.ttf", 0, &face)) { std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl; } FT_Set_Pixel_Sizes(face, 0, 48); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
2.4 加載128個經常使用字符(下降使用難度),存放到map中,使用完記得釋放freetype
for (GLubyte c = 0; c < 128; c++) { // Load character glyph if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { std::cout << "ERROR::FREETYPE: Failed to load Glyph" << std::endl; continue; } // Generate texture GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D( GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width, face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer); // Set texture options 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_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Now store character for later use Character character = { texture, glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows), glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top), static_cast<GLuint>(face->glyph->advance.x) }; Characters.insert(std::pair<GLchar, Character>(c, character)); } // Destroy FreeType once we're finished FT_Done_Face(face); FT_Done_FreeType(ft);
2.5 render loop中渲染兩端測試文字
注意,這裏渲染文字使用正交矩陣,沒有像以前使用投影矩陣,而且投影矩陣的左下角是(0, 0)座標ide
glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(SCR_WIDTH), 0.0f, static_cast<GLfloat>(SCR_HEIGHT));
RenderText(shader, "This is sample text", 25.0f, 25.0f, 1.0f, glm::vec3(0.5f, 0.8f, 0.2f)); RenderText(shader, "(C) LearnOpenGL.com", 540.0f, 570.0f, 0.5f, glm::vec3(0.3f, 0.7f, 0.9f));
RenderText的邏輯不復雜,直接看代碼理解便可svg
void RenderText(Shader &shader, std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color) { shader.use(); shader.setVec3("textColor", color.x, color.y, color.z); glActiveTexture(GL_TEXTURE0); glBindVertexArray(VAO); std::string::const_iterator c; for (c = text.begin(); c != text.end(); c++) { Character ch = Characters[*c]; GLfloat xpos = x + ch.Bearing.x * scale; GLfloat ypos = y - (ch.Size.y - ch.Bearing.y) * scale; GLfloat w = ch.Size.x * scale; GLfloat h = ch.Size.y * scale; // Update VBO for each character GLfloat vertices[6][4] = { {xpos, ypos + h, 0.0, 0.0}, {xpos, ypos, 0.0, 1.0}, {xpos + w, ypos, 1.0, 1.0}, {xpos, ypos + h, 0.0, 0.0}, {xpos + w, ypos, 1.0, 1.0}, {xpos + w, ypos + h, 1.0, 0.0}, }; // Render glyph texture over quad glBindTexture(GL_TEXTURE_2D, ch.TextureID); // Update content of VBO memory glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); glBindBuffer(GL_ARRAY_BUFFER, 0); //Render quad glDrawArrays(GL_TRIANGLES, 0, 6); // Now advance cursors for next glyph (note that advance is number of 1/64 pixels) x += (ch.Advance >> 6) * scale; } glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); }
3、補充:mac freetype安裝配置,不復雜,簡單說明
若是是Android/iOS,需經過freetype源碼編譯對應的平臺庫,前端直接引用freetype.js更方便oop
1. brew install freetype
安裝完成會在local/include/freetype2/下生成頭文件
由於freetype的代碼依賴結構,須要修改header文件,講freetype2目錄下的頭文件copy到include下,不然會編譯報錯

2. Xcode環境變量配置
2.1 xcode->preference->locations->custom paths

2.2 Search Paths 增長Header和 Library

2.3 Build Phases 增長庫依賴

4、完整代碼
#include <glad/glad.h> #include <GLFW/glfw3.h> #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> #include "Shader.h" #include "camera.h" #include "model.h" #include <iostream> #include <random> #include <ft2build.h> #include FT_FREETYPE_H // Holds all state information relevant to a character as loaded using FreeType struct Character { GLuint TextureID; // ID handle of the glyph texture glm::ivec2 Size; glm::ivec2 Bearing; // bearing 這裏翻譯成方位/方向 GLuint Advance; }; std::map<GLchar, Character> Characters; GLuint VAO, VBO; void RenderText(Shader &shader, std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color); void framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xpos, double ypos); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void processInput(GLFWwindow *window); unsigned int loadTexture(const char *path); unsigned int loadCubemap(vector<std::string> faces); void renderScene (const Shader &shader); void renderCube(); void RenderQuad(); void renderSphere(); // settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; bool blinn = false; bool blinnKeyPressed = false; bool gammaEnabled = true; bool gammaKeyPressed = false; bool bloom = true; bool hdr = true; //change with 'space' float exposure = 1.0f; // change with Q and E // camera Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); float lastX = (float)SCR_WIDTH / 2.0; float lastY = (float)SCR_HEIGHT / 2.0; bool firstMouse = true; // timing float deltaTime = 0.0f; float lastFrame = 0.0f; unsigned int draw_mode = 1; float lerp(float a, float b, float f) { return a + f * (b - a); } int main() { // glfw: initialize and configure // ------------------------------ glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // glfwWindowHint(GLFW_SAMPLES, 4); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif // glfw window creation // -------------------- GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "天哥學opengl", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetCursorPosCallback(window, mouse_callback); glfwSetScrollCallback(window, scroll_callback); // tell GLFW to capture our mouse // glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // glad: load all OpenGL function pointers // --------------------------------------- if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); // glPolygonMode(GL_FRONT_AND_BACK ,GL_LINE ); glEnable(GL_CULL_FACE); // glEnable(GL_BLEND); // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // build and compile shaders Shader shader("text.vs", "text.fs"); glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(SCR_WIDTH), 0.0f, static_cast<GLfloat>(SCR_HEIGHT)); shader.use(); shader.setMat4("projection", projection); // FreeType FT_Library ft; // All functions return a value different than 0 whenever an error occurred if (FT_Init_FreeType(&ft)) { std::cout << "ERROR::freetype: Could not init FreeType Library" << std::endl; } // Load font as face FT_Face face; if (FT_New_Face(ft, "fonts/Antonio-Bold.ttf", 0, &face)) { std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl; } FT_Set_Pixel_Sizes(face, 0, 48); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); for (GLubyte c = 0; c < 128; c++) { // Load character glyph if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { std::cout << "ERROR::FREETYPE: Failed to load Glyph" << std::endl; continue; } // Generate texture GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D( GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width, face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer); // Set texture options 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_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Now store character for later use Character character = { texture, glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows), glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top), static_cast<GLuint>(face->glyph->advance.x) }; Characters.insert(std::pair<GLchar, Character>(c, character)); } glBindTexture(GL_TEXTURE_2D, 0); // Destroy FreeType once we're finished FT_Done_Face(face); FT_Done_FreeType(ft); // Confiture VAO/VBO for texture quads glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); glBindVertexArray(0); glBindVertexArray(0); // render loop // ----------- while (!glfwWindowShouldClose(window)) { // per-frame time logic float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; processInput(window); //render glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); RenderText(shader, "This is sample text", 25.0f, 25.0f, 1.0f, glm::vec3(0.5f, 0.8f, 0.2f)); RenderText(shader, "(C) LearnOpenGL.com", 540.0f, 570.0f, 0.5f, glm::vec3(0.3f, 0.7f, 0.9f)); // ------------------------------------------------------------------------------- glfwSwapBuffers(window); glfwPollEvents(); } // glfw: terminate, clearing all previously allocated GLFW resources. // ------------------------------------------------------------------ glfwTerminate(); return 0; } void RenderText(Shader &shader, std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color) { shader.use(); shader.setVec3("textColor", color.x, color.y, color.z); glActiveTexture(GL_TEXTURE0); glBindVertexArray(VAO); std::string::const_iterator c; for (c = text.begin(); c != text.end(); c++) { Character ch = Characters[*c]; GLfloat xpos = x + ch.Bearing.x * scale; GLfloat ypos = y - (ch.Size.y - ch.Bearing.y) * scale; GLfloat w = ch.Size.x * scale; GLfloat h = ch.Size.y * scale; // Update VBO for each character GLfloat vertices[6][4] = { {xpos, ypos + h, 0.0, 0.0}, {xpos, ypos, 0.0, 1.0}, {xpos + w, ypos, 1.0, 1.0}, {xpos, ypos + h, 0.0, 0.0}, {xpos + w, ypos, 1.0, 1.0}, {xpos + w, ypos + h, 1.0, 0.0}, }; // Render glyph texture over quad glBindTexture(GL_TEXTURE_2D, ch.TextureID); // Update content of VBO memory glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); glBindBuffer(GL_ARRAY_BUFFER, 0); //Render quad glDrawArrays(GL_TRIANGLES, 0, 6); // Now advance cursors for next glyph (note that advance is number of 1/64 pixels) x += (ch.Advance >> 6) * scale; } glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); } // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly // --------------------------------------------------------------------------------------------------------- bool startRecord = false; void processInput(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS) { draw_mode = 1; } if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS) { draw_mode = 2; } if (glfwGetKey(window, GLFW_KEY_3) == GLFW_PRESS) { draw_mode = 3; } if (glfwGetKey(window, GLFW_KEY_4) == GLFW_PRESS) { draw_mode = 4; } if (glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS && !gammaKeyPressed) { gammaEnabled = !gammaEnabled; gammaKeyPressed = true; } if (glfwGetKey(window, GLFW_KEY_B) == GLFW_RELEASE) { gammaKeyPressed = false; } if (glfwGetKey(window, GLFW_KEY_Y)) { std::cout << "Y" << std::endl; startRecord = true; firstMouse = true; } if (glfwGetKey(window, GLFW_KEY_N)) { std::cout << "N" << std::endl; startRecord = false; } if (startRecord) { return; } if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.ProcessKeyboard(FORWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(BACKWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(LEFT, deltaTime); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(RIGHT, deltaTime); if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS) exposure -= 0.5 * deltaTime; if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS) exposure += 0.5 * deltaTime; if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS && !gammaKeyPressed) { hdr = !hdr; gammaKeyPressed = true; } if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_RELEASE) { gammaKeyPressed = false; } } // 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); } // glfw: whenever the mouse moves, this callback is called // ------------------------------------------------------- void mouse_callback(GLFWwindow* window, double xpos, double ypos) { // std::cout << "xpos : " << xpos << std::endl; // std::cout << "ypos : " << ypos << std::endl; if (startRecord) { return; } if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; } float xoffset = xpos - lastX; float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top lastX = xpos; lastY = ypos; // std::cout << "xoffset : " << xoffset << std::endl; // std::cout << "yoffset : " << yoffset << std::endl; camera.ProcessMouseMovement(xoffset, yoffset); } // glfw: whenever the mouse scroll wheel scrolls, this callback is called // ---------------------------------------------------------------------- void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(yoffset); } // utility function for loading a 2D texture from file // --------------------------------------------------- unsigned int loadTexture(char const * path) { unsigned int textureID; glGenTextures(1, &textureID); int width, height, nrComponents; unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0); if (data) { GLenum format; if (nrComponents == 1) format = GL_RED; else if (nrComponents == 3) format = GL_RGB; else if (nrComponents == 4) format = GL_RGBA; glBindTexture(GL_TEXTURE_2D, textureID); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); 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_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); stbi_image_free(data); } else { std::cout << "Texture failed to load at path: " << path << std::endl; stbi_image_free(data); } return textureID; } unsigned int loadCubemap(vector<std::string> faces) { unsigned int textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); int width, height, nrChannels; for (unsigned int i = 0; i < faces.size(); i++) { unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); stbi_image_free(data); } else { std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl; stbi_image_free(data); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); } return textureID; } void renderScene(const Shader &shader) { // room cube glm::mat4 model = glm::mat4(1.0f); model = glm::scale(model, glm::vec3(5.0f)); shader.setMat4("model", model); glDisable(GL_CULL_FACE); // note that we disable culling here since we render 'inside' the cube instead of the usual 'outside' which throws off the normal culling methods. shader.setInt("reverse_normals", 1); // A small little hack to invert normals when drawing cube from the inside so lighting still works. renderCube(); shader.setInt("reverse_normals", 0); // and of course disable it glEnable(GL_CULL_FACE); // cubes model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(4.0f, -3.5f, 0.0)); model = glm::scale(model, glm::vec3(0.5f)); shader.setMat4("model", model); renderCube(); model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(2.0f, 3.0f, 1.0)); model = glm::scale(model, glm::vec3(0.75f)); shader.setMat4("model", model); renderCube(); model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(-3.0f, -1.0f, 0.0)); model = glm::scale(model, glm::vec3(0.5f)); shader.setMat4("model", model); renderCube(); model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(-1.5f, 1.0f, 1.5)); model = glm::scale(model, glm::vec3(0.5f)); shader.setMat4("model", model); renderCube(); model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(-1.5f, 2.0f, -3.0)); model = glm::rotate(model, glm::radians(60.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0))); model = glm::scale(model, glm::vec3(0.75f)); shader.setMat4("model", model); renderCube(); } // renderCube() renders a 1x1 3D cube in NDC. // ------------------------------------------------- unsigned int cubeVAO = 0; unsigned int cubeVBO = 0; void renderCube() { // initialize (if necessary) if (cubeVAO == 0) { float vertices[] = { // back face -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, // top-left // front face -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left // left face -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right // right face 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left // bottom face -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right // top face -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left }; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &cubeVBO); // fill buffer glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // link vertex attributes glBindVertexArray(cubeVAO); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } glBindVertexArray(cubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0); } // RenderQuad() Renders a 1x1 quad in NDC unsigned int quadVAO = 0; unsigned int quadVBO; void RenderQuad() { if (quadVAO == 0) { GLfloat quadVertices[] = { // Positions // Texture Coords -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, }; // Setup plane VAO glGenVertexArrays(1, &quadVAO); glGenBuffers(1, &quadVBO); glBindVertexArray(quadVAO); glBindBuffer(GL_ARRAY_BUFFER, quadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); } glBindVertexArray(quadVAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); } unsigned int sphereVAO = 0; unsigned int indexCount; void renderSphere() { if (sphereVAO == 0) { glGenVertexArrays(1, &sphereVAO); unsigned int vbo, ebo; glGenBuffers(1, &vbo); glGenBuffers(1, &ebo); std::vector<glm::vec3> positions; std::vector<glm::vec2> uv; std::vector<glm::vec3> normals; std::vector<unsigned int> indices; const unsigned int X_SEGMENTS = 64; const unsigned int Y_SEGMENTS = 64; const float PI = 3.14159265359; for (unsigned int y = 0; y <= Y_SEGMENTS; ++y) { for (unsigned int x = 0; x <= X_SEGMENTS; ++x) { float xSegment = (float)x / (float)X_SEGMENTS; float ySegment = (float)y / (float)Y_SEGMENTS; float xPos = std::cos(xSegment * 2.0f * PI) * std::sin(ySegment * PI); float yPos = std::cos(ySegment * PI); float zPos = std::sin(xSegment * 2.0f * PI) * std::sin(ySegment * PI); positions.push_back(glm::vec3(xPos, yPos, zPos)); uv.push_back(glm::vec2(xSegment, ySegment)); normals.push_back(glm::vec3(xPos, yPos, zPos)); } } bool oddRow = false; for (unsigned int y = 0; y < Y_SEGMENTS; ++y) { if (!oddRow) // even rows: y == 0, y == 2; and so on { for (unsigned int x = 0; x <= X_SEGMENTS; ++x) { indices.push_back(y * (X_SEGMENTS + 1) + x); indices.push_back((y + 1) * (X_SEGMENTS + 1) + x); } } else { for (int x = X_SEGMENTS; x >= 0; --x) { indices.push_back((y + 1) * (X_SEGMENTS + 1) + x); indices.push_back(y * (X_SEGMENTS + 1) + x); } } oddRow = !oddRow; } indexCount = indices.size(); std::vector<float> data; for (unsigned int i = 0; i < positions.size(); ++i) { data.push_back(positions[i].x); data.push_back(positions[i].y); data.push_back(positions[i].z); if (uv.size() > 0) { data.push_back(uv[i].x); data.push_back(uv[i].y); } if (normals.size() > 0) { data.push_back(normals[i].x); data.push_back(normals[i].y); data.push_back(normals[i].z); } } glBindVertexArray(sphereVAO); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(float), &data[0], GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW); float stride = (3 + 2 + 3) * sizeof(float); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (void*)(3 * sizeof(float))); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, stride, (void*)(5 * sizeof(float))); } glBindVertexArray(sphereVAO); glDrawElements(GL_TRIANGLE_STRIP, indexCount, GL_UNSIGNED_INT, 0); }
本文同步分享在 博客「天叔」(JianShu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。