1、渲染线程与主线程的通信
两个线程之间的通信可以用如下方法: 在主线程中的 GLSurfaceView 实例可以调用
queueEvent( )方法传递一个 Runnable 给后台渲染线程,渲染线程可以调用 Activity 的 runOnUIThread()来传递事件 (event) 给主线程。
2、顶点数组中元素排列顺序
当定义三角形的时候,总是以逆时针的顺序排列项点; 这
称为卷曲顺序 ( winding order)。因为在任何地方都使用这种一致的卷曲顺序,可以优化
性能: 使用卷曲顺序可以指出一个三角形属于任何给定物体的前面或者后面,OpenGL 可
以忽略那些无论如何都无法被看到的后面的三角形。
3、opengl中物体构成
无论何时,如果想表示一个 OpenGL 中的物体,都要考虑如何用点、直线及三角形把它组合出来。
4、使Java层数据可以被OpenGL存取
在java层完成顶点的定义后,但是,在OpenGL可以存取它们之前,我们仍然需要完成另外一步。主要的问题是这些代码运行的环境与OpenGL运行的环境使用了不同的语言,我们需要理解如下两个主要的概念。
(1)当我们在模拟器或者设备上编译和运行Java代码的时候,它并不是直接运行在硬件上的;相反,它运行在一个特殊的环境上,即Dalvik虚拟机(Dalvik virtualmachine);运行在虚拟机上的代码不能直接访问本地环境(nativeenvironment),除非通过特定的API。
(2)Davik虚拟机还使用了垃圾回收(garbage collection)机制。这意味着,当虚拟机检测到一个变量、对象或者其他内存片段不再被使用时,就会把这些内存释放掉以备重用;它也能腾挪内存以提高空间使用效率。本地环境并不是这样工作的,它不期望内存块会被移来移去或者被自动释放。Android之所以这样设计,是因为开发者在开发程序的时候不必关心特定的CPU或者机器架构,也不必关心底层的内存管理。这通常都能工作得很好,除非要与本地系统交互比如OpenGL。OpenGL作为本地系统库直接运行在硬件上;没有虚拟机,也没有垃圾回收或内存压缩。
从 Java 调用本地代码
Dalvik方案是Android的主要特点之一,但是,如果代码运行在虚拟机内部,那它怎么与OpenGL通信呢?有两种技术,第一种技术是使用Java本地接口(JNI),这个技术已经由 Android 软件开发包提供了;当调用android.opengl.GLES20包里的方法时,软件开发包实际上就是在后台使用JNI调用本地系统库的。
把内存从 Java 堆复制到本地堆
第二种技术是改变内存分配的方式,Java 有一个特殊的类集合,它们可以分配本地内存块,并且把 Java 的数据复制到本地内存。本地内存可以被本地环境存取,而不受垃圾回收器的管控。
FloatBuffer用来在本地内存中存储数据。
FloatBuffer vertexData =ByteBuffer
.allocateDirect(tableVerticesWithTriangles.length * BYTES PER FLOAT).order(Byte0rder.native0rder()).asFloatBuffer();
让我们看一下代码的每一个部分。首先,我们使用ByteBuffer.allocateDirect()分配了一块本地内存,这块内存不会被垃圾回收器管理。这个方法需要知道要分配多少字节的内存块;因为顶点都存储在一个浮点数组里,并且每个浮点数有4个字节,所以这块内存的大小应该是tableVerticesWithTriangles.length* BYTES PER FLOAT。
5.顶点数据显示到屏幕上的过程
6.为什么使用着色器
在着色器出现之前,OpenGL只能使用一个固定的方法集合控制很少而有限的事情比如场景里有多少光线或者加多少雾;这些固定的 API很容易使用,但是它们很难扩展。你只能实现API提供的效果,而且仅此而已;几乎不能添加如卡通着色一样的自定义效果。
随着时间的推移,底层的硬件有了很大提高;设计OpenGL的人意识到这些API需要演进,并跟上这些变化。在OpenGLES2.0里,他们使用着色器加入了可编程API;为了保持简洁,他们把那些固定的API完全删除了,因此,用户必须使用着色器。现在用着色器控制每个顶点应该如何画到屏幕上,也控制所有点、直线和三角形上的每个片段应该如何绘制;这打开了一个新的、充满了无限可能的新世界。现在可以按每个像素实现光照和其他优美的效果,如卡通着色。只要可以用着色器语言表达出来,就可以加入任何理想的自定义效果。
7. OpenGL 怎样平滑地从一个点向另外一个点混合颜色
线性插值
直线:长度做线性插值
三角形:面积做线性插值
8、GL_TRIANGLE_FAN三角形扇的原理介绍
GL_TRIANGLE_FAN
是OpenGL中用于绘制三角形的一种模式。在这种模式下,第一个顶点被作为公共中心点,之后每个新加入的顶点与前一次添加的最后两个顶点以及公共中心点一起定义一个新的三角形。也就是说,每增加一个新顶点,都会生成一个新的扇形区域,并且所有三角形都共享这个公共中心点。
具体来说,当你定义了一系列的顶点,OpenGL会按照以下方式绘制三角形:第一个三角形由公共中心点、第二个顶点和第三个顶点构成;第二个三角形由公共中心点、第三个顶点和第四个顶点构成;依此类推,每增加一个顶点,都会与公共中心点和前一个顶点形成一个新的三角形。
这种模式非常适合用于绘制扇面或圆盘等具有公共中心区域的图形。例如,你可以使用GL_TRIANGLE_FAN
来绘制一个围绕某一点的旋转效果,或者创建一个以某点为中心的扇形区域。
需要注意的是,GL_TRIANGLE_FAN
模式的绘制效果会受到顶点顺序的影响,因此你需要确保顶点的顺序是正确的,以便生成你想要的图形。
总的来说,GL_TRIANGLE_FAN
原理基于公共中心点和新增顶点来定义并绘制一系列的三角形,实现具有公共中心区域的图形绘制。
代码示例如下:
private var tabUeVerticesWithTriangles = floatArrayOf(//x,y,r,g,b//桌面三角形0.0f, 0.0f, 1.0f, 1.0f, 1.0f,//公共中心点-0.5f, -0.5f, 0.7f, 0.7f, 0.7f,-0.25f, -0.5f, 0.0f, 1.0f, 0.0f,0.0f, -0.5f, 1.0f, 0.0f, 0.0f,0.25f, -0.5f, 0.0f, 1.0f, 0.0f,0.5f, -0.5f, 0.7f, 0.7f, 0.7f,0.5f, -0.25f, 0.0f, 1.0f, 0.0f,0.5f, 0.0f, 1.0f, 0.0f, 0.0f,0.5f, 0.25f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, 0.7f, 0.7f, 0.7f,0.25f, 0.5f, 0.0f, 1.0f, 0.0f,0.0f, 0.5f, 1.0f, 0.0f, 0.0f,-0.25f, 0.5f, 0.0f, 1.0f, 0.0f,-0.5f, 0.5f, 0.7f, 0.7f, 0.7f,-0.5f, 0.25f, 0.0f, 1.0f, 0.0f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f,-0.5f, -0.25f, 0.0f, 1.0f, 0.0f,-0.5f, -0.5f, 0.7f, 0.7f, 0.7f,//桌面分割线-0.5f, 0.0f, 1.0f, 0.0f, 0.0f,0.5f, 0.0f, 0.0f, 1.0f, 0.0f,//木槌0.0f, -0.25f, 0.0f, 0.0f, 1.0f,0.0f, 0.25f, 1.0f, 0.0f, 0.0f,//冰球0.0f, 0.0f,0.0f,1.0f,1.0f)override fun onDrawFrame(gl: GL10?) {glClear(GL_COLOR_BUFFER_BIT)glDrawArrays(GL_TRIANGLE_FAN, 0, 18)glDrawArrays(GL_LINES, 18, 2)glDrawArrays(GL_POINTS, 20, 1)glDrawArrays(GL_POINTS, 21, 1)glDrawArrays(GL_POINTS, 22, 1)}
运行截图如下:
9.直线颜色线性插值
示例如下
直线起始点坐标(-0.5,0),终点坐标(0.5,0)
片段着色器代码如下:
precision mediump float;
varying vec4 v_Color;
void main(){
if(gl_FragCoord.x>-0.5 && gl_FragCoord.x < 0.5){
gl_FragColor.r = v_Color.r*(gl_FragCoord.x +0.5);
gl_FragColor.g = v_Color.g*(gl_FragCoord.x +0.5);
gl_FragColor.b = v_Color.b*(gl_FragCoord.x +0.5);
} else {
gl_FragColor =v_Color;
}
}绘制直线如下:
glDrawArrays(GL_LINES, 18, 2)
10.OpenGL 如何把坐标映射到屏幕
归一化设备坐标:
无论是x还是y坐标,OpenG 都会把屏幕映射到[-1,1]的范围内。这就意味着屏幕的左边对应x轴的-1,而屏幕的右边对应 +1;屏幕的底边会对应y轴的-1,而屏幕的顶边就对应 +1,如图所示。
不管屏幕是什么形状和大小,这个坐标范围都是一样的,如果我们需要在屏幕上显示任何东西,都需要在这个范围内绘制它们。
11.正交投影矩阵——弥补屏幕的宽高比
在Android OpenGL中,正交投影矩阵的主要作用是定义一个视景体(Viewing Volume),即一个三维空间中的长方体区域,它决定了哪些物体应该被渲染到屏幕上。这个长方体区域被称为正交投影体。
正交投影的一个关键特性是,无论物体距离观察点的远近如何,它们在投影后的二维图像中都会保持相同的尺寸和形状。这意味着,在正交投影下,物体的大小不会因为距离的远近而改变,这与透视投影不同,透视投影中物体的大小会随着距离的远近而发生变化。
在OpenGL中,正交投影矩阵通常用于渲染那些不需要考虑透视效果的场景,比如2D游戏、UI界面等。在这些场景中,我们更关心的是物体在屏幕上的精确位置和大小,而不是它们因为距离观察点远近而产生的视觉变化。
在OpenGL中设置正交投影矩阵时,通常需要指定一个近裁剪面和一个远裁剪面,以及投影体的左右、上下边界。这些参数定义了投影体的范围和形状,从而决定了哪些物体应该被包含在渲染结果中。
总之,正交投影矩阵在Android OpenGL中扮演着定义渲染区域和保持物体形状尺寸不变的重要角色。
使用正交投影,不管多远或多近,所有的物体看上去大小总是相同的。
从虚拟坐标回到归一化设备坐标
当我们使用正交投影把虚拟坐标变换回归一化设备坐标时,实际上定义了三维世界内部的一个区域。在这个区域内的所有东西都会显示在屏幕上,而区域外的所有东西都会被裁剪掉。在下面的图像中,我们能在一个封闭的立方体内看到一个简单的场景(见图1)。当我们使用一个正交投影矩阵把这个立方体映射到屏幕上时,就会看到如图2所示的正交投影。
正交投影矩阵会把所有在左右之间、上下之间和远近之间的事物映射到归一化设备坐标中从 -1到1的范围,在这个范围内的所有事物在屏幕上都是可见的。
利用正交投影矩阵改变立方体的大小,以使我们可以在屏幕上看到或多或少的场景我们也能改变立方体的形状弥补屏幕的宽高比的影响。
正交投影矩阵方法
orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near, float far)
参数介绍
foat[]m:目标数组,这个数组的长度至少有16个元素,这样它才能存储正交投影矩阵;
int mOffset:结果矩阵起始的偏移值;
float left;x轴的最小范围;
float right:x轴的最大范围;
float bottom:y轴的最小范围;
float top:y轴的最大范围;
floatnear:z轴的最小范围;
float far:z轴的最大范围。
x,y轴放大或缩小实现:left,right,bottom,top乘以缩放比例即可。
x,y轴平移实现:left,right,bottom,top加减平移数值。
例如:
var aspectRatio: Float = 0.0fif (width > height) {//landscapeaspectRatio = (width.toFloat() / height.toFloat())} else {//portraitaspectRatio = (height.toFloat() / width.toFloat())}if (width > height) {//landscape水平放大orthoM(projectionMatrix, 0, -aspectRatio*0.2f, aspectRatio*0.2f, -1f, 1f, -1f, 1f);} else {//portrait垂直方向平移orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio+0.5f, aspectRatio+0.5f, -1f, 1f);}
12.透视投影矩阵
Android OpenGL的透视投影是3D图形渲染中的关键技术之一,它模拟了人类视觉系统中物体远近大小变化的感知。将现实3D模型转到2D屏幕上显示,下面是对Android OpenGL透视投影的详细解析:
(1) 基本原理
透视投影是从一个虚拟的观察点(或称为相机位置)出发,将3D空间中的物体投影到2D平面上。这个过程中,物体距离观察点越近,其在投影平面上显得越大;距离越远,则显得越小。这种投影方式能够产生强烈的空间感和立体感。
(2) 投影矩阵
实现透视投影的关键是设置透视投影矩阵。这个矩阵定义了从3D世界坐标到2D裁剪坐标的映射关系。
对宽高比和视野进行调整
如下通用的投影矩阵,它允许我们调整视野以及屏幕的宽高比。
参数说明:
a:如果我们想象一个相机拍摄的场景,这个变量就代表那个相机的焦距。焦距是由1/tan(视野/2)(tan表示正切函数)计算得到的。这个视野必须小于180度。比如,一个90度的视野,它的焦距会被设置为1/tan(90°/2),也就是1/1或者1
aspect:屏幕的宽高比,它等于宽度/高度
f:到远处平面的距离,必须是正值且大于到近处平面的距离
n:到近处平面的距离,必须是正值。比如,如果此值被设为1,那近处平面就位于一个z值为-1处
(3) 参数设置
透视投影矩阵的设置通常涉及以下几个关键参数:
- 视场角(Field of View, FOV):决定了观察者视野的大小,通常是以度为单位的夹角。较小的视场角会产生较大的放大效果,使得场景看起来更加紧凑;较大的视场角则会产生类似缩小的效果,是场景有较宽的视野。A为垂直视场角,B为水平视场角。
fov数值变化,引起场景变化。
- 宽高比(Aspect Ratio):通常是视口的宽度与高度的比值。这个参数对于保持场景的比例正确性非常重要。
- 近裁剪面(Near Clipping Plane)和远裁剪面(Far Clipping Plane):定义了视椎体的前后边界。位于这两个平面之间的物体才会被投影并渲染到屏幕上。
(4) 投影过程
- 顶点变换:首先,3D物体的顶点坐标会经过模型视图变换,从局部坐标系转换到世界坐标系,再进一步转换到相机坐标系(或称为观察坐标系)。
- 透视除法:在透视投影中,经过变换后的顶点坐标会进行透视除法。这一步是透视投影的关键,它确保了近处的物体在屏幕上显得大,远处的物体显得小。
- 裁剪与归一化:经过透视除法的坐标会进入裁剪空间,并进行裁剪测试。在裁剪过程中,位于视椎体之外的部分会被裁剪掉。剩余的坐标会经过归一化处理,最终映射到标准设备坐标系(NDC)上,准备进行后续的光栅化过程。
(5)注意事项
- 深度测试:在透视投影中,深度测试是确保正确渲染物体顺序的关键。通过比较片元的深度值,可以确定哪些物体在前面,哪些在后面,从而避免错误的遮挡关系。
- 优化性能:透视投影的计算相对复杂,可能会对性能产生影响。因此,在开发过程中需要注意优化算法和减少不必要的计算。
综上所述,Android OpenGL的透视投影通过模拟人类视觉系统的特点,实现了对3D场景的逼真渲染。掌握透视投影的原理和技巧对于创建高质量的3D图形应用至关重要。
13.模型投影矩阵
利用模型矩阵移动物体。
private val modelMatrix = FloatArray(16)setIdentityM(modelMatrix,0);
translateM(modelMatrix,0,0f,0f,-2.5f);//平移物体val temp = FloatArray(16)
multiplyMM(temp,0,projectionMatrix,0,modelMatrix,0);//透视投影矩阵乘以模型矩阵
System.arraycopy(temp,0,projectionMatrix,0,temp.size);
14.旋转矩阵
利用旋转矩阵,基于某个角度或者向量(如,围绕x,y,z轴))旋转物体。
private val modelMatrix = FloatArray(16)setIdentityM(modelMatrix,0);
rotateM(modelMatrix,0,-60f,1f,0f,0f)//围绕x轴,旋转物体-60度val temp = FloatArray(16)
multiplyMM(temp,0,projectionMatrix,0,modelMatrix,0);//透视投影矩阵乘以模型矩阵
System.arraycopy(temp,0,projectionMatrix,0,temp.size);