opengl es着色器原理和過程

上一篇文章具體參考上文:
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

發佈了46 篇原創文章 · 獲贊 22 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章