OpenGL学习教程之 材质

news2024/12/28 20:35:32

材质

 在真实世界里,每个物体会对光产生不同的反应。钢看起来比陶瓷花瓶更闪闪发光,一个木头箱子不会像钢箱子一样对光产生很强的反射。每个物体对镜面高光也有不同的反应。有些物体不会散射(Scatter)很多光却会反射(Reflect)很多光,结果看起来就有一个较小的高光点(Highlight),有些物体散射了很多,它们就会产生一个半径更大的高光。如果我们想要在OpenGL中模拟多种类型的物体,我们必须为每个物体分别定义材质(Material)属性。
 在前面的教程中,我们指定一个物体和一个光的颜色来定义物体的图像输出,并使之结合环境(Ambient)和镜面强度(Specular Intensity)元素。当描述物体的时候,我们可以使用3种光照元素:环境光照(Ambient Lighting)、漫反射光照(Diffuse Lighting)、镜面光照(Specular Lighting)定义一个材质颜色。通过为每个元素指定一个颜色,我们已经对物体的颜色输出有了精密的控制。现在把一个镜面高光元素添加到这三个颜色里,这是我们需要的所有材质属性:

#version 330 core
struct Material
{
	vec3 ambient;
	vec3 diffuse;
	vec3 specular;
	float shininess;
}
uniform Material material;

 在片段着色器中,我们创建一个结构体(Struct),来储存物体的材质属性。我们也可以把它们储存为独立的uniform值,但是作为一个结构体来储存可以更有条理。我们首先定义结构体的布局,然后简单声明一个uniform变量,以新创建的结构体作为它的类型。
 就像你所看到的,我们为每个冯氏光照模型的元素都定义一个颜色向量。ambient材质向量定义了在环境光照下这个物体反射的是什么颜色;通常这是和物体颜色相同的颜色。diffuse材质向量定义了在漫反射光照下物体的颜色。漫反射颜色被设置为(和环境光照一样)我们需要的物体颜色。specular材质向量设置的是物体受到的镜面光照的影响的颜色(或者可能是反射一个物体特定的镜面高光颜色)。最后,shininess影响镜面高光的散射/半径。
 这四个元素定义了一个物体的材质,通过它们我们能够模拟很多真实世界的材质。这里有一个列表devernay.free.fr展示了几种材质属性,这些材质属性模拟外部世界的真实材质。下面的图片展示了几种真实世界材质对我们的立方体的影响:
在这里插入图片描述在这里插入图片描述
 如你所见,正确地指定一个物体的材质属性,似乎就是改变我们物体的相关属性的比例。效果显然很引人注目,但是对于大多数真实效果,我们最终需要更加复杂的形状,而不单单是一个立方体。在后面的教程中,我们会讨论更复杂的形状。
 为一个物体赋予一款正确的材质是非常困难的,这需要大量实验和丰富的经验,所以由于错误的设置材质而毁了物体的画面质量是件经常发生的事。
 让我们试试在着色器中实现这样的一个材质系统。

设置材质

 我们在片段着色器中创建了一个uniform材质结构体,所以下面我们希望改变光照计算来顺应新的材质属性。由于所有材质元素都储存在结构体中,我们可以从uniform变量material取得它们:

void main()
{
	// 环境光
	vec3 ambient = lightColor * material.ambient;
	
	// 漫反射光
	vec3 norm = normalize(Normal);
	vec3 lightDir = normalize(lightPos - FragPos);
	flaot diff = max(dot(norm, lightDir), 0.0);
	vec3 diffuse = lightColor * (diff * material.diffuse);

	// 镜面高光
	vec3 viewDir = normalize(viewPos - FragPos);
	vec3 reflectDir = reflece(-lightDir, norm);
	float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
	vec3 specular = lightColor * (spec * material.specular);

	vec3 result = ambient + diffuse + specular;
	color = vec4(result, 1.0f);
}

 你可以看到,我们现在获得所有材质结构体的属性,无论在哪儿我们都需要它们,这次通过材质颜色的帮助,计算结果输出的颜色。物体的每个材质属性都乘以它们对应的光照元素。
 通过设置适当的uniform,我们可以在应用中设置物体的材质。当设置uniform时,GLSL中的一个结构体并不会被认为有什么特别之处。一个结构体值扮演uniform变量的封装体,所以如果我们希望填充这个结构体,我们就仍然必须设置结构体中的各个元素的uniform值,但是这次带有结构体名字作为前缀:

