opengl es着色器原理和过程

来源:互联网 发布:成都家政公司办公软件 编辑:程序博客网 时间:2024/05/21 14:59

上一篇文章具体参考上文:
Android上Java程序和Opengl通信方式和opengl es着色器

着色器原理:

我们之前多次介绍过OpenGL里面图形都是通过顶点着色器和片段着色器共同完成的,顶点着色器计算每个顶点在屏幕上的最终位置,OpenGL把这些顶点组装成点,直线,三角形并且分解成片段,会询问片段着色器每个片段的最终颜色,如果没有顶点着色器OpenGL就不知道在哪绘制图形,如果没有片段着色器就不知道要怎么绘制组成图形的点,直线,三角形的片段,所以他们总是一起工作的,最终一起合成屏幕上的一幅图像。

上文中我们已经编写了两个着色器,simple_vertex_shader.glsl和simple_fragment_shader.glsl,本文将具体讲解如何编译和使用。

加载着色器:

打开TextResourceReader类代码如下:

/*** * Excerpted from "OpenGL ES for Android", * published by The Pragmatic Bookshelf. * Copyrights apply to this code. It may not be used to create training material,  * courses, books, articles, and the like. Contact us if you are in doubt. * We make no guarantees that this code is fit for any purpose.  * Visit http://www.pragmaticprogrammer.com/titles/kbogla for more book information.***/package opengl.timothy.net.openglesproject_lesson2.util;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import android.content.Context;import android.content.res.Resources;public class TextResourceReader {    /**     * Reads in text from a resource file and returns a String containing the     * text.     */    public static String readTextFileFromResource(Context context,        int resourceId) {        StringBuilder body = new StringBuilder();        try {            InputStream inputStream =                 context.getResources().openRawResource(resourceId);            InputStreamReader inputStreamReader =                 new InputStreamReader(inputStream);            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);            String nextLine;            while ((nextLine = bufferedReader.readLine()) != null) {                body.append(nextLine);                body.append('\n');            }        } catch (IOException e) {            throw new RuntimeException(                "Could not open resource: " + resourceId, e);        } catch (Resources.NotFoundException nfe) {            throw new RuntimeException("Resource not found: " + resourceId, nfe);        }        return body.toString();    }}

readTextFileFromResource方法需要传入资源id,方法返回值为String类型,通过该方法就加载了raw里面定义的两种着色器代码。

编译着色器:
目的是通过加载着色器代码,返回一个代表着色器的对象。
在ShaderHelper类中

    /**     * Compiles a shader, returning the OpenGL object ID.     */    private static int compileShader(int type, String shaderCode) {        // Create a new shader object.        final int shaderObjectId = glCreateShader(type);        if (shaderObjectId == 0) {            if (LoggerConfig.ON) {                Log.w(TAG, "Could not create new shader.");            }            return 0;        }        // Pass in the shader source.        glShaderSource(shaderObjectId, shaderCode);        // Compile the shader.        glCompileShader(shaderObjectId);        // Get the compilation status.        final int[] compileStatus = new int[1];        glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);        if (LoggerConfig.ON) {            // Print the shader info log to the Android log output.            Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode + "\n:"                 + glGetShaderInfoLog(shaderObjectId));        }        // Verify the compile status.        if (compileStatus[0] == 0) {            // If it failed, delete the shader object.            glDeleteShader(shaderObjectId);            if (LoggerConfig.ON) {                Log.w(TAG, "Compilation of shader failed.");            }            return 0;        }        // Return the shader object ID.        return shaderObjectId;    }

第一个参数如果是顶点着色器则传入android.opengl.GLES20.GL_VERTEX_SHADER;片段着色器则是android.opengl.GLES20.GL_FRAGMENT_SHADER;第二个参数是之前的加载着色器返回的String。具体过程:
1.创建着色器对象GLES20.glCreateShader(type);
2. 传入着色器代码 GLES20.glShaderSource(shaderObjectId, shaderCode);
3. 编译着色器glCompileShader(shaderObjectId);
4. 获取编译状态 // Get the compilation status.
final int[] compileStatus = new int[1];
glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
可以通过 glGetShaderInfoLog(shaderObjectId)打印日志信息
同时if (compileStatus[0] == 0) 则表示编译失败,则删除着色器并且返回0,即:

        if (compileStatus[0] == 0) {            // If it failed, delete the shader object.            glDeleteShader(shaderObjectId);            if (LoggerConfig.ON) {                Log.w(TAG, "Compilation of shader failed.");            }            return 0;        }

成功则返回当前着色器的id即shaderObjectId.
在AirHockeyRenderer里面的onSurfaceCreated里面可以看到有对以上过程调用,最终得到
int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource);
int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderSource);
顶点着色器和片段着色器两个对象vertexShader 和fragmentShader 。

