1 前言

FBO离屏渲染 中使用 GLSurfaceView 来驱动 Renderer 渲染图片,为了隐藏 GLSurfaceView,将其设置为透明的,并且宽高都设置为1。本文将使用 EGL 代替 GLSurfaceView 生成 OpenGL ES 的渲染环境,实现离屏渲染,将渲染后的图片显示在 ImageView 上。

EGL 为 OpenGL ES 提供了绘制表面(或渲染画布),是 OpenGL ES 与显示设备的桥梁让 OpenGL ES 绘制的内容能够在呈现当前设备上。

EGL 环境创建分为以下5步:

1)创建EGLDisplay

EGLDisplay mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] versions = new int[2];
EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);

2)创建EGLConfig

int[] mEGLConfigAttrs = {EGL14.EGL_RED_SIZE, 8,EGL14.EGL_GREEN_SIZE, 8,EGL14.EGL_BLUE_SIZE, 8,EGL14.EGL_ALPHA_SIZE, 8,EGL14.EGL_DEPTH_SIZE, 8,EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] configNum = new int[1];
EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);
EGLConfig mEGLConfig = configs[0];

3)创建EGLContext

int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
EGLContext mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);

4)创建EGLSurface

int[] eglSurfaceAttrs = {EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE};
EGLSurface mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0);

5)绑定EGLSurface和EGLContext到显示设备(EGLDisplay)

EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);

读者如果对 OpenGL ES 不太熟悉,请回顾以下内容:

  • 绘制三角形​​​​​​
  • ​​​​​​绘制立方体
  • MVP矩阵变换
  • 透视变换原理

  • 纹理贴图
  • 正方形图片贴到圆形上
  • 凸镜贴图

  • FBO离屏渲染

本文完整代码资源见→EGL+FBO离屏渲染

项目目录如下:

2 案例

本案例实现了将彩色图片转换为灰色,并且使用 ImageView 显示转换后的图片。

MainActivity.java

package com.zhyan8.egl.activity;import android.graphics.Bitmap;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import com.zhyan8.egl.R;
import com.zhyan8.egl.model.Model;
import com.zhyan8.egl.opengl.MyEGLSurface;
import com.zhyan8.egl.opengl.MyRender;public class MainActivity extends AppCompatActivity implements Model.Callback {private ImageView mImageView;private MyEGLSurface mEGlSurface;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mImageView = findViewById(R.id.imageView);initEGLSurface();mEGlSurface.requestRender();}private void initEGLSurface() {mEGlSurface = new MyEGLSurface(this);MyRender render = new MyRender(getResources());render.setCallback(this);mEGlSurface.init(render);}@Overridepublic void onCall(final Bitmap bitmap) {runOnUiThread(() -> {mImageView.setImageBitmap(bitmap);});}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#556688"><ImageViewandroid:id="@+id/imageView"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitCenter"/></FrameLayout>

BaseEGLSurface.java

