如今VR这么火,感觉有必要学学OpenGL。什么是OpenGL ES ,OpenGL ES (OpenGL for Embedded System ) 为适用于嵌入式系统的一个免费二维和三维图形库。OpenGL ES 定义了一个在移动平台上能够支持 OpenGL 最基本功能的精简标准,以适应如手机,PDA 或其它消费者移动终端的显示系统。这篇文章不着重讲理论方法的东西,关于理论知识大家可以去看这个Android OpenGL ES 开发教程 从入门到精通。我们这里主要呈现如何实现。首先我们实现一个三角形,然后再去现实一个三棱锥。
实现三角形
效果图如下:
01.png Paste_Image.png这是一个旋转的三角形,本来想用gifcam截个动态图,但是效果不好。还是用图片代替了。基本效果能看出来。
实现的代码如下(TrangleRenderer.Java):
public class TrangleRenderer implements GLSurfaceView.Renderer {
private float[] mTriangleArray = {
0f, 1f, 0f, //p0
-1f, -1f, 0f, //p1
1f, -1f, 0f, //p2
};
private float[] colors = {
0f, 0f, 0f, 1f,
0f, 0f, 1f, 1f,
0f, 1f, 0f, 1f,
1f, 0f, 0f, 1f,
};
private FloatBuffer vertexBuffer;
private FloatBuffer colorBuffer;
private float angle;
public TrangleRenderer() {
//点相关
//先初始化buffer,数组的长度*4,因为一个float占4个字节
ByteBuffer bb = ByteBuffer.allocateDirect(mTriangleArray.length * 4);
//以本机字节顺序来修改此缓冲区的字节顺序
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
//将给定float[]数据从当前位置开始,依次写入此缓冲区
vertexBuffer.put(mTriangleArray);
//设置此缓冲区的位置。如果标记已定义并且大于新的位置,则要丢弃该标记。
vertexBuffer.position(0);
//颜色相关
ByteBuffer bb2 = ByteBuffer.allocateDirect(colors.length * 4);
bb2.order(ByteOrder.nativeOrder());
colorBuffer = bb2.asFloatBuffer();
colorBuffer.put(colors);
colorBuffer.position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置白色为清屏
gl.glClearColor(1, 1, 1, 1);
}
//屏幕解锁打开、屏幕发生旋转、对象创建时
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
float ratio = (float) width / height;
// 设置OpenGL场景的大小,(0,0)表示窗口内部视口的左下角,(w,h)指定了视口的大小
gl.glViewport(0, 0, width, height);
// 设置投影矩阵
gl.glMatrixMode(GL10.GL_PROJECTION);
// 重置投影矩阵
gl.glLoadIdentity();
// 设置视口的大小
gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
//以下两句声明,以后所有的变换都是针对模型(即我们绘制的图形)
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}
//这个方法默认会每隔一段时间调用一次
@Override
public void onDrawFrame(GL10 gl) {
// 通知OpenGL使用单一的颜色来渲染
// gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
// 清除屏幕和深度缓存
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// 重置当前的模型观察矩阵
gl.glLoadIdentity();
// 允许设置顶点
//GL10.GL_VERTEX_ARRAY顶点数组
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 允许设置颜色
//GL10.GL_COLOR_ARRAY颜色数组
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
//将三角形在z轴上移动
gl.glTranslatef(0f, 0.0f, -2.0f);
gl.glRotatef(angle, 0, 1, 0);
// 设置三角形
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
// 设置三角形颜色
gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);
// 绘制三角形
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
// 取消颜色设置
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
// 取消顶点设置
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
//绘制结束
gl.glFinish();
angle++;
}
}
上面代码每一步都解释比较清楚了。如果还是不明白,那就去把上面提到的教程看一遍。这里提一下,对于onSurfaceChanged方法,注意:代码的顺序和和坐标变换的过程是相反的。这个方法内的代码几乎可以作为固定写法。
下面将图像显示出来。
public class MainActivity extends AppCompatActivity {
private boolean supportsEs2;
private GLSurfaceView glView;
private float rotateDegreen = 0;
private TrangleRenderer glRenderer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkSupported();
if (supportsEs2) {
glView = new GLSurfaceView(this);
glRenderer = new TrangleRenderer();
glView.setRenderer(glRenderer);
setContentView(glView);
} else {
setContentView(R.layout.activity_main);
Toast.makeText(this, "当前设备不支持OpenGL ES 2.0!", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onResume() {
super.onResume();
if (glView != null) {
glView.onResume();
}
}
private void checkSupported() {
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
supportsEs2 = configurationInfo.reqGlEsVersion >= 0x2000;
boolean isEmulator = Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
&& (Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.startsWith("unknown")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK built for x86"));
supportsEs2 = supportsEs2 || isEmulator;
}
@Override
protected void onPause() {
super.onPause();
if (glView != null) {
glView.onPause();
}
}
}
GLSurfaceView的setRenderer方法接收一个Renderer用于显示3D模型的视图。checkSupported方法检测当前设备是否支持OpenGL。至此,我们就可以在手机上看到一个旋转的三角型了,下面看看如何实现一个旋转的三棱锥。
实现三棱锥
这里就涉及到了“面”相关的概念,什么是前面,什么是后面,以及多少个点组成一个面。如果面没搞清楚,很可能就画不出一个3D视图了。
实现的效果图:
360手机助手截图1206_15_15_01.png 360手机助手截图1206_15_15_02.png 360手机助手截图1206_15_15_03.png实现代码如下(PyramidRenderer .java):
public class PyramidRenderer implements GLSurfaceView.Renderer {
private float[] mTriangleArray = {
0f, 1f, 0f, //p0
-1f, -1f, 0f, //p1
1f, -1f, 0f, //p2
0f, 0f, 1f //p3
};
//定义面的顶点的顺序很重要 因为顶点的顺序定义了面的朝向(前向或是后向)
private short indices[] = new short[]{
0, 1, 2,
0, 3, 1,
0, 2, 3,
2, 1, 3
};
private float[] colors = {
0f, 0f, 0f, 1f,
0f, 0f, 1f, 1f,
0f, 1f, 0f, 1f,
1f, 0f, 0f, 1f,
};
private FloatBuffer vertexBuffer;
private FloatBuffer colorBuffer;
private ShortBuffer indexBuffer;
private float angle;
public PyramidRenderer() {
//点相关
//先初始化buffer,数组的长度*4,因为一个float占4个字节
ByteBuffer bb = ByteBuffer.allocateDirect(mTriangleArray.length * 4);
//以本机字节顺序来修改此缓冲区的字节顺序
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
//将给定float[]数据从当前位置开始,依次写入此缓冲区
vertexBuffer.put(mTriangleArray);
//设置此缓冲区的位置。如果标记已定义并且大于新的位置,则要丢弃该标记。
vertexBuffer.position(0);
//颜色相关
ByteBuffer bb2 = ByteBuffer.allocateDirect(colors.length * 4);
bb2.order(ByteOrder.nativeOrder());
colorBuffer = bb2.asFloatBuffer();
colorBuffer.put(colors);
colorBuffer.position(0);
//面相关
ByteBuffer bb3 = ByteBuffer.allocateDirect(indices.length * 2);
bb3.order(ByteOrder.nativeOrder());
indexBuffer = bb3.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置白色为清屏
gl.glClearColor(1, 1, 1, 1);
gl.glEnable(GL10.GL_DEPTH_TEST); // 启用深度缓存
gl.glClearDepthf(1.0f); // 设置深度缓存值
gl.glDepthFunc(GL10.GL_LEQUAL); // 设置深度缓存比较函数
gl.glShadeModel(GL10.GL_SMOOTH);// 设置阴影模式GL_SMOOTH
// Really nice perspective calculations.
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
}
//屏幕解锁打开、屏幕发生旋转、对象创建时
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
float ratio = (float) width / height;
// 设置OpenGL场景的大小,(0,0)表示窗口内部视口的左下角,(w,h)指定了视口的大小
gl.glViewport(0, 0, width, height);
// 设置投影矩阵
gl.glMatrixMode(GL10.GL_PROJECTION);
// 重置投影矩阵
gl.glLoadIdentity();
// 设置视口的大小
gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
//以下两句声明,以后所有的变换都是针对模型(即我们绘制的图形)
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}
@Override
public void onDrawFrame(GL10 gl) {
// 通知OpenGL使用单一的颜色来渲染
// gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
// 清除屏幕和深度缓存
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef(0, 0, -4);
gl.glRotatef(angle, 0, 1, 0);
gl.glFrontFace(GL10.GL_CCW);
//打开忽略后面设置
gl.glEnable(GL10.GL_CULL_FACE);
//两个参数GL_FRONT和GL_BACK分别表示禁用多边形正面或者背面上的光照、阴影和颜色计算及操作,消除不必要的渲染计算
gl.glCullFace(GL10.GL_FRONT);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glDrawElements(GL10.GL_TRIANGLES, indices.length,
GL10.GL_UNSIGNED_SHORT, indexBuffer);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisable(GL10.GL_CULL_FACE);
//绘制结束
gl.glFinish();
angle++;
}
}
然后,修改一下MainActivity.java中的代码,将TrangleRenderer替换为PyramidRenderer就可以在屏幕上显示出一个旋转的三棱锥了。
欢迎关注公众号,互助交流。
Paste_Image.png
网友评论