一、理论基础

纹理与纹理坐标

在前面的学习中,我们已经成功在窗口中绘制出了三角形,并且我们通过顶点数据为每一个顶点设置了颜色,而三角形内的点的颜色则有硬件通过插值计算得来。但是,在更多时候,我们不会使用顶点的颜色属性,会用一张图片直接定义三角形中每一个点的颜色,这张图片就被称之为纹理

你可以将纹理看作是一张贴在三角形上的图片。而图片贴在三角形上的方式是多样的,可以正着贴,也可以倒着贴,当然也可以斜着贴。为每一个顶点设置纹理坐标(UV或TexCoord)即可固定纹理贴在三角形上的方式。

二、具体用法

1、顶点数据

我们稍微修改上一章提供的代码。

首先,我们提供的顶点属性如下:

triangle = np.array([# 顶点NDC坐标       纹理坐标-0.5, -0.5, 0,           0, 0,0.5, -0.5, 0,          1, 0,0,  0.5, 0,      0.5, 1
], dtype=np.float32)

可以看到,我们不再提供顶点的颜色属性,转而提供顶点的纹理坐标属性。纹理坐标是一个vec2类型的变量,故只需要两个浮点数即可。

同时,由于顶点数据发生了改变,我们原来的解释顶点数据的语句也需要发生改变:

shader.setAttrib("aPos", 3, GL_FLOAT, 20, 0)
shader.setAttrib("aTex", 2, GL_FLOAT, 20, 12)

我们不再设置aColor变量,而设置顶点的aTex变量,它是一个vec2类型的变量,故其size为2。注意,此时顶点属性的步长有原来的24字节变成了20字节(相比于原来少了一个浮点数)。

以下为完整的Python代码:

import numpy as np
from OpenGL.GL import *
from OpenGL.arrays.vbo import VBO# 导入Window类和Shader类
from shader import Shader
from window import Windoww = Window(1920, 1080, "Test")triangle = np.array([-0.5, -0.5, 0, 0, 0,0.5, -0.5, 0, 1, 0,0, 0.5, 0, 0, 0.5, 1
], dtype=np.float32)vao = glGenVertexArrays(1)
glBindVertexArray(vao)vbo = VBO(triangle, GL_STATIC_DRAW)
vbo.bind()# 导入顶点着色器文件和片元着色器文件
shader = Shader("base.vert", "base.frag")
shader.setAttrib("aPos", 3, GL_FLOAT, 20, 0)
shader.setAttrib("aTex", 2, GL_FLOAT, 20, 12)def render():shader.use()glBindVertexArray(vao)glDrawArrays(GL_TRIANGLES, 0, 3)w.loop(render)

同时,着色器程序也要发生改变:

顶点着色器:

#version 330 core
in vec3 aPos;
in vec2 aTex;   // 顶点的纹理坐标属性
out vec2 uv;    // 将纹理坐标传给片元着色器void main()
{gl_Position = vec4(aPos, 1.0f);uv = aTex;    // 将纹理坐标传给片元着色器
}

由于纹理坐标是针对三角形内每一个像素的,所以我们要把纹理坐标传给片元着色器。

片元着色器:

#version 330 core
in vec2 uv; // 纹理坐标
out vec4 FragColor;
uniform sampler2D tex;  // 纹理void main()
{FragColor = texture(tex, uv); // 从纹理中采样
}

我们接收了从顶点着色器传来的纹理坐标in vec2 uv。我们定义了一个表示纹理的变量tex,它的变量类型为sampler2D,注意这个变量是一个uniform变量,因为我们之后要通过Python代码把纹理数据传给片元着色器。

在主函数中,调用了texture(tex, uv)函数,这个函数的含义是通过纹理坐标在纹理的相应位置进行采样,获得一个vec4的颜色值,并将颜色值赋给这个像素以显示纹理。

这时候如果我们运行代码,只能看到一片漆黑,因为我们还没有把纹理数据传给着色器,texture函数无法获取到正确的值。

2、将纹理数据传给GPU

本文使用的纹理图:

图源自LearnOpenGL CN。

接下来介绍在PyOpenGL中,如何将纹理数据传给GPU

首先,我们通过Python的PIL库来读取图片:

from PIL import Imageimg = Image.open("wall.jpg") # 读取文件
img = np.array(img, np.int8) # 将Image转为np.int8类型的numpy数组(PyOpenGL规定必须要这种类型)
texHeight, texWidth, _ = img.shape # 获取图片的宽高

