美文网首页
Android OpenGL ES 画出三棱锥

Android OpenGL ES 画出三棱锥

作者: 4492161067d0 | 来源:发表于2016-12-09 17:26 被阅读228次

如今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

相关文章

网友评论

      本文标题:Android OpenGL ES 画出三棱锥

      本文链接:https://www.haomeiwen.com/subject/asxymttx.html