Android opengles 实现触碰屏幕,根据运动轨迹画直线的功能

Android opengles 实现触碰屏幕,根据运动轨迹画直线的功能

  • 目录
    • 引言
    • 第一步,先自己学会绘制一条固定坐标的直线
    • 第二步,动态的绘制一条直线
    • 第三步,坐标转换
    • 第四步,绘制多条直线
    • 代码

目录

补一张效果图:
Android opengles 实现触碰屏幕,根据运动轨迹画直线的功能_第1张图片
在手机里显示线很清楚的,可能是屏幕录制帧数太低了,显示出来都看不清,实际是没问题的,有机会用另一个手机拍屏幕传一个吧,2333.
Android opengles 实现触碰屏幕,根据运动轨迹画直线的功能_第2张图片

宽度加粗了4倍,这下应该能看清楚了hhhh。

引言

由于项目功能需要,要做一个能自定义画直线的功能,网上找了许多文章或项目demo,没找到画直线的,反而是更进一步的实现类似“绘画板”功能的代码和教程较多;但是,这样的功能下,假如使用者想画一个规则的图形——比如直线,那么必须手不能抖笔直的画出来才行,应用到我这个项目里的话实在太反人类了,很不合适,于是就只好自己做了。

由于本人实力有限,也是刚接触opengles这玩意,磨磨蹭蹭拼拼凑凑了好几天总算是勉强实现了画直线的功能。

其实,做着做着就感觉这东西和android开发基本没啥关系了,无非就是android给他做了界面与交互而已,不过后续也要学习Opengl的也算是开个头吧,唠叨有点多了,下面是正文。

按步骤来说,想要实现根据手指运动轨迹画直线主要有以下几个要点:

  1. 实现一条固定坐标的直线的绘制----->>>>手动绘制一条直线
  2. 实现 屏幕坐标向opengles坐标的转换
  3. 任意绘制多条直线

第一步,先自己学会绘制一条固定坐标的直线

如何绘制一条直线参考官方文档里的三角形案例可以轻松实现,有现成案例,很多博主也是根据这些内容写的教程:https://developer.android.com/training/graphics/opengl/environment
本文是也是基于官方文档的三角形绘制案例改写的,具体如何绘制固定直线。
由于我没有中途保存过绘制直线的代码,下面提供的是已经实现自定义画线功能后的代码了,建议自己实现绘制一条固定坐标直线后再看后面的内容。

第二步,动态的绘制一条直线

仅仅实现这个功能其实也很简单,在renderer或者glsurfaceView里设置一个触摸事件,动态的更新直线类中的起点与终点的坐标即可。相关事件参考代码如下:(重点是事件内容)
Activity中设置时间的oncreate函数如下:

@Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        glSurfaceView = new OneGlSurfaceView(this);
        mRenderer = new OneGlRenderer();
        glSurfaceView.setRenderer(mRenderer);
        setContentView(glSurfaceView);
        glSurfaceView.setOnTouchListener(this);//给view监听触摸事件
    }

之后是重写的Touch事件

@Override
    public boolean onTouch(View v, MotionEvent event) {
     


        switch (event.getAction()) {
     
            /**
             * 点击的开始位置
             */
            case MotionEvent.ACTION_DOWN:
                float[] out = mRenderer.handleTouch(event.getX(),event.getY(),mRenderer.straightLine);
                currentLines = new StraightLine(new float[] {
     0, 0, 0});
                currentLines.setX_start(out[0]);//设置起点x坐标
                currentLines.setY_start(out[1]);//设置直线起点y坐标 取按下的坐标为起点
                                           
                break;
            /**
             * 触屏实时位置
             */
            case MotionEvent.ACTION_MOVE:
                //out数组保存坐标转换后的xyz坐标
                out = mRenderer.handleTouch(event.getX(),event.getY(),mRenderer.straightLine);
                Log.i("setPointer", String.format("x: %f, y: %f", event.getX(), event.getY()));
                Log.i("setPointer", String.format("x-start: %f, y-start: %f, z-start:%f", mRenderer.straightLine.getX_start(), mRenderer.straightLine.getY_start(),mRenderer.straightLine.getZ_start()));
                Log.i("setPointer", String.format("x-end: %f, y-end: %f, z-end:%f", out[0], out[1],out[2]));
                Log.i("setPointer", String.format("POS: %d", mRenderer.straightLine.pointBufferPos));
                

                currentLines.setX_end(out[0]);//设置直线终点x坐标 
                currentLines.setY_end(out[1]);//设置直线终点y坐标 随着移动不断更新
                // out[2]中存着z坐标,但是平面用不上,所以z坐标不变,依旧是固定值0              
                break;
            /**
             * 离开屏幕的位置
             */
            case MotionEvent.ACTION_UP:
                
                
                break;
            default:
                break;
        }
        /**
         * 注意返回值
         * true:view继续响应Touch操作;
         * false:view不再响应Touch操作,故此处若为false,只能显示起始位置,不能显示实时位置和结束位置
         */

        return true;

    }

