跟着LearnOpenGL学习11--材质

news2025/1/22 16:11:13

文章目录

  • 一、材质
  • 二、设置材质
  • 三、光的属性
  • 四、不同的光源颜色

一、材质

在现实世界里,每个物体会对光产生不同的反应。

比如,钢制物体看起来通常会比陶土花瓶更闪闪发光,一个木头箱子也不会与一个钢制箱子反射同样程度的光。

有些物体反射光的时候不会有太多的散射(Scatter),因而产生较小的高光点,而有些物体则会散射很多,产生一个有着更大半径的高光点。

如果我们想要在OpenGL中模拟多种类型的物体,我们必须针对每种表面定义不同的材质(Material)属性。

在跟着LearnOpenGL学习10–基础光照这一篇中,我们定义了一个物体和光的颜色,并结合环境光与镜面强度分量,来决定物体的视觉输出。

当描述一个表面时,我们可以分别为三个光照分量定义一个材质颜色(Material Color):

  • 环境光照(Ambient Lighting)
  • 漫反射光照(Diffuse Lighting)
  • 镜面光照(Specular Lighting)

通过为每个分量指定一个颜色,我们就能够对表面的颜色输出有细粒度的控制了。

现在,我们再添加一个反光度(Shininess)分量,结合上述的三个颜色,我们就有了全部所需的材质属性了:

片段着色器

struct Material { //材质描述结构体
    vec3 ambient;       //环境光照
    vec3 diffuse;       //漫反射光照
    vec3 specular;      //镜面反射光照
    float shininess;    //反光度
};

uniform Material material;  //材质

在片段着色器中,我们创建一个结构体(Struct)来储存物体的材质属性。
我们也可以把它们储存为独立的uniform值,但是作为一个结构体来储存会更有条理一些。
我们首先定义结构体的布局(Layout),然后简单地以刚创建的结构体作为类型声明一个uniform变量。

如你所见,我们为冯氏光照模型的每个分量都定义一个颜色向量:

  • ambient材质向量:定义了在环境光照下这个表面反射的是什么颜色,通常与表面的颜色相同。
  • diffuse材质向量:定义了在漫反射光照下表面的颜色。漫反射颜色(和环境光照一样)也被设置为我们期望的物体颜色。
  • specular材质向量:设置的是表面上镜面高光的颜色(或者甚至可能反映一个特定表面的颜色)。
  • shininess材质向量:影响镜面高光的散射/半径。

有这4个元素定义一个物体的材质,我们能够模拟很多现实世界中的材质。如下表格展示了一系列材质属性,它们模拟了现实世界中的真实材质。下图展示了几组现实世界的材质参数值对我们的立方体的影响:

材质表格

