基础五:实战画个三角形
例子出自:http://blog.csdn.net/niu_gao/article/details/8624490
在其他基础中提到了我们使用GLSurfaceView来做,那么就要用它的继承扩展子类,自定义的,因为可能要设置其他触发效果。这里以My
GLSurfaceView为例。
一、初始化OpenGL ES的环境
在My
GLSurfaceView的构造器中:
需要初始化OpenGL ES的版本
设置Renderer
并设置Renderer的绘制模式。
代码分别为:
setEGLContextClientVersion(2);//2.0版本
setRenderer(this);//需要实现Renderer类,之后再说
setRenderMode(RENDERMODE_WHEN_DIRTY);//只有在绘制数据改变
时才绘制view
上文提到的Renderer被实现之后,会有重写三个实现方法
public void onSurfaceCreated(GL10 unused, EGLConfig config)
public void onSurfaceChanged(GL10 unused, int width, int height)
public void onDrawFrame(GL10 unused)
在基础2中已经提到过方法都是做什么的了,这里主要说这三个方法配合OpenGL的库来如何使用
在onSurfaceCreated方法中写: GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);//设置背景的颜色
在onDrawFrame方法中写:GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);//重绘背景色
在onSurfaceChanged方法中写:GLES20.glViewport(0, 0, width, height);
以上就是对OpenGL ES的环境搭建初始化
二、定义形状
定义形状只是单纯的定义一个形状在画布坐标系上的顶点坐标位置,与绘制顺序暂无关系。
在定义形状时,所有的形状以坐标点的连线来呈现,OpenGL中采用了三角形原则和逆时针原则。
在定义形状时的逆时针原则是指,将图形坐标以逆时针的顺序写到数组中,到时会定义绘制顺序,这样不会导致错乱。
2D图形绘制和3D图形绘制时,代表坐标的数字个数当然不同,2D图形绘制是X、Y两个值来代表一个坐标,所以先定义每个顶点坐标的数字个数,一般且大多数人,这样写:
static final int COORDS_PER_VERTEX =2;//3D同理改为3
当然,你就算绘制2D图形,能不能用3D的代码达到效果呢,当然能,上句代码设置为3,到时Z坐标设置为0就行了。
接下来设置每个顶点的基本坐标,一般这样写,用float是使图形更精确,并且一般都用float:
这个是三角形的三个点坐标,Z坐标都设置为0了,看得出来这里的上句代码设置的为3,当3D效果处理的。
static float triangleCoords[] = { // 按逆时针方向顺序:
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
};
三、将形状顶点坐标存入缓冲,以备调用
OpenGL是针对设备GPU的底层库,是C语言的API,如果想缓冲入一个数组,并被C语言库调用的话,就不能使用Java常规数据类型如String、Byte等。
在字节存储方向上,Java中有特定类型ByteBuffer用来做字节缓冲。
定义好字节缓冲后,可以转为浮点缓冲。
首先初始化一个ByteBuffer对象,并告之应该有多大(float类型对应4倍字节):
ByteBuffer
bb
=
ByteBuffer
.
allocateDirect
(
triangleCoords
.
length
*
4
);
然后设置这个bb对象的字节顺序,按照本地字节序
bb
.
order
(
ByteOrder
.
nativeOrder
());
将字节缓冲转为浮点缓冲
vertexBuffer
=
bb
.
asFloatBuffer
();
然后将浮点坐标数组放入浮点缓冲中
vertexBuffer
.
put
(
triangleCoords
);
最后设置从哪里读起
vertexBuffer
.
position
(
0
);
这样vertexBuffer就存好并设置好初始读取位置了。
如果有要求绘制顺序,那么绘制顺序的数组也需要放入缓冲供C语言库调用,那首先定义绘制顺序如下写法:
private
short
drawOrder
[]
=
{
0
,
1
,
2
,
0
,
2
,
3
};
//
顶点的绘制顺序
那在形状的顶点坐标存入浮点缓冲中的同时,也要将绘制顺序顶点存入short缓冲中,如下:
// 为绘制列表初始化字节缓冲
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (对应顺序的坐标数 * 2)short是2字节
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
这样drawListBuffer中就存好了绘制顺序顶点的short缓冲了。
四、绘制图形
在renderer的创建方法中,初始化形状顶点坐标缓冲。
在绘制图形中,有一个关键词为shader的东西,从头至尾都会涉及,也是OpengGL的主角。
Shader——着色器,在绘制图形时,有顶点着色器和片元着色器两种:
VertexShader-用于渲染形状的顶点的OpenGLES 图形代码。
FragmentShader-用于渲染形状的外观(颜色或纹理)的OpenGLES 代码。
而Program这个int型对象,是从Opengl中直接创建的,包含了以上两种Shader(需要手动添加)。
VertexShader是用来绘制形状,而FragmentShader是用来上色的。
这两个Shader在Java代码中以final String呈现,实际上是
OpenGLShading Language (GLSL)
代码语句组成,样例写法如下:
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
这个语言的代码,需要被编译,所以在一般的Renderer中都会有loadShader这个静态方法专用来编译Shader,写法如下:
public static int loadShader(int type, String shaderCode){
// 创建一个vertex shader类型(GLES20.GL_VERTEX_SHADER)
// 或fragment shader类型(GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
// 将源码添加到shader并编译之
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
编译好Shader后,要将Shader放入Program中,并进行链接,写法如下:
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgram = GLES20.glCreateProgram(); // 创建一个空的OpenGL ES Program
GLES20.glAttachShader(mProgram, vertexShader); // 将vertex shader添加到program
GLES20.glAttachShader(mProgram, fragmentShader); // 将fragment shader添加到program
GLES20.glLinkProgram(mProgram); // 创建可执行的 OpenGL ES program
以上第四节内容,是在说两种Shader的创建,以及Shader的编译,最后添加到一个Program中。
现在有了着色器程序——Shader+Program,接下来就是将之前准备好的顶点缓冲和着色器程序对接起来。这样就绘制好了抽象的图形,要想将它显示出来,只需把这个对接的过程方法写在Renderer的
onDrawFrame()
方法中无限重画。
一般会将这个对接过程写为draw方法中,写法如下:
public void draw() {
// 将program加入OpenGL ES环境中
GLES20.glUseProgram(mProgram);
// 获取指向vertex shader的成员vPosition的 handle
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 启用一个指向三角形的顶点数组的handle
GLES20.glEnableVertexAttribArray(mPositionHandle);
// 准备三角形的坐标数据
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);//这个就是将顶点缓冲与程序连接的代码
// 获取指向fragment shader的成员vColor的handle
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 设置三角形的颜色
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// 画三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用指向三角形的顶点数组
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
上一节
下一节