简单解释一下,首先众所周知,Renderer有如下三个默认存在的方法:
onSurfaceCreated() - 调用一次以设置视图的 OpenGL ES 环境。
onDrawFrame() - 每次重新绘制视图时调用。
onSurfaceChanged() - 当视图的几何图形发生变化(例如当设备的屏幕方向发生变化)时调用。
于是,由于每次我们触摸移动,更新坐标以后,图形就发生了变化,需要重新绘制图形,所以会重新调用renderer类当中的onSurfaceChanged()onDrawFrame()
一般的直线类的绘制函数draw()会放在**onDrawFrame()**里。

第三步,坐标转换

opengl内部的坐标范围是(-1,1),而我们通过触摸事件获取到的是屏幕坐标,想要直接把屏幕坐标作为点的坐标传值肯定是不行的。
有一点需要特别注意的是,GLES20中对于Matrix.frustumM的左右边缘上下界一定要设置为(-1,1)否则会出现坐标转换的分布无法覆盖整个屏幕,从而出现坐标转换的差异。(有不少教程是直选用了部分象限,因而左右的上下界设置为了Ratio的值)
上述代码中,我用了一个out的数组存储坐标转换后的坐标,转换函数我写在了我的Renderer类里,主要是调用了GLU库里的 GLU.gluUnProject()函数,在对得到的坐标,用变换矩阵相乘,由于笔者还没学习图形学,所以具体原理并不是很懂,但是网上有不少教程有对此做说明。
(PS:虽然我看了以后还是一知半解,估计等我学了图形学后就明白了,大概)。

public float[] handleTouch(float rx, float ry,StraightLine line) {
     
        float[] near_xyz = line.unProject(rx, ry, 0, mVMatrix, mProjMatrix, viewWidth, viewHeight);
        return near_xyz;
    }

UnProject()函数如下.

public float[] unProject(float xTouch, float yTouch, float winz,
                              float[] viewMatrix,
                              float[] projMatrix,
                              int width, int height) {
     
        int[] viewport = {
     0, 0, width, height};

        float[] out = new float[3];
        float[] temp = new float[4];
        float[] temp2 = new float[4];
        // get the near and far ords for the click

        float winx = xTouch, winy = (float) viewport[3] - yTouch;

        int result = GLU.gluUnProject(winx, winy, winz, viewMatrix, 0, projMatrix, 0, viewport, 0, temp, 0);

        Matrix.multiplyMV(temp2, 0, viewMatrix, 0, temp, 0);
        if (result == 1) {
     
            out[0] = temp2[0] / temp2[3];
            out[1] = temp2[1] / temp2[3];
            out[2] = temp2[2] / temp2[3];
        }
        return out;
    }

第四步,绘制多条直线

简单说明一下原理,定义一个直线类的ArrayList,不断的存储直线,每次遍历ArrayList将里面所有的直线类都绘制一遍,没想到吧,你以为是你在原先的画面上一条一条线的加上去的,实际上是每次所有的线都给你重新绘制了一边哒!!!
由于计算机处理速度很快,就给了你一种是一条线一条线加进去的错觉,hhh。
下面放代码,有两种,一种写在Activity里的:

