LearnOpenGL-入门-纹理

news2024/11/15 8:33:36

本人刚学OpenGL不久且自学,文中定有代码、术语等错误,欢迎指正

我写的项目地址:https://github.com/liujianjie/LearnOpenGLProject

LearnOpenGL中文官网:https://learnopengl-cn.github.io/

文章目录

  • 纹理
    • 纹理环绕方式
    • 纹理过滤
    • 多级渐远纹理
  • 加载与创建纹理
    • stb_image.h
    • 生成纹理
  • 应用纹理
    • 给顶点数据添加纹理坐标
    • 对应的glsl
    • 绑定纹理和绘画
    • 效果
  • 纹理单元

纹理

  • 简介

    • 若给每个顶点添加颜色来增加图形的细节,会增加开销,所以用纹理。

    • 纹理是一个2D图片,可以认为纹理附在物体表面上,其实是根据当前片段的uv值,采样纹理- 的颜色值作为当前片段的颜色。

  • 纹理坐标

    • 每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样

      三角形有三个顶点,对应3个纹理坐标。

    • 使用纹理坐标获取纹理颜色叫做采样(Sampling)

    • 重点理解

      三角形只设置3个顶点的纹理坐标就行了,接下来它们会被传片段着色器中,它会为每个片段进行纹理坐标的插值

      比如下面那条线(0,0)-(1.0)的各个像素点,会变插值成为浮点小数,从而形成小数点的纹理坐标

纹理环绕方式

纹理坐标的范围通常是从(0, 0)到(1, 1),那如果我们把纹理坐标设置在范围之外

环绕方式描述
GL_REPEAT对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER超出的坐标为用户指定的边缘颜色。

当纹理坐标超出默认范围时,每个选项都有不同的视觉效果输出

前面提到的每个选项都可以使用glTexParameter*函数对单独的一个坐标轴设置(st(如果是使用3D纹理那么还有一个r)它们和xyz是等价的):

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

纹理过滤

  • 简介

    纹理坐标不依赖于分辨率,它可以是任意浮点值,所以OpenGL需要知道怎样将纹理像素映射到纹理坐标。

    对应前面说的:下面那条线(0,0)-(1.0)的各个像素点,会变插值成为浮点小数,从而形成小数点的纹理坐标

  • 过滤方式

    • GL_NEAREST:邻近过滤,OpenGL会选择中心点最接近纹理坐标的那个像素,左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色

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

  • 对比效果

    • GL_NEARSET:颗粒感
    • GL_LINEAR:更平滑的图案
  • 使用

    • 放大:线性过滤
    • 缩小:邻近过滤
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    

多级渐远纹理

  • 问题引出

    当远处的物体和它的纹理有一样高的分辨率,由于在远处,物体所占的片段较小,OpenGL为这些片段在高分辨率纹理中获得正确的颜色变得困难。所以会不真实,且浪费内存

  • 多级渐远纹理(Mipmap)简介

    它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一

  • OpenGL提供函数创建Mipmap

    glGenerateMipmaps

  • 过滤方式

    在渲染中切换多级渐远纹理级别(Level)时,OpenGL在两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界

    过滤方式描述
    GL_NEAREST_MIPMAP_NEAREST使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样
    GL_LINEAR_MIPMAP_NEAREST使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
    GL_NEAREST_MIPMAP_LINEAR在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样
    GL_LINEAR_MIPMAP_LINEAR在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    // 放大时,用GL_LINEAR
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    

    常见的错误是:将放大过滤的选项设置为多级渐远纹理过滤选项之一,这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理

加载与创建纹理

  • 引出

    由于自己写,难,所以使用第三方库stb_image

  • 下载地址

    https://github.com/nothings/stb/blob/master/stb_image.h

