OpenGl ES 2.0 Learn For Android(一)世界是三角形的
为什么说世界是三角形的呢?常规我们理解的世界——三维世界,由点线面构成。这是一个比较常规的认知。那么,理解opengl的世界的时候,也当然由这个逻辑去想象。但在实际绘制的时候,无限这种概念没法实际的在计算机里进行描述。所以opengl的世界,事实上由点、线段、和三角形来组成。为什么不是四边形呢?我想,可能是创始者的强迫症。希望设计是123的逻辑,不是124这种跳过3的逻辑。当然,我只是乱猜。
前一篇的内容在这里。
OpenGl ES 2.0 Learn For Android
1. 三角形的基础——顶点
即使是世界是三角形构成的。但三角形终究也是一个一个的点,所以,opengl也是从点开始。
讲到点,自然要讲到坐标系。opengl使用的是右手三维坐标系。
也就是说,我们拿着手机,正对着它。想象里面的绘制坐标的话,向右是X轴正方向,向上是Y轴正方向,向你面部的位置是Z轴正方向。而且,这个世界只能显示[-1,1]的内容(这里的话不一定对,有可能之后来修正)。那么我们现在先只看一个面,所以可以暂时忽略Z轴,理解成原点在手机屏幕正中央,向右是X轴正方向,向上是Y轴正方向。
OpenGL作为本地系统库是直接运行在硬件上的,在Android里,而我们的代码是运行在Dalvik(现在是ART)上,导致OpenGL无法去读取我们的数据,所以有两种方案解决上述问题:
从Java调用本地代码(JNI)。
一般我们调用GLES20包,就是在后台使用JNI。-
把内存从Java堆复制到本地堆。意思就是改变内存分配的方式,Java有个特殊的类集合,把Java数据复制到本地内存中。
FloatBuffer使用方式可以参照下面博客:
java中的float缓冲区FloatBuffer
对应代码使用:
private final FloatBuffer mVertexData;
private float[] mTrianglePoints = {-0.5f, -0.5f, 0.5f, -0.5f, 0f, 0.5f};
mVertexData = ByteBuffer
.allocateDirect(mTrianglePoints.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mVertexData.put(mTrianglePoints);
现在的话,我们知道有了点,但是怎么告诉opengl呢?
2. OpenGL:顶点着色器和片段着色器###
一般我们定义好物体的顶点,被读取到本地内存中,在绘制到屏幕的时候,需要通过管道进行传输,这类管道其实也成为着色器。
着色器:会告诉GPU如何处理绘制数据
在OpenGL中,总共有两种类型的着色器:
(1)顶点着色器(Vertex Shader)生成每个顶点的最终位置,针对每个顶点,它都会执行一次;一旦最终位置确定了,OpenGL就可以把这些可见顶点的集合组装成点、直线、以及三角形
(2)片段着色器(Fragment Shader)为组成的点、直线、三角形的每个片段生成最终的颜色,针对每个片段,都会执行一次;一个片段是一个小的、单一颜色的长方形区域,类似于计算机屏幕上的一个像素。
一旦最后颜色生成后,OpenGl就会把它们写到一块称为帧缓冲区(frame buffer)的内存块中,然后Android会把这块帧缓冲区显示到屏幕上。
在代码里,我们可以完成一个最简单的顶点着色器:
private String mVertexShaderCode =
"attribute vec4 a_Position; \n" +
"void main() \n" +
"{ \n" +
" gl_Position = a_Position; \n" +
"} \n";
同样的,完成一个最简单的片段着色器:
private String mFragmentShaderCode =
"precision mediump float; \n" +
"uniform vec4 u_Color; \n" +
"void main() \n" +
"{ \n" +
" gl_FragColor = u_Color; \n" +
"}";
关于attribute
和uniform
,可以参看这篇博客:OpenGL ES 三种类型修饰 uniform attribute varying
颜色是四维向量,很容易理解(R,G,B,A)。顶点我们之前说过是三维的,它这里是(X,Y,Z,W)。W是什么我们之后讲。
3. OpenGL ES的编译连接过程###
这里要讲到,GLES20的接口,都是先生成一个实例,拿到一个句柄,再对这个句柄指向的实例进行操作。熟悉C语言的应该会比较眼熟这个流程。java入行的会感到一点别扭。
1.shader的连接过程:
glCreateShader->glShaderSource->glCompileShader
2.program的生成过程:
glCreateProgram->glAttachShader->glLinkProgram
program是将一个顶点着色器和一个片段着色器链接在一起生成一个对象。没有片段着色器,OpenGL就不知道怎么绘制那些组成每个点、直线和三角形的片段;没有顶点着色器,OpenGL就不知道在哪里绘制这些片段。
//create vertex shader
int vertexShader = compileShader(GL_VERTEX_SHADER, mVertexShaderCode);
//create fragment shader
int fragmentShader = compileShader(GL_FRAGMENT_SHADER, mFragmentShaderCode);
//create program and link two shader
mShaderProgram = linkProgram(vertexShader,fragmentShader);
//use this program
glUseProgram(mShaderProgram);
4. OpenGL ES的赋值绘制过程###
program拿到后,就是对变量进行赋值绘制了。
uColorLocation = glGetUniformLocation(mShaderProgram, "u_Color");
aPositionLocation = glGetAttribLocation(mShaderProgram, "a_Position");
// Bind our data, specified by the variable vertexData, to the vertex
// attribute at location A_POSITION_LOCATION.
mVertexData.position(0);
glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT,
false, 0,mVertexData);
glEnableVertexAttribArray(aPositionLocation);
glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
上面拿到了"u_Color"
和"a_Position"
对应的变量,并对"a_Position"
也就是顶点变量进行了赋值了一个顶点数组,对"u_Color"
赋值为白色。
在绘制的时候
@Override
public void onDrawFrame(GL10 glUnused) {
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
这里是从顶点数组下标0开始,绘制3个顶点。glDrawArrays
具体我就不介绍了。这里的参数有些不同,可以参看
https://www.khronos.org/registry/OpenGL-Refpages/es2.0/
下面是效果图。
demo地址:
https://github.com/YueZhiFengMing/LearnOpenGl/tree/master/SecondDrawTriangle
参考资料###
- java中的float缓冲区FloatBuffer
- OpenGL ES 三种类型修饰 uniform attribute varying
- https://www.khronos.org/registry/OpenGL-Refpages/es2.0/
- 《OpenGL ES应用开发实践指南:Android卷》