事实上,随着显示技术的发展,渲染管线将不复存在了,顶点着色器和渲染管线统一被流处理器(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_Position
和gl_PointSize
就是着色器中的特殊全局变量,它接收输入。
a_Position
就是我们定义的一个变量,