OpenGL学习2020-12-16

  • 一.在Manifest.xml中加入OpenGL清单声明
  • 二.继承并实例化 *GLSurfaceView* 和*GLSurfaceView.Renderer*
  • 三.(屏幕 - 视图)比例适配
  • 四.使用 OpenGL ES 显示图形
    • 1.绘制背景
    • 2.创建图形类
  • 五.添加动画、触摸反馈
    • 1.添加动画
    • 2.响应触摸事件
  • 六.展示2D图片

前言:
本篇仅为个人学习记录,是我看了一遍官方文档的总结,并非专业教程,它的第一作用是我在以后运用OpenGL的过程中能快捷地查看基础用法,即好记性不如翻文章,第二作用是能帮助完全不了解OpenGL的人快速入门,即用一个小时时间达到我看了一周官方文档的OpenGL的了解程度,之后进阶再看其他文章就更容易上手。
提示:
看代码是最好连同注释一起看了,方便理解,官方文档原版注释,我怕我翻译变味就不翻了。

参考:官方文档

一.在Manifest.xml中加入OpenGL清单声明

首先新建一个项目,在Manifest.xml中加入这句,用啥版本的就写啥版本的。

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

版本适配

  • 2.0 ——> Android 2.2(API 级别 8)开始可用
  • 3.0 ——> Android 4.3(API 级别 18)开始可用
  • 3.1 ——> Android 5.0(API 级别 21)开始可用

二.继承并实例化 GLSurfaceViewGLSurfaceView.Renderer

自定义MyGLSurfaceView继承GLSurfaceView


import android.content.Context;
import android.opengl.GLSurfaceView;public class MyGLSurfaceView extends GLSurfaceView {private final MyGLRenderer renderer;public MyGLSurfaceView(Context context){super(context);// Create an OpenGL ES 2.0 contextsetEGLContextClientVersion(2);renderer = new MyGLRenderer();// Set the Renderer for drawing on the GLSurfaceViewsetRenderer(renderer);// Render the view only when there is a change in the drawing data//setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);}
}
  • setEGLContextClientVersion(version):通知默认的EGLContextFactory和默认的EGLConfigChooser,选择哪个EGLContext客户端版本。注意:1)必须用在 setRenderer() 之前;2)应该设置Manifest.xml里标明的版本。
  • setRenderMode(renderMode):默认模式是‘持续渲染’
    RENDERMODE_CONTINUOUSLY顾名思义就不停的绘制;‘指示渲染’ RENDERMODE_WHEN_DIRTY是只在surface被创建时或者调用 requestRender() 时才会绘制,可以省电,提高系统性能,能让gpu和cpu休息,好处多多,注意该方法要在 setRenderer() 之后调用。

自定义MyGLRenderer实现GLSurfaceView.Renderer


import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;import android.opengl.GLSurfaceView;public class MyGLRenderer implements GLSurfaceView.Renderer {public void onSurfaceCreated(GL10 unused, EGLConfig config) {}public void onDrawFrame(GL10 unused) {}public void onSurfaceChanged(GL10 unused, int width, int height) {}
}

这里要明白这三个重写的方法的意义:

  • onSurfaceCreated(GL10 unused, EGLConfig config):在SurfaceView创建时候调用一次,一般只调用一次,在这里面,我们一般用来做一些初始化操作,比如实例图形类;
  • onDrawFrame(GL10 unused):这个简单,绘制,相当于 Viewdraw()
  • onSurfaceChanged(GL10 unused, int width, int height):当 GLSurfaceView 的大小发生改变时会调用(例如屏幕翻转、代码改变View的大小),在最开始创建时会调用一次。

MainActivity.class 中实例MyGLSurfaceView


public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);MyGLSurfaceView mglsv = new MyGLSurfaceView(this);ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.MATCH_PARENT);addContentView(mglsv,lp);}
}

这时,你的界面就是这样的:

三.(屏幕 - 视图)比例适配

首先来说说为什么要比例适配,一句话:因为OpenGL 假定屏幕采用均匀的方形坐标系,即它认为你的屏幕是正方形的,看图(左边未适配,右边已适配):

