惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
The Register - Security
The Register - Security
S
Secure Thoughts
有赞技术团队
有赞技术团队
Hacker News - Newest:
Hacker News - Newest: "LLM"
月光博客
月光博客
Engineering at Meta
Engineering at Meta
www.infosecurity-magazine.com
www.infosecurity-magazine.com
WordPress大学
WordPress大学
N
News | PayPal Newsroom
F
Fortinet All Blogs
Forbes - Security
Forbes - Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Y
Y Combinator Blog
L
LINUX DO - 最新话题
Google Online Security Blog
Google Online Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
阮一峰的网络日志
阮一峰的网络日志
Application and Cybersecurity Blog
Application and Cybersecurity Blog
V2EX - 技术
V2EX - 技术
H
Heimdal Security Blog
G
Google Developers Blog
Vercel News
Vercel News
Martin Fowler
Martin Fowler
TaoSecurity Blog
TaoSecurity Blog
腾讯CDC
Cyberwarzone
Cyberwarzone
罗磊的独立博客
S
Securelist
T
Tor Project blog
Recorded Future
Recorded Future
小众软件
小众软件
Attack and Defense Labs
Attack and Defense Labs
L
LINUX DO - 热门话题
The Cloudflare Blog
爱范儿
爱范儿
S
Security @ Cisco Blogs
雷峰网
雷峰网
Schneier on Security
Schneier on Security
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
S
Schneier on Security
大猫的无限游戏
大猫的无限游戏
Recent Announcements
Recent Announcements
Blog — PlanetScale
Blog — PlanetScale
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
Latest news
Latest news
M
MIT News - Artificial intelligence

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
OpenGL 学习系列---纹理
音视频开发进阶 · 2018-03-21 · via 音视频开发进阶

一个专注音视频领域的小圈子

避免图片丢失,建议阅读微信原文:

https://mp.weixin.qq.com/s/Y3FsQrJJWQogD3PWWo1cVQ 接下来探索纹理了。 纹理,简单的理解就是一副图像。而把一副图像映射到图形上的过程,叫做纹理映射

比如有如下图形和三角形,想要把图形中的一部分映射到三角形上。

结果就是这样的:

这就是纹理映射的一个小小例子。

基本原理

要注意到,OpenGL 绘制的物体是 3D 的,而纹理是 2D 的,那么纹理映射就是将 2D 的纹理映射到 3D 的物体上,可以想象成用一张纸裹着一个物体一样,不过要按照一定规律来。

OpenGL 中绘制的物体是有坐标系的,每个点都对应 x、y、z 坐标,而纹理也有着它的坐标,只要 3D 物体中的每个点都对应了 2D 纹理中的某个点,那么就可以把纹理映射到 3D 物体上去了。

纹理的坐标,叫做纹理坐标系。它的范围只有 $[0,0]$ 到 $[1,1]$ 。

它的坐标原点位于左下角,水平向右为 S 轴,竖直向上为 Y 轴。不论实际的纹理图片尺寸大小如何,横向、纵向坐标最大值都是 1 。

例如:实际图为 512 x 256 像素分辨率,则横向第 512 个像素对应纹理坐标为 1 ,纵向第 256 个像素对应纹理坐标为 1 。不过,纹理图最好是采用像素为 2 的 n 次方的纹理图。

纹理映射的基本思想就是:首先为图元中的每个顶点指定恰当的纹理坐标,然后通过纹理坐标在纹理图中可以确定选中的纹理区域,最后将选中纹理区域中的内容根据纹理坐标映射到指定的图元上。

纹理映射在 OpenGL 的渲染管线上的体现:在渲染管线中,先进行顶点着色器,绘制出物体的大致形状,之后会进行光栅化,将物体光栅化为许多片段组成,然后再进行片段着色器,将图形的每个片段进行着色。

那么就需要在 顶点着色器 中将纹理的坐标传入,在光栅化阶段,纹理坐标将根据 顶点着色器 对它的处理以及 片段和各顶点的位置关系 插值产生,然后才是将插值计算后的结果传入到片段着色器中。

着色器操作

相比直接绘制图形,使用纹理后,着色器也要改变了。

顶点着色器:

attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;
uniform mat4 u_Matrix;

void main() {
    v_TextureCoordinates = a_TextureCoordinates ;
    gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
}

在顶点着色器中多了 v_TextureCoordinates 变量,它是 varying 类型,意思为可变类型,在光栅化处理时会对该变量进行处理,随后传入到片段着色器中。

片段着色器

precision mediump float;
uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;

void main(){
	// 未使用纹理的颜色赋值 : gl_FragColor = u_Color;
    gl_FragColor = texture2D(u_TextureUnit,v_TextureCoordinates);
}

v_TextureCoordinates1变量就是接受来自顶点着色器传的值,u_TextureUnit变量就是使用的采样器,类型是sampler2D

使用纹理后的片段着色器要使用 texture2D 函数给颜色赋值。

texture2D函数的作用就是采样,从纹理中采取像素赋值给 gl_FragColor变量,也就是最后的颜色。

上层代码

大致了解了着色器代码,接着就是上层的 Java 代码了。

