OpenGL基础9:纹理

其实上一章就是在讲纹理了,不过上一章的目的仅是在想办法生成一张有纹理的矩形

一、纹理坐标与环绕方式

为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分,每个顶点都会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样,之后在图形的其它片段上进行片段插值(Fragment Interpolation)

  • 我们很长的一段时间只需要考虑2D的图象,2D纹理坐标范围为(0~1),起始于左下角(0, 0),终止于右上角(1, 1)

使用纹理坐标获取纹理颜色就叫做采样(Sampling),如果我们取(0.5, 0.5)相当于就是获得图片最中间的点的颜色,但是如果我们采样超出了范围呢?例如对(1.5, 0.5)进行采样

是的并不会报错,而是通过一定的算法,映射到图片中的某个点(重复图像)又或者显示指定颜色,这个映射方法即是环绕方式,常规的环绕方式有如下4种:

  1. GL_REPEAT:默认行为,重复纹理图像
  2. GL_MIRRORED_REPEAT:重复纹理图像,镜像放置
  3. GL_CLAMP_TO_EDGE:坐标被约束在0到1之间,例如(1.5, 0.5)就会被强制改为(1, 0.5),超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果
  4. GL_CLAMP_TO_BORDER:超出的坐标为用户指定的边缘颜色

其中前面提到的每个选项都可以使用 glTexParameter 函数对单独的一个坐标轴设置(s, t, r,等价于x、y、z),前一章的代码中正有用到这个函数!

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

如果我们用第4中环绕方式,我们还需要指定边缘的颜色,否则默认为黑,代码如下

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

二、纹理过滤

如果你的电脑是2k的分辨率,那么很显然,桌面壁纸肯定也要选≥2k的,如果分辨率远低于这个值,那么桌面就会变得非常模糊,纹理也一样,我们上面只是指定了坐标,如果纹理贴图的分辨率非常低,那么OpenGL就需要知道怎样将纹理像素(Texture Pixel)映射到纹理坐标,并进行纹理过滤,图片变模糊其实就是纹理过滤的一种

  • 所谓的分辨率即使一个又一个的点,例如一张图片的分辨率为64 * 64,那么它就由那么多点组成,如果你想将这张图片显示在一个256 * 256的画布上,那么很显然就需要特定的算法去铺平整个画布了

有六种过滤方式:

  • GL_NEAREST:邻近过滤,默认过滤方式,对于画布中的每个像素,直接同等与离对应纹理坐标最近的像素
  • GL_LINEAR:线性过滤:对于画布中的每个像素,根据对应纹理坐标最近的4个点,插值得到当前像素的颜色(混合色)
  • GL_NEAREST_MIPMAP_NEAREST:使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样
  • GL_LINEAR_MIPMAP_NEAREST:使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
  • GL_NEAREST_MIPMAP_LINEAR:在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样
  • GL_LINEAR_MIPMAP_LINEAR:在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样

先看前2种过滤方式:(在过滤之前,画布中的每一个像素点会按照位置等比映射到纹理坐标中)

应该也猜到了,邻近过滤即是所谓的“马赛克风”,而线性过滤即使“模糊处理”

还记得前一章用了4个 glTexParameteri 函数嘛,后面2个即是设置其过滤方式

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

其中第二个参数表示当前过滤方式是在纹理进行放大(Magnify)还是缩小(Minify)时进行

再看后4种过滤方式:里面提到了多级渐远纹理(Mipmap),这其实属于一种优化

  • glGenerateMipmap:为当前绑定的纹理自动生成所有需要的多级渐远纹理

为什么需要优化:假设3D场景中有一块箱子,当这块箱子离你非常远的时候,那么这块箱子上的纹理理论上就不需要特别高的分辨率,然而OpenGL拿到的纹理资源分辨率可能会很高,这样OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色,不但如此对这块箱子使用高分辨率的纹理还会比较占内存

优化的方法:即是利用多级渐远纹理这个手段,也就是对于一张纹理生成一系列的纹理图象,每一个纹理图像都是前一个的二分之一,根据物体相对玩家的距离,openGL选择最适合对应大小的两张多级渐远纹理,之后进行的操作就取决过滤方式的设置了

很明显,这4种过滤方式只适用于缩小过滤

三、纹理单元

