赞
踩
材质本质是一个数据集,主要功能就是给渲染器提供数据和光照算法。
如果我们想要在OpenGL中模拟多种类型的物体,我们必须针对每种表面定义不同的材质(Material)属性。
我们可以分别为三个光照分量定义一个材质颜色(Material Color):环境光照(Ambient Lighting)
、漫反射光照(Diffuse Lighting)
和镜面光照(Specular Lighting)
。通过为每个分量指定一个颜色,我们就能够对表面的颜色输出有细粒度的控制了。
再添加一个反光度(Shininess)分量,设置材质属性:
#version 330 core
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
//在片段着色器中,我们创建一个结构体(Struct)来储存物体的材质属性。
uniform Material material;
可以通过设置适当的uniform来设置应用中物体。
GLSL中一个结构体在设置uniform时并无任何区别,结构体只是充当uniform变量们的一个命名空间。所以如果想填充这个结构体的话,我们必须设置每个单独的uniform,但要以结构体名为前缀:
lightingShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
lightingShader.setFloat("material.shininess", 32.0f);
一个光源对它的ambient、diffuse和specular光照分量有着不同的强度。
为光照属性创建类似材质结构体:
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
和材质uniform一样,我们需要更新片段着色器:
vec3 ambient = light.ambient * material.ambient;
vec3 diffuse = light.diffuse * (diff * material.diffuse);
vec3 specular = light.specular * (spec * material.specular);
创建材质的顶点着色器和片元着色器
materials.vs
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; out vec3 FragPos; out vec3 Normal; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { FragPos = vec3(model * vec4(aPos, 1.0)); //在顶点着色器中,我们可以使用inverse和transpose函数自己生成这个法线矩阵,这两个函数对所有类型矩阵都有效 Normal = mat3(transpose(inverse(model))) * aNormal; gl_Position = projection * view * vec4(FragPos, 1.0); }
materials.fs
#version 330 core out vec4 FragColor; struct Material { vec3 ambient; vec3 diffuse; vec3 specular; float shininess; }; struct Light { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; }; in vec3 FragPos; in vec3 Normal; uniform vec3 viewPos; uniform Material material; uniform Light light; void main() { // ambient vec3 ambient = light.ambient * material.ambient; // diffuse vec3 norm = normalize(Normal); vec3 lightDir = normalize(light.position - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = light.diffuse * (diff * material.diffuse); // specular vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); vec3 specular = light.specular * (spec * material.specular); vec3 result = ambient + diffuse + specular; FragColor = vec4(result, 1.0); }
创建灯光的顶点着色器和片元着色器
light_mrt_cube.vs
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
light_mrt_cube.fs
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
完整源码
#include <glad/glad.h> #include <GLFW/glfw3.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> // https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/shader_m.h #include <shader_s.h> // https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/camera.h #include <camera.h> void InitGLFW(); bool CreateWindow(); bool InitGLAD(); // 窗口大小改变时调用 void framebuffer_size_callback(GLFWwindow *window, int width, int height); void mouse_callback(GLFWwindow *window, double xposIn, double yposIn); void scroll_callback(GLFWwindow *window, double xoffset, double yoffset); void processInput(GLFWwindow *window); // settings 窗口宽高 const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; // 相机 Camera camera(glm::vec3(0.0f, 0.0f, 6.0f)); float lastX = static_cast<float>(SCR_WIDTH) / 2.0; float lastY = static_cast<float>(SCR_HEIGHT) / 2.0; bool firstMouse = true; // timing float deltaTime = 0.0f; // time between current frame and last frame float lastFrame = 0.0f; // lighting glm::vec3 lightPos(0.5f, 0.5f, 1.0f); GLFWwindow *window; int main() { InitGLFW(); // 初始化GLFW bool isCreated = CreateWindow(); // 创建一个窗口对象 if (!isCreated) return -1; bool isGLAD = InitGLAD(); // 初始化GLAD,传入加载系统相关opengl函数指针的函数 if (!isGLAD) return -1; // 启用深度测试 glEnable(GL_DEPTH_TEST); // 构建和编译着色程序 Shader lightingShader("shader/P1_Basic/08_LightModel/materials.vs", "shader/P1_Basic/08_LightModel/materials.fs"); Shader lightCubeShader("shader/P1_Basic/08_LightModel/light_mrt_cube.vs", "shader/P1_Basic/08_LightModel/light_mrt_cube.fs"); // 设置顶点数据(和缓冲区)并配置顶点属性 // 1.设置立方体顶点输入 一共需要36个顶点 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 }; // 2.设置索引缓冲对象 unsigned int VBO, cubeVAO; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindVertexArray(cubeVAO); // position attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // normal attribute glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // second, configure the light's VAO (VBO stays the same; the vertices are the same for the light object which is also a 3D cube) unsigned int lightCubeVAO; glGenVertexArrays(1, &lightCubeVAO); glBindVertexArray(lightCubeVAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); // note that we update the lamp's position attribute's stride to reflect the updated buffer data glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 循环渲染 while (!glfwWindowShouldClose(window)) { // 计算帧间隔时间 float currentFrame = static_cast<float>(glfwGetTime()); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; // 输入 processInput(window); // 渲染 // 清除颜色缓冲 glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // 清除深度缓冲 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // be sure to activate shader when setting uniforms/drawing objects lightingShader.use(); lightingShader.setVec3("light.position", lightPos); lightingShader.setVec3("viewPos", camera.Position); // light properties glm::vec3 lightColor; lightColor.x = static_cast<float>(sin(glfwGetTime() * 2.0)); lightColor.y = static_cast<float>(sin(glfwGetTime() * 0.7)); lightColor.z = static_cast<float>(sin(glfwGetTime() * 1.3)); glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f); // decrease the influence glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // low influence lightingShader.setVec3("light.ambient", ambientColor); lightingShader.setVec3("light.diffuse", diffuseColor); lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f); // material properties lightingShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f); lightingShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f); lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f); // specular lighting doesn't have full effect on this object's material lightingShader.setFloat("material.shininess", 32.0f); // view/projection transformations glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); lightingShader.setMat4("projection", projection); lightingShader.setMat4("view", view); // world transformation glm::mat4 model = glm::mat4(1.2f); lightingShader.setMat4("model", model); // render the cube glBindVertexArray(cubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); // also draw the lamp object lightCubeShader.use(); lightCubeShader.setMat4("projection", projection); lightCubeShader.setMat4("view", view); model = glm::mat4(0.6f); model = glm::translate(model, lightPos); model = glm::scale(model, glm::vec3(0.5f)); // a smaller cube lightCubeShader.setMat4("model", model); glBindVertexArray(lightCubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); // 检查并调用事件,交换缓冲 glfwSwapBuffers(window); glfwPollEvents(); } // 可选:一旦资源超出其用途,就取消分配所有资源: glDeleteVertexArrays(1, &cubeVAO); glDeleteVertexArrays(1, &lightCubeVAO); glDeleteBuffers(1, &VBO); // 释放/删除之前的分配的所有资源 glfwTerminate(); return 0; } void InitGLFW() { // 初始化GLFW glfwInit(); // 配置GLFW 第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择; // 第二个参数接受一个整型,用来设置这个选项的值。 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); } bool CreateWindow() { // 创建一个窗口对象 window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; // 创建失败,终止程序 glfwTerminate(); return false; } // 将我们窗口的上下文设置为当前线程的主上下文 glfwMakeContextCurrent(window); // 设置窗口大小改变时的回调函数 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // 设置鼠标移动回调函数 glfwSetCursorPosCallback(window, mouse_callback); // 设置滚轮滚动回调函数 glfwSetScrollCallback(window, scroll_callback); // 隐藏并捕捉鼠标 // glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); return true; } bool InitGLAD() { // 初始化GLAD,传入加载系统相关opengl函数指针的函数 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; // 初始化失败,终止程序 return false; } return true; } // 窗口大小改变时调用 void framebuffer_size_callback(GLFWwindow *window, int width, int height) { // 设置窗口的维度 glViewport(0, 0, width, height); } // 输入 void processInput(GLFWwindow *window) { // 当用户按下esc键,我们设置window窗口的windowShouldClose属性为true // 关闭应用程序 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); } // 鼠标移动回调函数 void mouse_callback(GLFWwindow *window, double xposIn, double yposIn) { // 长按T键,鼠标才能控制相机 if (glfwGetKey(window, GLFW_KEY_1) != GLFW_PRESS) return; float xpos = static_cast<float>(xposIn); float ypos = static_cast<float>(yposIn); 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; camera.ProcessMouseMovement(xoffset, yoffset); } // 鼠标滚轮滚动回调函数 void scroll_callback(GLFWwindow *window, double xoffset, double yoffset) { camera.ProcessMouseScroll(static_cast<float>(yoffset)); }
运行效果:
纹理与贴图关系
事实上,纹理与贴图原理是一样的,贴图也叫纹理贴图,其实都是使用一张覆盖物体的图像,让我们能够逐片段索引其独立的颜色值。
只是在光照场景中,它通常叫做一个漫反射贴图(Diffuse Map)(3D艺术家通常都这么叫它),它是一个表现了物体所有的漫反射颜色的纹理图像。
在着色器中使用漫反射贴图的方法和纹理教程中是完全一样的。
但这次我们会将纹理储存为Material结构体中的一个sampler2D。我们将之前定义的vec3漫反射颜色向量替换为漫反射贴图:
struct Material {
sampler2D diffuse;
vec3 specular;
float shininess;
};
...
in vec2 TexCoords;
注意sampler2D是所谓的不透明类型(Opaque Type),也就是说我们不能将它实例化,只能通过uniform来定义它。如果我们使用除uniform以外的方法(比如函数的参数)实例化这个结构体,GLSL会抛出一些奇怪的错误。这同样也适用于任何封装了不透明类型的结构体。
然后,我们将在片段着色器中再次需要纹理坐标,所以我们声明一个额外的输入变量。接下来我们只需要从纹理中采样片段的漫反射颜色值即可:
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
将环境光的材质颜色设置为漫反射材质颜色同样的值:
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
脚本实现
创建顶点着色器和片元着色器
lighting_maps.vs
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; out vec3 FragPos; out vec3 Normal; out vec2 TexCoords; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { FragPos = vec3(model * vec4(aPos, 1.0)); Normal = mat3(transpose(inverse(model))) * aNormal; TexCoords = aTexCoords; gl_Position = projection * view * vec4(FragPos, 1.0); }
lighting_maps.fs
#version 330 core out vec4 FragColor; struct Material { sampler2D diffuse; vec3 specular; float shininess; }; struct Light { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; }; in vec3 FragPos; in vec3 Normal; in vec2 TexCoords; uniform vec3 viewPos; uniform Material material; uniform Light light; void main() { // ambient vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb; // diffuse vec3 norm = normalize(Normal); vec3 lightDir = normalize(light.position - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb; // specular vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); vec3 specular = light.specular * (spec * material.specular); vec3 result = ambient + diffuse + specular; FragColor = vec4(result, 1.0); }
完整源码
#include <glad/glad.h> #include <GLFW/glfw3.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> // https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/shader_m.h #include <shader_s.h> // https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/camera.h #include <camera.h> #include <iostream> void InitGLFW(); bool CreateWindow(); bool InitGLAD(); // 窗口大小改变时调用 void framebuffer_size_callback(GLFWwindow *window, int width, int height); void mouse_callback(GLFWwindow *window, double xposIn, double yposIn); void scroll_callback(GLFWwindow *window, double xoffset, double yoffset); void processInput(GLFWwindow *window); unsigned int loadTexture(const char *path); // settings 窗口宽高 const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; // 相机 Camera camera(glm::vec3(0.0f, 0.0f, 6.0f)); float lastX = static_cast<float>(SCR_WIDTH) / 2.0; float lastY = static_cast<float>(SCR_HEIGHT) / 2.0; bool firstMouse = true; // timing float deltaTime = 0.0f; // time between current frame and last frame float lastFrame = 0.0f; // lighting glm::vec3 lightPos(0.5f, 0.5f, 1.0f); GLFWwindow *window; int main() { InitGLFW(); // 初始化GLFW bool isCreated = CreateWindow(); // 创建一个窗口对象 if (!isCreated) return -1; bool isGLAD = InitGLAD(); // 初始化GLAD,传入加载系统相关opengl函数指针的函数 if (!isGLAD) return -1; // 启用深度测试 glEnable(GL_DEPTH_TEST); // 构建和编译着色程序 Shader lightingShader("shader/P1_Basic/08_LightModel/lighting_maps.vs", "shader/P1_Basic/08_LightModel/lighting_maps.fs"); Shader lightCubeShader("shader/P1_Basic/08_LightModel/light_mrt_cube.vs", "shader/P1_Basic/08_LightModel/light_mrt_cube.fs"); // 设置顶点数据(和缓冲区)并配置顶点属性 // 1.设置立方体顶点输入 一共需要36个顶点 float vertices[] = { // positions // normals // texture coords -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.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, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f}; // 2.设置索引缓冲对象 unsigned int VBO, cubeVAO; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindVertexArray(cubeVAO); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(3 * sizeof(float))); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(6 * sizeof(float))); glEnableVertexAttribArray(2); // second, configure the light's VAO (VBO stays the same; the vertices are the same for the light object which is also a 3D cube) unsigned int lightCubeVAO; glGenVertexArrays(1, &lightCubeVAO); glBindVertexArray(lightCubeVAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); // note that we update the lamp's position attribute's stride to reflect the updated buffer data glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0); glEnableVertexAttribArray(0); // 加载纹理 https://learnopengl-cn.github.io/img/02/04/container2.png unsigned int diffuseMap = loadTexture("image/04_Textures/container2.png"); // shader configuration // -------------------- lightingShader.use(); lightingShader.setInt("material.diffuse", 0); // 循环渲染 while (!glfwWindowShouldClose(window)) { // 计算帧间隔时间 float currentFrame = static_cast<float>(glfwGetTime()); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; // 输入 processInput(window); // 渲染 // 清除颜色缓冲 glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // 清除深度缓冲 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // be sure to activate shader when setting uniforms/drawing objects lightingShader.use(); lightingShader.setVec3("light.position", lightPos); lightingShader.setVec3("viewPos", camera.Position); // light properties lightingShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f); lightingShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f); // material properties lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f); lightingShader.setFloat("material.shininess", 64.0f); // view/projection transformations glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); lightingShader.setMat4("projection", projection); lightingShader.setMat4("view", view); // world transformation glm::mat4 model = glm::mat4(1.0f); lightingShader.setMat4("model", model); // bind diffuse map glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, diffuseMap); // render the cube glBindVertexArray(cubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); // also draw the lamp object lightCubeShader.use(); lightCubeShader.setMat4("projection", projection); lightCubeShader.setMat4("view", view); model = glm::mat4(1.0f); model = glm::translate(model, lightPos); model = glm::scale(model, glm::vec3(0.4f)); // a smaller cube lightCubeShader.setMat4("model", model); glBindVertexArray(lightCubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); // 检查并调用事件,交换缓冲 glfwSwapBuffers(window); glfwPollEvents(); } // 可选:一旦资源超出其用途,就取消分配所有资源: glDeleteVertexArrays(1, &cubeVAO); glDeleteVertexArrays(1, &lightCubeVAO); glDeleteBuffers(1, &VBO); // 释放/删除之前的分配的所有资源 glfwTerminate(); return 0; } void InitGLFW() { // 初始化GLFW glfwInit(); // 配置GLFW 第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择; // 第二个参数接受一个整型,用来设置这个选项的值。 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); } bool CreateWindow() { // 创建一个窗口对象 window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; // 创建失败,终止程序 glfwTerminate(); return false; } // 将我们窗口的上下文设置为当前线程的主上下文 glfwMakeContextCurrent(window); // 设置窗口大小改变时的回调函数 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // 设置鼠标移动回调函数 glfwSetCursorPosCallback(window, mouse_callback); // 设置滚轮滚动回调函数 glfwSetScrollCallback(window, scroll_callback); // 隐藏并捕捉鼠标 // glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); return true; } bool InitGLAD() { // 初始化GLAD,传入加载系统相关opengl函数指针的函数 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; // 初始化失败,终止程序 return false; } return true; } // 窗口大小改变时调用 void framebuffer_size_callback(GLFWwindow *window, int width, int height) { // 设置窗口的维度 glViewport(0, 0, width, height); } // 输入 void processInput(GLFWwindow *window) { // 当用户按下esc键,我们设置window窗口的windowShouldClose属性为true // 关闭应用程序 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); } // 鼠标移动回调函数 void mouse_callback(GLFWwindow *window, double xposIn, double yposIn) { // 长按T键,鼠标才能控制相机 if (glfwGetKey(window, GLFW_KEY_1) != GLFW_PRESS) return; float xpos = static_cast<float>(xposIn); float ypos = static_cast<float>(yposIn); 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; camera.ProcessMouseMovement(xoffset, yoffset); } // 鼠标滚轮滚动回调函数 void scroll_callback(GLFWwindow *window, double xoffset, double yoffset) { camera.ProcessMouseScroll(static_cast<float>(yoffset)); } 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; }
运行效果 :
我们同样可以使用一个专门用于镜面高光的纹理贴图。这也就意味着我们需要生成一个黑白的(如果你想得话也可以是彩色的)纹理,来定义物体每部分的镜面光强度。
镜面高光的强度可以通过图像每个像素的亮度
来获取。镜面光贴图上的每个像素都可以由一个颜色向量来表示,比如说黑色代表颜色向量vec3(0.0),灰色代表颜色向量vec3(0.5)。
在片段着色器中,我们接下来会取样对应的颜色值并将它乘以光源的镜面强度。一个像素越「白」,乘积就会越大,物体的镜面光分量就会越亮。
从实际角度来说,木头其实也有镜面高光,尽管它的反光度(Shininess)很小(更多的光被散射),影响也比较小,但是为了教学目的,我们可以假设木头不会对镜面光有任何反应。
采样镜面光贴图
创建片段着色器的材质属性,让其接受一个sampler2D而不是vec3作为镜面光分量:
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
然后,通过采样镜面光贴图,来获取片段所对应的镜面光强度:
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
FragColor = vec4(ambient + diffuse + specular, 1.0);
脚本实现
我们只创建片元着色器,顶点着色器和上面漫反射一样的,源码也类似
源码
... // 加载纹理 // https://learnopengl-cn.github.io/img/02/04/container2.png unsigned int diffuseMap = loadTexture("image/04_Textures/container2.png"); // https://learnopengl-cn.github.io/img/02/04/container2_specular.png unsigned int specularMap = loadTexture("image/04_Textures/container2_specular.png"); ... // 循环渲染 while (!glfwWindowShouldClose(window)){ ... // bind diffuse map glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, diffuseMap); // bind specular map glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, specularMap); ... }
lighting_specular_maps.fs
#version 330 core out vec4 FragColor; struct Material { sampler2D diffuse; sampler2D specular; float shininess; }; struct Light { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; }; in vec3 FragPos; in vec3 Normal; in vec2 TexCoords; uniform vec3 viewPos; uniform Material material; uniform Light light; void main() { // ambient vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb; // diffuse vec3 norm = normalize(Normal); vec3 lightDir = normalize(light.position - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb; // specular vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb; vec3 result = ambient + diffuse + specular; FragColor = vec4(result, 1.0); }
运行效果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。