名字环境光照漫反射光照镜面反射光照反射强度
emerald(翡翠)0.0215, 0.1745, 0.02150.07568, 0.61424, 0.075680.633, 0.727811, 0.6330.6
jade(玉)0.135, 0.2225, 0.15750.54, 0.89, 0.630.316228, 0.316228, 0.3162280.1
obsidian(黑曜石)0.05375, 0.05, 0.066250.18275, 0.17, 0.225250.332741, 0.328634, 0.3464350.3
pearl(珍珠)0.25, 0.20725, 0.207251, 0.829, 0.8290.296648, 0.296648, 0.2966480.088
ruby(红宝石)0.1745, 0.01175, 0.011750.61424, 0.04136, 0.041360.727811, 0.626959, 0.6269590.6
turquoise()绿松石0.1, 0.18725, 0.17450.396, 0.74151, 0.691020.297254, 0.30829, 0.3066780.1
brass(黄铜)0.329412, 0.223529, 0.0274510.780392, 0.568627, 0.1137250.992157, 0.941176, 0.8078430.21794872
bronze(青铜)0.2125, 0.1275, 0.0540.714, 0.4284, 0.181440.393548, 0.271906, 0.1667210.2
chrome(铬)0.25, 0.25, 0.250.4, 0.4, 0.40.774597, 0.774597, 0.7745970.6
copper(铜)0.19125, 0.0735, 0.02250.7038, 0.27048, 0.08280.256777, 0.137622, 0.0860140.1
gold(黄金)0.24725, 0.1995, 0.07450.75164, 0.60648,0.226480.628281, 0.555802, 0.3660650.4
silver(银)0.19225, 0.19225, 0.192250.50754, 0.50754, 0.507540.508273, 0.508273, 0.5082730.4
black plastic(黑色塑料)0.0, 0.0, 0.00.01, 0.01, 0.010.50, 0.50, 0.500.25
cyan plastic(青色塑料)0.0, 0.1, 0.060.0, 0.50980392, 0.509803920.50196078, 0.50196078, 0.501960780.25
green plastic(绿色塑料)0.0, 0.0, 0.00.1, 0.35, 0.10.45, 0.55, 0.450.25
red plastic(红色塑料)0.0, 0.0, 0.00.5, 0.0, 0.00.7, 0.6, 0.60.25
white plastic(白色塑料)0.0, 0.0, 0.00.55, 0.55, 0.550.70, 0.70, 0.700.25
yellow plastic(黄色塑料)0.0, 0.0, 0.00.5, 0.5, 0.00.60, 0.60, 0.500.25
black rubber(黑色橡胶)0.02, 0.02, 0.020.01, 0.01, 0.010.4, 0.4, 0.40.78125
cyan rubber(青色橡胶)0.0, 0.05, 0.050.4, 0.5, 0.50.04, 0.7, 0.70.78125
green rubber(绿色橡胶)0.0, 0.05, 0.00.4, 0.5, 0.40.04, 0.7, 0.040.78125
red rubber(红色橡胶)0.05, 0.0, 0.00.5, 0.4, 0.40.7, 0.04, 0.040.78125
white rubber(白色橡胶)0.05, 0.05, 0.050.5, 0.5, 0.50.7, 0.7, 0.70.78125
yellow rubber(黄色橡胶)0.05, 0.05, 0.00.5, 0.5, 0.40.7, 0.7, 0.040.78125

在这里插入图片描述
可以看到,通过正确地指定一个物体的材质属性,我们对这个物体的感知也就不同了。效果非常明显,但是要想获得更真实的效果,我们需要以更复杂的形状替换这个立方体。

搞清楚一个物体正确的材质设定是个困难的工程,这主要需要实验和丰富的经验。用了不合适的材质而毁了物体的视觉质量是件经常发生的事。

让我们试着在着色器中实现这样的一个材质系统。


二、设置材质

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

片段着色器

#version 330 core

in vec3 FragPos;
in vec3 Normal;

out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos;   //观察者坐标

struct Material { //材质描述结构体
    vec3 ambient;       //环境光照
    vec3 diffuse;       //漫反射光照
    vec3 specular;      //镜面反射光照
    float shininess;    //反光度
};

uniform Material material;  //材质

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

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

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

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

可以看到,我们现在在需要的地方访问了材质结构体中的所有属性,并且这次是根据材质的颜色来计算最终的输出颜色的。物体的每个材质属性都乘上了它们各自对应的光照分量。

我们现在可以通过设置适当的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);

我们将环境光和漫反射分量设置成我们想要让物体所拥有的颜色,而将镜面分量设置为一个中等亮度的颜色,我们不希望镜面分量过于强烈。我们仍将反光度保持为32。

现在我们能够轻松地在应用中影响物体的材质了。运行程序,你会得到像这样的结果:
在这里插入图片描述


全部代码

main.cpp

#include "mainwindow.h"
#include <QApplication>

//在包含GLFW的头文件之前包含了GLAD的头文件;
//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h);
//所以需要在其它依赖于OpenGL的头文件之前包含GLAD;
#include <glad/glad.h>
#include <GLFW/glfw3.h>

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

#include <iostream>
#include <QDebug>

#include "shader.h"
#include "stb_image.h"
#include "camera.h"

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 = 1920;
const unsigned int SCR_HEIGHT = 1080;

//Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX =  SCR_WIDTH / 2.0;
float lastY =  SCR_HEIGHT / 2.0;
bool firstMouse = true;

float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间

//Light
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

