渲染 创建视图 1 2 3 4 5 6 7 //版本 mGlSurfaceView.setEGLContextClientVersion(2); // 设置渲染器(这个渲染器的类非常重要) mGlSurfaceView.setRenderer(mRenderer); // 设置渲染模式为根据需要来渲染(RENDERMODE_CONTINUOUSLY) mGlSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); mGlSurfaceView.requestRender();
坐标
顶点坐标:在OpenGL中,顶点坐标使用的是笛卡尔右手坐标系(世界坐标系)。分X Y Z 3个轴,X轴朝右为正,Y轴朝上为正,Z轴垂直屏幕朝外为正。X Y Z 3个轴的最大与最小值为 1 和 -1。
屏幕坐标:屏幕坐标系,就是应用在设备屏幕上的坐标系,也就是图形最终显示的地方。X轴朝右为正,Y轴朝下为正。
纹理坐标:原点在左下角,X轴朝右为正,Y轴朝上为正,X Y 轴的最大与最小值为 0 和 1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 //顶点坐标 static final float VERTICE[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; //纹理坐标 对应顶点坐标 与之映射 static final float TEXTURE[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, };
注意:纹理坐标与顶点坐标非必需一一对应,纹理的坐标会导致图片的缩放显示。
顶点着色器(Vertex Shader),片元着色器(Fragment Shader) 顶点着色器处理传入的顶点数据,包括顶点位置、顶点颜色、光照等,每个顶点都会执行一次。
片元着色器主要目的是计算一个像素的最终颜色。片元并不是真正意义上的像素。而是包含了很多状态的集合,这些状态用来计算最终颜色。这些状态包括但不限于它的屏幕坐标,深度信息,法线,纹理坐标等。
所以,顶点着色器用于绘制顶点,片元着色器用于给顶点连线后所包围的区域填充颜色,可以简单的理解成windows中画图的填充工具。所以下边是生成简单着色器的两个源码: GLSL基础语法介绍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public static final String NO_FILTER_VERTEX_SHADER = "" + //顶点在画布中的位置 "attribute vec4 position;\n" + // 纹理映射,纹理坐标是纹理映射的一部分。这意味着你想要对你的纹理进行某种滤镜操作的时候会用到它。 "attribute vec4 inputTextureCoordinate;\n" + " \n" + //变量,负责顶点着色器负责和片段着色器交流,以及信息的共享 "varying vec2 textureCoordinate;\n" + " \n" + "void main()\n" + "{\n" + " gl_Position = position;\n" + //我们取出这个顶点中纹理坐标的 X 和 Y 的位置。我们只关心 inputTextureCoordinate 中的前两个参数,X 和 Y。这个坐标最开始是通过 4 个属性存在顶点着色器里的,但我们只需要其中的两个。 " textureCoordinate = inputTextureCoordinate.xy;\n" + "}"; /** * 这个着色器实际上不会改变图像中的任何东西。它是一个直通着色器,意味着我们输入每一个像素,然后输出完全相同的像素。 */ public static final String NO_FILTER_FRAGMENT_SHADER = "" + //因为片段着色器作用在每一个像素上,我们需要一个方法来确定我们当前在分析哪一个像素/片段。它需要存储像素的 X 和 Y 坐标。我们接收到的是当前在顶点着色器被设置好的纹理坐标。 "varying highp vec2 textureCoordinate;\n" + " \n" + //为了处理图像,我们从应用中接收一个图片的引用,我们把它当做一个 2D 的纹理。这个数据类型被叫做 sampler2D ,这是因为我们要从这个 2D 纹理中采样出一个点来进行处理。 "uniform sampler2D inputImageTexture;\n" + " \n" + "void main()\n" + "{\n" + //这是一个 GLSL 特有的方法:texture2D,顾名思义,创建一个 2D 的纹理。它采用我们之前声明过的属性作为参数来决定被处理的像素的颜色。这个颜色然后被设置给另外一个内建变量,gl_FragColor。因为片段着色器的唯一目的就是确定一个像素的颜色,gl_FragColor 本质上就是我们片段着色器的返回语句。 " gl_FragColor = texture2D(inputImageTexture, textureCoordinate);\n" + "}";
有了源码,我们就可以创建着色器: 我们首先要做的是创建一个着色器对象,注意还是用ID来引用的,所以我们储存这个顶点着色器的ID为unsigned int,然后用glCreateShader创建这个着色器,我们把需要创建的着色器类型以参数形式提供给glCreateShader,由于我们正在创建一个顶点着色器,传递的参数是GLES20.GL_VERTEX_SHADER
1 int iShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
下一步我们把这个着色器源码附加到着色器对象上,然后编译它:第一个参数是要编译的着色器对象,第二参数指定了传递的源码字符串.
1 2 GLES20.glShaderSource(iShader, strSource); GLES20.glCompileShader(iShader);
同时,我们要检测在调用glCompileShader后编译是否成功了,如果没成功的话,也希望知道错误是什么,这以便修复它们.如果结果非0,即编译成功。
1 2 3 4 5 6 int[] compiled = new int[1]; GLES20.glGetShaderiv(iShader, GLES20.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] == 0) { Log.d("Load Shader Failed", "Compilation\n" + GLES20.glGetShaderInfoLog(iShader)); return 0; }
片元着色器的创建和顶点一样,知识参数不同和源码不同。参数类型是GLES20.GL_FRAGMENT_SHADER
着色器程序 通过glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用,然后我们需要把之前编译的着色器附加到程序对象上,最后用glLinkProgram链接它们。着色器对象链接到程序对象以后,删除着色器对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 创建程序,返回对象的ID iProgId = GLES20.glCreateProgram(); // 向程序中加入顶点着色器 GLES20.glAttachShader(iProgId, iVShader); // 向程序中加入片元着色器 GLES20.glAttachShader(iProgId, iFShader); // 链接程序 GLES20.glLinkProgram(iProgId); // 获取program的链接情况 GLES20.glGetProgramiv(iProgId, GLES20.GL_LINK_STATUS, link, 0); if (link[0] <= 0) { Log.d("Load Program", "Linking Failed"); return 0; } GLES20.glDeleteShader(iVShader); GLES20.glDeleteShader(iFShader); // 获取着色器中的属性引用id(传入的字符串就是我们着色器脚本中的属性名) mGLAttribPosition = GLES20.glGetAttribLocation(mGLProgId, "position"); mGLUniformTexture = GLES20.glGetUniformLocation(mGLProgId, "inputImageTexture"); mGLAttribTextureCoordinate = GLES20.glGetAttribLocation(mGLProgId, "inputTextureCoordinate"); //glClearColor为glClear清除颜色缓冲区时指定RGBA值(也就是所有的颜色都会被替换成指定的RGBA值)。每个值的取值范围都是0.0~1.0,超出范围的将被截断。 GLES20.glClearColor(0, 0, 0, 1);
纹理ID 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static int loadTexture(final IntBuffer data, final Size size, final int usedTexId) { int textures[] = new int[1]; if (usedTexId == NO_TEXTURE) { GLES20.glGenTextures(1, textures, 0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, size.width, size.height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data); } else { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, usedTexId); GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, size.width, size.height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data); textures[0] = usedTexId; } return textures[0]; }
绘制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 使用某套shader程序 GLES20.glUseProgram(mGLProgId); // 设置缓冲区起始位置 cubeBuffer.position(0); // 顶点位置数据传入着色器,为画笔指定顶点位置数据(mGLAttribPosition) GLES20.glVertexAttribPointer(mGLAttribPosition, 2, GLES20.GL_FLOAT, false, 0, cubeBuffer); // 允许使用顶点坐标数组 GLES20.glEnableVertexAttribArray(mGLAttribPosition); textureBuffer.position(0); // 纹理坐标数据传入着色器,为画笔指定纹理数据 GLES20.glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GLES20.GL_FLOAT, false, 0, textureBuffer); GLES20.glEnableVertexAttribArray(mGLAttribTextureCoordinate); if (textureId != OpenGlUtils.NO_TEXTURE) { //设置纹理单元(sampler2D) GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); //设置uniform采样器的位置值, 也就是mGLUniformTexture纹理单元。 GLES20.glUniform1i(mGLUniformTexture, 0); } onDrawArraysPre(); //// 绘制 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); GLES20.glDisableVertexAttribArray(mGLAttribPosition); GLES20.glDisableVertexAttribArray(mGLAttribTextureCoordinate);
滤镜纹理 1 2 3 4 5 6 7 protected void onDrawArraysPre() { if (mToneCurveTexture[0] != OpenGlUtils.NO_TEXTURE) { GLES20.glActiveTexture(GLES20.GL_TEXTURE3); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mToneCurveTexture[0]); GLES20.glUniform1i(mToneCurveTextureUniformLocation, 3); } }
通过上述代码就可以实现纹理滤镜的效果了。但是这个滤镜的纹理是如何获取到的呢? 简单介绍两种方式:
ps acv 文件 acv文件是ps用来设置曲线效果,不同曲线参数可以呈现出不同的滤镜效果。我们可以对acv文件进行读取,可以得到:Curves曲线信息数组,每个curvers可以分别获取到R,G,B 的PointF数组信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public void setFromCurveFileInputStream(InputStream input) { try { int version = readShort(input); int totalCurves = readShort(input); ArrayList<PointF[]> curves = new ArrayList<PointF[]>(totalCurves); float pointRate = 1.0f / 255; for (int i = 0; i < totalCurves; i++) { // 2 bytes, Count of points in the curve (short integer from 2...19) short pointCount = readShort(input); PointF[] points = new PointF[pointCount]; // point count * 4 // Curve points. Each curve point is a pair of short integers where // the first number is the output value (vertical coordinate on the // Curves dialog graph) and the second is the input value. All coordinates have range 0 to 255. for (int j = 0; j < pointCount; j++) { short y = readShort(input); short x = readShort(input); points[j] = new PointF(x * pointRate, y * pointRate); } curves.add(points); } input.close(); mRgbCompositeControlPoints = curves.get(0); mRedControlPoints = curves.get(1); mGreenControlPoints = curves.get(2); mBlueControlPoints = curves.get(3); } catch (IOException e) { e.printStackTrace(); } }
然后根据RGB信息生成纹理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 设置纹理单元 GLES20.glActiveTexture(GLES20.GL_TEXTURE3); // 绑定纹理 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mToneCurveTexture[0]); if ((mRedCurve.size() >= 256) && (mGreenCurve.size() >= 256) && (mBlueCurve.size() >= 256) && (mRgbCompositeCurve.size() >= 256)) { byte[] toneCurveByteArray = new byte[256 * 4]; for (int currentCurveIndex = 0; currentCurveIndex < 256; currentCurveIndex++) { // BGRA for upload to texture toneCurveByteArray[currentCurveIndex * 4 + 2] = (byte) ((int) Math.min(Math.max(currentCurveIndex + mBlueCurve.get(currentCurveIndex) + mRgbCompositeCurve.get(currentCurveIndex), 0), 255) & 0xff); toneCurveByteArray[currentCurveIndex * 4 + 1] = (byte) ((int) Math.min(Math.max(currentCurveIndex + mGreenCurve.get(currentCurveIndex) + mRgbCompositeCurve.get(currentCurveIndex), 0), 255) & 0xff); toneCurveByteArray[currentCurveIndex * 4] = (byte) ((int) Math.min(Math.max(currentCurveIndex + mRedCurve.get(currentCurveIndex) + mRgbCompositeCurve.get(currentCurveIndex), 0), 255) & 0xff); toneCurveByteArray[currentCurveIndex * 4 + 3] = (byte) (255 & 0xff); } GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, 256 /*width*/, 1 /*height*/, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, ByteBuffer.wrap(toneCurveByteArray)); }
图片纹理 通过图片生成纹理ID,也就是滤镜图片:看一看资源文件目录信息:
下面是json和着色器源码:
json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { "filterList": [{ "type": "filter", "name": "amaro", "vertexShader": "", "fragmentShader": "fragment.glsl", "uniformList":["blowoutTexture", "overlayTexture", "mapTexture"], "uniformData": { "blowoutTexture": "blowout.png", "overlayTexture": "overlay.png", "mapTexture": "map.png" }, "strength": 1.0, "texelOffset": 0, "audioPath": "", "audioLooping": 1 }] }
fragment.glsl内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 precision mediump float; varying mediump vec2 textureCoordinate; uniform sampler2D inputTexture; uniform sampler2D blowoutTexture; //blowout; uniform sampler2D overlayTexture; //overlay; uniform sampler2D mapTexture; //map uniform float strength; void main() { vec4 originColor = texture2D(inputTexture, textureCoordinate.xy); vec4 texel = texture2D(inputTexture, textureCoordinate.xy); vec3 bbTexel = texture2D(blowoutTexture, textureCoordinate.xy).rgb; texel.r = texture2D(overlayTexture, vec2(bbTexel.r, texel.r)).r; texel.g = texture2D(overlayTexture, vec2(bbTexel.g, texel.g)).g; texel.b = texture2D(overlayTexture, vec2(bbTexel.b, texel.b)).b; vec4 mapped; mapped.r = texture2D(mapTexture, vec2(texel.r, 0.16666)).r; mapped.g = texture2D(mapTexture, vec2(texel.g, 0.5)).g; mapped.b = texture2D(mapTexture, vec2(texel.b, 0.83333)).b; mapped.a = 1.0; mapped.rgb = mix(originColor.rgb, mapped.rgb, strength); gl_FragColor = mapped; }
效果图
1 2 3 4 5 6 static final float TEXTURE[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, };
渲染流程总结 染流程如下:顶点数据(Vertices) > 顶点着色器(Vertex Shader) > 图元装配(Assembly) > 几何着色器(Geometry Shader) > 光栅化(Rasterization) > 片元着色器(Fragment Shader) > 逐片元处理(Per-Fragment Operations) > 帧缓冲(FrameBuffer)。再经过双缓冲的交换(SwapBuffer),渲染内容就显示到了屏幕上。
总结 根据渲染流程,我们知道,美颜的操作即是对着色器编码的源码进行编写。采用不同的算法,实现不同的美颜功能。同时滤镜的纹理,可以是ps的acv文件,也可以是图片,着色器源码针对不同的 资源分别解析和使用。
参考资料 OpenGL ES
OpenGL入门1.2:渲染管线简介,画三角形
GLES2.0中文API
OpenGLES 绘制图片纹理
Ursprünglicher Link: http://nunu03.github.io/2020/08/13/GPUImage渲染流程解析/
Copyright-Erklärung: 转载请注明出处.