stb_image.h

  • 简介

    单头文件图像加载库

  • 使用

    下载好std_image.h后,将它以stb_image.h的名字加入工程中

    新建一个cpp文件,并写入以下代码

    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"
    

    通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了。

    这个新的cpp文件得放在项目下,不然会报错
    请添加图片描述

  • 具体使用代码

    int width, height, nrChannels;
    unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
    
  • 细节

    stb_image.h能够在图像加载时帮助我们翻转y轴

    stbi_set_flip_vertically_on_load(true);
    

    因为OpenGL要求y轴0.0坐标是在图片的左下角的,但是图片的y轴0.0坐标通常在左上角。

生成纹理

  • 使用代码

    和之前生成的OpenGL对象一样,纹理也是使用ID引用的

    unsigned int texture;
    glGenTextures(1, &texture);
    

    绑定纹理

    glBindTexture(GL_TEXTURE_2D, texture);
    

    使用前面stb_image.h载入的图片数据生成一个纹理

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
    
  • 至此完整代码

    unsigned int texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    // 为当前绑定的纹理对象设置环绕、过滤方式
    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);
    

应用纹理

给顶点数据添加纹理坐标

float vertices[] = {
//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
};

告诉OpenGL我们新的顶点格式

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);

对应的glsl

#version 330 core
layout (location = 0) in vec3 aPos;// 0 -12
layout (location = 1) in vec3 aColor;// 12-24
layout (location = 2) in vec2 aTexCoord;// 24-32

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}
#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;

void main()
{
    // 只有纹理
    FragColor = texture(ourTexture, TexCoord);
    // 纹理和颜色混合
    // FragColor = texture(texture1, TexCoord) * vec4(ourColor, 1.0);
}
  • texture:来采样纹理的颜色

    第一个参数是纹理采样器,第二个参数是对应的纹理坐标

绑定纹理和绘画

在glDrawElements之前,绑定已加载的纹理到纹理单元0位置,片段着色器的采样器默认位置值为0

glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

效果

  • 单纹理

  • 纹理和颜色混合

纹理单元

  • 引出

    sampler2D变量是uniform,上面却没用glUniform给它赋值。

  • 原因

    上面只有一个纹理,它的默认纹理单元是0,它是默认的激活纹理单元,且glsl的采样器默认位置值为0,所以上面代码没有用glUniform

  • 纹理单元

    • 可以用glUniform1i给纹理采样器分配一个位置值,这样能够在一个片段着色器中设置多个纹理

    • 一个纹理的位置值通常称为一个纹理单元

  • 前提

    激活对应的纹理单元

    glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
    glBindTexture(GL_TEXTURE_2D, texture);
    

    GL_TEXTURE0总是被激活

  • 片段着色器使用两个纹理采样器

    #version 330 core
    ...
    
    uniform sampler2D texture1;
    uniform sampler2D texture2;
    
    void main()
    {
        FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
    }
    
    • mix函数说明:
      • 根据第三个参数进行线性插值。
      • 如果第三个值是0.0,它会返回第一个输入;
      • 如果是1.0,会返回第二个输入值。
      • 0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。

    绑定两个纹理到对应的纹理单元

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture1);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texture2);
    
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    

    glUniform1i设置OpenGL每个着色器采样器属于哪个纹理单元

    ourShader.use(); // 不要忘记在设置uniform变量之前激活着色器程序!
    // 手动设置,采样器texture1的位置值是0
    glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); 
    // 或者使用着色器类设置,采样器texture2的位置值是1
    ourShader.setInt("texture2", 1); 
    // 也是执行:glUniform1i(glGetUniformLocation(ourShader.ID, "texture2"), 1);
    
    while(...) 
    {
        [...]
    }
    
  • 效果

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

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

相关文章

3.抽象工厂模式(Abstract Factory)

与工厂模式对比 工厂模式 工厂模式是类创建模式。在工厂模式中&#xff0c;只需要生产同一种产品&#xff0c;只不过是生产厂家不同。 所以产品类的设计&#xff1a; 抽象的产品类Product具体的产品类Product_A&#xff0c;Product_B, Product_C, Product_D…… 工厂的设计…