using namespace std;

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //MainWindow w;
    //w.show();

    //初始化GLFW
    //--------------------
    glfwInit();

    //配置GLFW
    //--------------------
    //告诉GLFW使用的OpenGL本是3.3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    //告诉GLFW使用的是核心模式(Core-profile)
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    //创建一个新的OpenGL环境和窗口
    //-----------------------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();    //glfw销毁窗口和OpenGL环境,并释放资源
        return -1;
    }

    //设置参数window中的窗口所关联的OpenGL环境为当前环境
    //-----------------------------------
    glfwMakeContextCurrent(window);

    //设置窗口尺寸改变大小时的回调函数(窗口尺寸发送改变时会自动调用)
    //-----------------------------------
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    //设置鼠标事件的回调函数(鼠标移动时会自动调用)
    //-----------------------------------
    glfwSetCursorPosCallback(window, mouse_callback);

    //设置鼠标滚轮事件的回调函数(鼠标滚轮移动时会自动调用)
    //-----------------------------------
    glfwSetScrollCallback(window, scroll_callback);

    //告诉GLFW捕捉鼠标
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    //glad加载系统相关的OpenGL函数指针
    //---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

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

    Shader objectShader("C:/Qt_Pro/OpenGL_GLFW/shader/shader.vs","C:/Qt_Pro/OpenGL_GLFW/shader/shader.fs");
    Shader lightShader("C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.vs","C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.fs");


    //顶点数据
    //---------------------------------------------------------------------
    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
    };

    //物体
    //----------------------------------------------------------------
    unsigned int VBO, objectVAO;
    glGenVertexArrays(1, &objectVAO);     //创建顶点数组对象
    glGenBuffers(1, &VBO);          //创建顶点缓冲对象

    glBindBuffer(GL_ARRAY_BUFFER, VBO);     //将VBO与GL_ARRAY_BUFFER缓冲区绑定
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);  //将顶点数据复制到GL_ARRAY_BUFFER缓冲区,之后可通过VBO进行操作

    glBindVertexArray(objectVAO);         //绑定VAO

    //设定顶点属性指针
    //位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    //法向量属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    //光源(VBO用上面的)
    //----------------------------------------------------------------
    unsigned int lightVAO;
    glGenVertexArrays(1, &lightVAO);     //创建顶点数组对象
    glBindVertexArray(lightVAO);         //绑定VAO

    glBindBuffer(GL_ARRAY_BUFFER, VBO);     //将VBO与GL_ARRAY_BUFFER缓冲区绑定

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);


    //渲染循环
    //我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口;
    //我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入;
    //因此,我们需要在程序中添加一个while循环,它能在我们让GLFW退出前一直保持运行;
    //------------------------------------------------------------------------------
    while (!glfwWindowShouldClose(window))  //如果用户准备关闭参数window所指定的窗口,那么此接口将会返回GL_TRUE,否则将会返回GL_FALSE
    {
        //更新时间差
        float currentFrame = static_cast<float>(glfwGetTime());
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        //用户输入
        //------------------------------------------------------------------------------
        processInput(window);   //检测是否有输入

        //渲染指令
        //------------------------------------------------------------------------------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        //被投光物体
        //============================================================
        //激活着色器程序对象
        objectShader.use();
        objectShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
        objectShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
        objectShader.setVec3("lightPos", lightPos);
        objectShader.setVec3("viewPos", camera.Position);

        objectShader.setVec3("material.ambient",  1.0f, 0.5f, 0.31f);
        objectShader.setVec3("material.diffuse",  1.0f, 0.5f, 0.31f);
        objectShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
        objectShader.setFloat("material.shininess", 32.0f);

        //创建变换矩阵
        glm::mat4 model = glm::mat4(1.0f);
        objectShader.setMat4("model", model);
        glm::mat4 view = camera.GetViewMatrix();
        objectShader.setMat4("view", view);
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        objectShader.setMat4("projection", projection);

        //绘制三角形
        glBindVertexArray(objectVAO);             //绑定VAO
        glDrawArrays(GL_TRIANGLES, 0, 36);
        //============================================================

        //光源
        //============================================================
        //激活着色器程序对象
        lightShader.use();
        lightShader.setMat4("view", view);
        lightShader.setMat4("projection", projection);
        model = glm::mat4(1.0f);
        model = glm::translate(model, lightPos);
        model = glm::scale(model, glm::vec3(0.2f));
        lightShader.setMat4("model", model);

        //绘制三角形
        glBindVertexArray(lightVAO);             //绑定VAO
        glDrawArrays(GL_TRIANGLES, 0, 36);
        //============================================================

        //告诉GLFW检查所有等待处理的事件和消息,包括操作系统和窗口系统中应当处理的消息。如果有消息正在等待,它会先处理这些消息再返回;否则该函数会立即返回
        //---------------------------------------------------------------------------------------------------------------------------------
        glfwPollEvents();

        //请求窗口系统将参数window关联的后缓存画面呈现给用户(双缓冲绘图)
        //------------------------------------------------------------------------------
        glfwSwapBuffers(window);
    }

    //释放资源
    glDeleteVertexArrays(1, &objectVAO);
    glDeleteVertexArrays(1, &lightVAO);
    glDeleteBuffers(1, &VBO);
    //glDeleteProgram(objectShader);
    //glDeleteProgram(lightShader);

    //glfw销毁窗口和OpenGL环境,并释放资源(之后必须再次调用glfwInit()才能使用大多数GLFW函数)
    //------------------------------------------------------------------
    glfwTerminate();


    return a.exec();
}

