PyOpenGL代码实战(五):纹理
一、理论基础
纹理与纹理坐标
在前面的学习中,我们已经成功在窗口中绘制出了三角形,并且我们通过顶点数据为每一个顶点设置了颜色,而三角形内的点的颜色则有硬件通过插值计算得来。但是,在更多时候,我们不会使用顶点的颜色属性,会用一张图片直接定义三角形中每一个点的颜色,这张图片就被称之为纹理。
你可以将纹理看作是一张贴在三角形上的图片。而图片贴在三角形上的方式是多样的,可以正着贴,也可以倒着贴,当然也可以斜着贴。为每一个顶点设置纹理坐标(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_RGB
和GL_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代码实战(五):纹理相关推荐
- [原创].NET 分布式架构开发实战五 Framework改进篇
原文:[原创].NET 分布式架构开发实战五 Framework改进篇 .NET 分布式架构开发实战五 Framework改进篇 前言:本来打算这篇文章来写DAL的重构的,现在计划有点改变.之前的文章 ...
- 【运筹优化】元启发式算法详解:模拟退火算法(Simulated Annealing,SA)+ 案例讲解代码实战
文章目录 一.介绍 二.基础知识 2.1 局部搜索(或蒙特卡罗)算法 2.2 Metropolis 算法 2.3 模拟退火算法 三.原理 3.1 Statistical Equilibrium 统计平 ...
- 【自然语言处理】Word2Vec 词向量模型详解 + Python代码实战
文章目录 一.词向量引入 二.词向量模型 三.训练数据构建 四.不同模型对比 4.1 CBOW 4.2 Skip-gram 模型 4.3 CBOW 和 Skip-gram 对比 五.词向量训练过程 5 ...
- Android组件化实战五: APT的高级用法JavaPoet
Android 组件化实战一: Gradle基础语法 Android 组件化实战二: 项目部署 Android组件化实战三: 模块之间的交互 Android组件化实战四: APT的介绍与使用 Andr ...
- 【强化学习】PPO算法求解倒立摆问题 + Pytorch代码实战
文章目录 一.倒立摆问题介绍 二.PPO算法简介 三.详细资料 四.Python代码实战 4.1 运行前配置 4.2 主要代码 4.3 运行结果展示 4.4 关于可视化的设置 一.倒立摆问题介绍 Ag ...
- 六、PageRank算法与代码实战【CS224W】(Datawhale组队学习)
开源内容:https://github.com/TommyZihao/zihao_course/tree/main/CS224W 子豪兄B 站视频:https://space.bilibili.com ...
- 深度学习代码实战演示_Tensorflow_卷积神经网络CNN_循环神经网络RNN_长短时记忆网络LSTM_对抗生成网络GAN
前言 经过大半年断断续续的学习和实践,终于将深度学习的基础知识看完了,虽然还有很多比较深入的内容没有涉及到,但也是感觉收获满满.因为是断断续续的学习做笔记写代码跑实验,所以笔记也零零散散的散落在每个角 ...
- 算法工程师面试问题及资料超详细合集(多家公司算法岗面经/代码实战/网课/竞赛等)
这里是算法江湖,传授AI武林秘籍. 资源目录: 一.算法工程师 Github.牛客网.知乎.个人博客.微信公众号.其他 二.机器学习 面试问题.资料.代码实战 三.深度学习 面试.资料.代码实战Pyt ...
- 一文弄懂元学习 (Meta Learing)(附代码实战)《繁凡的深度学习笔记》第 15 章 元学习详解 (上)万字中文综述
<繁凡的深度学习笔记>第 15 章 元学习详解 (上)万字中文综述(DL笔记整理系列) 3043331995@qq.com https://fanfansann.blog.csdn.net ...
最新文章
- Ubuntu创始人谈Windows 7、Chrome OS
- 转整型_SPI转can芯片CSM300详解、Linux驱动移植调试笔记
- 如何用Python读取Excel中图片?又如何用Python往Excel中写入图片?
- 有关计算机组成原理知识的论文,关于计算机组成原理的论文_计算机组成原理_图灵机的组成...
- 63 SD配置-交货凭证配置-定义交货的项目类别
- 通用easyui查询页面组件
- 解决Error: ENOENT: no such file or directory, scandir ‘xxx\node-sass\vendor‘
- 内容为王时代“内卷”急,安全风控一旦疏漏很要命!
- 【Python】python网络协议
- 七彩虹断剑C.A320M-K PRO V14安装PCI-E X4转m.2转接卡,并用m.2固态作为系统盘
- [Daozy][区块链 EOS 课程]第2课 EOS编译和启动
- python3中expected an indented block(缩进问题)
- Android开发笔记01-TextView01
- 适合前端Vue开发童鞋的跨平台Weex
- 文本 去除重复行(sublime Text3 ,正则表达式)
- 程序启动,遇到Process finished with exit code 1 解决方法
- 【Ubuntu 1】ubuntu的软件包
- 在Ubuntu虚拟机中安装VMware tools异常中断 Unable to start services for VMware Tools
- 2021年,薪酬最高的5种编程语言
- 中国裁判网-爬虫-2018.09.28
热门文章
- matlab xn,matlab定义变量x1到xn
- CryptoJS 加密的使用方法
- Could not connect to SMTP host: smtp.***.com, port: 465, response: -1
- 全球及中国粉煤灰PFA行业行业发展动态与前景趋势预测报告2022-2028年
- iOS 开发者证书、描述文件等详解
- 绿色版,便携版,破解版,精简版区别
- 商业周刊:MySpace兴衰沉浮启示录(转)
- 字体抗锯齿(-webkit-font-smoothing)
- 温室大棚控制系统智能轻松种菜
- 阅读笔记:黑碳对于冰雪辐射效应的影响