GLint matAmbientLoc = glGetUniformLocation(lightingShader.Program, "material.ambient");
GLint matDiffuseLoc = glGetUniformLocation(lightingShader.Program, "material.diffuse");
GLint matSpecularLoc = glGetUniformLocation(lightingShader.Program, "material.specular");
GLint matShineLoc = glGetUniformLocation(lightingShader.Program, "material.shininess");

glUniform3f(matAmbientLoc, 1.0f, 0.5f, 0.31f);
glUniform3f(matDiffuseLoc, 1.0f, 0.5f, 0.31f);
glUniform3f(matSpecularLoc, 0.5f, 0.5f, 0.5f);
glUniform1f(matShineLoc, 32.0f);

 l我们将ambient和diffuse元素设置成我们想要让物体所呈现的颜色,设置物体的specular元素为中等亮度颜色;我们不希望specular元素对这个指定物体产生过于强烈的影响。我们同样设置shininess为32。我们现在可以简单的在应用中影响物体的材质。
 运行程序,会得到下面这样的结果:
在这里插入图片描述)
 看起来很奇怪不是吗?

完整的代码

 顶点着色器:

#version 330 core
layout( location = 0 ) in vec3 position;
layout( location = 1 ) in vec3 normal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat3 normatr;

out vec3 FragPos;
out vec3 Normal;

void main()
{
	gl_Position = projection * view * model * vec4(position, 1.0f);
	FragPos = vec3(model * vec4(position, 1.0f));
	Normal  = normatr * normal;
}

 片段着色器:

#version 330 core
struct Material
{
	vec3 ambient;
	vec3 diffuse;
	vec3 specular;
	float shininess;
};
uniform Material material;

// 输入顶点位置和法向量
in vec3 FragPos;
in vec3 Normal;

// 输出顶点的颜色
out vec4 color;

// 顶点本身的颜色
uniform vec3 objectColor;
// 光源的颜色和位置
uniform vec3 lightColor;
uniform vec3 lightPos;
// 观察者的位置(世界坐标)
uniform vec3 viewPos;

void main()
{
    // 环境光
    vec3 ambient = lightColor * material.ambient;

    // 漫反射光
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = lightColor * (diff * material.diffuse);

    // 镜面高光
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = lightColor * (spec * material.specular);  

    vec3 result = ambient + diffuse + specular;
    color = vec4(result, 1.0f);
}

 主程序:

// 标准输出
#include <string>

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// 着色器类和摄像机类
#include "Shader.h"
#include "Camera.h"

// GLM 数学库
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// 图片数据读取库
#include <SOIL.h>

// 定义窗口大小
GLuint screenWidth = 800, screenHeight = 600;

// GLFW窗口需要注册的函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
// 处理摄像机位置移动的函数
void Do_Movement();

// 定义摄像机
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// 定义数组存储按键信息
bool keys[1024];
// 
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

glm::vec3 lightPos(1.2f, 1.0f, -0.5f);