接一下上一章的代码,其实有两个问题

  1. 前面讲了采样器(sampler),是一个供纹理对象使用的内建数据类型,在片段着色器中,我们定义的sampler2D变量是个uniform,然而我们却并没有使用glUniform给其赋值
  2. 仔细看下,我们生成的图片,居然是上下颠倒的,越是不对称的图象越容易发现

对于①,还记得顶点着色器的layout嘛,它代表着对应属性的位置值,这里的sampler其实和它一样,也是位置值,一个纹理的位置值通常称为一个纹理单元(Texture Unit),当你不使用glUniform对其赋值的时候,默认为0

纹理单元的主要目的就是让我们在着色器中可以使用多于一个的纹理,默认激活位置值0的纹理单元,其它纹理单元需要手动激活

现在可以稍微修改代码,混合两张纹理,如下:

一样的,我们生成两个纹理图象,那么同样的代码再来一份就可以了,其中textureA和textureB即是绑定的两张纹理

int picWidth, picHeight;
glGenTextures(1, &textureA);
glBindTexture(GL_TEXTURE_2D, textureA);
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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
unsigned char* image = SOIL_load_image("timg.jpg", &picWidth, &picHeight, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, picWidth, picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);

glGenTextures(1, &textureB);
glBindTexture(GL_TEXTURE_2D, textureB);
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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
image = SOIL_load_image("minecraftA.jpg", &picWidth, &picHeight, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, picWidth, picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);

可以看到操作是一样的,紧接着修改片段着色器

#version 330 core
out vec4 color;
in vec4 colorIn;
in vec2 texIn;
uniform sampler2D texOutA;
uniform sampler2D texOutB;
void main()
{
    color = mix(texture(texOutA, texIn), texture(texOutB, texIn), 0.8);
}
  • mix(a, b, x):其中a和b为颜色参数,第三个值为插值参数,设这个参数为x%,那么这个mix方法最终就会返回 x% 的第二个颜色 + (1-x%)的第一个颜色(显然如果这个值为1相当于直接取第二个参数b)

渲染流程修改如下:

glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);
  • glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1):设置正确的位置值(纹理单元)
  • glActiveTexture(GL_TEXTURE0):激活对应的纹理单元,激活的纹理单元取决于参数最后的数字,我们也可以使用类似于(GL_TEXTURE0 + 1)的方法来表示 GL_TEXTURE1

对于②,现在可以成功运行并且生成一张上下颠倒的图片,出现这种情况的原因是OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部,也就是第一个像素的位置

我们现在可以暂时通过调整顶点着色器来解决,当然方法非常暴力,将y轴拿1减去就好了

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec4 color;
layout (location = 2) in vec2 texture;
out vec4 colorIn;
out vec2 texIn;
void main()
{
    gl_Position = vec4(position, 1.0);
    colorIn = color;
    texIn = vec2(texture.x, 1.0f - texture.y);
}

完整代码:

#include<iostream>
#include<opengl/glew.h>
#define GLEW_STATIC
#include<GLFW/glfw3.h>
#include"Shader.h"
#include<opengl/freeglut.h>
#include<SOIL.h>

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
const GLuint WIDTH = 800, HEIGHT = 600;

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
    glfwMakeContextCurrent(window);
    glfwSetKeyCallback(window, key_callback);
    glewExperimental = GL_TRUE;
    glewInit();

    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    glViewport(0, 0, width, height);

    Shader shaderYellow("VShader.txt", "FShaderY.txt");

    GLfloat vertices[] =
    {
        -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
        0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
        -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
        0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
    };
    GLuint indices[] =
    {
        0, 1, 2,        //用前3个顶点绘制第一个三角形
        1, 2, 3         //用后3个顶点绘制第二个三角形
    };
    GLuint VBO, EBO, VAO, textureA, textureB;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);

    int picWidth, picHeight;
    glGenTextures(1, &textureA);
    glBindTexture(GL_TEXTURE_2D, textureA);
    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_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    unsigned char* image = SOIL_load_image("timg.jpg", &picWidth, &picHeight, 0, SOIL_LOAD_RGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, picWidth, picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    SOIL_free_image_data(image);

    glGenTextures(1, &textureB);
    glBindTexture(GL_TEXTURE_2D, textureB);
    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_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    image = SOIL_load_image("minecraftA.jpg", &picWidth, &picHeight, 0, SOIL_LOAD_RGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, picWidth, picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    SOIL_free_image_data(image);
    glBindTexture(GL_TEXTURE_2D, 0);


    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        shaderYellow.Use();
        glBindTexture(GL_TEXTURE_2D, textureA);
        glUniform1i(glGetUniformLocation(shaderYellow.Program, "texOutA"), 0);
        glActiveTexture(GL_TEXTURE0 + 1);
        glBindTexture(GL_TEXTURE_2D, textureB);
        glUniform1i(glGetUniformLocation(shaderYellow.Program, "texOutB"), 1);

        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glBindVertexArray(0);
        glfwSwapBuffers(window);
    }
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glfwTerminate();
    return 0;
}

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}