BFC的含义以及应用

什么是BFC? BFC全称是Block Formatting context&#xff0c;翻译过来就是块级格式化上下文。简单来说&#xff0c;BFC是一个完全独立的空间。让空间里的子元素不会影响到外面的布局。&#x1f603;&#x1f603;&#x1f603; 如何触发BFC呢&#xff1f; mdn给了如下方式&a…

HMM-理论补充

目录 一.隐马尔科夫模型 二.HMM定义 三.隐马尔科夫模型的贝叶斯网络 四.HMM的确定 五.HMM的参数 六.HMM的参数总结 七.HMM的两个基本性质 1.齐次假设&#xff1a; 2.观测独立性假设&#xff1a; 八.HMM举例 九.HMM的3个基本问题 十.概率计算问题 1.直接算法 2.前向…

C语言学习笔记——程序环境和预处理

目录 前言 一、程序环境 1. 翻译环境 1.1 主要过程 1.2 编译过程 2. 运行环境 二、预处理 1. 预定义符号 2. #define 2.1 #define定义标识符 2.2 #define定义宏 2.3 命名约定和移除定义 3. 条件编译 4. 文件包含 结束语 前言 每次我们写完代码运行的时候都…

刷题28-有效的变位词

32-有效的变位词 解题思路&#xff1a; 注意变位词的条件&#xff0c;当两个字符串完全相等或者长度不等时&#xff0c;就不是变位词。 把字符串中的字符映射成整型数组&#xff0c;统计每个字符出现的次数 注意数组怎么初始化&#xff1a; int [] s1new int[26]代码如下&a…

C语言数据结构(一)—— 数据结构理论、线性表【动态数组、链表(企业版单向链表)】

数据结构理论1.1 数据数据&#xff1a;是描述客观事物的符号&#xff0c;是计算机中可以操作的对象&#xff0c;是能被计算机识别&#xff0c;并输入给计算机处理的符号集合。数据不仅仅包括整型、实型等数值类型&#xff0c;还包括字符及声音、图像、视频等非数值类型。1.2数据…

决策树、随机森林、极端随机树(ERT)

声明&#xff1a;本文仅为个人学习记录所用&#xff0c;参考较多&#xff0c;如有侵权&#xff0c;联系删除 决策树 通俗来说&#xff0c;决策树分类的思想类似于找对象。现想象一个女孩的母亲要给这个女孩介绍男朋友&#xff0c;于是有了下面的对话&#xff1a; 女儿&#x…

C++17 nodiscard标记符

文章目录前言弃值表达式nodiscard标记符函数非弃值声明类/枚举类/结构 非弃值声明返回类引用与类指针前言 在C 17中引入了一个标记符nodiscard&#xff0c;用于声明一个 “非弃值(no-discard)表达式”。那么在开始之前&#xff0c;我们需要了解一下什么是弃值表达式。 弃值表…

LearnOpenGL-入门-着色器

本人刚学OpenGL不久且自学&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/LearnOpenGLProject LearnOpenGL中文官网&#xff1a;https://learnopengl-cn.github.io/ 文章目录着色器GLSL数据类型输入与输…

SpringMVC - 12 - 注解配置SpringMVC(完全注解开发)

文章目录注解配置SpringMVC1、创建初始化类WebInit&#xff0c;代替web.xml2、创建SpringConfig配置类&#xff0c;代替spring的配置文件3、创建WebConfig配置类&#xff0c;代替SpringMVC的配置文件4、TestController进行测试注解配置SpringMVC 使用配置类和注解代替web.xml和…

友云生态全球高峰论坛暨长沙首届私董会隆重召开

