text: #include <glad/glad.h>
  #include <GLFW/glfw3.h>
  
  #include <glm/glm.hpp>
  #include <glm/gtc/matrix_transform.hpp>
  #include <glm/gtc/type_ptr.hpp>
  
  #include "MyShader.h"
  #include "stb_image/stb_image.h"
  
  #include <iostream>
  
  void framebuffer_size_callback(GLFWwindow* window, int width, int height);
  void processInput(GLFWwindow *window);
  
  // settings
  const unsigned int SCR_WIDTH = 800;
  const unsigned int SCR_HEIGHT = 600;
  
  //Exercise 4:
  float mixValue = 0.2f;
  
  
  int main()
  {
      // 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);
  
  #ifdef __APPLE__
      glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
  #endif
  
      // glfw window creation
      // --------------------
      GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
      if (window == NULL)
      {
          std::cout << "Failed to create GLFW window" << std::endl;
          glfwTerminate();
          return -1;
      }
      glfwMakeContextCurrent(window);
      glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
  
      // glad: load all OpenGL function pointers
      // ---------------------------------------
      if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
      {
          std::cout << "Failed to initialize GLAD" << std::endl;
          return -1;
      }
  
  
      // build and compile our shader program
      // ------------------------------------
      // in CLion, you should Run -> Edit Configure -> Working directory to choose the current workspace.
      Shader ourShader("shader/vS.vert", "shader/fS.frag"); // you can name your shader files however you like
  
      // set up vertex data (and buffer(s)) and configure vertex attributes
      // ------------------------------------------------------------------
      float 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
      };
  
  //    // Exercise 2:
  //    float vertices[] = {
  //            // positions        // colors          // texture coords
  //            0.5f,  0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  2.0f, 2.0f,   // top right
  //            0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  2.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, 2.0f    // top left
  //    };
  
      // Exercise 3: display only the center pixels of the texture image on the rectangle
      // so that individual pixels are getting visible by changing the texture coordinates.
  //    float vertices[] = {
  //            // positions        // colors          // texture coords
  //            0.5f,  0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  0.55f, 0.55f,   // top right
  //            0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  0.55f, 0.45f,   // bottom right
  //           -0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f,  0.45f, 0.45f,   // bottom left
  //           -0.5f,  0.5f, 0.0f,  1.0f, 1.0f, 0.0f,  0.45f, 0.55f    // top left
  //    };
  
      unsigned int indices[] = {
              0, 1, 3,
              1, 2, 3
      };
  
      unsigned int VBO, VAO, EBO;
      glGenVertexArrays(1, &VAO);
      glGenBuffers(1, &VBO);
      glGenBuffers(1, &EBO);
      // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
      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(float), (void *) 0);
      glEnableVertexAttribArray(0);
      // color attribute
      glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) (3 * sizeof(float)));
      glEnableVertexAttribArray(1);
      // texture attribute
      glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) (6 * sizeof(float)));
      glEnableVertexAttribArray(2);
      //--------------------------------------------------------------
  
      //-----------------------Texture--------------------------------
      unsigned int texture1, texture2;
  
      // refer by ID
      // glGenTextures():
      // first is input generate the texture number
      // second is the array to store the texture ID(now is the single one because is just 1 input)
      glGenTextures(1, &texture1);
  
      // Bind the texture
      glBindTexture(GL_TEXTURE_2D, texture1);
  
      // set the texture wrapping/filtering options (on the currently bound texture object)
      // first: target of nD textures
      // second: options we want to set and for which texture axis
      // third: texture wrapping mode
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  
      // Exercise 2: display 4 smiley faces on a single container image clamped at its edge
  //    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
  //    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
  
  
      // 为层级过滤提供放大/缩小的选项，使用 层级or非层级 线性过滤/邻近过滤？
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//放大用层级过滤不会产生效果，层级过滤只对缩小有效
  
  //    // Exercise 3:
  //    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  //    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  
      //load the picture as texture
      int width, height, nrChannels;
  
      // make the y-axis become correct rather than flip down
      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("texture/container.jpg", &width, &height, &nrChannels, 0);
      if (data)
      {
          // first: texture target:generate a texture on the currently bound texture object at the same target
          // second: mipmap level for which we want to create a texture if you want to set level manually, the base level is 0（层级渐远）
          // third: what kind of format we want to store the texture
          // 4 & 5th: the final texture width and height(internal_format)
          // 6th: border: always 0（历史原因）
          // 7 & 8th: format type and data type of the source image
          //    We loaded the image with RGB values and stored them as chars (bytes) so we'll pass in the corresponding values
          // 9th: pointer to the actual image data in memory
          glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
          glGenerateMipmap(
                  GL_TEXTURE_2D);//automatically generate all the required mipmaps for the currently bound texture.
      }
      else
          std::cout << "Fail to load texture" << std::endl;
      // free the memory
      stbi_image_free(data);
  
      // texture2
      glGenTextures(1, &texture2);
      glBindTexture(GL_TEXTURE_2D, texture2);
      // set the texture wrapping parameters
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
                      GL_REPEAT);    // set texture wrapping to GL_REPEAT (default 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);
  
      // Exercise 3:
  //    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  //    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  
      // load image, create texture and generate mipmaps
      data = stbi_load("texture/awesomeface.png", &width, &height, &nrChannels, 0);
      if (data)
      {
          // note that the awesomeface.png has transparency and thus an alpha channel, so make sure to tell OpenGL the data type is of GL_RGBA
          glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
          glGenerateMipmap(GL_TEXTURE_2D);
      }
      else
      {
          std::cout << "Failed to load texture" << std::endl;
      }
      stbi_image_free(data);
  
      // tell opengl for each sampler to which texture unit it belongs to (only has to be done once)
      // -------------------------------------------------------------------------------------------
      ourShader.use(); // don't forget to activate/use the shader before setting uniforms!
      // either set it manually like so:
      glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);
      // or set it via the texture class
      ourShader.setInt("texture2", 1);
  
      //-------------------------------------------------------------------------------------------------
  
  
      // render loop
      // -----------
      while (!glfwWindowShouldClose(window))
      {
          // input
          // -----
          processInput(window);
  
          // render
          // ------
          glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
          glClear(GL_COLOR_BUFFER_BIT);
  
          // bind Texture with texture units
          glActiveTexture(GL_TEXTURE0);
          glBindTexture(GL_TEXTURE_2D, texture1);
          glActiveTexture(GL_TEXTURE1);
          glBindTexture(GL_TEXTURE_2D, texture2);
  
          //Exercise 4: set the texture mix value in the shader
          ourShader.setFloat("mixValue", mixValue);
  
          // render the container
          ourShader.use();
          glBindVertexArray(VAO);
          glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
  
          // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
          // -------------------------------------------------------------------------------
          glfwSwapBuffers(window);
          glfwPollEvents();
      }
  
      // optional: de-allocate all resources once they've outlived their purpose:
      // ------------------------------------------------------------------------
      glDeleteVertexArrays(1, &VAO);
      glDeleteBuffers(1, &VBO);
      glDeleteBuffers(1, &EBO);
  
      // glfw: terminate, clearing all previously allocated GLFW resources.
      // ------------------------------------------------------------------
      glfwTerminate();
      return 0;
  }
  
  // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
  // ---------------------------------------------------------------------------------------------------------
  void processInput(GLFWwindow *window)
  {
      if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
          glfwSetWindowShouldClose(window, true);
  
      //Exercise 4:
      if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
      {
          mixValue += 0.001f; // change this value accordingly (might be too slow or too fast based on system hardware)
          if (mixValue >= 1.0f)
              mixValue = 1.0f;
      }
      if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
      {
          mixValue -= 0.001f; // change this value accordingly (might be too slow or too fast based on system hardware)
          if (mixValue <= 0.0f)
              mixValue = 0.0f;
      }
  }
  
  // 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);
  }