1 OpenGL 简介
The Industry's Foundation for High Performance Graphics(高性能图形的行业基础) OpenGL(全写Open Graphics Library)是个定义了一个跨编程语言、跨平台的编程接口的规格,它用于三维图象(二维的亦可)。OpenGL是个专业的图形程序接口,是一个功能强 大,调用方便的底层图形库。由于许多在计算机界具有领导地位的计算机公司纷纷采用OpenGL作为三维图形应用程序界面,而且它具有广泛的移植性,因此是从事三维图形技术人员必须掌握的。
2 OpenGL ES
专门为内嵌以及我们用的手机移动设计的2D/3D轻量级图形库。OpenGL ES 1.x版和2.x版本,Android 平台在SDK2.0之前支持OpenGL ES1.1 ,SDK2.0以后支持OpenGL ES 2.0.
OpenGL ES 1.X版
对于固定功能硬件:OpenGL ES1.1是相对于opengl1.5规范定义的,它强调API的硬件加速,但完全向后兼容1.0。它提供了增强的功能、改进的图像质量和优化以提高性能,同时减少内存带宽使用以节省电力.
OpenGL ES 2.X版 用于可编程硬件:OpenGL ES2.0是相对于OpenGL 2.0规范定义的,它强调可编程的3D图形管道,它具有创建着色器和编程对象的能力,以及用OpenGL ES着色语言编写顶点和片段着色器的能力。opengles2.0不支持opengles1.x的固定函数转换和片段管道。
2 Android OpenGL ES 开发框架
OpenGL ES 开发包 Android.opengl.*; GLSurfaceView //OPenGL视图 Renderer//渲染器
3 基本图形的绘制(2D)
在openGL里基础的就是绘制三角形,其他多边形都是基于分割成多个三角形来实现的我们这里来看三角形和正方形.
3.1 渲染器的实现
首先需要实现渲染器接口Render并且重写3个函数一个是在 创建窗口的时候 会调用的onSurfaceCreated:
lass GLRender2 : GLSurfaceView.Renderer {
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
gl?.glMatrixMode(GL10.GL_MODELVIEW)
//创建窗口的时候调用的函数
//告诉系统对透视修正/优化新能
gl?.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_NICEST)
//设置清理屏幕的颜色
//绘图的时候是一直循环的
//参数值0~1
gl?.glClearColor(0.5f,0f,0f,1f)
//启用深度缓存
//绘制物体能背面不可见
gl?.glEnable(GL10.GL_DEPTH_TEST)
}
}
以及窗口改变的时候会调用的
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
//窗口改变的时候对一些数据调整
var radio:Float = width.toFloat() / height
//设置视口 OpenGl 场景的大小
gl?.glViewport(0,0,width,height)
//设置投影矩阵为透视投影
gl?.glMatrixMode(GL10.GL_PROJECTION)
//重制投影矩阵 (单位矩阵)
gl?.glLoadIdentity()
//创建一个透视投影矩阵
gl?.glFrustumf(-radio,radio,-1f,1f,1f,10f)
//!以下两句声明,以后所有的变换都是针对模型(即我们绘制的图形)
gl?.glMatrixMode(GL10.GL_MODELVIEW)
gl?.glLoadIdentity()
}
和绘制图像时候调用onDrawFrame的
override fun onDrawFrame(gl: GL10?) {
//绘制
gl?.glClear(GL10.GL_COLOR_BUFFER_BIT or GL10.GL_DEPTH_BUFFER_BIT )
//设置模型视图矩阵
gl?.glMatrixMode(GL10.GL_MODELVIEW)
//重置矩阵
gl?.glLoadIdentity()
//视点变化
//眼睛位置 观察方向
GLU.gluLookAt(gl,0f,0f,3f,0f,0f,0f,0f,1f,0f)
gl?.glEnableClientState(GL10.GL_VERTEX_ARRAY)
gl?.glVertexPointer(3,GL10.GL_FLOAT,0,triggerBuffer)
绘制流程首先告诉OpenGL ES允许设置顶点: gl?.glEnableClientState(GL10.GL_VERTEX_ARRAY)
之前要定义三角形顶点数组:
//三角形
var triggerBuffer: FloatBuffer? = BufferUtil.iFloatBuffer(floatArrayOf(0f,1f,0f,
-1f,-1f,0f,
1f,-1f,0f))
绘制: gl?.glVertexPointer(3,GL10.GL_FLOAT,0,triggerBuffer)
四边形:
//正方形
gl?.glLoadIdentity()
gl?.glColor4f(0.5f, 0.5f, 1.0f, 1.0f);
gl?.glTranslatef(1.0f,0.0f,-4.0f)
//gl?.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);
//旋转
gl?.glRotatef(rotateQuad,0.0f,1.0f,0.0f)
gl?.glVertexPointer(3,GL10.GL_FLOAT,0,quarterBuffer)
//三角形带(多个三角形)
gl?.glDrawArrays(GL10.GL_TRIANGLE_STRIP,0,4)
3.2 图形变换
首先讲个概念OpenGL坐标系:
OpenGL 采用下图的右手坐标系
- 转移 在3D空间移动物体(Translate)
- 旋转 绕x,y,或者z(Rote)
- 缩放 改变大小,放大缩小 (Scale)
函数: glTranslatef :平移 glRotatef :旋转 glScalef:缩放
//将三角形在z轴上移动
gl?.glTranslatef(-3f, 0.0f, -2.0f)
//放大
gl?.glScalef(2.0f,2.0f,2.0f)
4 高级图形的绘制(3D)
这边就以20面体作为例子 glDrawElements(mode count type,indices) 原理是绘制一系列的三角形,并且通过索引数组。
索引数组 就是我们在绘制一个复杂物体时,可以使用glDrawElements方法绘制一系列三角形,这一组特殊的数据需要对应顶点数组中每一个数据的索引号,我们把这组特殊的数据称之为"索引数组"
private val vertices = BufferUtil.iFloatBuffer(floatArrayOf(
0f, -0.525731f, 0.850651f,
0.850651f, 0f, 0.525731f,
0.850651f, 0f, -0.525731f,
-0.850651f, 0f, -0.525731f,
-0.850651f, 0f, 0.525731f,
-0.525731f, 0.850651f, 0f,
0.525731f, 0.850651f, 0f,
0.525731f, -0.850651f, 0f,
-0.525731f, -0.850651f, 0f,
0f, -0.525731f, -0.850651f,
0f, 0.525731f, -0.850651f,
0f, 0.525731f, 0.850651f))
//索引
private val icosahedronFaces = BufferUtil.iBytetBuffer(byteArrayOf(
1, 2, 6,
1, 7, 2,
3, 4, 5,
4, 3, 8,
6, 5, 11,
5, 6, 10,
9, 10, 2,
10, 9, 3,
7, 8, 9,
8, 7, 0,
11, 0, 1,
0, 11, 4,
6, 2, 10,
1, 6, 11,
3, 5, 10,
5, 4, 11,
2, 7, 9,
7, 1, 0,
3, 9, 8,
4, 8, 0))
绘制:
gl.glNormalPointer(GL10.GL_FLOAT,0,normals)
gl.glDrawElements(GL10.GL_TRIANGLES, 60, GL10.GL_UNSIGNED_BYTE, icosahedronFaces);
正交和透视
视口的概念其实是为了让图像看起来更加逼真。
opengl es中具有两种视口类型:正交和透视。为更好理解 ,可以看看铁路
正交
gl.glOrthof(-ratio, ratio, -1f, 1f , 1.0f, 1000.0f);
透视
gl.glFrustumf(-ratio, ratio, -1f, 1f, 1.0f, 1000.0f)
5 光效
阴影模型
阴影有两种模式:GL_FLAT(恒定) 、GL_DMOOTH(平滑) 1、每一个像素都是一样的颜色,阴影,不真实 2、平滑算法,颜色有过渡性看起来真实
光效三要素
环境(ambient) 无法确定光源
FloatBuffer light0Ambient = FloatBuffer.wrap(new float[]{0.1f,0.1f,0.1f,1.0f})
gl.glLightfv()
散射元素(diffuse) 就像是穿透光纤或从一睹白墙反射的光线。散射光线是发散的,因而较柔和,不会光斑.
FloatBuffer light0Diffuse = FloatBuffer.wrap(new float[]{0.7f,0.7f,0.7,f,1.0f})
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_DIFFUSE,li)
高光元素(specular) 光线直接照射并反射形成物体上的"热点"或者光泽.
光源 光源属性包括 位置(GL_POSITION)、方向(x,y,z向量)、角度GL_SPOT(中心线两边的角度,必须小180度)
顶点法线 垂直于指定多边形表面的向量(或直线)
//允许设置法线数组 gl.glEnableClientState(GL_NORMAL_ARRAY) gl.glNormalPointer(GL10.GL_FLOAT,0,mormals)
fun setLight(gl:GL10) {
gl.glEnable(GL10.GL_LIGHTING)
//环境光
var light0Ambient:FloatBuffer ?= BufferUtil.iFloatBuffer(floatArrayOf(0.1f,0.1f,0.1f,1.0f))
//开启0号光源
gl.glEnable(GL10.GL_LIGHT0)
//设置环境光
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_AMBIENT,light0Ambient)
//散射光
var light0Diffuse :FloatBuffer ?= BufferUtil.iFloatBuffer(floatArrayOf(0.7f,0.7f,0.7f,1.0f))
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_DIFFUSE,light0Diffuse)
//高光
var light0Specuar :FloatBuffer ?= BufferUtil.iFloatBuffer(floatArrayOf(0.7f,0.7f,0.7f,1.0f))
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_SPECULAR,light0Specuar)
//光源的位置
var light0Position:FloatBuffer ?= BufferUtil.iFloatBuffer(floatArrayOf(0.0f,10.0f,10.0f,0.0f))
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_POSITION,light0Position)
//光线方向
var light0Direction : FloatBuffer ?= BufferUtil.iFloatBuffer(floatArrayOf(0.0f,0.0f,1.0f))
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_SPOT_DIRECTION,light0Direction)
gl.glLightf(GL10.GL_LIGHT0,GL10.GL_SPOT_CUTOFF,45.0f)
}
6材质
定义材质的反射光来定义材质。如果一个材质定义为反射红光,那么在白光侠红色 材质没有颜色,也就是反射光效能力。为什么要呢------》 每个物体对镜面高光也有不同的反应,有些物体反射光的时候不会有太多的散射(Scatter),因而产生一个较小的高光点,而有些物体则会散射很多,产生一个有着更大半径的高光点,如果我们想要在OpenGL中模拟多种类型的物体,我们必须为每个物体分别定义一个材质属性。
fun setMaterial(gl : GL10){
//环境元素和散射元素颜色一样的
// var ambientAndDiffuse = BufferUtil.iFloatBuffer(floatArrayOf(0.0f,0.1f,0.9f,1.0f))
//gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_AMBIENT_AND_DIFFUSE,ambientAndDiffuse)
//-----分开设置
//环境元素颜色
var ambient = BufferUtil.iFloatBuffer(floatArrayOf(0.0f,0.1f,0.9f,1.0f))
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_AMBIENT,ambient)
//散射元素颜色
var diffuse = BufferUtil.iFloatBuffer(floatArrayOf(0.9f,0.0f,0.1f,1.0f))
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_DIFFUSE,diffuse)
//高光元素颜色
var specular = BufferUtil.iFloatBuffer(floatArrayOf(0.9f,0.9f,0.0f,1.0f))
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_SPECULAR,specular)
//设置反射度
gl.glMaterialf(GL10.GL_FRONT_AND_BACK,GL10.GL_SHININESS,25.0f)
//自发光颜色
var emission = BufferUtil.iFloatBuffer(floatArrayOf(0.0f,0.4f,0.0f,1.0f))
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_EMISSION,emission)
}
7 纹理
说的白一点就是贴图
1 开启纹理
开关: glEnable(GL_TEXTURE_2D) glEnable(GL_BLEND)//混色 gl.BlendFunc(GL_ONE,GL_SRC_COLOR)//设置混色 颜色通道,不透明 //基于源像素alpha通道半透明 glBlendFunc(GL_SRC_ALPHA,GL_ONE)
2 创建纹理
每一个图像都是一个纹理,纹理不能直接显示,除非映射。有一个例外,就是对允许你将图像绘制于指定点所谓的 点精灵(Point spites),但是它有自己的一套规则,所以那是一个单独的注意。通常的情况下,任何你希望现实给用户的图像 必须防止在由顶点定义的三角形中,有点像粘贴纸。
glGenTextures
3 绑定纹理
使得纹理处于活动状态。一次只能激活一个纹理。活动的或者"被绑定"的纹理是绘制多边形时使用的纹理,也是新纹理数据将加载其上纹理,所以在提供函数数据前必须绑定纹理,这以为着每个纹理 至少被绑定一次。
glBinfTextture void glBindTexture( int target,//类型 int texture//名称 );
4 生成纹理
在我们第一次绑定纹理后必须为OpenGL ES提供纹理的图像数据。一旦具有了格式的位图数据,我们就可以屌用GLUtils.texImage2D传递图像给OpenGL ES 一旦你传递图像给OpenGL ES,它就会分配内存以拥有一份自己的数据拷贝,所以你可以释放所有使用的与图像有关的内存,而且你必须这样做除非你的程序由更重要的与数据相关的任务 每个像素占用4个字节,所以忘记释放纹理图像数据的内存会导致OOM。
/**
* A version of texImage2D that determines the internalFormat and type
* automatically.
*
* @param target 类型
* @param level
* @param bitmap
* @param border
*/
public static void texImage2D(int target, int level, Bitmap bitmap,
int border)
局限性: 用于纹理的图像宽、高必须为乘方,比如 2,4,8,16,63,128
5 纹理的坐标(映射)
当纹理映射启动后绘图时,你必须为OpenGLES 提供其他数据,即顶点数组中各个顶点 纹理坐标。纹理坐标定义了图像哪一个部分将映射到多边形
纹理系统坐标 不使用x,y 而是s,t(可以具体查查资料)
6 图像配置(线性插值 重复 拉升)
纹理需要被收缩(GL_TEXTURE_MIN_FILTER)或者放大(GL_TEXTURE_MAG_FILTER)到适合多边形的尺寸。
void glTexParameterx(
int target,
int pname,属性放大、缩小
int param 算法种类
);