在MyGLRenderer创建投影和相机视图矩阵:

    private final float[] vPMatrix= new float[16];private final float[] projMatrix= new float[16];private final float[] vMatrix= new float[16];public void onSurfaceCreated(GL10 unused, EGLConfig config) {}public void onSurfaceChanged(GL10 unused, int width, int height) {GLES20.glViewport(0, 0, width, height);float ratio = (float) width / height;// create a projection matrix from device screen geometryMatrix.frustumM(projMatrix, 0, -ratio, ratio, -1, 1, 3, 7);}public void onDrawFrame(GL10 unused) {...// Create a camera view matrixMatrix.setLookAtM(vMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);// Combine the projection and camera view matricesMatrix.multiplyMM(vPMatrix, 0, projMatrix, 0, vMatrix, 0);// Draw objects...}
  • glViewport(x, y, width, height) :设置画布的位置和宽高。x、y 以像素为单位,指定了画布的左下角位置。width、height 表示这个画布矩形的宽度和高度。

  • Matrix.multiplyMM(result, resultOffset, lhs, lhsOffset, rhs, rhsOffset):将两个矩阵合并(即相乘) 。result为目标矩阵(就是装合并之后的容器矩阵)、lhs和rhs是用来结合的两个矩阵。

  • glFrustumM(m, offset, left, Right, bottom, top, near, far):根据参数创建一个透视投影矩阵。除了near、far(视景体剪裁距离)其他的我也没懂怎么用,大家基本是固定的-ratio,ratio,-1,1。

  • Matrix.setLookAtM(rm, rmOffset, eyeX, eyeY, eyeZ, centerX, centerY,centerZ, upX, upY, upZ):设置相机位置,各个参数顾名思义就行了,最后三个参数表示(0,0,0)→(upX,upY,upZ)的方向为相机正上方的方向。

其他的就不解释了,注释写的很清楚了。

现在,你的GLSurfaceView就能以正确的比例显示了,不过你的界面还是这样:

四.使用 OpenGL ES 显示图形

1.绘制背景

自定义类实现 GLSurfaceview.Renderer

public void onSurfaceCreated(GL10 unused, EGLConfig config) {// Set the background frame colorGLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);···}public void onDrawFrame(GL10 unused) {// Redraw background colorGLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);···}
  • glClearColor(red,green,blue,alpha)
    glClear(mask):改变背景颜色。前者设置好清除颜色,后者利用前一个函数设置好的当前清除颜色设置窗口颜色。

2.创建图形类

三角形:


public class Triangle {private FloatBuffer vertexBuffer;// number of coordinates per vertex in this arraystatic final int COORDS_PER_VERTEX = 3;static float triangleCoords[] = {   // in counterclockwise order:0.0f,  0.622008459f, 0.0f, // top-0.5f, -0.311004243f, 0.0f, // bottom left0.5f, -0.311004243f, 0.0f  // bottom right};// Set color with red, green, blue and alpha (opacity) valuesfloat color[] = { 1.0f, 0f, 0f, 1.0f };public Triangle() {// initialize vertex byte buffer for shape coordinatesByteBuffer bb = ByteBuffer.allocateDirect(// (number of coordinate values * 4 bytes per float)triangleCoords.length * 4);// use the device hardware's native byte orderbb.order(ByteOrder.nativeOrder());// create a floating point buffer from the ByteBuffervertexBuffer = bb.asFloatBuffer();// add the coordinates to the FloatBuffervertexBuffer.put(triangleCoords);// set the buffer to read the first coordinatevertexBuffer.position(0);}}

要绘制三角形,您必须先定义其坐标。在 OpenGL 中,执行此操作的典型方式是为坐标定义浮点数的顶点数组。为了最大限度地提高工作效率,您可以将这些坐标写入 ByteBuffer 中,它会传递到 OpenGL ES 图形管道进行处理。(官方文档的原话,下面是其他文章找来的解释)

  • OpenGL并不是对堆里面的数据进行操作,而是在直接内存中(Direct Memory),即操作的数据需要保存到NIO里面的Buffer对象中。而我们上面声明的float[]对象保存在堆中,因此,需要我们将float[]对象转为java.nio.Buffer对象
  • OpenGL在底层的实现是C语言,与Java默认的数据存储字节顺序可能不同,即大端小端问题。因此,为了保险起见,在将数据传递给OpenGL之前,我们需要指明使用本机的存储顺序,即bb.order(ByteOrder.nativeOrder())

注意:形状的坐标是按照逆时针顺序定义的。绘制顺序非常重要,因为它定义了哪一边是形状的正面(您通常想要绘制的那一面),哪一边是背面(您可以使用 OpenGL ES 面剔除功能选择不绘制的那一面)。

正方形:


public class Square {private FloatBuffer vertexBuffer;private ShortBuffer drawListBuffer;// number of coordinates per vertex in this arraystatic final int COORDS_PER_VERTEX = 3;static float[] squareCoords = {-0.5f,  0.5f, 0.0f,   // top left-0.5f, -0.5f, 0.0f,   // bottom left0.5f, -0.5f, 0.0f,   // bottom right0.5f,  0.5f, 0.0f }; // top right//绘制顺序:0,1,2形成一个三角形,0,2,3形成一个三角形private short[] drawOrder = { 0, 1, 2, 0, 2, 3 }; // order to draw verticespublic Square() {// initialize vertex byte buffer for shape coordinatesByteBuffer bb = ByteBuffer.allocateDirect(// (# of coordinate values * 4 bytes per float)squareCoords.length * 4);bb.order(ByteOrder.nativeOrder());vertexBuffer = bb.asFloatBuffer();vertexBuffer.put(squareCoords);vertexBuffer.position(0);// initialize byte buffer for the draw listByteBuffer dlb = ByteBuffer.allocateDirect(// (# of coordinate values * 2 bytes per short)drawOrder.length * 2);dlb.order(ByteOrder.nativeOrder());drawListBuffer = dlb.asShortBuffer();drawListBuffer.put(drawOrder);drawListBuffer.position(0);}}

在 OpenGL 中定义三角形非常简单,如果希望定义复杂一点的图形比如方形,有多种方式可以执行此操作,但在 OpenGL ES 中绘制此类形状的典型方式是使用两个绘制在一起的三角形:

同样,对于表示该形状的两个三角形,您应按逆时针顺序定义顶点,并将这些值放入 ByteBuffer 中。为了避免两次定义这两个三角形共用的两个坐标,请使用绘制列表告知 OpenGL ES 图形管道如何绘制这些顶点。

完善三角形图形类:

public class Triangle {···private final String vertexShaderCode =// This matrix member variable provides a hook to manipulate// the coordinates of the objects that use this vertex shader"uniform mat4 vPMatrix;" +"attribute vec4 vPosition;" +"void main() {" +// the matrix must be included as a modifier of gl_Position// Note that the uMVPMatrix factor *must be first* in order// for the matrix multiplication product to be correct."  gl_Position = uMVPMatrix * vPosition;" +"}";private final String fragmentShaderCode ="precision mediump float;" +"uniform vec4 vColor;" +"void main() {" +"  gl_FragColor = vColor;" +"}";private final int mProgram;private int positionHandle;private int colorHandle;private final int vertexCount = triangleCoords.length // COORDS_PER_VERTEX;private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertexpublic Triangle() {···int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);// create empty OpenGL ES ProgrammProgram = GLES20.glCreateProgram();// add the vertex shader to programGLES20.glAttachShader(mProgram, vertexShader);// add the fragment shader to programGLES20.glAttachShader(mProgram, fragmentShader);// creates OpenGL ES program executablesGLES20.glLinkProgram(mProgram);}public void draw(float[] vPMatrix) {// Add program to OpenGL ES environmentGLES20.glUseProgram(mProgram);// get handle to vertex shader's vPosition memberpositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");// Enable a handle to the triangle verticesGLES20.glEnableVertexAttribArray(positionHandle);// Prepare the triangle coordinate dataGLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,GLES20.GL_FLOAT, false,vertexStride, vertexBuffer);// get handle to fragment shader's vColor membercolorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");// Set color for drawing the triangleGLES20.glUniform4fv(colorHandle, 1, color, 0);// get handle to fragment shader's vPMatrix menbermMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");// Apply the combined projection and camera view transformationsGLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);// Draw the triangleGLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);// Disable vertex arrayGLES20.glDisableVertexAttribArray(positionHandle);}
}

上面,在自定义 Triangle 的构造方法中,完成了实例化program和shader并将它们关联。在 draw() 中为program各个组件设置数据并绘制。program和shader的解释如下:

顶点着色程序:用于渲染形状的顶点的 OpenGL ES 图形代码。
片段着色程序:用于使用颜色或纹理渲染形状面的 OpenGL ES 代码。
程序:包含您希望用于绘制一个或多个形状的着色程序的 OpenGL ES 对象。

您至少需要一个顶点着色程序绘制形状,以及一个 Fragment 着色程序为该形状着色。您还必须对这些着色程序进行编译,然后将其添加到之后用于绘制形状的 OpenGL ES 程序中(这些都是官方文档的原话)。

  • GLES20.glGetUniformLocation(program, name):获取着色器程序中,指定为uniform类型变量的id。
  • GLES20.glGetAttribLocation(program,name):获取着色器程序中,指定为attribute类型变量的id。
  • GLES20.glDrawArrays(mode, first, count)GLES20.glDrawElements(mode,count,type,indices) :绘制。后者可以传入绘制顺序buffer,便于重复利用顶点。

如果你要绘制上面的方形,就要这样写

 GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, dlb);

MyGLRenderer 类中添加 loadShader() 方法,顾名思义就是加载着色器的静态方法:


public class MyGLRenderer implements GLSurfaceView.Renderer {···public static int loadShader(int type, String shaderCode){// create a vertex shader type (GLES20.GL_VERTEX_SHADER)// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)int shader = GLES20.glCreateShader(type);// add the source code to the shader and compile itGLES20.glShaderSource(shader, shaderCode);GLES20.glCompileShader(shader);return shader;}
}

其他方法看注释就很清楚了。完善了Triangle后,直接在RendereronDrawFrame() 中调用triangle.draw() 就能显示图形了:

Triangle triangle;@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {···triangle = new Triangle();}public void onDrawFrame(GL10 unused) {...// Draw objectstriangle.draw(vPMatrix);}

五.添加动画、触摸反馈

1.添加动画

修改MyGLRenderer类:

private float[] rotationMatrix = new float[16];@Overridepublic void onDrawFrame(GL10 gl) {float[] scratch = new float[16];...// Create a rotation transformation for the trianglelong time = SystemClock.uptimeMillis() % 4000L;float angle = 0.090f * ((int) time);Matrix.setRotateM(rotationMatrix, 0, angle, 0, 0, -1.0f);// Combine the rotation matrix with the projection and camera view// Note that the vPMatrix factor *must be first* in order// for the matrix multiplication product to be correct.Matrix.multiplyMM(scratch, 0, vPMatrix, 0, rotationMatrix, 0);// Draw trianglemTriangle.draw(scratch);}

上面代码看注释也很清楚了,在把‘投影×机视图(矩阵)’传递给三角形的渲染程序之前,先‘×旋转(矩阵)’。

Matrix.setRotateM() 等操作矩阵的方法请移步其他专业教程,看不看也行,顾名思义用起来就懂了。

现在你的程序是这样的:

2.响应触摸事件

MyGLSurfaceView 设置监听事件并设置渲染模式为‘指示渲染’以提高性能、添加触摸事件:

public class OneGlSurfaceView extends GLSurfaceView {···private final float TOUCH_SCALE_FACTOR = .5f;private float previousX;private float previousY;···public MyGLSurfaceView(Context context) {...// Render the view only when there is a change in the drawing datasetRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);}@Overridepublic boolean onTouchEvent(MotionEvent e) {// MotionEvent reports input details from the touch screen// and other input controls. In this case, you are only// interested in events where the touch position changed.float x = e.getX();float y = e.getY();switch (e.getAction()) {case MotionEvent.ACTION_MOVE:float dx = x - previousX;float dy = y - previousY;// reverse direction of rotation above the mid-lineif (y > getHeight() / 2) {dx = dx * -1 ;}// reverse direction of rotation to left of the mid-lineif (x < getWidth() / 2) {dy = dy * -1 ;}renderer.setAngle(renderer.getAngle() +((dx + dy) * TOUCH_SCALE_FACTOR));requestRender();}previousX = x;previousY = y;return true;}

MyGLRenderer 类中修改相应的方法:


public class MyGLRenderer implements GLSurfaceView.Renderer {...public volatile float mAngle;public float getAngle() {return mAngle;}public void setAngle(float angle) {mAngle = angle;}public void onDrawFrame(GL10 gl) {...float[] scratch = new float[16];// Create a rotation for the triangle// long time = SystemClock.uptimeMillis() % 4000L;// float angle = 0.090f * ((int) time);Matrix.setRotateM(rotationMatrix, 0, mAngle, 0, 0, -1.0f);// Combine the rotation matrix with the projection and camera view// Note that the vPMatrix factor *must be first* in order// for the matrix multiplication product to be correct.Matrix.multiplyMM(scratch, 0, vPMatrix, 0, rotationMatrix, 0);// Draw trianglemTriangle.draw(scratch);}
}

不多说直接上效果图:

好了,以上就是官方文档中的全部内容了。

六.展示2D图片

完善Square

public class Square {private static String vertexShaderCode = "uniform mat4 uMVPMatrix;" +"attribute vec4 vPosition;" +"attribute vec2 a_texCoord;" +"varying vec2 v_texCoord;" +"void main() {" +"  gl_Position = uMVPMatrix * vPosition;" +"  v_texCoord = a_texCoord;" +"}";private static String fragmentShaderCode =  "precision mediump float;" +"varying vec2 v_texCoord;" +"uniform sampler2D s_texture;" +"void main() {" +"  gl_FragColor = texture2D(s_texture, v_texCoord);" +"}";//各种bufferprivate FloatBuffer vertexBuffer;private FloatBuffer textureBuffer;private ShortBuffer drawListBuffer;//表示数组中每两个数组成一个坐标private static int COORDS_PER_TEXTURE = 2;//纹理坐标,x和y都∈[0,1]static float textureCoords[] = {//画个纹理坐标系:0f, 0f,//左上 (0,0)O·—— —— —— ——x(1,0)0f, 1f,//左下        \    纹理1f, 1f,//右下           \    图片             1f, 0f //右上     (1,1)\y          ·(1,1)};Bitmap bitmap;int mProgram, mPositionHandle, mTexCoordHandle, mMatrixHandle,mTexSamplerHandle;public Square(Context context) {···//将float[]转换成FloatBuffer,同顶点坐标一样textureBuffer = ByteBuffer.allocateDirect(textureCoords.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();textureBuffer.put(textureCoords);textureBuffer.position(0);//获取图片bitmap = BitmapFactory.decodeResource(context.getResources(), R.raw.beauty);//数据转换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);//用起来GLES20.glUseProgram(mProgram);//获取渲染程序中各个属性的hookermPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_texCoord");mMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");mTexSamplerHandle = GLES20.glGetUniformLocation(mProgram, "s_texture");//顶点坐标数据传入到渲染程序对应的位置GLES20.glEnableVertexAttribArray(mPositionHandle);GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 12, vertexBuffer);//纹理坐标数据传入到渲染程序对应的位置GLES20.glEnableVertexAttribArray(mTexCoordHandle);GLES20.glVertexAttribPointer(mTexCoordHandle, COORDS_PER_TEXTURE, GLES20.GL_FLOAT, false, 8, textureBuffer);//创建并绑定纹理int[] textures = new int[1];GLES20.glGenTextures(textures.length, textures, 0);int textureId = textures[0];GLES20.glActiveTexture(GLES20.GL_TEXTURE0);GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);bitmap.recycle();}public void draw(float[] mvpMatrix) {GLES20.glUniformMatrix4fv(mMatrixHandle, 1, false, mvpMatrix, 0);GLES20.glUniform1i(mTexSamplerHandle, 0);GLES20.glDrawElements(GLES20.GL_TRIANGLES,drawOrder.length,GLES20.GL_UNSIGNED_SHORT,drawListBuffer);}
}

修改MyGLRenderer类:

Context context;Square square;public OneGlRenderer(Context context) {this.context = context;}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {    ···square= new Square(context);}public void onDrawFrame(GL10 unused) {...// Draw objects
//        triangle.draw(vPMatrix);square.draw(vPMatrix);}

现在你的应用就是这样的(图片是百度上下的,正方形):

OpenGL Android 安卓 入门 GLES20 初学者 初级 官方相关推荐

  1. Android安卓——入门学习

    在正式动手开发学习之前,首先了解一下安卓开发,让自己首先在主观印象中认识安卓的开发.所以本次学习主要是理论方面的知识,让大家对安卓有一个大概的了解. 本人在学习安卓时使用的是Android Studi ...

  2. android自定义美颜相机完整程序,Android OpenGL ES从入门到进阶(一)—— 五分钟开发一款美颜相机...

    源码链接:https://github.com/smzhldr/AGLFramework 一.前言 商店里有数十款的美颜相机类产品,其实现原理基本上都是以OpenGL ES为核心的特效处理,大神可以忽 ...

  3. android安卓开发入门视频教程资料百度网盘下载

    android安卓开发入门视频教程资料讲解安卓核心基础,包含视频+笔记,适合新手入门学习. 百度网盘:https://pan.baidu.com/s/1uciMAAa97nm5RSLILtdPdg&a ...

  4. 谷歌官方推荐《Android开发入门精编》,极致经典,堪称Android入门教程的天花板

    我仍记得2015年我决定做安卓开发的那天,这是我一生中做出的最好决定之一.到现在已经接近7年,最初的时候,并没有人告诉我如何做才是正确的.我犯了很多错误,浪费了很多时间. 两年之后,我进入谷歌,我有机 ...

  5. android 美颜相机开发,Android OpenGL ES从入门到进阶(一)—— 五分钟开发一款美颜相机...

    源码链接:https://github.com/smzhldr/AGLFramework 一.前言 商店里有数十款的美颜相机类产品,以及像抖音,唱吧之类带有视频的软件,功能很强大,其实现原理基本上都是 ...

  6. 初学者必读Android开发入门之路

    初学者必读Android开发入门之路 [IT168评论]本人一直致力于嵌入式相关知识和技术在中国大陆地区的技术传播及嵌入式产品及移动设备的系统和应用程序开发,近两年主要专注于3G技术领域,重点是研究A ...

  7. Android OpenGL ES从入门到进阶(一)—— 五分钟开发一款美颜相机

    源码链接:https://github.com/smzhldr/AGLFramework 基础知识入门篇(Hello Triangle) 渲染纹理到屏幕 GLSurfaceView预览相机 简单易用的 ...

  8. Android新手入门,怎样才是正确的学习方式

    对于android新手入门,遇到的学习瓶颈和困难是无限大的,新手入门,没有一个好学的学习方向,没有一个学习规划,更多的则是在百度上搜索,查阅相关资料,如果没有辅导,纯粹就是瞎摸索.百度上面搜索资料,更 ...

  9. Android Framework入门介绍

    Android Framework入门介绍 https://blog.csdn.net/fu_kevin0606/article/details/79532710 framework概述 Androi ...

最新文章

  1. POJ - 1358 Housing Complexes(二分图最大匹配)
  2. 第三次学JAVA再学不好就吃翔(part100)--文件名称过滤器
  3. 提高tomcat的并发能力
  4. 自用的获取时间 传值是获取剩余时间 不传是获取当前时间
  5. Postgresql 截取字符串
  6. E - 最长上升子序列
  7. matlab打乱矩阵行,matlab 中,怎么让一个矩阵按某一列排列,并且行也跟着变动?...
  8. vmtools 安装不上的方法 我这里介绍下vm14 Ubuntu的系统
  9. [转]SQL Collation冲突解决 临时表
  10. H5开发,打包成APK
  11. 深入学习华为云IOT云平台与LiteOS轻量级物联网系统
  12. 阿里研究员吴翰清:世界需要什么样的智能系统?
  13. 证件照底色一般是什么颜色 证件照底色更换软件推荐
  14. 春节晚报 | 2月1日 星期二 | 快手推出首届“新春招工会”;罗永浩称“不做VR和元宇宙”;戴姆勒正式更名为梅赛德斯-奔驰...
  15. nginx 之 http 转 https (两种方式)
  16. 使用神经网络中的卷积核生成语谱图
  17. java pdf to word_java pdf转word 高效不失真
  18. 【CISSP备考笔记】第7章:安全运营
  19. mac charles 安装教程、使用教程
  20. 【5G之道】第六章:下行链路物理层处理

热门文章

  1. js根据开始日期和相隔天数计算出结束日期
  2. java u盘_Java检测Windows的U盘插入详解
  3. SMTP协议:使用telnet发邮件【纯纯小白】
  4. 关于最小二乘估计的一点理解和感悟
  5. Autoware.universe 和 carla simulator 联合仿真
  6. 【微服】单体、SOA、微服务
  7. Cython基础--Cython的函数
  8. 字符串在html中的页面中的换行
  9. 复选框 html 操作,HTML页面中复选框的操作方法
  10. 电磁波 -- 频谱介绍