package com.zhyan8.egl.opengl;import android.content.Context;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.util.DisplayMetrics;
import android.view.WindowManager;public class BaseEGLSurface {protected EGLDisplay mEGLDisplay;protected EGLConfig mEGLConfig;protected EGLContext mEGLContext;protected EGLSurface mEGLSurface;protected Context mContext;protected Renderer mRenderer;protected EglStatus mEglStatus = EglStatus.INVALID;protected int mWidth;protected int mHeight;public BaseEGLSurface(Context context) {mContext = context;WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics displayMetrics = new DisplayMetrics();mWindowManager.getDefaultDisplay().getRealMetrics(displayMetrics);mWidth = displayMetrics.widthPixels;mHeight = displayMetrics.heightPixels;}public BaseEGLSurface(Context context, int width, int height) {mContext = context;mWidth = width;mHeight = height;}// 设置渲染器public void setRenderer(Renderer renderer) {mRenderer = renderer;}// EGLDisplay宽高发生变化public void onSurfaceChanged(int width, int height) {mWidth = width;mHeight = height;EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);createSurface();mEglStatus = EglStatus.CREATED;}// 请求渲染public void requestRender() {if (mEglStatus == mEglStatus.INVALID) {return;}if (mEglStatus == EglStatus.INITIALIZED) {mRenderer.onSurfaceCreated();mRenderer.onSurfaceChanged(mWidth, mHeight);mEglStatus = EglStatus.CREATED;}if (mEglStatus == EglStatus.CREATED || mEglStatus == EglStatus.DRAW) {mRenderer.onDrawFrame();mEglStatus = EglStatus.DRAW;}}// 创建EGL环境public void createEGLEnv() {createDisplay();createConfig();createContext();createSurface();makeCurrent();}// 销毁EGL环境public void destroyEGLEnv() {// 与显示设备解绑EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);// 销毁 EGLSurfaceEGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);// 销毁EGLContextEGL14.eglDestroyContext(mEGLDisplay, mEGLContext);// 销毁EGLDisplay(显示设备)EGL14.eglTerminate(mEGLDisplay);mEGLContext = null;mEGLSurface = null;mEGLDisplay = null;}// 1.创建EGLDisplayprivate void createDisplay() {mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);int[] versions = new int[2];EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);}// 2.创建EGLConfigprivate void createConfig() {EGLConfig[] configs = new EGLConfig[1];int[] configNum = new int[1];EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);if (configNum[0] > 0) {mEGLConfig = configs[0];}}// 3.创建EGLContextprivate void createContext() {if (mEGLConfig != null) {mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);}}// 4.创建EGLSurfaceprivate void createSurface() {if (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {int[] eglSurfaceAttrs = {EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE};mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0);}}// 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay)private void makeCurrent() {if (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);mEglStatus = EglStatus.INITIALIZED;}}// EGLConfig参数private int[] mEGLConfigAttrs = {EGL14.EGL_RED_SIZE, 8,EGL14.EGL_GREEN_SIZE, 8,EGL14.EGL_BLUE_SIZE, 8,EGL14.EGL_ALPHA_SIZE, 8,EGL14.EGL_DEPTH_SIZE, 8,EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,EGL14.EGL_NONE};// EGLContext参数private int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};// EGL状态enum EglStatus {INVALID, INITIALIZED, CREATED, DRAW}// 渲染器接口interface Renderer {void onSurfaceCreated();void onSurfaceChanged(int width, int height);void onDrawFrame();}
}

MyEGLSurface.java

package com.zhyan8.egl.opengl;import android.content.Context;public class MyEGLSurface extends BaseEGLSurface {public MyEGLSurface(Context context) {super(context);}public MyEGLSurface(Context context, int width, int height) {super(context, width, height);}public void init(Renderer renderer) {setRenderer(renderer);createEGLEnv();}
}

MyRender.java