public class StraightLineActivity extends AppCompatActivity implements View.OnTouchListener {
     
    private OneGlSurfaceView glSurfaceView;
    private OneGlRenderer mRenderer;
    public StraightLine currentLines = null;  //当前绘制的线
    public List<StraightLine> linesList = new ArrayList<>(); //当前绘制线的表

    public long frameCount = 0;  //共绘制了多少帧
    private float ratio;
    private int viewWidth;
    private int viewHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        glSurfaceView = new OneGlSurfaceView(this);
        mRenderer = new OneGlRenderer();
        glSurfaceView.setRenderer(mRenderer);
        setContentView(glSurfaceView);
        //glSurfaceView.setOnTouchListener(this);
    }
    @Override
    protected void onPause() {
     
        super.onPause();
        if (glSurfaceView != null) {
     
            glSurfaceView.onPause();
        }
    }
    @Override
    protected void onResume() {
     
        super.onResume();
        if (glSurfaceView != null) {
     
            glSurfaceView.onResume();
        }
    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
     


        switch (event.getAction()) {
     
            /**
             * 点击的开始位置
             */
            case MotionEvent.ACTION_DOWN:
                float[] out = mRenderer.handleTouch(event.getX(),event.getY(),mRenderer.straightLine);
                currentLines = new StraightLine(new float[] {
     0, 0, 0});
                currentLines.setX_start(out[0]);
                currentLines.setY_start(out[1]);
                synchronized (linesList) {
     
                    linesList.add(currentLines);
                }
                break;
            /**
             * 触屏实时位置
             */
            case MotionEvent.ACTION_MOVE:
                out = mRenderer.handleTouch(event.getX(),event.getY(),mRenderer.straightLine);
                Log.i("setPointer", String.format("x: %f, y: %f", event.getX(), event.getY()));
                Log.i("setPointer", String.format("x-start: %f, y-start: %f, z-start:%f", mRenderer.straightLine.getX_start(), mRenderer.straightLine.getY_start(),mRenderer.straightLine.getZ_start()));
                Log.i("setPointer", String.format("x-end: %f, y-end: %f, z-end:%f", out[0], out[1],out[2]));
                Log.i("setPointer", String.format("POS: %d", mRenderer.straightLine.pointBufferPos));
               
                currentLines.setX_end(out[0]);
                currentLines.setY_end(out[1]);
                currentLines.draw(mRenderer.mProjMatrix,mRenderer.mVMatrix);
                
                break;
            /**
             * 离开屏幕的位置
             */
            case MotionEvent.ACTION_UP:
                
                synchronized (linesList) {
     
                    linesList.add(currentLines);
                }
                break;
            default:
                break;
        }
        /**
         * 注意返回值
         * true:view继续响应Touch操作;
         * false:view不再响应Touch操作,故此处若为false,只能显示起始位置,不能显示实时位置和结束位置
         */

        return true;

    }
}

另一种,写render里的这种方法得现在SurfaceView里重写onTouchEvent

@Override
    public boolean onTouchEvent(MotionEvent event) {
     

        renderer.setPointer(event);
        return true;
    }

然后,renderer里写**setPointer()**方法