//检测是否有输入
//---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)   //ESC键,退出
        glfwSetWindowShouldClose(window, true);

    float cameraSpeed = static_cast<float>(2.5 * deltaTime);
    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);
}

//给glfw窗口注册的尺寸改变回调函数
//---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    //确保视口匹配新的窗口尺寸,请注意:宽度和高度将比视网膜显示器上指定的大得多
    glViewport(0, 0, width, height);
}
// 鼠标移动时的回调函数
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
    float xpos = static_cast<float>(xposIn);
    float ypos = static_cast<float>(yposIn);

    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = lastX - xpos;
    float yoffset = ypos - lastY;   //翻转,因为Y轴是从下到上越来越大

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

//鼠标滚轮的回调函数
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(static_cast<float>(yoffset));
}

三、光的属性

不过看起来真的不太对劲?这个物体太亮了。

物体过亮的原因是环境光、漫反射和镜面光这三个颜色对任何一个光源都全力反射。

光源对环境光、漫反射和镜面光分量也分别具有不同的强度。前面的章节中,我们通过使用一个强度值改变环境光和镜面光强度的方式解决了这个问题。我们想做类似的事情,但是这次是要为每个光照分量分别指定一个强度向量。

如果我们假设lightColor是vec3(1.0),代码会看起来像这样:

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

所以物体的每个材质属性对每一个光照分量都返回了最大的强度。

对单个光源来说,这些vec3(1.0)值同样可以对每种光源分别改变,而这通常就是我们想要的。

现在,物体的环境光分量完全地影响了立方体的颜色,可是环境光分量实际上不应该对最终的颜色有这么大的影响,所以我们会将光源的环境光强度设置为一个小一点的值,从而限制环境光颜色:

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

我们可以用同样的方式影响光源的漫反射和镜面光强度。这和我们在上一节中所做的极为相似,你可以认为我们已经创建了一些光照属性来影响各个光照分量。我们希望为光照属性创建类似材质结构体的东西:

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;
  • 一个光源对它的ambientdiffusespecular光照分量有着不同的强度。
  • 环境光照通常被设置为一个比较低的强度,因为我们不希望环境光颜色太过主导。
  • 光源的漫反射分量通常被设置为我们希望光所具有的那个颜色,通常是一个比较明亮的白色。
  • 镜面光分量通常会保持为vec3(1.0),以最大强度发光。
  • 注意:我们也将光源的位置向量加入了结构体。

和材质uniform一样,我们需要更新片段着色器:

片段着色器

#version 330 core

in vec3 FragPos;
in vec3 Normal;

out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos;   //观察者坐标

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;

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

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

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

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

我们接下来在应用中设置光照强度:

objectShader.setVec3("light.ambient",  0.2f, 0.2f, 0.2f);
objectShader.setVec3("light.diffuse",  0.5f, 0.5f, 0.5f); // 将光照调暗了一些以搭配场景
objectShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);

现在我们已经调整了光照对物体材质的影响,我们得到了一个与上一节很相似的视觉效果。但这次我们有了对光照和物体材质的完全掌控:

在这里插入图片描述
在这里插入图片描述


四、不同的光源颜色

到目前为止,我们都只对光源设置了从白到灰到黑范围内的颜色,这样只会改变物体各个分量的强度,而不是它的真正颜色。

由于现在能够非常容易地访问光照的属性了,我们可以随着时间改变它们的颜色,从而获得一些非常有意思的效果。

由于所有的东西都在片段着色器中配置好了,修改光源的颜色非常简单,并立刻创造一些很有趣的效果:

//光照强度
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
objectShader.setVec3("light.ambient", ambientColor);
objectShader.setVec3("light.diffuse", diffuseColor);
objectShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);

你可以看到,不同的光照颜色能够极大地影响物体的最终颜色输出。由于光照颜色能够直接影响物体能够反射的颜色,这对视觉输出有着显著的影响。

在这里插入图片描述

全部代码

#include "mainwindow.h"
#include <QApplication>

//在包含GLFW的头文件之前包含了GLAD的头文件;
//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h);
//所以需要在其它依赖于OpenGL的头文件之前包含GLAD;
#include <glad/glad.h>
#include <GLFW/glfw3.h>

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

#include <iostream>
#include <QDebug>

#include "shader.h"
#include "stb_image.h"
#include "camera.h"

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 = 1920;
const unsigned int SCR_HEIGHT = 1080;

//Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX =  SCR_WIDTH / 2.0;
float lastY =  SCR_HEIGHT / 2.0;
bool firstMouse = true;

float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间

//Light
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

