OpenGL 学习系列--基础的绘制流程(1),贼好用的Android学习路线集合

由于这两个着色器对于最后图形显示效果至关重要,并且它们还是可以通过编程来控制的,这也是为什么可编程渲染管线要优于固定编程管线了。

事实上,随着显示技术的发展,渲染管线将不复存在了,顶点着色器和渲染管线统一被流处理器(Stream Processors)所取代。

但是目前手机上 OpenGL 还是使用渲染管线中,有了渲染管线,我们就可以完成点的形状绘制和着色两大问题了,接下来的工作也是围绕这条渲染管线开始的。

内存拷贝

当定义完了顶点坐标,并且明确了下一步:顶点坐标将要通过渲染管线进行一系列处理,那么接下来就是如何把顶点坐标传递给渲染管线了。

OpenGL 的实现是由显示设备厂商提供的,它作为本地系统库直接运行在硬件上。而我们定义的顶点 Java 代码是运行在虚拟机上的,这就涉及到了如何把 Java 层的内存复制到 Native 层了。

一种方法是直接使用JNI开发,直接调用本地系统库,也就是用 C++ 来开发 OpenGL,这种实现肯定要学会的。

另一种方法就是在 Java 层把内存块复制到 Native 层。

使用ByteBuffer.allocateDirect()方法就可以分配一块 Native 内存,这块内存不会被 Java 的垃圾回收器管理。

它的使用方法大致都一样,抽出公共的模板:

// 声明一个字节缓冲区 FloatBuffer
private FloatBuffer floatBuffer;
// 定义顶点数据
float[] vertexData = new float[16];
// FloatBuffer 初始化工作并放入顶点数据
floatBuffer = ByteBuffer
.allocateDirect(vertexData.length * Constant.BYTES_PRE_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);

allocateDirect方法分配了内存并指定了大小之后,下一步就是告诉 ByteBuffer 按照本地字节序组织它的内容。本地字节序是指,当一个值占用多个字节时,比如 32 位整型数,字节按照从最重要位到最不重要位或者相反顺序排列。

接下来asFloatBuffer方法可以得到一个反映底层字节的 FloatBuffer 类实例,避免直接操作单独的字节,而是使用浮点数。

最后,通过put方法就可以把数据从 Java 层内存复制到 Native 层了,当进程结束时,这块内存就会被释放掉。

顶点着色器

接下来可编程的部分了,定义着色器(Shader)程序。

使用不同的着色器对输入的图元数据执行计算操作,判断它们的位置、颜色,以及其他渲染属性。

首先是顶点着色器。

在渲染管线中传输的每个顶点坐标位置,OpenGL 都会调用一个顶点着色器来处理顶点相关的数据,这个处理过程可以很复杂,也可以很简单。

想要定义一个着色器程序,还要通过一种特殊的语言去编写:OpenGL Shading Language,简称GLSL.

GLSL语言类似于 C 语言或者 Java 语言,它的程序入口也是一个名为main的函数。关于 GLSL 的部分,完全可以单独写一篇博客了,暂时先不详细阐述。

下面就是一个简单的顶点着色器程序:

attribute vec4 a_Position;
void main()
{
gl_Position = a_Position;
gl_PointSize = 30.0;
}

着色器类似于一个函数调用的方式——数据传输进来,经过处理,然后再传输出去。

其中,gl_Positiongl_PointSize就是着色器中的特殊全局变量,它接收输入。

a_Position就是我们定义的一个变量,它是vec4类型的。而attribute只能存在于顶点着色器中,一般用于保存顶点数据,它可以在数据缓冲区中读取数据。

数据缓存区中的顶点坐标会赋值给 a_Position ,a_Position 会传递给 gl_Position。

而 gl_PointSize 则固定了点的大小为 30。

有了顶点着色器,就能够为每个顶点生成最终的位置,接下来就是定义片段着色器。

根据上图的渲染管线,顶点着色器到片段着色器之间,还要经过组装图元光栅化图元

光栅化技术

移动设备的显示屏由成百上千个小的、独立的部件组成,他们称为像素。每个像素通常由三个单独的子组件构成,它们发出红色、绿色和蓝色的光,因为每个像素都非常小,人的眼睛会把红色、绿色和蓝色的光混合在一起,从而创造出巨量的颜色范围。

OpenGL 就是通过 光栅化 技术的过程把每个点、直线及三角形分解成大量的小片段,它们可以映射到移动设备显示屏的像素上,从而生成一幅图像。这些片段类似于显示屏上的像素,每一个都包含单一的纯色。

如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

OpenGL 通过光栅化技术把一条直线映射为一个片段集合,显示系统通常会把这些片段直接映射到屏幕上的像素,结果一个片段就对应一个像素。

明白了这样的显示原理,就可以在其中做一些操作了,这就是片段着色器的功能了。

片段着色器

片段着色器的主要目的就是告诉 GPU 每个片段的最终颜色应该是什么。