public void setPointer(MotionEvent event) {
     
        this.x = event.getX();
        this.y = event.getY();
        this.z = 0f;
        switch (event.getAction()) {
     
            case MotionEvent.ACTION_DOWN:
                currentLines = new GLLine();
                synchronized (linesList) {
     
                    linesList.add(currentLines);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("setPointer", String.format("x: %f, y: %f", x, y,z));
               
                this.x = x / height * 4f;
                this.y = -y / height * 4f;
                this.z = 0;
                currentLines.drawLine(x, y,z);
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
    }

以上两种方法都可以。
到这里位置,理论上来说肯定是可以实现画多条线了,但是笔者当时到这一步以后发现还是只能绘制一条直线,排查了半天终于发现了问题所在。
由于我是参考的官方文档的draw()方法,里面有一句:

GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

没注释掉这句的话,每次绘制他都会吧之前的清空掉,说是清空,其实就是全变成透明色了,前面的线重新绘制的时候就没了,每次就只剩下最后一次绘制的线了。

public class StraightLineActivity extends AppCompatActivity implements View.OnTouchListener {
     
    private OneGlSurfaceView glSurfaceView;
    private OneGlRenderer mRenderer;
    public StraightLine currentLines = null;  //当前绘制的线
    public List<StraightLine> linesList = new ArrayList<>(); //当前绘制线的表

    public long frameCount = 0;  //共绘制了多少帧
    private float ratio;
    private int viewWidth;
    private int viewHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        glSurfaceView = new OneGlSurfaceView(this);
        mRenderer = new OneGlRenderer();
        glSurfaceView.setRenderer(mRenderer);
        setContentView(glSurfaceView);
        //glSurfaceView.setOnTouchListener(this);
    }
    @Override
    protected void onPause() {
     
        super.onPause();
        if (glSurfaceView != null) {
     
            glSurfaceView.onPause();
        }
    }
    @Override
    protected void onResume() {
     
        super.onResume();
        if (glSurfaceView != null) {
     
            glSurfaceView.onResume();
        }
    }
}

代码

由于本身也是参考了很多网上开源的资源做出来的,所以会各种相似,希望大家不要介意。
(1)在XML文件里面申明Opengles的使用

    <!-- Tell the system this app requires OpenGL ES 3.0. -->
    <uses-feature android:glEsVersion="0x00030000" android:required="true" />

(2)GLSurfaceView
这个很简单,我的理解是基本就是给图形一个显示的空间,本身并不复杂,无需过多关注。

public class OneGlSurfaceView extends GLSurfaceView {
     
    private OneGlRenderer mRenderer;

    public OneGlSurfaceView(Context context) {
     
        super(context);
        // Create an OpenGL ES 2.0 context
        setEGLContextClientVersion(2);

        // Set the Renderer for drawing on the GLSurfaceView
        //setRenderer((Renderer) mRenderer);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
     

        mRenderer.setPointer(event);
        return true;
    }

    @Override
    public void setRenderer(Renderer renderer) {
     
        super.setRenderer(renderer);
        this.mRenderer = (OneGlRenderer) renderer;
    }
}

(3)Renderer类

public class OneGlRenderer implements Renderer, com.example.ty.openglndk.GLSurfaceView.Renderer {
     

    int viewWidth, viewHeight;

    float[] mVMatrix = new float[16];
    float[] mProjMatrix = new float[16];
    private int mwidth,mheight;
    float ratio;

    StraightLine straightLine;
    private StraightLine currentLines = null;  //当前绘制的线
    private List<StraightLine> linesList = new ArrayList<>(); //当前绘制线的表

    @Override
    public void onSurfaceCreated(GL10 gl, javax.microedition.khronos.egl.EGLConfig config) {
     
        // Set the background frame color
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    }
    @Override
    public void onDrawFrame(GL10 gl) {
     
        // Redraw background color
        //GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        // 清除屏幕和深度缓存
        //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);
        //反走样
        gl.glEnable(GL10.GL_BLEND);
        //线条抗锯齿
        gl.glEnable(GL10.GL_LINE_SMOOTH);

        /***************绘制模型**************/
        //StraightLine straightLine = new StraightLine();
        currentLines = new StraightLine(new float[] {
     0, 0, 0});
        currentLines.draw(mProjMatrix,mVMatrix);
        drawModel(gl);
        /************************************/

        // 取消颜色设置
        gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
        // 取消顶点设置
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

        //绘制结束
        gl.glFinish();


    }
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
     
        GLES20.glViewport(0, 0, width, height);

        viewWidth = width;
        viewHeight = height;


        // 设置透明色为清屏
        //gl.glClearColor(0, 0, 0, 0);
        mwidth = width;
        mheight = height;
        ratio = (float) width / height;
        // 设置OpenGL场景的大小,(0,0)表示窗口内部视口的左下角,(w,h)指定了视口的大小
        gl.glViewport(0, 0, width, height);
        // 设置投影矩阵
        gl.glMatrixMode(GL10.GL_PROJECTION);
        // 重置投影矩阵
        gl.glLoadIdentity();
        // 设置视口的大小
        gl.glFrustumf(-1, 1, -1, 1, 1, 10);

        //以下两句声明,以后所有的变换都是针对模型(即我们绘制的图形)
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();

        Matrix.frustumM(mProjMatrix, 0, -1, 1, -1, 1, 3, 10);
        Matrix.setLookAtM(mVMatrix,0, 2,2,9, 0f,0f,0f, 0f,1.0f,0.0f);
    }

    public static int loadShader(int type, String shaderCode){
     

        // 创造顶点着色器类型(GLES20.GL_VERTEX_SHADER)
        // 或者是片段着色器类型 (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);
        // 添加上面编写的着色器代码并编译它
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }
    public float[] handleTouch(float rx, float ry,StraightLine line) {
     
        float[] near_xyz = line.unProject(rx, ry, 0, mVMatrix, mProjMatrix, viewWidth, viewHeight);
        float[] far_xyz = line.unProject(rx, ry, 1, mVMatrix, mProjMatrix, viewWidth, viewHeight);

        return near_xyz;
    }

    /**帧绘制**/
    public void drawModel(GL10 gl) {
     
        int count = 0;
        synchronized (linesList) {
     
            for(StraightLine line : linesList) {
     
                count++;
                Log.i("drawModel", String.format("调用次数: %d",count));
                line.draw(mProjMatrix,mVMatrix);
            }
        }

    }
    public void setPointer(MotionEvent event) {
     
        switch (event.getAction()) {
     
            /**
             * 点击的开始位置
             */
            case MotionEvent.ACTION_DOWN:
                float[] out = handleTouch(event.getX(),event.getY(),currentLines);
                currentLines = new StraightLine(new float[] {
     0, 0, 0});
                currentLines.setX_start(out[0]);
                currentLines.setY_start(out[1]);

                //mRenderer.straightLine.x_start=out[0];
                //mRenderer.straightLine.y_start=out[1];
                //mRenderer.straightLine.touch(out[0],out[1],0);
                //mRenderer.straightLine.z_start=out[2];
                //tvTouchShowStart.setText("起始位置:(" + event.getX() + "," + event.getY());
                break;
            /**
             * 触屏实时位置
             */
            case MotionEvent.ACTION_MOVE:
                //tvTouchShow.setText("实时位置:(" + event.getX() + "," + event.getY());
                out = handleTouch(event.getX(),event.getY(),currentLines);
                Log.i("setPointer", String.format("x: %f, y: %f", event.getX(), event.getY()));
                Log.i("setPointer", String.format("x-start: %f, y-start: %f, z-start:%f", currentLines.getX_start(), currentLines.getY_start(),currentLines.getZ_start()));
                Log.i("setPointer", String.format("x-end: %f, y-end: %f, z-end:%f", out[0], out[1],out[2]));
                Log.i("setPointer", String.format("POS: %d", currentLines.pointBufferPos));
                /*int pos = mRenderer.straightLine.pointBufferPos;
                if(mRenderer.straightLine.pointBufferPos/3%2!=0){//假如此时顶点数组内的点坐标数量不为偶数

                    mRenderer.straightLine.touch(out[0],out[1],0,pos);
                    mRenderer.straightLine.vertexCount++;
                }*/

                currentLines.setX_end(out[0]);
                currentLines.setY_end(out[1]);

                //currentLines.draw(mProjMatrix,mVMatrix);

                //mRenderer.straightLine.x_end=out[0];
                //mRenderer.straightLine.y_end=out[1];
                //mRenderer.straightLine.touch(out[0],out[1],0,pos);
                //mRenderer.straightLine.z_end=out[2];
                break;
            /**
             * 离开屏幕的位置
             */
            case MotionEvent.ACTION_UP:
                //tvTouchShow.setText("结束位置:(" + event.getX() + "," + event.getY());
                //straightLine.y_end=event.getY()/1000;
                out = handleTouch(event.getX(),event.getY(),currentLines);
                //mRenderer.straightLine.x_end=out[0];
                //mRenderer.straightLine.y_end=out[1];
                //mRenderer.straightLine.z_end=out[2];
                //mRenderer.straightLine.touch(out[0],out[1],0);
                synchronized (linesList) {
     
                    linesList.add(currentLines);
                }
                currentLines.setX_end(out[0]);
                currentLines.setY_end(out[1]);
                Log.i("setPointer", String.format("List中直线个数:%d",linesList.size()));
                Log.i("setPointer", String.format("List中直线1:x1:%f  y1:%f   z1:%f",linesList.get(0).getX_start(),linesList.get(0).getY_start(),linesList.get(0).getZ_start()));
                Log.i("setPointer", String.format("List中直线1:x2:%f  y2:%f   z2:%f",linesList.get(0).getX_end(),linesList.get(0).getY_end(),linesList.get(0).getZ_end()));
                break;
            default:
                break;
        }
        /**
         * 注意返回值
         * true:view继续响应Touch操作;
         * false:view不再响应Touch操作,故此处若为false,只能显示起始位置,不能显示实时位置和结束位置
         */


    }
}