// The MAIN function, from here we start our application and run our Game loop
int main()
{
    // 初始化GLFW 
    glfwInit();
    // 设置glfw使用的OpenGL版本
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    // 设置使用OpenGL的核心模式
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // 设置窗口大小可改变性 为不可变
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
    // GLFW会自动创建一个每像素4个子采样点的深度和样本缓冲。这也意味着所有缓冲的大小都增长了4倍。
    glfwWindowHint(GLFW_SAMPLES, 4);

    // 创建GLFW的窗口
    GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr);
    // 设置window窗口线程为主线程
    glfwMakeContextCurrent(window);

    // 注册窗口的事件
    glfwSetKeyCallback(window, key_callback);         // 按键检测
    glfwSetCursorPosCallback(window, mouse_callback); // 鼠标移动检测 
    glfwSetScrollCallback(window, scroll_callback);   // 滚轮滑动检测

    // 设置隐藏光标模式
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // 设置GLEW为更现代的模式
    glewExperimental = GL_TRUE;
    // 初始化GLEW
    glewInit();

    // 设置视口的位置和大小(位置是相对于窗口左下角的)
    glViewport(0, 0, screenWidth, screenHeight);

    // 开启深度测试功能
    glEnable(GL_DEPTH_TEST);

    // 根据顶点着色器和片段着色器位置创建着色器程序
    Shader lightingShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");
    Shader lampShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightVertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightFragmentShader.txt");

    // 顶点数据(位置+纹理坐标)
    GLfloat 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
    };

    // 设置VBO和VAO
    GLuint VBO, VAO;
    // 先申请VAO和VBO的显存
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // 绑定VAO
    glBindVertexArray(VAO);
    // 绑定VBO
    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); // 解绑VAO

    GLuint lightVAO;
    glGenVertexArrays(1, &lightVAO);
    glBindVertexArray(lightVAO);
    // 只需要绑定VBO不用再次设置VBO的数据,因为容器(物体)的VBO数据中已经包含了正确的立方体顶点数据
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 设置灯立方体的顶点属性指针(仅设置灯的顶点数据)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    glBindVertexArray(0);

    // 游戏循环
    while (!glfwWindowShouldClose(window))
    {
        // 计算帧相差时间
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // 窗口的事件检测
        glfwPollEvents();
        // 根据事件移动摄像机
        Do_Movement();

        // 设置清空屏幕颜色缓存所使用颜色
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        // 清空 屏幕颜色缓存、深度缓存
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 被光源照亮的普通物体的着色器
        lightingShader.Use();

        GLint objectColorLoc = glGetUniformLocation(lightingShader.Program, "objectColor");
        GLint lightColorLoc = glGetUniformLocation(lightingShader.Program, "lightColor");
        GLint lightPosLoc = glGetUniformLocation(lightingShader.Program, "lightPos");
        GLint viewPosLoc = glGetUniformLocation(lightingShader.Program, "viewPos");
        glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);// 我们所熟悉的珊瑚红
        glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 依旧把光源设置为白色
        glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z);
        glUniform3f(viewPosLoc, camera.Position.x, camera.Position.y, camera.Position.z);

        // 创建观察矩阵( 只要创建矩阵记得初始化= glm::mat4(1.0f) )
        glm::mat4 view = glm::mat4(1.0f);
        // 获取观察矩阵(根据摄像机的状态)
        view = camera.GetViewMatrix();
        // 创建投影矩阵
        glm::mat4 projection = glm::mat4(1.0f);
        // 计算投影矩阵(fov视野为摄像机的属性camera.Zoom)
        projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 1000.0f);
        // 计算顶点着色器中矩阵的位置值
        GLint modelLoc = glGetUniformLocation(lightingShader.Program, "model");
        GLint viewLoc = glGetUniformLocation(lightingShader.Program, "view");
        GLint projLoc = glGetUniformLocation(lightingShader.Program, "projection");

        // 将观察矩阵和投影矩阵传入对应的位置(记得转换)
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

        // 绑定VAO
        glBindVertexArray(VAO);   
        // 计算模型矩阵
        glm::mat4 model = glm::mat4(1.0f);
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        // 根据模型矩阵model计算正规矩阵normatr。inverse取逆和transpose转置 函数使用glm对应函数
        GLuint normalLoc = glGetUniformLocation(lightingShader.Program, "normatr");
        glm::mat3 normatr = (glm::mat3)(glm::transpose(glm::inverse(model)));
        // 向物体的顶点着色器传入正规矩阵normatr
        glUniformMatrix3fv(normalLoc, 1, GL_FALSE, glm::value_ptr(normatr));

        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

		// 设置材质
        GLint matAmbientLoc = glGetUniformLocation(lightingShader.Program, "material.ambient");
        GLint matDiffuseLoc = glGetUniformLocation(lightingShader.Program, "material.diffuse");
        GLint matSpecularLoc = glGetUniformLocation(lightingShader.Program, "material.specular");
        GLint matShineLoc = glGetUniformLocation(lightingShader.Program, "material.shininess");

        glUniform3f(matAmbientLoc, 1.0f, 0.5f, 0.31f);
        glUniform3f(matDiffuseLoc, 1.0f, 0.5f, 0.31f);
        glUniform3f(matSpecularLoc, 0.5f, 0.5f, 0.5f);
        glUniform1f(matShineLoc, 32.0f);

        // 使用光源着色器
        lampShader.Use();
        modelLoc = glGetUniformLocation(lampShader.Program, "model");
        viewLoc = glGetUniformLocation(lampShader.Program, "view");
        projLoc = glGetUniformLocation(lampShader.Program, "projection");

        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

        model = glm::mat4(1.0f);
        GLfloat lightX = sin(glfwGetTime());
        GLfloat lightZ = cos(glfwGetTime());
        GLfloat lightY = 1;
        lightPos = glm::vec3(lightX, lightY, lightZ);
        model = glm::translate(model, lightPos);
        model = glm::scale(model, glm::vec3(0.1f)); // Make it a smaller cube
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glBindVertexArray(lightVAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        // 交换前后缓冲区
        glfwSwapBuffers(window);
    }
    // 释放VAO和VBO的显存
    glDeleteVertexArrays(1, &VAO);
    glDeleteVertexArrays(1, &lightVAO);
    glDeleteBuffers(1, &VBO);
    // 释放glfw的内存
    glfwTerminate();
    return 0;
}