对于基本图元的每个片段,片段着色器都会被调用一次,因此,如果一个三角形被映射到 10000 个片段,那么片段着色器就会被调用 10000 次。

下面就是一个简单的片段着色器程序:

precision mediump float;
uniform vec4 u_Color;
void main()
{
gl_FragColor = u_Color;
}

其中,gl_FragColor变量就是 OpenGL 最终渲染出来的颜色的全局变量,而u_Color就是我们定义的变量,通过在 Java 层绑定到 u_Color变量并给它赋值,就会传递到 Native 层的gl_FragColor中。

而第一行的mediump指的就是片段着色器的精度了,有三种可选,这里用中等精度就行了。uniform则表示该变量是不可变的了,也就是固定颜色了,目前显示固定颜色就好了。

编译 OpenGL 程序

明白了着色器的功能和光栅化技术之后,对渲染管线的流程也就更加清楚了,接下来就是编译 OpenGL 的程序了。

编译 OpenGL 程序基本流程如下:

  • 编译着色器
  • 创建 OpenGL 程序和着色器链接
  • 验证 OpenGL 程序
  • 确定使用 OpenGL 程序
编译着色器

创建新的文件编写着色器程序,然后再从文件以字符串的形式中读取文件内容。这样会比把着色器程序写成字符串的形式更加清晰。

当读取了着色器程序内容之后,就可以编译了。

// 编译顶点着色器
public static int compileVertexShader(String shaderCode) {
return compileShader(GL_VERTEX_SHADER, shaderCode);
}

// 编译片段着色器
public static int compleFragmentShader(String shaderCode) {
return compileShader(GL_FRAGMENT_SHADER, shaderCode);
}

// 根据类型编译着色器
private static int compileShader(int type, String shaderCode) {
// 根据不同的类型创建着色器 ID
final int shaderObjectId = glCreateShader(type);
if (shaderObjectId == 0) {
return 0;
}
// 将着色器 ID 和着色器程序内容连接
glShaderSource(shaderObjectId, shaderCode);
// 编译着色器
glCompileShader(shaderObjectId);
// 以下为验证编译结果是否失败
final int[] compileStatsu = new int[1];
glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatsu, 0);
if ((compileStatsu[0] == 0)) {
// 失败则删除
glDeleteShader(shaderObjectId);
return 0;
}
return shaderObjectId;
}

以上程序主要就是通过glCreateShader方法创建了着色器 ID,然后通过glShaderSource连接上着色器程序内容,接下来通过glCompileShader编译着色器,最后通过glGetShaderiv验证是否失败。

glGetShaderiv函数比较通用,在着色器阶段和 OpenGL 程序阶段都会通过它来验证结果。

创建 OpenGL 程序和着色器链接

接下来就是创建 OpenGL 程序并加着色器加进来。

public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
// 创建 OpenGL 程序 ID
final int programObjectId = glCreateProgram();
if (programObjectId == 0) {
return 0;
}
// 链接上 顶点着色器
glAttachShader(programObjectId, vertexShaderId);
// 链接上 片段着色器
glAttachShader(programObjectId, fragmentShaderId);
// 链接着色器之后,链接 OpenGL 程序
glLinkProgram(programObjectId);
final int[] linkStatus = new int[1];
// 验证链接结果是否失败
glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] == 0) {
// 失败则删除 OpenGL 程序
glDeleteProgram(programObjectId);
return 0;
}
return programObjectId;
}

首先通过glCreateProgram程序创建 OpenGL 程序,然后通过glAttachShader将着色器程序 ID 添加上 OpenGL 程序,接下来通过glLinkProgram链接 OpenGL 程序,最后通过glGetProgramiv来验证链接是否失败。

验证 OpenGL 程序

链接了 OpenGL 程序后,就是验证 OpenGL 是否可用。

public static boolean validateProgram(int programObjectId) {
glValidateProgram(programObjectId);
final int[] validateStatus = new int[1];
glGetProgramiv(programObjectId, GL_VALIDATE_STATUS, validateStatus, 0);
return validateStatus[0] != 0;

}

通过glValidateProgram函数验证,并再次通过glGetProgramiv函数验证是否失败。

确定使用 OpenGL 程序

当一切完成后,就是确定使用该 OpenGL 程序了。

// 创建 OpenGL 程序过程
public static int buildProgram(Context context, int vertexShaderSource, int fragmentShaderSource) {
int program;

int vertexShader = compileVertexShader(
TextResourceReader.readTextFileFromResource(context, vertexShaderSource));

int fragmentShader = compleFragmentShader(
TextResourceReader.readTextFileFromResource(context, fragmentShaderSource));

program = linkProgram(vertexShader, fragmentShader);

validateProgram(program);

return program;
}

// 创建完毕后,确定使用
mProgram = ShaderHelper.buildProgram(context, R.raw.point_vertex_shader

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

总结

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

一线互联网面试专题

379页的Android进阶知识大全

379页的Android进阶知识大全

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

,欢迎戳这里加入程序员的圈子,让我们一起学习成长!**](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值