using namespace std;

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //MainWindow w;
    //w.show();

    //初始化GLFW
    //--------------------
    glfwInit();

    //配置GLFW
    //--------------------
    //告诉GLFW使用的OpenGL本是3.3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    //告诉GLFW使用的是核心模式(Core-profile)
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    //创建一个新的OpenGL环境和窗口
    //-----------------------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();    //glfw销毁窗口和OpenGL环境,并释放资源
        return -1;
    }

    //设置参数window中的窗口所关联的OpenGL环境为当前环境
    //-----------------------------------
    glfwMakeContextCurrent(window);

    //设置窗口尺寸改变大小时的回调函数(窗口尺寸发送改变时会自动调用)
    //-----------------------------------
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    //设置鼠标事件的回调函数(鼠标移动时会自动调用)
    //-----------------------------------
    glfwSetCursorPosCallback(window, mouse_callback);

    //设置鼠标滚轮事件的回调函数(鼠标滚轮移动时会自动调用)
    //-----------------------------------
    glfwSetScrollCallback(window, scroll_callback);

    //告诉GLFW捕捉鼠标
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    //glad加载系统相关的OpenGL函数指针
    //---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

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

    Shader objectShader("C:/Qt_Pro/OpenGL_GLFW/shader/shader.vs","C:/Qt_Pro/OpenGL_GLFW/shader/shader.fs");
    Shader lightShader("C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.vs","C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.fs");


    //顶点数据
    //---------------------------------------------------------------------
    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
    };

    //物体
    //----------------------------------------------------------------
    unsigned int VBO, objectVAO;
    glGenVertexArrays(1, &objectVAO);     //创建顶点数组对象
    glGenBuffers(1, &VBO);          //创建顶点缓冲对象

    glBindBuffer(GL_ARRAY_BUFFER, VBO);     //将VBO与GL_ARRAY_BUFFER缓冲区绑定
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);  //将顶点数据复制到GL_ARRAY_BUFFER缓冲区,之后可通过VBO进行操作

    glBindVertexArray(objectVAO);         //绑定VAO

    //设定顶点属性指针
    //位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    //法向量属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    //光源(VBO用上面的)
    //----------------------------------------------------------------
    unsigned int lightVAO;
    glGenVertexArrays(1, &lightVAO);     //创建顶点数组对象
    glBindVertexArray(lightVAO);         //绑定VAO

    glBindBuffer(GL_ARRAY_BUFFER, VBO);     //将VBO与GL_ARRAY_BUFFER缓冲区绑定

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);


    //渲染循环
    //我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口;
    //我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入;
    //因此,我们需要在程序中添加一个while循环,它能在我们让GLFW退出前一直保持运行;
    //------------------------------------------------------------------------------
    while (!glfwWindowShouldClose(window))  //如果用户准备关闭参数window所指定的窗口,那么此接口将会返回GL_TRUE,否则将会返回GL_FALSE
    {
        //更新时间差
        float currentFrame = static_cast<float>(glfwGetTime());
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        //用户输入
        //------------------------------------------------------------------------------
        processInput(window);   //检测是否有输入

        //渲染指令
        //------------------------------------------------------------------------------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        //被投光物体
        //============================================================
        //激活着色器程序对象
        objectShader.use();
        objectShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
        objectShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
        objectShader.setVec3("lightPos", lightPos);
        objectShader.setVec3("viewPos", camera.Position);

        //光照强度
        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
        objectShader.setVec3("light.ambient", ambientColor);
        objectShader.setVec3("light.diffuse", diffuseColor);
        objectShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);

        //材质
        objectShader.setVec3("material.ambient",  1.0f, 0.5f, 0.31f);
        objectShader.setVec3("material.diffuse",  1.0f, 0.5f, 0.31f);
        objectShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
        objectShader.setFloat("material.shininess", 32.0f);

        //创建变换矩阵
        glm::mat4 model = glm::mat4(1.0f);
        objectShader.setMat4("model", model);
        glm::mat4 view = camera.GetViewMatrix();
        objectShader.setMat4("view", view);
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        objectShader.setMat4("projection", projection);

        //绘制三角形
        glBindVertexArray(objectVAO);             //绑定VAO
        glDrawArrays(GL_TRIANGLES, 0, 36);
        //============================================================

        //光源
        //============================================================
        //激活着色器程序对象
        lightShader.use();
        lightShader.setMat4("view", view);
        lightShader.setMat4("projection", projection);
        model = glm::mat4(1.0f);
        model = glm::translate(model, lightPos);
        model = glm::scale(model, glm::vec3(0.2f));
        lightShader.setMat4("model", model);

        //绘制三角形
        glBindVertexArray(lightVAO);             //绑定VAO
        glDrawArrays(GL_TRIANGLES, 0, 36);
        //============================================================

        //告诉GLFW检查所有等待处理的事件和消息,包括操作系统和窗口系统中应当处理的消息。如果有消息正在等待,它会先处理这些消息再返回;否则该函数会立即返回
        //---------------------------------------------------------------------------------------------------------------------------------
        glfwPollEvents();

        //请求窗口系统将参数window关联的后缓存画面呈现给用户(双缓冲绘图)
        //------------------------------------------------------------------------------
        glfwSwapBuffers(window);
    }

    //释放资源
    glDeleteVertexArrays(1, &objectVAO);
    glDeleteVertexArrays(1, &lightVAO);
    glDeleteBuffers(1, &VBO);
    //glDeleteProgram(objectShader);
    //glDeleteProgram(lightShader);

    //glfw销毁窗口和OpenGL环境,并释放资源(之后必须再次调用glfwInit()才能使用大多数GLFW函数)
    //------------------------------------------------------------------
    glfwTerminate();


    return a.exec();
}

//检测是否有输入
//---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)   //ESC键,退出
        glfwSetWindowShouldClose(window, true);

    float cameraSpeed = static_cast<float>(2.5 * deltaTime);
    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);
}

//给glfw窗口注册的尺寸改变回调函数
//---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    //确保视口匹配新的窗口尺寸,请注意:宽度和高度将比视网膜显示器上指定的大得多
    glViewport(0, 0, width, height);
}
// 鼠标移动时的回调函数
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
    float xpos = static_cast<float>(xposIn);
    float ypos = static_cast<float>(yposIn);

    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = lastX - xpos;
    float yoffset = ypos - lastY;   //翻转,因为Y轴是从下到上越来越大

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