链接程序:
program = ShaderHelper.linkProgram(vertexShader, fragmentShader);
我们已经知道顶点着色器和片段着色器是需要一起使用的,使用需要连接成一个对象,具体ShaderHelper.linkProgram代码如下:

 /**     * Links a vertex shader and a fragment shader together into an OpenGL     * program. Returns the OpenGL program object ID, or 0 if linking failed.     */    public static int linkProgram(int vertexShaderId, int fragmentShaderId) {        // Create a new program object.        final int programObjectId = glCreateProgram();        if (programObjectId == 0) {            if (LoggerConfig.ON) {                Log.w(TAG, "Could not create new program");            }            return 0;        }        // Attach the vertex shader to the program.        glAttachShader(programObjectId, vertexShaderId);        // Attach the fragment shader to the program.        glAttachShader(programObjectId, fragmentShaderId);        // Link the two shaders together into a program.        glLinkProgram(programObjectId);        // Get the link status.        final int[] linkStatus = new int[1];        glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);        if (LoggerConfig.ON) {            // Print the program info log to the Android log output.            Log.v(TAG, "Results of linking program:\n"                 + glGetProgramInfoLog(programObjectId));                    }        // Verify the link status.        if (linkStatus[0] == 0) {            // If it failed, delete the program object.            glDeleteProgram(programObjectId);            if (LoggerConfig.ON) {                Log.w(TAG, "Linking of program failed.");            }            return 0;        }        // Return the program object ID.        return programObjectId;    }

首先创建程序对象 GLES20.glCreateProgram(),GLES20.glAttachShader方法将顶点着色器和片段着色器添加到程序对象上,最后 GLES20.glLinkProgram(programObjectId)连接对象,如果成功则返回programObjectId否则删除 GLES20.glDeleteProgram(programObjectId).
回答AirHockeyRenderer的onSurfaceCreated方法,在linkProgram之后 ShaderHelper.validateProgram(program);这个是来验证程序对象的,是否存在一些问题,打印日志。
最后, GLES20.glUseProgram(program);是告诉opengl任何时候绘制东西到屏幕都需要使用这里的程序对象。

取值:

在simple_fragment_shader.glsl和simple_vertex_shader.glsl中我们定义了类型为attribute(属性)的a_Position和类型为uniform的u_Color,通过 uColorLocation = GLES20.glGetUniformLocation(program, U_COLOR);

    aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION);  这两个方法我们就可以告诉opengl在哪里读取这两个属性的值,相当于是告诉opengl这两个属性的值的位置,并且将位置信息存入uColorLocation和aPositionLocation。关联属性和缓冲区: vertexData.position(0);    glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT,         false, 0, vertexData);        这个方法是告诉opengl从之前定义的顶点缓冲区vertexData里找到a_Position对应的数据,同时由于每个缓冲区是本地一块内存,为了保证是从内存起始位置读取,所以调用之前需要先将缓冲区指针移动到最初位置即vertexData.position(0)。glVertexAttribPointer参数含义如下表:

glVertexAttribPointer方法各个参数含义
glVertexAttribPointer方法各个参数含义
glVertexAttribPointer参数不正确的话可能导致一些问题。同时有了该方法,opengl就知道去哪里读取属性a_Position的值了。最后调用 glEnableVertexAttribArray(aPositionLocation);使这一切有效。
截止目前,我们已经完成了顶点着色器的功能,找到了绘制点,直线,三角形的位置信息即顶点缓冲区的值。接下来具体怎么绘制?就是片段着色器的事了。

片段着色器绘制过程:
在AirHockeyRenderer的onDrawFrame方法里面, glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f); 方法是用来更新着色器里面的u_Color的值,同时uniform没有默认值,如果uniform是vec4类型的则需要提供所有4个分量的值即红,绿,蓝和透明度,本文指定的4个分量值为1.0f, 1.0f, 1.0f, 1.0f即白色不透明。
glDrawArrays(GL_TRIANGLES, 0, 6);就是具体绘制了,第一个参数GL_TRIANGLES指的是绘制三角形,第二个参数告诉opengl从浮点数组tableVerticesWithTriangles的开头位置读取顶点,第三个参数是读取6个顶点,一个三角形3顶点,读取6个的话就是2个三角形即一个长方形桌子。当我们在之前调用glVertexAttribPointer的时候第二个参数表示每个顶点的分量,比如是2维坐标就是2,三维坐标就是3等等,本例子POSITION_COMPONENT_COUNT值为2则一共是读取浮点数组tableVerticesWithTriangles的前12个值,即
// Triangle 1
0f, 0f,
9f, 14f,
0f, 14f,
// Triangle 2
0f, 0f,
9f, 0f,
9f, 14f
接下来就是 // Draw the center dividing line.
glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_LINES, 6, 2); 就是花了一条红色的线,是从第6个顶点后在读入两个顶点画成一条线GL_LINES,即
// Line 1
0f, 7f,
9f, 7f,
同理后面代码是绘制出两个木槌:

        // Draw the first mallet blue.                glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);                glDrawArrays(GL_POINTS, 8, 1);        // Draw the second mallet red.        glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);                glDrawArrays(GL_POINTS, 9, 1);

即坐标 tableVerticesWithTriangles里面坐标值
// Mallets
4.5f, 2f,
4.5f, 12f。
在simple_vertex_shader.glsl里面有gl_PointSize = 10.0;其实就是给点(木槌)的绘制设置大小。下一讲我们继续深入颜色和着色。
本文代码地址:
https://github.com/pangrui201/OpenGlesProject/tree/master/OpenGlesProject_lesson2

原创粉丝点击