// 根据按键信息移动摄像机
void Do_Movement()
{
    // 如果某个键按下,就执行摄像机对应的方法(更新摄像机的位置)
    if (keys[GLFW_KEY_W])
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (keys[GLFW_KEY_S])
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (keys[GLFW_KEY_A])
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (keys[GLFW_KEY_D])
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// 按键回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    // 如果按下ESE则设置窗口应该关闭
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (key >= 0 && key < 1024)
    {
        // 如果按下某一个键,则设置其keys为true,如果松开则设置回false
        if (action == GLFW_PRESS)
            keys[key] = true;
        else if (action == GLFW_RELEASE)
            keys[key] = false;
    }
}

// 鼠标移动的回调函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    // 第一次进窗口鼠标坐标很大,所以第一次调用函数我们需要把它设置为一个正常的值
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    // 计算鼠标在竖直方向和水平方向的偏移(屏幕坐标系往右x大,但往下是y大,所以运算顺序不同)
    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos;  // Reversed since y-coordinates go from bottom to left

    // 更新鼠标的坐标
    lastX = xpos;
    lastY = ypos;

    // 调用摄像机的鼠标移动函数(根据位置偏移更新摄像机方式)
    camera.ProcessMouseMovement(xoffset, yoffset);
}

// 鼠标滚动的回调函数
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    // 调用摄像机的鼠标滚动函数(根据位置偏移更新摄像机fov视野大小)
    camera.ProcessMouseScroll(yoffset);
}

光的属性

 这个物体太亮了。物体过亮的原因是环境、漫反射和镜面三个颜色任何一个光源都会去全力反射。光源对环境、漫反射和镜面元素同时具有不同的强度。前面的教程,我们通过使用一个强度值改变环境和镜面强度的方式解决了这个问题。我们想做一个相同的系统,但是这次为每个光照元素指定了强度向量。如果我们想象lightColor是vec3(1.0),代码看起来像是这样:

vec3 ambient = vec3(1.0f) * material.ambient;
vec3 diffuse = vec3(1.0f) * (diff * material.diffuse);
vec3 specular = vec3(1.0f) * (spec * material.specular);

 所以物体的每个材质属性返回了每个光照元素的全强度。这些vec3(1.0)值可以各自独立的影响各个光源,这通常就是我们想要的。现在物体的ambient元素完全地展示了立方体的颜色,可是环境元素不应该对最终颜色有这么大的影响,所以我们要设置光的ambient亮度为一个小一点的值,从而限制环境色:

vec3 result = vec3(0.1f) * material.ambient;

 我们可以用同样的方式影响光源diffuse和specular的强度。这和我们前面教程所做的极为相似;你可以说我们已经创建了一些光的属性来各自独立地影响每个光照元素。我们希望为光的属性创建一些与材质结构体相似的东西:

struct Light
{
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
uniform Light light;

 一个光源的ambient、diffuse和specular光都有不同的亮度。环境光通常设置为一个比较低的亮度,因为我们不希望环境色太过显眼。光源的diffuse元素通常设置为我们希望光所具有的颜色;经常是一个明亮的白色。specular元素通常被设置为vec3(1.0f)类型的全强度发光。要记住的是我们同样把光的位置添加到结构体中。
 就像材质uniform一样,需要更新片段着色器:

vec3 ambient = light.ambient * material.ambient;
vec3 diffuse = light.diffuse * (diff * material.diffuse);
vec3 specular = light.specular * (spec * material.specular);