由友云生态主办的“赢在巅峰决策未来”在友云生态全球新消费、新金融、新资本高峰论坛暨长沙首届私董会于2023年2月22日隆重召开&#xff01;本场私董会以“赢在巅峰 决策未来”为主题&#xff0c;从思维、定位、格局、布局四个角度探讨未来发展的全新动能。作为行业标杆性盛会…

网络应用之HTTP响应报文

HTTP响应报文学习目标能够知道HTTP响应报文的结构1. HTTP响应报文分析HTTP 响应报文效果图:响应报文说明:--- 响应行/状态行 --- HTTP/1.1 200 OK # HTTP协议版本 状态码 状态描述 --- 响应头 --- Server: Tengine # 服务器名称 Content-Type: text/html; charsetUTF-8 # 内容类…

【RabbitMQ笔记06】消息队列RabbitMQ七种模式之Topics主题模式

这篇文章&#xff0c;主要介绍消息队列RabbitMQ七种模式之Topics主题模式。 目录 一、消息队列 1.1、主题模式&#xff08;Topics&#xff09; 1.2、案例代码 &#xff08;1&#xff09;引入依赖 &#xff08;2&#xff09;编写生产者 &#xff08;3&#xff09;编写消费…

MapReduce 性能优化

MapReduce用于大规模数据集的并行运算&#xff0c;所以性能优化也是需要重点关注的内容&#xff0c;下面是我在学习过程中&#xff0c;对于MapReduce 性能优化的点&#xff0c;分享大家学习&#xff0c;enjoy~~ MapReduce的运行流程 以上是MapReduce的运行流程&#xff0c;所以…

Zebec社区上线ZIP-2(地平线升级行动)提案,海量激励将被释放

此前&#xff0c;Zebec社区在上线了投票治理系统Zebec Node后&#xff0c;曾上线了首个提案ZIP-1&#xff0c;对 Nautilus Chain 的推出进行了投票&#xff0c;作为 Zebec Chain 上线前的“先行链”&#xff0c;该链得到了社区用户的欢迎&#xff0c;投通过票的比例高达98.3%。…

负载均衡:LVS 笔记(二)

文章目录LVS 二层负载均衡机制LVS 三层负载均衡机制LVS 四层负载均衡机制LVS 调度算法轮叫调度&#xff08;RR&#xff09;加权轮叫调度&#xff08;WRR&#xff09;最小连接调度&#xff08;LC&#xff09;加权最小连接调度&#xff08;WLC&#xff09;基于局部性的最少链接调…

Apache Hadoop、HDFS介绍

目录Hadoop介绍Hadoop集群HDFS分布式文件系统基础文件系统与分布式文件系统HDFS简介HDFS shell命令行HDFS工作流程与机制HDFS集群角色与职责HDFS写数据流程&#xff08;上传文件&#xff09;HDFS读数据流程&#xff08;下载文件&#xff09;Hadoop介绍 用Java语言实现开源 允许…

SpringBoot:SpringBoot简介与快速入门(1)

SpringBoot快速入门1. SpringBoot简介2. SpringBoot快速入门2.1 创建SpringBoot项目&#xff08;必须联网&#xff0c;要不然创建失败&#xff0c;在模块3会讲到原因&#xff09;2.2 编写对应的Controller类2.3 启动测试3. Spring官网构建工程4. SpringBoot工程快速启动4.1 为什…

自学大数据的第一天

默认跳过基础部分,直接搞集群的部分,期间用到的linux基础默认大伙都会了(不会的话可以现用现查) Hadoop集群搭建 集群特点: 1,逻辑上分离~集群之间没有依赖,互不影响 2,某些进程往往部署在一台服务器上,但是属于不同的集群 3,MapReduce 是计算框架,代码层面的处理逻辑 集群的…

mindspore的MLP模型(多层感知机)

导入模块 import hashlib import os import tarfile import zipfile import requests import numpy as np import pandas as pd import mindspore import mindspore.dataset as ds from mindspore import nn import mindspore.ops as ops import mindspore.numpy as mnp from …