package com.zhyan8.egl.opengl;import android.content.res.Resources;
import android.opengl.GLES30;
import com.zhyan8.egl.model.Model;public class MyRender implements BaseEGLSurface.Renderer {private Model mModel;public MyRender(Resources resources) {mModel = new Model(resources);}@Overridepublic void onSurfaceCreated() {//设置背景颜色GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);//启动深度测试GLES30.glEnable(GLES30.GL_DEPTH_TEST);//创建程序idmModel.onModelCreate();}@Overridepublic void onSurfaceChanged(int width, int height) {mModel.onModelChange(width, height);}@Overridepublic void onDrawFrame() {GLES30.glClearColor(0.5f, 0.7f, 0.3f, 1.0f);// 将颜色缓存区设置为预设的颜色GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);// 启用顶点的数组句柄GLES30.glEnableVertexAttribArray(0);GLES30.glEnableVertexAttribArray(1);// 绘制模型mModel.onModelDraw();// 禁止顶点数组句柄GLES30.glDisableVertexAttribArray(0);GLES30.glDisableVertexAttribArray(1);}public void setCallback(Model.Callback callback) {mModel.setCallback(callback);}
}

Model.java

package com.zhyan8.egl.model;import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.opengl.GLES30;
import com.zhyan8.egl.R;
import com.zhyan8.egl.utils.ArraysUtils;
import com.zhyan8.egl.utils.ShaderUtils;
import com.zhyan8.egl.utils.TextureUtils;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;public class Model {private static final int TEXTURE_DIMENSION = 2; // 纹理坐标维度private static final int VERTEX_DIMENSION = 3; // 顶点坐标维度private Callback mCallback;private Resources mResources;private float mVertex[] = {-1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f};private float[] mFboTexture = {0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f};protected FloatBuffer mVertexBuffer;protected FloatBuffer mFboTextureBuffer;// 帧缓冲对象 - 颜色、深度、模板附着点,纹理对象可以连接到帧缓冲区对象的颜色附着点private int[] mFrameBufferId = new int[1];private int[] mTextureId = new int[2];private int mProgramId;private Point mBitmapSize = new Point();public Model(Resources resources) {mResources = resources;mVertexBuffer = ArraysUtils.getFloatBuffer(mVertex);mFboTextureBuffer = ArraysUtils.getFloatBuffer(mFboTexture);}// 模型创建public void onModelCreate() {mProgramId = ShaderUtils.createProgram(mResources, R.raw.vertex_shader, R.raw.fragment_shader);TextureUtils.loadTexture(mResources, R.raw.xxx, mBitmapSize, mTextureId, mFrameBufferId);}// 模型参数变化public void onModelChange(int width, int height) {GLES30.glViewport(0, 0, mBitmapSize.x, mBitmapSize.y);}// 模型绘制public void onModelDraw() {GLES30.glUseProgram(mProgramId);// 准备顶点坐标和纹理坐标GLES30.glVertexAttribPointer(0, VERTEX_DIMENSION, GLES30.GL_FLOAT, false, 0, mVertexBuffer);GLES30.glVertexAttribPointer(1, TEXTURE_DIMENSION, GLES30.GL_FLOAT, false, 0, mFboTextureBuffer);// 激活纹理GLES30.glActiveTexture(GLES30.GL_TEXTURE);// 绑定纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureId[0]);// 绑定缓存GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferId[0]);// 绘制贴图GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);showBitmap();}private void showBitmap() {// 分配字节缓区大小, 一个像素4个字节ByteBuffer byteBuffer = ByteBuffer.allocate(mBitmapSize.x * mBitmapSize.y * Integer.BYTES);GLES30.glReadPixels(0, 0, mBitmapSize.x, mBitmapSize.y, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, byteBuffer);Bitmap bitmap = Bitmap.createBitmap(mBitmapSize.x, mBitmapSize.y, Bitmap.Config.ARGB_8888);// 从缓存区读二进制缓冲数据bitmap.copyPixelsFromBuffer(byteBuffer);// 回调mCallback.onCall(bitmap);}public void setCallback(Callback callback) {mCallback = callback;}public interface Callback{void onCall(Bitmap bitmap);}
}

ShaderUtils.java

package com.zhyan8.egl.utils;import android.content.res.Resources;
import android.opengl.GLES30;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;public class ShaderUtils {//创建程序idpublic static int createProgram(Resources resources, int vertexShaderResId, int fragmentShaderResId) {final int vertexShaderId = compileShader(resources, GLES30.GL_VERTEX_SHADER, vertexShaderResId);final int fragmentShaderId = compileShader(resources, GLES30.GL_FRAGMENT_SHADER, fragmentShaderResId);return linkProgram(vertexShaderId, fragmentShaderId);}//通过外部资源编译着色器private static int compileShader(Resources resources, int type, int shaderId){String shaderCode = readShaderFromResource(resources, shaderId);return compileShader(type, shaderCode);}//通过代码片段编译着色器private static int compileShader(int type, String shaderCode){int shader = GLES30.glCreateShader(type);GLES30.glShaderSource(shader, shaderCode);GLES30.glCompileShader(shader);return shader;}//链接到着色器private static int linkProgram(int vertexShaderId, int fragmentShaderId) {final int programId = GLES30.glCreateProgram();//将顶点着色器加入到程序GLES30.glAttachShader(programId, vertexShaderId);//将片元着色器加入到程序GLES30.glAttachShader(programId, fragmentShaderId);//链接着色器程序GLES30.glLinkProgram(programId);return programId;}//从shader文件读出字符串private static String readShaderFromResource(Resources resources, int shaderId) {InputStream is = resources.openRawResource(shaderId);BufferedReader br = new BufferedReader(new InputStreamReader(is));String line;StringBuilder sb = new StringBuilder();try {while ((line = br.readLine()) != null) {sb.append(line);sb.append("\n");}br.close();} catch (Exception e) {e.printStackTrace();}return sb.toString();}
}

TextureUtils.java

package com.zhyan8.egl.utils;import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.opengl.GLES30;
import android.opengl.GLUtils;public class TextureUtils {// 加载纹理贴图public static void loadTexture(Resources resources, int resourceId, Point bitmapSize, int[] textureId, int[] frameBufferId) {BitmapFactory.Options options = new BitmapFactory.Options();options.inScaled = false;Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId, options);bitmapSize.set(bitmap.getWidth(), bitmap.getHeight());// 生成纹理idGLES30.glGenTextures(2, textureId, 0);for (int i = 0; i < 2; i++) {// 绑定纹理到OpenGLGLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[i]);GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);if (i == 0) {// 第一个纹理对象给渲染管线(加载bitmap到纹理中)GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap, 0);} else {// 第二个纹理对象给帧缓冲区GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap.getWidth(), bitmap.getHeight(),0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);}// 取消绑定纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);}// 创建帧缓存idGLES30.glGenFramebuffers(1, frameBufferId, 0);// 绑定帧缓存GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId[0]);// 将第二个纹理附着在帧缓存的颜色附着点上GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textureId[1], 0);// 取消绑定帧缓存GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);}
}

ArraysUtils.java

package com.zhyan8.egl.utils;import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;public class ArraysUtils {public static FloatBuffer getFloatBuffer(float[] floatArr) {FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();fb.put(floatArr);fb.position(0);return fb;}
}

vertex_shader.glsl

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec2 aTextureCoord;
out vec2 vTexCoord;
void main() {gl_Position  = vPosition;vTexCoord = aTextureCoord;
}

fragment_shader.glsl

#version 300 es
precision mediump float;
uniform sampler2D uTextureUnit;
in vec2 vTexCoord;
out vec4 fragColor;
void main() {vec4 color = texture(uTextureUnit, vTexCoord);float rgb = color.g;vec4 c = vec4(rgb, rgb, rgb, color.a);fragColor = c;
}

3 运行效果

原图:

处理后:

【OpenGL ES】EGL+FBO离屏渲染相关推荐

  1. android 离屏渲染 简单书,Android OpenGL ES 8.FrameBuffer离屏渲染

    作用 FrameBuffer Object,也称FBO,离屏渲染,可以摆脱屏幕的束缚,在后台做图像处理. 理解 FrameBuffer和Texture绑定,FrameBuffer犹如画板,而Textu ...

  2. OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏渲染,解决透明冲突,画中画)

    OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏录制,解决透明冲突) 1.水印签名罢工了? 不知道大家有没注意到,之前我们使用MediaCodec录制的视频,水印签名那部分区 ...

  3. OpenGL ES EGL eglCreatePbufferSurface

    目录 一. EGL 前言 二. EGL 绘制流程简介 三.eglCreatePbufferSurface 函数简介 1.eglCreatePbufferSurface 简介 2.eglCreatePb ...

  4. OpenGL ES EGL 简介

    目录 一.EGL 简介 二.EGL 跨平台之 ANGLE 1.ANGLE 支持跨平台 2.ANGLE 支持渲染器 3.ANGLE 下载地址 三.EGL 坐标系 四.EGL 绘图步骤 五.猜你喜欢 零基 ...

  5. OpenGL ES EGL eglDestroyContext

    目录 一. EGL 前言 二. EGL 绘制流程简介 三.eglDestroyContext 函数简介 四.eglDestroyContext 使用 四.猜你喜欢 零基础 OpenGL ES 学习路线 ...

  6. OpenGL ES EGL eglQueryContext

    目录 一. EGL 前言 二. EGL 绘制流程简介 三.eglQueryContext 函数简介 四.eglQueryContext 使用 四.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : ...

  7. SDL2源码分析之OpenGL ES在windows上的渲染过程

    SDL2源码分析之OpenGL ES在windows上的渲染过程 更新于2018年11月4日. 更新于2018年11月21日. ffmpeg + SDL2实现的简易播放器 ffmpeg和SDL非常强大 ...

  8. OpenGL ES EGL eglDestroySurface

    目录 一. EGL 前言 二. EGL 绘制流程简介 三.eglDestroySurface 函数简介 四.eglDestroySurface 使用 四.猜你喜欢 零基础 OpenGL ES 学习路线 ...

  9. OpenGL ES EGL eglSwapBuffer

    目录 一. EGL 前言 二. EGL 绘制流程简介 三.eglSwapBuffer 函数简介 四.关于多个 EGLContext 五.共享 EGLContext 六.猜你喜欢 零基础 OpenGL ...

  10. WebGL—实现使用FBO离屏渲染(亦同拷贝纹理)off-screen rendering的两种方式

    1.离屏渲染使用场景: 1.游戏中的小地图: 2.画中画场景: 3.游戏中观战模式的多场景场合: 4.镜像场景--比如汽车游戏当中的倒车镜,采用的就是离屏渲染技术,在倒车镜上安装一个摄像机,把摄像机渲 ...