 然后我们要在应用里设置光的亮度:

GLint lightAmbientLoc = glGetUniformLocation(lightingShader.Program, "light.ambient");
GLint lightDiffuseLoc = glGetUniformLocation(lightingShader.Program, "light.diffuse");
GLint lightSpecularLoc = glGetUniformLocation(lightingShader.Program, "light.specular");

glUniform3f(lightAmbientLoc, 0.2f, 0.2f, 0.2f);
glUniform3f(lightDiffuseLoc, 0.5f, 0.5f, 0.5f);// 让我们把这个光调暗一点,这样会看起来更自然
glUniform3f(lightSpecularLoc, 1.0f, 1.0f, 1.0f);

 现在,我们调整了光是如何影响物体所有的材质的,我们得到一个更像前面教程的视觉输出。这次我们完全控制了物体光照和材质:
在这里插入图片描述)
 现在改变物体的外观相对简单了些。我们做点更有趣的事!

完整代码

 片段着色器:

#version 330 core
struct Material
{
	vec3 ambient;
	vec3 diffuse;
	vec3 specular;
	float shininess;
};
uniform Material material;

struct Light
{
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
uniform Light light;

// 输入顶点位置和法向量
in vec3 FragPos;
in vec3 Normal;

// 输出顶点的颜色
out vec4 color;

// 顶点本身的颜色
uniform vec3 objectColor;
// 光源的颜色和位置
uniform vec3 lightColor;
uniform vec3 lightPos;
// 观察者的位置(世界坐标)
uniform vec3 viewPos;

void main()
{
    // 环境光
    vec3 ambient = light.ambient * material.ambient;

    // 漫反射光
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * (diff * material.diffuse);

    // 镜面高光
    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;
    color = vec4(result, 1.0f);
}

不同的光源颜色

 目前为止,我们使用光源的颜色仅仅去改变物体各个元素的强度(通过选用从白到灰到黑范围内的颜色),并没有影响物体的真实颜色(只是强度)。由于现在能够非常容易地访问光的属性了,我们可以随着时间改变它们的颜色来获得一些有很意思的效果。由于所有东西都已经在片段着色器做好了,改变光的颜色很简单,我们可以立即创建出一些有趣的效果:
在这里插入图片描述)
 如你所见,不同光的颜色极大地影响了物体的颜色输出。由于光的颜色直接影响物体反射的颜色(你可能想起在颜色教程中有讨论过),它对视觉输出有显著的影响。
 利用sin和glfwGetTime改变光的环境和漫反射颜色,我们可以随着时间流逝简单的改变光源颜色:

glm::vec3 lightColor; lightColor.x = sin(glfwGetTime() * 2.0f);
lightColor.y = sin(glfwGetTime() * 0.7f);
lightColor.z = sin(glfwGetTime() * 1.3f);

glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f);
glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f);