在OpenGL中,纹理也是一个对象。通过glGenTextures(1)函数可以创建纹理对象,通过glBindTexture(GL_TEXTURE_2D, tex)来绑定纹理对象:

tex = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, tex)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, img)
glGenerateMipmap(GL_TEXTURE_2D)

glTexImage2D,这个函数用于将纹理数据传递给GPU。其参数较多,我们注意介绍:

第一个参数:纹理类型。纹理类型主要有两种。

纹理类型 含义
GL_TEXTURE_2D 2D纹理
GL_TEXTURE_CUBE_MAP 立方体纹理

立方体纹理将在以后介绍。

第二个参数:指定多级渐远纹理(Mipmap)的级别。关于Mipmap,我们稍后介绍。现在只需要知道,这个参数一般填0即可。

第三个参数:把纹理存储为何种形式。主要有GL_RGBGL_RGBA两种。

第四、第五个参数:纹理的宽高。

第六个参数:由于历史遗留问题,这个参数总是填0。

第七、第八个参数:纹理的格式和数据类型。

第九个参数:纹理数据。

这里有一个表,解释了内部格式(参数三)、图片格式(参数七)、数据类型(参数八)之间的关系:

内部格式 图片格式 数据类型
GL_RGB GL_RGB GL_UNSIGNED_BYTE
GL_RGBA GL_RGBA GL_UNSIGNED_BYTE
GL_ALPHA GL_ALPHA GL_UNSIGNED_BYTE

上表只是列出了常用的几种格式,并非 OpenGL 支持的所有格式。

从上表可以看出,参数三和参数七一般相同,参数八一般为GL_UNSIGNED_BYTE

glGenerateMipmap:生成多级渐远纹理(Mipmap)。这里有一篇微软对Mipmap的介绍。简而言之,Mipmap就是对于同一张图像,当它的尺寸发生变化时,我们用不同像素的纹理来显示这个图像,这种方法可以显著的改善因图片缩小带来的失真问题,但会占用更多的内存。通过调用glGenerateMipmap,OpenGL会自动为我们生成不同像素的纹理序列。

3、将纹理索引传给着色器

glActiveTexture(GL_TEXTURE0)
shader.setUniform("tex", 0)

对于每一个纹理,我们都需要为它分配一个索引值,以便着色器能通过索引值获取到纹理数据;另外,当同一个着色器需要多张纹理时,也需要用索引值来区分不同的纹理。glActiveTexture(GL_TEXTURE0)为当前绑定的纹理分配了索引值0,该函数的参数为GL_TEXTURE{X},X可以是0~31。再通过设置uniform变量的方法,将该索引值传递给着色器。

4、纹理相关设置

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_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)

通过glTexParameteri,可以设置当前绑定的纹理的一些属性。该函数有三个参数,第一个参数为纹理类型,第二个参数为要修改的纹理的属性,第三个参数为该属性的值。

属性 含义
GL_TEXTURE_WRAP_S 纹理X轴的环绕方式
GL_TEXTURE_WRAP_T 纹理Y轴的环绕方式
GL_TEXTURE_MAG_FILTER 纹理放大时的插值算法
GL_TEXTURE_MIN_FILTER 纹理缩小时的插值算法

上表中有两个名词需要解释:

首先,环绕方式,是指当纹理坐标在[0, 1]之外时的处理方式。

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

其次,插值方式,即当图片放大缩小时,如何处理纹理。

插值方式 描述
GL_NEAREST 最近邻插值
GL_LINEAR 双线性插值
GL_LINEAR_MIPMAP_LINEAR 三线性插值

这三种插值算法的具体含义这里不做介绍,有兴趣的读者可以自行查阅资料。注意查阅资料时区分双三次插值和三线性插值。

5、结果

至此,我们得到了绘制纹理的完整代码:

import numpy as np
from OpenGL.GL import *
from OpenGL.arrays.vbo import VBO
from PIL import Image
from shader import Shader
from window import Window
# 创建窗口
w = Window(1920, 1080, "Test")
# 顶点数据
triangle = np.array([-0.5, -0.5, 0, 0, 0,0.5, -0.5, 0, 1, 0,0, 0.5, 0, 0, 0.5, 1
], dtype=np.float32)
# 创建VAO
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
# 创建VBO
vbo = VBO(triangle, GL_STATIC_DRAW)
vbo.bind()
# 导入顶点着色器文件和片元着色器文件
shader = Shader("base.vert", "base.frag")
# 设置顶点属性
shader.setAttrib("aPos", 3, GL_FLOAT, 20, 0)
shader.setAttrib("aTex", 2, GL_FLOAT, 20, 12)
# 读取纹理数据
img = Image.open("wall.jpg")
img = np.array(img, np.int8)
texWidth, texHeight, _ = img.shape
# 创建纹理对象
tex = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, tex)
# 将纹理数据传给GPU
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, img)
glGenerateMipmap(GL_TEXTURE_2D)
# 将纹理索引传给着色器
glActiveTexture(GL_TEXTURE0)
shader.setUniform("tex", 0)
# 配置
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_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
# 渲染循环
def render():shader.use()glBindVertexArray(vao)glDrawArrays(GL_TRIANGLES, 0, 3)
w.loop(render)

着色器代码在上面已经给出。

最终结果图:

三、封装

下面我们将与纹理有关的操作封装成Texture类,以提高程序的复用性和可扩展性。

from OpenGL.GL import *
from PIL import Image
import numpy as npclass Texture:def __init__(self, imgPath, idx=0, texType=GL_TEXTURE_2D,imgType=GL_RGB, innerType=None, dataType=GL_UNSIGNED_BYTE):if not innerType:innerType = imgTypeself.idx = idx# 创建纹理对象self.tex = glGenTextures(1)glBindTexture(GL_TEXTURE_2D, self.tex)# 读取纹理数据img = Image.open(imgPath)img = np.array(img, np.int8)self.h, self.w, _ = img.shape# 将纹理数据传给GPUglTexImage2D(texType, 0, innerType, self.h, self.w, 0, imgType, dataType, img)glGenerateMipmap(texType)# 纹理设置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_MAG_FILTER, GL_LINEAR)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)def setIndex(self, shader, name=None):""" 设置纹理索引 """if not name:name = "tex" + str(self.idx)glBindTexture(GL_TEXTURE_2D, self.tex)glActiveTexture(GL_TEXTURE0 + self.idx)shader.setUniform(name, self.idx)

封装后,主程序代码:

import numpy as np
from OpenGL.GL import *
from OpenGL.arrays.vbo import VBO
from shader import Shader
from texture import Texture
from window import Window# 创建窗口
w = Window(1920, 1080, "Test")
# 顶点数据
triangle = np.array([-0.5, -0.5, 0, 0, 0,0.5, -0.5, 0, 1, 0,0, 0.5, 0, 0.5, 1
], dtype=np.float32)
# VAO
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
# VBO
vbo = VBO(triangle, GL_STATIC_DRAW)
vbo.bind()
# 导入顶点着色器文件和片元着色器文件
shader = Shader("base.vert", "base.frag")
shader.setAttrib("aPos", 3, GL_FLOAT, 20, 0)
shader.setAttrib("aTex", 2, GL_FLOAT, 20, 12)
# 创建并设置纹理
tex = Texture("wall.jpg")
tex.setIndex(shader, "tex")# 渲染循环
def render():shader.use()glBindVertexArray(vao)glDrawArrays(GL_TRIANGLES, 0, 3)w.loop(render)

四、结语

本文介绍了在OpenGL中如何使用纹理。在这次以及之前的教程中,我们给出的坐标都是NDC坐标,在下一节中,我们将介绍将模型坐标转换为NDC坐标的方法。