最新文章

  1. 2022-2028年中国酱腌菜行业市场研究及前瞻分析报告
  2. 半导体物理与器件_上海交通大学874半导体物理2班开课啦!
  3. c语言每瓶啤酒2元答案,【原创源码】C语言 一个喝啤酒小游戏的编程实现(菜鸟级)...
  4. java学习-http中get请求的非ascii参数如何编码解码探讨
  5. word中链接到目标后返回快捷键
  6. 背完这442句英语,你的口语绝对不成问题了
  7. 蚂蚁组件 axure 蚂蚁_蚂蚁属性细微差别
  8. JavaScript 的 defer 与 async
  9. Java多线程——线程安全问题
  10. Splash特征描述子
  11. 好用的android剪辑软件,最好用的视频剪辑app软件有哪些?自媒体人都在用的六款app软件...
  12. 从王者荣耀看设计模式(十六.原型模式)
  13. SMAA算法详解 - SMAABlendingWeightCalculationVS
  14. 2017杭州云栖大会 智能客服专场预热 — 用心服务客户,用云助力客服
  15. android 音频切换分析,Android音频可视化操作
  16. 阿里云、蚂蚁区块链医疗解决方案首次应用于未来医院电子处方
  17. 基于FPGA扰码的实现
  18. 弹性容器----六大属性(5、项目在交叉轴上的对齐方式)
  19. 就业困惑!Linux程序员的就业方向
  20. Spring Cloud Discovery——Apache Zookeeper Discovery

热门文章

  1. 使用Unity连接Bmob后端云
  2. Snowy Smile (HDU - 6638,稀疏矩阵子矩阵最大和)
  3. Connection error: QRedisClient compiled without ssh support
  4. Gromacs 的第一步_能量最小化
  5. 企业为什么要构建双活数据中心?F5怎么样?
  6. 电脑分区合并——灰色解决方法
  7. 【刷题篇】鹅厂文化衫问题
  8. 解决Laravel5.5版本框架缺少vender目录报错问题
  9. 巨象指纹浏览器的反追踪技术原理
  10. Linux查询状态的命令,LINUX常用的系统状态查询命令