glUniform3f(lightAmbientLoc, ambientColor.x, ambientColor.y, ambientColor.z);
glUniform3f(lightDiffuseLoc, diffuseColor.x, diffuseColor.y, diffuseColor.z);```

练习

 你能像我们教程一开始那样根据一些材质的属性来模拟一个真实世界的物体吗? 注意材质表中的环境光颜色与漫反射光的颜色可能不一样,因为他们并没有把光照强度考虑进去来模拟,你需要将光照颜色的强度改为vec(1.0f)来输出正确的结果:
在这里插入图片描述
 核心代码:

// 设置材质
        GLint matAmbientLoc = glGetUniformLocation(lightingShader.Program, "material.ambient");
        GLint matDiffuseLoc = glGetUniformLocation(lightingShader.Program, "material.diffuse");
        GLint matSpecularLoc = glGetUniformLocation(lightingShader.Program, "material.specular");
        GLint matShineLoc = glGetUniformLocation(lightingShader.Program, "material.shininess");

        glUniform3f(matAmbientLoc, 0.135f, 0.2225f, 0.1575f);
        glUniform3f(matDiffuseLoc, 0.54f, 0.89f, 0.63f);
        glUniform3f(matSpecularLoc, 0.316228f, 0.316228f, 0.316228f);
        glUniform1f(matShineLoc, 32.0f);

        // 设置光源属性
        GLint lightAmbientLoc = glGetUniformLocation(lightingShader.Program, "light.ambient");
        GLint lightDiffuseLoc = glGetUniformLocation(lightingShader.Program, "light.diffuse");
        GLint lightSpecularLoc = glGetUniformLocation(lightingShader.Program, "light.specular");

        glUniform3f(lightAmbientLoc, 1.0f, 1.0f, 1.0f);
        glUniform3f(lightDiffuseLoc, 1.0f, 1.0f, 1.0f);// 让我们把这个光调暗一点,这样会看起来更自然
        glUniform3f(lightSpecularLoc, 1.0f, 1.0f, 1.0f);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/490697.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Wireshark的安装及基本使用【计算机网络】

Wireshark的安装与基本使用【计算机网络】 前言推荐Wireshark的安装与基本使用一、下载二、安装三、使用技巧四、简单使用4.1 捕获4.2 简单介绍4.3 过滤 问题 最后 前言 2023-5-4 20:51:42 以下内容源自《【计算机网络】》 仅供学习交流使用 推荐 Wireshark的下载安装及简单…

macOS 13.4Beta 4(22F5059b)With OpenCore 0.9.2开发版 and winPE双引导分区原版镜像

镜像特点 完全由黑果魏叔官方制作&#xff0c;针对各种机型进行默认配置&#xff0c;让黑苹果安装不再困难。系统镜像设置为双引导分区&#xff0c;全面去除clover引导分区&#xff08;如有需要&#xff0c;可以自行直接替换opencore分区文件为clover引导文件&#xff09;备注…

【软考高项笔记】第3章 信息系统治理(针对甲方)3.1 IT治理

第3章 信息系统治理&#xff08;针对甲方&#xff09; 3.1 IT治理 不同于管理&#xff0c;角度更高3.1.1 IT治理基础 目标价值 与业务目标一致 有效利用信息与数据资源 风险管理 管理层次 最高管理层 &#xff08;定目标&#xff0c;战略&#xff09; 执行管理层 &#xff08…

MySQL中添加环境变量和初始化MySQL

添加环境变量 环境变量里面有很多选项&#xff0c;这里我们只用到Path 这个参数。为什么在初始化的开始要添加环境变量呢?在黑框(即CMD) 中输入一个可执行程序的名字&#xff0c;Windows会先在环境变量中的Path所指的路径中寻找一遍&#xff0c;如果找到了就直接执行&#xf…

Android 开发中常见的架构设计模式组件化、插件化和模块化

在 Android 中&#xff0c;组件化、插件化和模块化都是很常见的架构设计手段&#xff0c;用于提高应用开发的灵活性、扩展性和复用性。组件化、插件化和模块化可以混合使用&#xff0c;根据项目的需求和规模选择合适的方案。 组件化(Componentization) 组件化是指将一个大型的…

国产!全志科技T507-H工业核心板( 4核ARM Cortex-A5)规格书

1核心板简介 创龙科技 SOM-TLT507 是一款基于全志科技 T507-H 处理器设计的 4 核 ARM Cortex-A 53 全国产工业核心板,主频高达 1.416GHz 。核心板 CPU 、ROM 、RAM、电源、晶振等所有元器件均采用国产工业级方案,国产化率 100%。 核心板通过邮票孔连接方式引出 MIPI CSI 、…

设计模式——模板方法模式

是什么&#xff1f; 在我们的实际开发中尝尝会遇到这种问题&#xff1a;在设计一个系统时知道了算法所需要的关键步骤&#xff0c;而且确定了这些步骤的执行顺序&#xff0c;但是某些步骤的具体实现还不知道&#xff0c;或者说某些步骤的实现与具体的环境相关&#xff0c;例如每…

CypherRat使用

cypherrat3.5 安卓远控&#xff0c;很早之前在tg下的&#xff0c;现在可能有高版本的 使用感受 图形化界面&#xff08;相比于msf&#xff09;可用于演练新场景&#xff0c;实战的话只能用xx破解版或是瑟瑟apk让人忽视风险继续安装由于国内安卓&#xff0c;部分功能失效 生成…

Android 拍照以及相册中选择(适配高版本)————上传多张照片之压缩处理、在线预览可伸缩放大(二)

______ Introduction ______ 前言 上一篇文章刚给大家总结完&#xff0c;关于上传头像的功能。此文章所述 主要是关于上传头像的具体流程以及如何对照片做裁剪处理&#xff0c;回调给控件显示&#xff1b;当然重中之重适配了Android高版本&#xff0c;有兴趣的大家可以去看一下…

PyQt5桌面应用开发(6):文件对话框

本文目录 PyQt5桌面应用系列介绍QFileDialog的静态接口QFileDialog的对象接口 示例结论后记 PyQt5桌面应用系列 PyQt5桌面应用开发&#xff08;1&#xff09;&#xff1a;需求分析 PyQt5桌面应用开发&#xff08;2&#xff09;&#xff1a;事件循环 PyQt5桌面应用开发&#xff…

linux安装node_exporter

下载 Download | Prometheus 解压 tar xvzf node_exporter-1.5.0.darwin-amd64.tar.gz 解压后有三个文件&#xff0c;分别是LICENSE、node_exporter、NOTICE 将node_exporter移动到/etc下 mv mode_exporter /etc/systemd/system 创建node_exporter.service文件 touch node_exp…

基于matlab模拟雷达接收机测试信号

一、前言 本例展示了如何模拟单基地脉冲雷达的接收信号以估计目标范围。单基地雷达将发射器与接收器并置。发射器产生一个脉冲&#xff0c;该脉冲击中目标并产生接收器接收的回波。通过测量回波在时间上的位置&#xff0c;我们可以估计目标的范围。 本示例重点介绍脉冲雷达系统…

【线程安全】死锁问题及解决方案

1. 什么是死锁 比如上一次讲到 synchronized 的时候&#xff0c;一个线程&#xff0c;对同一个对象连续加锁两次&#xff0c;如果出现阻塞等待&#xff0c;代表这个锁是不可重入锁&#xff0c;这样的线程&#xff0c;也就称为死锁&#xff01; 一旦程序进入死锁了就会导致线程僵…

低代码开发重要工具:JVS列表页字段样式配置说明

列表页中&#xff0c;通常存在各种各样的样式控制&#xff0c;例如字段宽度需要可调、字段的颜色根据内容变化等&#xff0c;那么我们接下来介绍下字段的样式控制的内容以及对应的效果。 1、字段样式控制配置位置 进入列表页的 数据配置界面&#xff0c;每个字段可以有独立的配…

在外远程控制我的世界服务器 - MCSM面板【端口映射】

文章目录 概述1.MCSManager 安装2.内网穿透2.1 安装cpolar内网穿透 3. 访问公网地址4.固定公网地址4.1 保留一个二级子域名4.2 配置固定二级域名4.3 访问固定公网地址 5. 设置节点公网地址6. 固定节点公网地址6.1 保留一个固定tcp地址6.2 配置固定TCP地址 转载自远程穿透文章&a…

从零开始学习Linux运维,成为IT领域翘楚(七)

文章目录 &#x1f525;Linux下常用软件安装_JDK和Tomcat安装&#x1f525;Linux下常用软件安装_MySQL安装&#x1f525;Linux下常用软件安装_MySQL卸载 &#x1f525;Linux下常用软件安装_JDK和Tomcat安装 Jdk 安装 解压jdk安装包 tar -zxvf jdk-8u201-linux-x64.tar.gz -C/…

中国核心生态区类型及土地利用数据有哪些,如何进行获取

全国生态功能区划是在全国生态调查的基础上&#xff0c;分析区域生态特征、生态系统服务功能与生态敏感性空间分异规律&#xff0c;确定不同地域单元的主导生态功能&#xff0c;制定全国生态功能区划&#xff0c;对贯彻落实科学发展观&#xff0c;牢固树立生态文明观念&#xf…

顺序存储二叉树线索化二叉树

顺序存储二叉树&线索化二叉树 文章目录 顺序存储二叉树&线索化二叉树顺序存储二叉树介绍代码实现 线索化二叉树介绍代码实现 顺序存储二叉树 介绍 背景&#xff1a;从数据存储来看&#xff0c;数组存储方式和树的存储方式可以相互转换&#xff0c;即数组可以转换成树…

我把大厂起诉了,协商拿了2N,整理点经验心得给大家,关于离职时如何让自己利益最大化!...

离职时&#xff0c;如何让自己的利益最大化&#xff1f; 一位跟大厂仲裁&#xff0c;并通过协商拿到2n的网友分享了自己的经验心得&#xff0c;干货满满&#xff0c;下面是要点总结&#xff1a; 1.大部分裁员都是违法裁员&#xff0c;应该给2n&#xff0c;但公司不会承认&#…

排序算法 — 归并排序

文章目录 归并排序介绍从下往上的归并排序从上往下的归并排序 归并排序实现从上往下的归并排序从下往上的归并排序 归并排序的时间复杂度和稳定性归并排序时间复杂度归并排序稳定性 代码实现核心&总结 每日一道算法&#xff0c;提高脑力。第五天(时隔7天&#xff0c;终于回…