//鼠标滚轮的回调函数
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(static_cast<float>(yoffset));
}

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

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

相关文章

器件的静态特性

器件的静态特性 静态特性&#xff08;伏安特性&#xff09; 1.器件在导通或关断的状态下&#xff0c;其电压与电流对应关系。 2.静态过程体现器件最基本的电压与电流稳态特性。 动态特性&#xff08;开关特性&#xff09; 1.器件在开或关过程中&#xff0c;其电压、电流随时…

关于java循环结构for

关于java循环结构for 在上一篇文章中&#xff0c;我们了解到了while和do…while的结构以及用法&#xff0c;这篇文章我们主要学习一下最常用的循环结构&#xff0c;for结构&#x1f600;&#xff0c;这个结构理解起来相对while结构会难一些&#xff0c;本篇文章内容会很多&…

深入Mybatis数据源

数据源是持久层框架中最核心的组件之一&#xff0c;在实际工作中比较常见的数据源有 C3P0、Apache Common DBCP、Proxool 等。作为一款成熟的持久化框架&#xff0c;MyBatis 不仅自己提供了一套数据源实现&#xff0c;而且还能够方便地集成第三方数据源。 javax.sql.DataSourc…

Linux之缓冲区的理解

目录 一、问题引入 二、缓冲区 1、什么是缓冲区 2、刷新策略 3、缓冲区由谁提供 4、重看问题 三、缓冲区的简单实现 一、问题引入 我们先来看看下面的代码&#xff1a;我们使用了C语言接口和系统调用接口来进行文件操作。在代码的最后&#xff0c;我们还使用fork函数创建…

单纯形的几何意义 Simplex

单纯形是 n 维空间 n1 个仿射无关的点的集合的凸包。在几何意义上&#xff1a; 1维单纯形是一个线段2维单纯形是一个三角形3维单纯形是一个四面体&#xff08;tetrahedron&#xff09;

MySQL线上慢SQL问题分析处理小记

相同数据量表结构&#xff0c;线上执行12s 本地执行0.1s过程分析 1. 慢SQL信息 SELECT t1.id,t2.idFROM t_platform_target_standard_target_index t1LEFT JOIN t_platform_target_standard t2 ON t1.target_number t2.target_numberWHERE t1.delete_flag 0 AND t2.user_num …

Linux上管理不同版本的 JDK

当在 Linux 上管理不同版本的 JDK 时&#xff0c;使用 yum 和 dnf 可以方便地安装和切换不同的 JDK 版本。本文将介绍如何通过这两个包管理工具安装 JDK 1.8 和 JDK 11&#xff0c;并利用软连接动态关联这些版本。 安装 JDK 1.8 和 JDK 11 使用 yum 安装 JDK 1.8 打开终端并…

如何在 Linux 中配置 firewalld 规则

什么是FirewallD “firewalld”是firewall daemon。它提供了一个动态管理的防火墙&#xff0c;带有一个非常强大的过滤系统&#xff0c;称为 Netfilter&#xff0c;由 Linux 内核提供。 FirewallD 使用zones和services的概念&#xff0c;而 iptables 使用chain和rules。与 ip…

22款奔驰S450L升级主动式氛围灯 浪漫婉转的氛围感

主动式氛围灯有263个可多色渐变的LED光源&#xff0c;营造出全情沉浸的动态光影氛围。结合智能驾驶辅助系统&#xff0c;可在转向或检测到危险时&#xff0c;予以红色环境光提示&#xff0c;令光影艺术彰显智能魅力。配件有6个氛围灯&#xff0c;1个电脑模块。 1、气候&#xf…

Spring系列学习四、Spring数据访问

Spring数据访问 一、Spring中的JDBC模板介绍1、新建SpringBoot应用2、引入依赖&#xff1a;3、配置数据库连接&#xff0c;注入dbcTemplate对象&#xff0c;执行查询&#xff1a;4&#xff0c;测试验证&#xff1a; 二、整合MyBatis Plus1&#xff0c;在你的项目中添加MyBatis …