其中其中 Shader.h 可以在上上章找到代码,没有改动,两个着色器的代码在上面

四、扩展

①我们代码中还是用了颜色属性,但是上面并没有体现出来,只有纹理

其实只需要把纹理颜色和顶点颜色在片段着色器中相乘就可以做到混合的效果了,代码改动和效果如下:

#version 330 core
out vec4 color;
in vec2 texIn;
in vec4 colorIn;
uniform sampler2D texOutA;
uniform sampler2D texOutB;
void main()
{
    color = mix(texture(texOutA, texIn), texture(texOutB, texIn), 0.8) * colorIn;
}

②上面代码中的mix参数是写死的0.8,我们可以尝试控制这个值,实现可以通过上和下键来改变mc游戏截图的可见度

片段着色器修改如下:

#version 330 core
out vec4 color;
in vec2 texIn;
in vec4 colorIn;
uniform sampler2D texOutA;
uniform sampler2D texOutB;
uniform float mixVal;
void main()
{
    color = mix(texture(texOutA, texIn), texture(texOutB, texIn), mixVal);
}

完整代码:

#include<iostream>
#include<opengl/glew.h>
#define GLEW_STATIC
#include<GLFW/glfw3.h>
#include"Shader.h"
#include<opengl/freeglut.h>
#include<SOIL.h>

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
const GLuint WIDTH = 800, HEIGHT = 600;
GLfloat mixVal = 0.2f;

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
    glfwMakeContextCurrent(window);
    glfwSetKeyCallback(window, key_callback);
    glewExperimental = GL_TRUE;
    glewInit();

    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    glViewport(0, 0, width, height);

    Shader shaderYellow("VShader.txt", "FShaderY.txt");

    GLfloat vertices[] =
    {
        -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
        0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
        -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
        0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
    };
    GLuint indices[] =
    {
        0, 1, 2,        //用前3个顶点绘制第一个三角形
        1, 2, 3         //用后3个顶点绘制第二个三角形
    };
    GLuint VBO, EBO, VAO, textureA, textureB;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);

    int picWidth, picHeight;
    glGenTextures(1, &textureA);
    glBindTexture(GL_TEXTURE_2D, textureA);
    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_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    unsigned char* image = SOIL_load_image("timg.jpg", &picWidth, &picHeight, 0, SOIL_LOAD_RGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, picWidth, picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    SOIL_free_image_data(image);

    glGenTextures(1, &textureB);
    glBindTexture(GL_TEXTURE_2D, textureB);
    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_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    image = SOIL_load_image("minecraftA.jpg", &picWidth, &picHeight, 0, SOIL_LOAD_RGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, picWidth, picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    SOIL_free_image_data(image);
    glBindTexture(GL_TEXTURE_2D, 0);


    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        shaderYellow.Use();
        glBindTexture(GL_TEXTURE_2D, textureA);
        glUniform1i(glGetUniformLocation(shaderYellow.Program, "texOutA"), 0);
        glActiveTexture(GL_TEXTURE0 + 1);
        glBindTexture(GL_TEXTURE_2D, textureB);
        glUniform1i(glGetUniformLocation(shaderYellow.Program, "texOutB"), 1);

        glUniform1f(glGetUniformLocation(shaderYellow.Program, "mixVal"), mixVal);
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glBindVertexArray(0);
        glfwSwapBuffers(window);
    }
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glfwTerminate();
    return 0;
}

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (key == GLFW_KEY_UP && action == GLFW_PRESS)
    {
        mixVal += 0.1f;
        if (mixVal >= 1.0f)
            mixVal = 1.0f;
    }
    if (key == GLFW_KEY_DOWN && action == GLFW_PRESS)
    {
        mixVal -= 0.1f;
        if (mixVal <= 0.0f)
            mixVal = 0.0f;
    }
}
原创文章 1134 获赞 1439 访问量 61万+

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/106062221
今日推荐