最近因为兴趣所向,开始学习OpenGL绘图。本文以“画球体”为点,小结一下最近所学。
然后,如果想要实现比较复杂的效果(比如3D),Canvas就很难胜任了。了解了一下,目前大部分Android游戏都是用OpenGL来实现。
OpenGL是何方神圣?实际上,最终图像(不管是2D还是3D)都是显示在显示屏上,所以最终操作肯定是对一个2D的显示内存进行操作的。而OpenGL就是提供了很多方法,帮助我们定义空间立体模型,然后通过我们输入的各种参数,计算出映射矩阵,最终在显示屏幕上体现出效果。
OpenGL ES (OpenGL for Embedded Systems)是专门OpenGL的API子集,专门用于手机等嵌入式平台。简单理解就是,专门开发给“低端”的环境。删减了很多不必要的方法,留下了最基本的。
import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.opengl.GLU; import android.opengl.GLSurfaceView.Renderer; public class OpenGLRenderer4 implements Renderer { // 环境光 private final float[] mat_ambient = { 0.2f, 0.3f, 0.4f, 1.0f }; private FloatBuffer mat_ambient_buf; // 平行入射光 private final float[] mat_diffuse = { 0.4f, 0.6f, 0.8f, 1.0f }; private FloatBuffer mat_diffuse_buf; // 高亮区域 private final float[] mat_specular = { 0.2f * 0.4f, 0.2f * 0.6f, 0.2f * 0.8f, 1.0f }; private FloatBuffer mat_specular_buf; private Sphere mSphere = new Sphere(); public volatile float mLightX = 10f; public volatile float mLightY = 10f; public volatile float mLightZ = 10f; @Override public void onDrawFrame(GL10 gl) { // 清楚屏幕和深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 重置当前的模型观察矩阵 gl.glLoadIdentity(); gl.glEnable(GL10.GL_LIGHTING); gl.glEnable(GL10.GL_LIGHT0); // 材质 gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, mat_ambient_buf); gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, mat_diffuse_buf); gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, mat_specular_buf); // 镜面指数 0~128 越小越粗糙 gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 96.0f); //光源位置 float[] light_position = {mLightX, mLightY, mLightZ, 0.0f}; ByteBuffer mpbb = ByteBuffer.allocateDirect(light_position.length*4); mpbb.order(ByteOrder.nativeOrder()); FloatBuffer mat_posiBuf = mpbb.asFloatBuffer(); mat_posiBuf.put(light_position); mat_posiBuf.position(0); gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, mat_posiBuf); gl.glTranslatef(0.0f, 0.0f, -3.0f); mSphere.draw(gl); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { // 设置输出屏幕大小 gl.glViewport(0, 0, width, height); // 设置投影矩阵 gl.glMatrixMode(GL10.GL_PROJECTION); // 重置投影矩阵 gl.glLoadIdentity(); // 设置视口大小 // gl.glFrustumf(0, width, 0, height, 0.1f, 100.0f); GLU.gluPerspective(gl, 90.0f, (float) width / height, 0.1f, 50.0f); // 选择模型观察矩阵 gl.glMatrixMode(GL10.GL_MODELVIEW); // 重置模型观察矩阵 gl.glLoadIdentity(); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig arg1) { // 对透视进行修正 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); // 背景:黑色 gl.glClearColor(0, 0.0f, 0.0f, 0.0f); // 启动阴影平滑 gl.glShadeModel(GL10.GL_SMOOTH); // 复位深度缓存 gl.glClearDepthf(1.0f); // 启动深度测试 gl.glEnable(GL10.GL_DEPTH_TEST); // 所做深度测试的类型 gl.glDepthFunc(GL10.GL_LEQUAL); initBuffers(); } private void initBuffers() { ByteBuffer bufTemp = ByteBuffer.allocateDirect(mat_ambient.length * 4); bufTemp.order(ByteOrder.nativeOrder()); mat_ambient_buf = bufTemp.asFloatBuffer(); mat_ambient_buf.put(mat_ambient); mat_ambient_buf.position(0); bufTemp = ByteBuffer.allocateDirect(mat_diffuse.length * 4); bufTemp.order(ByteOrder.nativeOrder()); mat_diffuse_buf = bufTemp.asFloatBuffer(); mat_diffuse_buf.put(mat_diffuse); mat_diffuse_buf.position(0); bufTemp = ByteBuffer.allocateDirect(mat_specular.length * 4); bufTemp.order(ByteOrder.nativeOrder()); mat_specular_buf = bufTemp.asFloatBuffer(); mat_specular_buf.put(mat_specular); mat_specular_buf.position(0); } }
import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; // 计算球面顶点 public class Sphere { public void draw(GL10 gl) { float angleA, angleB; float cos, sin; float r1, r2; float h1, h2; float step = 30.0f; float[][] v = new float[32][3]; ByteBuffer vbb; FloatBuffer vBuf; vbb = ByteBuffer.allocateDirect(v.length * v[0].length * 4); vbb.order(ByteOrder.nativeOrder()); vBuf = vbb.asFloatBuffer(); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); for (angleA = -90.0f; angleA < 90.0f; angleA += step) { int n = 0; r1 = (float)Math.cos(angleA * Math.PI / 180.0); r2 = (float)Math.cos((angleA + step) * Math.PI / 180.0); h1 = (float)Math.sin(angleA * Math.PI / 180.0); h2 = (float)Math.sin((angleA + step) * Math.PI / 180.0); // 固定纬度, 360 度旋转遍历一条纬线 for (angleB = 0.0f; angleB <= 360.0f; angleB += step) { cos = (float)Math.cos(angleB * Math.PI / 180.0); sin = -(float)Math.sin(angleB * Math.PI / 180.0); v[n][0] = (r2 * cos); v[n][1] = (h2); v[n][2] = (r2 * sin); v[n + 1][0] = (r1 * cos); v[n + 1][1] = (h1); v[n + 1][2] = (r1 * sin); vBuf.put(v[n]); vBuf.put(v[n + 1]); n += 2; if(n>31){ vBuf.position(0); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vBuf); gl.glNormalPointer(GL10.GL_FLOAT, 0, vBuf); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, n); n = 0; angleB -= step; } } vBuf.position(0); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vBuf); gl.glNormalPointer(GL10.GL_FLOAT, 0, vBuf); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, n); } gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); gl.glDisableClientState(GL10.GL_NORMAL_ARRAY); } }
import android.content.Context; import android.opengl.GLSurfaceView; import android.view.MotionEvent; public class OpenGLView extends GLSurfaceView { private OpenGLRenderer4 mRenderer; private float mDownX = 0.0f; private float mDownY = 0.0f; public OpenGLView(Context context) { super(context); mRenderer = new OpenGLRenderer4(); this.setRenderer(mRenderer); } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: mDownX = event.getX(); mDownY = event.getY(); return true; case MotionEvent.ACTION_UP: return true; case MotionEvent.ACTION_MOVE: float mX = event.getX(); float mY = event.getY(); mRenderer.mLightX += (mX-mDownX)/10; mRenderer.mLightY -= (mY-mDownY)/10; mDownX = mX; mDownY = mY; return true; default: return super.onTouchEvent(event); } } }
import android.os.Bundle; import android.app.Activity; import android.view.Window; import android.view.WindowManager; public class MainActivity extends Activity { private OpenGLView mOpenGLView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 去标题栏 requestWindowFeature(Window.FEATURE_NO_TITLE); //设置全屏 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); mOpenGLView = new OpenGLView(this); setContentView(mOpenGLView); } }