(4)StraightLine 直线类
opengles中直线的绘制需要起始两个点的坐标,并且点的坐标都是3维的,比较关键或者看不懂的内容我都在注释里标注了。
官方文档中只提供了画三角形的实例,但是画三角形的原理与画直线是一样的,所以就根据三角形的案例直接修改了,下面是代码。

public class StraightLine {
     
//顶点着色程序 - 用于渲染形状的顶点的 OpenGL ES 图形代码,官网里直接有提供现成的。
    private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "void main() {" +
                    "  gl_Position = vPosition;" +
                    "}";
//片段着色程序 - 用于使用颜色或纹理渲染形状面的 OpenGL ES 代码,同上官网提供。
    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";
    private FloatBuffer vertexBuffer;//节点缓冲区
    private boolean updateVertex = false;

    public static float x_start=0.0f;//初始起点坐标
    public static float y_start=0.5f;
    public static float z_start=0.0f;
    public static float x_end=0.5f;//初始终点坐标
    public static float y_end=-0.5f;
    public static float z_end=0.0f;
    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;//每3个节点一个坐标
    float triangleCoords[] = {
        // in counterclockwise order:
            x_start,  y_start, z_start, // t
            x_end, y_end, z_end
    };
    int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    float[] mMVPMatrix = new float[16];//投影矩阵
    float[] mMMatrix = new float[16];//变换矩阵,用来做乘法的
    private float[] mMVMatrix = new float[16];//视角矩阵
    private float[] position;//位置
    public StraightLine(float[] position) {
     
        this.position = position;

        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个float占4个字节
        ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * 4);
        // 数组排列用nativeOrder
        bb.order(ByteOrder.nativeOrder());
        // 从ByteBuffer创建一个浮点缓冲区
        vertexBuffer = bb.asFloatBuffer();
        // 将坐标添加到FloatBuffer
        vertexBuffer.put(triangleCoords);
        // 设置缓冲区来读取第一个坐标
        vertexBuffer.position(0);
        pointBufferPos = 0;//记录当前数组位置