PyOpenGL代码实战(五):纹理相关推荐

  1. [原创].NET 分布式架构开发实战五 Framework改进篇

    原文:[原创].NET 分布式架构开发实战五 Framework改进篇 .NET 分布式架构开发实战五 Framework改进篇 前言:本来打算这篇文章来写DAL的重构的,现在计划有点改变.之前的文章 ...

  2. 【运筹优化】元启发式算法详解:模拟退火算法(Simulated Annealing,SA)+ 案例讲解代码实战

    文章目录 一.介绍 二.基础知识 2.1 局部搜索(或蒙特卡罗)算法 2.2 Metropolis 算法 2.3 模拟退火算法 三.原理 3.1 Statistical Equilibrium 统计平 ...

  3. 【自然语言处理】Word2Vec 词向量模型详解 + Python代码实战

    文章目录 一.词向量引入 二.词向量模型 三.训练数据构建 四.不同模型对比 4.1 CBOW 4.2 Skip-gram 模型 4.3 CBOW 和 Skip-gram 对比 五.词向量训练过程 5 ...

  4. Android组件化实战五: APT的高级用法JavaPoet

    Android 组件化实战一: Gradle基础语法 Android 组件化实战二: 项目部署 Android组件化实战三: 模块之间的交互 Android组件化实战四: APT的介绍与使用 Andr ...

  5. 【强化学习】PPO算法求解倒立摆问题 + Pytorch代码实战

    文章目录 一.倒立摆问题介绍 二.PPO算法简介 三.详细资料 四.Python代码实战 4.1 运行前配置 4.2 主要代码 4.3 运行结果展示 4.4 关于可视化的设置 一.倒立摆问题介绍 Ag ...

  6. 六、PageRank算法与代码实战【CS224W】(Datawhale组队学习)

    开源内容:https://github.com/TommyZihao/zihao_course/tree/main/CS224W 子豪兄B 站视频:https://space.bilibili.com ...

  7. 深度学习代码实战演示_Tensorflow_卷积神经网络CNN_循环神经网络RNN_长短时记忆网络LSTM_对抗生成网络GAN

    前言 经过大半年断断续续的学习和实践,终于将深度学习的基础知识看完了,虽然还有很多比较深入的内容没有涉及到,但也是感觉收获满满.因为是断断续续的学习做笔记写代码跑实验,所以笔记也零零散散的散落在每个角 ...

  8. 算法工程师面试问题及资料超详细合集(多家公司算法岗面经/代码实战/网课/竞赛等)

    这里是算法江湖,传授AI武林秘籍. 资源目录: 一.算法工程师 Github.牛客网.知乎.个人博客.微信公众号.其他 二.机器学习 面试问题.资料.代码实战 三.深度学习 面试.资料.代码实战Pyt ...

  9. 一文弄懂元学习 (Meta Learing)(附代码实战)《繁凡的深度学习笔记》第 15 章 元学习详解 (上)万字中文综述

    <繁凡的深度学习笔记>第 15 章 元学习详解 (上)万字中文综述(DL笔记整理系列) 3043331995@qq.com https://fanfansann.blog.csdn.net ...

最新文章

  1. Ubuntu创始人谈Windows 7、Chrome OS
  2. 转整型_SPI转can芯片CSM300详解、Linux驱动移植调试笔记
  3. 如何用Python读取Excel中图片?又如何用Python往Excel中写入图片?
  4. 有关计算机组成原理知识的论文,关于计算机组成原理的论文_计算机组成原理_图灵机的组成...
  5. 63 SD配置-交货凭证配置-定义交货的项目类别
  6. 通用easyui查询页面组件
  7. 解决Error: ENOENT: no such file or directory, scandir ‘xxx\node-sass\vendor‘
  8. 内容为王时代“内卷”急,安全风控一旦疏漏很要命!
  9. 【Python】python网络协议
  10. 七彩虹断剑C.A320M-K PRO V14安装PCI-E X4转m.2转接卡,并用m.2固态作为系统盘
  11. [Daozy][区块链 EOS 课程]第2课 EOS编译和启动
  12. python3中expected an indented block(缩进问题)
  13. Android开发笔记01-TextView01
  14. 适合前端Vue开发童鞋的跨平台Weex
  15. 文本 去除重复行(sublime Text3 ,正则表达式)
  16. 程序启动,遇到Process finished with exit code 1 解决方法
  17. 【Ubuntu 1】ubuntu的软件包
  18. 在Ubuntu虚拟机中安装VMware tools异常中断 Unable to start services for VMware Tools
  19. 2021年,薪酬最高的5种编程语言
  20. 中国裁判网-爬虫-2018.09.28

热门文章

  1. matlab xn,matlab定义变量x1到xn
  2. CryptoJS 加密的使用方法
  3. Could not connect to SMTP host: smtp.***.com, port: 465, response: -1
  4. 全球及中国粉煤灰PFA行业行业发展动态与前景趋势预测报告2022-2028年
  5. iOS 开发者证书、描述文件等详解
  6. 绿色版,便携版,破解版,精简版区别
  7. 商业周刊:MySpace兴衰沉浮启示录(转)
  8. 字体抗锯齿(-webkit-font-smoothing)
  9. 温室大棚控制系统智能轻松种菜
  10. 阅读笔记:黑碳对于冰雪辐射效应的影响