和要创建一个 OpenGL ProgramId 类似,使用纹理也需要创建一个纹理 ID。

	 /**
     * 返回加载图像后的 OpenGl 纹理的 ID
     * @param context
     * @param resourceId
     * @return
     */
    public static int loadTexture(Context context, int resourceId) {
        final int[] textureObjectIds = new int[1];
        glGenTextures(1, textureObjectIds, 0);
        if (textureObjectIds[0] == 0) {
            Timber.d("Could not generate a new OpenGL texture object.");
            return 0;
        }
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = false;
        final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);

        if (bitmap == null) {
            Timber.d("resource Id could not be decoded");
            glDeleteTextures(1, textureObjectIds, 0);
            return 0;
        }

        glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);

        // 设置缩小的情况下过滤方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        // 设置放大的情况下过滤方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // 加载纹理到 OpenGL,读入 Bitmap 定义的位图数据,并把它复制到当前绑定的纹理对象
        // 当前绑定的纹理对象就会被附加上纹理图像。
        texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);

        bitmap.recycle();
        
        // 为当前绑定的纹理自动生成所有需要的多级渐远纹理
        // 生成 MIP 贴图
        glGenerateMipmap(GL_TEXTURE_2D);

        // 解除与纹理的绑定,避免用其他的纹理方法意外地改变这个纹理
        glBindTexture(GL_TEXTURE_2D, 0);

        return textureObjectIds[0];
    }
  1. 首先使用 glGenTextures 创建纹理 ID。
  2. 如果创建失败,则使用 glDeleteTextures 删除并退出。
  3. 创建成功之后,使用 glBindTexture 函数将纹理 ID 和纹理目标绑定。
  4. 之后会设置纹理在缩小和放大情况下的过滤方式。
  5. 再使用 texImage2D 将纹理目标和 Bitmap 图片绑定。
  6. 使用 glGenerateMipmap 函数生成多级渐远纹理和 MIP 纹理贴图。
  7. 再使用 glBindTexture函数解除绑定。

glBindTexture 函数

这里要重点说一下 glBindTexture 函数。

它的作用是绑定纹理名到指定的当前活动纹理单元,当一个纹理绑定到一个目标时,目标纹理单元先前绑定的纹理对象将被自动断开。纹理目标默认绑定的是 0 ,所以要断开时,也再将纹理目标绑定到 0 就好了。

所以在代码的最后调用了 glBindTexture(GL_TEXTURE_2D, 0) 来解除绑定。

当一个纹理被绑定时,在绑定的目标上的 OpenGL 操作将作用到绑定的纹理上,并且,对绑定的目标的查询也将返回其上绑定的纹理的状态。

也就是说,这个纹理目标成为了被绑定到它上面的纹理的别名,而纹理名称为 0 则会引用到它的默认纹理。所以,当后续对纹理目标调用 glTexParameteri 函数设置过滤方式,其实也是对纹理设置的过滤方式。

绑定纹理中的值

创建并且设置了纹理着色器ID之后,就需要绑定并设置在着色器语言中的变量了。

		// 绑定着色器脚本中的变量
        uTextureUnitAttr = glGetUniformLocation(mProgram, U_TEXTURE_UNIT)
		mTextureId = TextureHelper.loadTexture(mContext,R.drawable.texture)
		// 激活纹理单元
        glActiveTexture(GL_TEXTURE0)
        // 绑定纹理目标
        glBindTexture(GL_TEXTURE_2D, mTextureId)
        // 给片段着色器中的采样器变量 sample2D 赋值
        glUniform1i(uTextureUnitAttr, 0)

在着色器脚本中定义了 uniform 类型的采样器变量 sampler2D,在上层的应用代码需要将它绑定并赋值。而 varying 类型的变量由顶点着色器传过去,不需要另外赋值了。

接下来要使用 glActiveTexture 函数激活纹理单元。在一个系统中,纹理单元的数据是有限的,在源码中从 GL_TEXTURE0 到 GL_TEXTURE31 共定义了三十二个纹理单元,但具体数量根据机型而定。

通过 GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 常量可以查询到。

   var intBuffer:IntBuffer = IntBuffer.allocate(1)
   glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,intBuffer)
   LogUtil.d("max combined texture image units " + intBuffer[0])

激活了纹理单元,还需要再绑定纹理目标。

一个纹理单元包含了多个类型纹理目标,如:GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP 等等。

因为纹理单元是纹理的一个别名,所以对纹理单元所做的操作,都相当于对纹理操作的。把一些对纹理所做的操作提取到函数里,最后再加载纹理,并绑定到纹理目标上。

使用glUniform1i函数为采样器进行赋值为 0 ,这是和激活纹理单元相对应的。因为激活的纹理单元为 0 ,所以赋值也是为 0 。如果这里不一致,直接就看不到任何东西了。

实际效果

当绑定并设置好片段着色器中的值之后,接下来的流程就和绘制基本图形一样了。

具体的绘制操作都在片段着色器里面定义了,而在上层代码中就不用花费很多心思了,在顶点着色器不变的情况下,甚至可以只改变片段着色器的值来绘制不同的纹理效果。

总结 & 名词混淆点

在上面既是纹理单元又是纹理目标的很容易搞混,梳理一下概念:

形如 GL_TEXTURE0、GL_TEXTURE1、GL_TEXTURE2 的就是纹理单元,一台机子上纹理单元数量是有限的,依具体机型而定。而 glActiveTexture 则是激活具体的纹理单元。

一个纹理单元又包含多个类型的纹理目标,有:GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP 等等。

通过 glGenTextures 函数生成的 int 类型的值就是纹理,通过 glBindTexture 函数将纹理目标和纹理绑定后,对纹理目标所进行的操作都反映到对纹理上。

纹理目标需要通过 texImage2D 函数附加上 Bitmap 位图。

具体代码详情,可以参考我的 Github 项目:

https://github.com/glumes/AndroidOpenGLTutorial

参考

  1. http://blog.csdn.net/opengl_es/article/details/19852277
  2. http://blog.csdn.net/artisans/article/details/76695614

原创文章,转载请注明来源:    OpenGL 学习系列---纹理