欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【LearnOpenGL CN】入门:着色器+纹理+变换+摄像机

程序员文章站 2022-07-16 21:38:59
...

LearnOpenGL CN网址:https://learnopengl-cn.github.io/

目录

uniform的使用

在着色器中声明

设置与更新

更多属性

抽象一个着色器类

纹理

UV位置

纹理环绕方式

纹理过滤

多级渐远纹理(空间换时间)

图像加载库std_image.h

纹理对象处理

矩阵

配置GLM

MVP矩阵变换

摄像机类

Forward

Right

Up


uniform的使用

在着色器中声明

#version 330 core
out vec4 FragColor;
// 在OpenGL程序代码中设定这个变量
uniform vec4 ourColor; 

void main()
{
    FragColor = ourColor;
}

设置与更新

while(!glfwWindowShouldClose(window))
{
    processInput(window);

//----渲染设置-----------------------------------
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(shaderProgram);

    // 更新uniform颜色
    float timeValue = glfwGetTime();
    float greenValue = sin(timeValue) / 2.0f + 0.5f;

// 查询uniform ourColor的位置值
// 查询uniform地址不要求之前使用过着色器程序
// 更新一个uniform之前你必须先使用程序(调用glUseProgram)
// 因为它是在当前**的着色器程序中设置uniform的。
    int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
//将其设置为指定值
    glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

    // 绘制三角形
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);
//----渲染设置-----------------------------------

    // 交换缓冲并查询IO事件
    glfwSwapBuffers(window);
    glfwPollEvents();
}

更多属性

【LearnOpenGL CN】入门:着色器+纹理+变换+摄像机

const char *vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 1) in vec3 aColor;\n"
"out vec3 ourColor;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos, 1.0);\n"
"   ourColor = aColor;\n"
"}\0";

const char *fragmentShaderSource = 
"#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 ourColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(ourColor, 1.0f);\n"
"}\n\0";

int main()
{
// 窗口和GLAD
// 编译与链接shader
    ...

// 输入顶点数据,包括位置和颜色
    float vertices[] = {
        // positions         // colors
         0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  // bottom left
         0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f   // top 

    };

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 顶点位置属性数组
    //0:localtion数值,按上述顶点着色器其指定的是位置信息
    //3:该属性大小,为3个浮点数
    //GL_FLOAT:属性的每个值都是float
    //GL_FALSE:是否希望数据标准归一化,位置属性不需要归一化
    //6 * sizeof(float):相邻相同属性的间隔
    //(void*)0:位置数据在缓冲中起始位置的偏移量(Offset)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

// 顶点颜色属性数组
    //1:localtion数值,按上述顶点着色器其指定的是位置信息
    //3:该属性大小,为3个浮点数
    //GL_FLOAT:属性的每个值都是float
    //GL_FALSE:是否希望数据标准归一化,位置属性不需要归一化
    //6 * sizeof(float):相邻相同属性的间隔
    //(void*)(3 * sizeof(float)):位置数据在缓冲中起始位置的偏移量(Offset)
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    glUseProgram(shaderProgram);

    while (!glfwWindowShouldClose(window))
    {
        ...
    }

    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);

    glfwTerminate();
    return 0;
}

抽象一个着色器类

编写、编译、管理着色器是件麻烦事。我们可以写一个类进行一层抽象,它可以从硬盘读取着色器,然后编译并链接它们,并对它们进行错误检测。

...
Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
...
while(...)
{
...
    ourShader.use();
    ourShader.setFloat("someUniform", 1.0f);
...
}

纹理

UV位置

【LearnOpenGL CN】入门:着色器+纹理+变换+摄像机

纹理环绕方式

【LearnOpenGL CN】入门:着色器+纹理+变换+摄像机

纹理过滤

【LearnOpenGL CN】入门:着色器+纹理+变换+摄像机

  • GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色。

【LearnOpenGL CN】入门:着色器+纹理+变换+摄像机

  • GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。

【LearnOpenGL CN】入门:着色器+纹理+变换+摄像机

多级渐远纹理(空间换时间)

由于远处的物体可能只产生很少的片段,OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。在小物体上这会产生不真实的感觉,更不用说对它们使用高分辨率纹理浪费内存的问题了。OpenGL使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好。

在渲染中切换多级渐远纹理级别(Level)时,OpenGL在两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界。就像普通的纹理过滤一样,切换多级渐远纹理级别时你也可以在两个不同多级渐远纹理级别之间使用NEAREST和LINEAR过滤

图像加载库std_image.h

使用纹理之前要做的第一件事是把它们加载到我们的应用中。纹理图像可能被储存为各种各样的格式,每种都有自己的数据结构和排列,所以我们如何才能把这些图像加载到应用中呢?一个解决方案是选一个需要的文件格式,比如.PNG,然后自己写一个图像加载器,把图像转化为字节序列。写自己的图像加载器虽然不难,但仍然挺麻烦的,而且如果要支持更多文件格式呢?你就不得不为每种你希望支持的格式写加载器了。另一个解决方案也许是一种更好的选择,使用一个支持多种流行格式的图像加载库来为我们解决这个问题。比如说我们要用的stb_image.h库。

纹理对象处理

//1 创建对象
	unsigned int texture;
	glGenTextures(1, &texture);
//2 绑定对象
	glBindTexture(GL_TEXTURE_2D, texture); 

//3 设置对象属性
	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);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	int width, height, nrChannels;
	unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
	if (data)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		std::cout << "Failed to load texture" << std::endl;
	}
	stbi_image_free(data);

	while (!glfwWindowShouldClose(window))
	{
		...

		glBindTexture(GL_TEXTURE_2D, texture);

		ourShader.use();
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}
// 多个纹理时
    unsigned int texture1, texture2;
    // texture 1
    glGenTextures(1, &texture1);
    glBindTexture(GL_TEXTURE_2D, texture1);
    ...
    // texture 2
    // ---------
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);
    ...

// 设置每个uniform sampler2D对象
    // 设置uniform时需要**shader程序对象
    ourShader.use();
    // 使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元
    // either set it manually like so:
    glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);
    // or set it via the texture class
    ourShader.setInt("texture2", 1);

    while (!glfwWindowShouldClose(window))
    {
        ...
        // bind textures on corresponding texture units
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);
        ...
    }

矩阵

  • 配置GLM

【LearnOpenGL CN】入门:着色器+纹理+变换+摄像机

【LearnOpenGL CN】入门:着色器+纹理+变换+摄像机

  • MVP矩阵变换

glEnable(GL_DEPTH_TEST);
while (!glfwWindowShouldClose(window))
    {
// 注意深度缓冲
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//绑定贴图
        ...
        // activate shader
        ourShader.use();

// MVP
        glm::mat4 model         = glm::mat4(1.0f); 
        glm::mat4 view          = glm::mat4(1.0f);
        glm::mat4 projection    = glm::mat4(1.0f);
        model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.5f, 1.0f, 0.0f));
        view  = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
        projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        // retrieve the matrix uniform locations
        unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model");
        unsigned int viewLoc  = glGetUniformLocation(ourShader.ID, "view");
        // pass them to the shaders (3 different ways)
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]);
        // note: currently we set the projection matrix each frame, but since the projection matrix rarely changes it's often best practice to set it outside the main loop only once.
        ourShader.setMat4("projection", projection);

        // render box
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

摄像机类

Forward

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

Up

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);