IP地址的四大类型:动态IP、固定IP、实体IP、虚拟IP的区别与应用

在网络通信中&#xff0c;IP地址是设备在互联网上唯一标识的关键元素。动态IP、固定IP、实体IP和虚拟IP是四种不同类型的IP地址&#xff0c;它们各自具有独特的特点和应用场景。 1. 动态IP地址&#xff1a; 动态IP地址是由Internet Service Provider&#xff08;ISP&#xff…

【嵌入式开发学习必备专栏】

文章目录 嵌入式开发学习必备专栏1.1 ARM Coresight SoC-400/SoC-600 专栏导读目录1.1.1 Performance Profiling1.1.2 ARM Coresight Debug 工具系列1.1.2.1 ARM DS5 系列1.1.2.2 劳特巴赫 Trace32 系列1.1.2.3 JTAG OpenOCD 系列 1.2 ARM Cache 专栏1.3 ARM AMBA Bus 专栏1.3.…

vue3 组件之间传值

vue3 组件之间传值 非常好&#xff0c;为啥突然开这样一篇博文&#xff0c;首先是因为 vue3 是未来发展的趋势。其次&#xff0c;vue 官方已经确认&#xff0c;将于2023年最后一天停止对 vue2 项目的维护&#xff0c;这个是官方发出的通知&#xff0c;并且呢&#xff0c;尤雨溪…

FPGA设计时序约束十四、Set_External_Delay

一、序言 在时序约束中对clock的约束还存在一种特殊的延时约束set external delay。set external delay如字面含义&#xff0c;设置外部的时延值&#xff0c;但这个外部时延主要是指反馈时延&#xff0c;即信号从FPGA的output端口输出后经过外部电路回到输入端口的时延值。 二…

JavaSE语法之十二:Object类

文章目录 一、概念二、获取对象信息三、对象比较equals方法四、hashcode方法 一、概念 Object是Java默认提供的一个类。Java里面除了Object类&#xff0c;所有的类都是存在继承关系的&#xff0c;默认会继承Object父类&#xff0c;即所有的类的对象都可以使用Object的引用进行…

前端基础(三十七):属性结构数据进行关键字筛选

效果 核心源码 type MenuItem {label: string;key: string | number;icon?: React.ReactNode;children?: MenuItem[];type?: group; }function filterTreeData(tree: MenuItem[], keyword: string): MenuItem[] {return tree.filter((node: MenuItem) > {if (node.labe…

macOS系统打开Linux的方法

第一步 按下[command空格键]调出搜索框&#xff0c;输入“终端”&#xff0c;打开图上第一个 第二步 如图先输入"sudo -i"&#xff0c;敲回车键&#xff0c;再输入开机密码&#xff0c;再敲回车键就可以打开。注意&#xff1a;这里的密码输入不会显示在页面。 如果要…

西城微科|打气泵芯片方案SIC8833

SIC8833作为一款高性能的打气泵方案芯片&#xff0c;这款芯片是一个带24bitADC的8位RISC MCU&#xff0c;内置8k16位OTP程序存储器。具体24位双向I/O口的特性&#xff0c;广泛应用于气压检测和精密测量及控制系统&#xff0c;能满足用户的不同需求和应用场景。 以下是打气泵方案…

Observer观察者模式(组件协作)

观察者模式&#xff08;组件协作&#xff09; 链接&#xff1a;观察者模式实例代码 解析 目的 在软件构建过程中&#xff0c;我们需要为某些对象建立一种“通知依赖关系” ——一个对象&#xff08;目标对象&#xff09;的状态发生改变&#xff0c;所有的依赖对象&#xff0…

关于Sql数据库中去掉字段的所有空格

这篇文章主要介绍了Sql数据库中去掉字段的所有空格小结篇,本文通过示例代码给大家介绍的非常详细&#xff0c;对大家的学习或工作具有一定的参考借鉴价值&#xff0c;需要的朋友可以参考下 − Sql数据库中去掉字段的所有空格 字符前的空格&#xff0c;用ltrim(string) 字符…