        int vertexShader = OneGlRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = OneGlRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // 创建空的OpenGL ES程序
        mProgram = GLES20.glCreateProgram();

        // 添加顶点着色器到程序中
        GLES20.glAttachShader(mProgram, vertexShader);

        // 添加片段着色器到程序中
        GLES20.glAttachShader(mProgram, fragmentShader);

        // 创建OpenGL ES程序可执行文件
        GLES20.glLinkProgram(mProgram);
    }
    public void draw(float[] projMatrix, float[] viewMatrix) {
     

        //GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        // 将程序添加到OpenGL ES环境
        GLES20.glUseProgram(mProgram);

        // 获取顶点着色器的位置的句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // 启用三角形顶点位置的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        //准备三角形坐标数据
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // 获取片段着色器的颜色的句柄
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

/**实现屏幕坐标向坐标转换的核心代码**/
        Matrix.setIdentityM(mMMatrix, 0);
        Matrix.translateM(mMMatrix, 0, position[0], position[1], position[2]);
        Matrix.multiplyMM(mMVMatrix, 0, viewMatrix, 0, mMMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, projMatrix, 0, mMVMatrix, 0);


        // 设置绘制三角形的颜色
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        // 绘制三角形
        GLES20.glDrawArrays(GLES20.GL_LINE_STRIP, 0, vertexCount);

        // 禁用顶点数组
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
    public float[] unProject(float xTouch, float yTouch, float winz,
                              float[] viewMatrix,
                              float[] projMatrix,
                              int width, int height) {
     
        int[] viewport = {
     0, 0, width, height};

        float[] out = new float[3];
        float[] temp = new float[4];
        float[] temp2 = new float[4];
        // get the near and far ords for the click

        float winx = xTouch, winy = (float) viewport[3] - yTouch;

        int result = GLU.gluUnProject(winx, winy, winz, viewMatrix, 0, projMatrix, 0, viewport, 0, temp, 0);

        Matrix.multiplyMV(temp2, 0, viewMatrix, 0, temp, 0);
        if (result == 1) {
     
            out[0] = temp2[0] / temp2[3];
            out[1] = temp2[1] / temp2[3];
            out[2] = temp2[2] / temp2[3];
        }
        return out;
    }
}

(5)Activity

public class StraightLineActivity extends AppCompatActivity implements View.OnTouchListener {
     
    private OneGlSurfaceView glSurfaceView;
    private OneGlRenderer mRenderer;
    public StraightLine currentLines = null;  //当前绘制的线
    public List<StraightLine> linesList = new ArrayList<>(); //当前绘制线的表

    public long frameCount = 0;  //共绘制了多少帧
    private float ratio;
    private int viewWidth;
    private int viewHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        glSurfaceView = new OneGlSurfaceView(this);
        mRenderer = new OneGlRenderer();
        glSurfaceView.setRenderer(mRenderer);
        setContentView(glSurfaceView);
        //glSurfaceView.setOnTouchListener(this);
    }
    @Override
    protected void onPause() {
     
        super.onPause();
        if (glSurfaceView != null) {
     
            glSurfaceView.onPause();
        }
    }
    @Override
    protected void onResume() {
     
        super.onResume();
        if (glSurfaceView != null) {
     
            glSurfaceView.onResume();
        }
    }
}

美中不足的是,为了能够实时显示直线的轨迹,其实每次最后一条线绘制了两次,按下去拖动的时候显示的是,draw的线条,手指离开时drawmodel才会把它的直线画出来,此时draw的直线也完成了绘制。
(PS:通过加粗线条以后,基本看不出来了,哈哈哈)

 /***************绘制模型**************/
        //StraightLine straightLine = new StraightLine();
        currentLines = new StraightLine(new float[] {
     0, 0, 0});
        currentLines.draw(mProjMatrix,mVMatrix);
        drawModel(gl);
        /************************************/

之所以这么做是因为,引入同步机制以后,发现绘制轨迹的时候,由于只取起点与终点,没有中间的轨迹,虽然画的很准,但是在手指起来之前是看不到自己画的线的。

我知道我这么描述你们估计听不懂,想看看啥样子的可以把draw函数注释了自己看看,哈哈哈。如果有大佬知道怎么解决这个问题的欢迎评论。

补个图,注释以后是这样的
Android opengles 实现触碰屏幕,根据运动轨迹画直线的功能_第3张图片

参考文献如下:
[1]: https://blog.csdn.net/soely/article/details/79183525
[2]: https://blog.csdn.net/cjzjolly/article/details/81667087?utm_medium=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param_right&depth_1-utm_source=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param_right

你可能感兴趣的:(android,opengles,android,java,opengles)