[ - OpenGLES3.0 - ] 第一集 主線 - 打開新世界的大門

問:學OpenGL能幹嗎? 答: 隨心所欲。編程

提及OpenGLES,你們可能都敬而遠之,其實它並無想象中的那麼可怕,固然也並無那麼容易
都0202年了,本系列使用OpenGLES3.0,這是一次有預謀的計劃:數組

  • [- 多媒體 -] OpenGLES3.0 接入視頻實現特效 - 引言
  • [ - OpenGLES3.0 - ] 第一集 主線 - 打開新世界的大門
  • [ - OpenGLES3.0 - ] 第二集 主線 - 繪製面與圖片貼圖
  • [ - OpenGLES3.0 - ] 第三集 主線 - shader着色器詳解與圖片特效處理
  • [ - OpenGLES3.0 - ] 第四集 支線1 - 相機接入OpenGLES3.0實現特效
  • [ - OpenGLES3.0 - ] 第五集 支線1 - 視頻接入OpenGLES3.0實現特效
  • [ - OpenGLES3.0 - ] 第六集 主線 - OpenGL視口詳解與矩陣變換(上篇)
  • [ - OpenGLES3.0 - ] 第七集 主線 - OpenGL視口詳解與矩陣變換(下篇)
  • [ - OpenGLES3.0 - ] 第八集 支線2 - 複雜面的繪製
  • [ - OpenGLES3.0 - ] 第九集 支線2 - 立體圖形的繪製
  • [ - OpenGLES3.0 - ] 第十集 支線2 - OpenGLES展示建模軟件3D模型

1.黑屏的實現

1.1 GLSurfaceView的使用

Android中OpenGL經過GLSurfaceView進行展示,實現Renderer接口
實現接口方法:onSurfaceCreatedonSurfaceChangedonDrawFrame緩存

public class GLWorld extends GLSurfaceView implements GLSurfaceView.Renderer {
    private static final String TAG = "GLWorld";
    public GLWorld(Context context) {
        this(context,null);
    }

    public GLWorld(Context context, AttributeSet attrs) {
        super(context, attrs);
        setEGLContextClientVersion(3);//設置OpenGL ES 3.0 context
        setRenderer(this);//設置渲染器
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        Log.e(TAG, "onSurfaceCreated: " );
        GLES30.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);//rgba
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        Log.e(TAG, "onSurfaceChanged: " );
        GLES30.glViewport(0, 0, width, height);//設置GL視口
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        Log.e(TAG, "onDrawFrame: " );
    }
}
複製代碼

日誌以下: 默認onDrawFrame約隔16ms會不斷刷新bash

2020-01-10 13:34:35.368 3934-4035/com.toly1994.tolygl E/GLWorld: onSurfaceCreated: 
2020-01-10 13:34:35.368 3934-4035/com.toly1994.tolygl E/GLWorld: onSurfaceChanged: 
2020-01-10 13:34:35.368 3934-4035/com.toly1994.tolygl E/GLWorld: onDrawFrame: 
2020-01-10 13:34:35.440 3934-4035/com.toly1994.tolygl E/GLWorld: onDrawFrame: 
2020-01-10 13:34:35.458 3934-4035/com.toly1994.tolygl E/GLWorld: onDrawFrame: 
2020-01-10 13:34:35.461 3934-4035/com.toly1994.tolygl E/GLWorld: onDrawFrame: 
複製代碼

1.2 GLWorld的使用

它就至關於一個View,如今直接放到setContentView中,即可以顯示微信

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(GLWorld(this))
    }
}
複製代碼

2.從點線開始提及

如今你對OpenGLES的認識就像眼前的黑屏同樣,一無所知
咱們須要去點亮它,展示出一個新世界,大門即將打開,請扶好站穩。
這道光,你扎住了,就已經贏了一大半,抓不住,就......算了吧app


2.1 GLPoint的編寫

爲了避免混亂和方便使用,建立一個GLPoint類負責點的繪製測試ide

[1] 準備頂點着色代碼和片斷着色代碼
[2] 準備頂點和顏色數據
[3] 加載着色器代碼並初始化程序
[4] 繪製邏輯 (添加程序->啓用頂點->繪製)
複製代碼
public class GLPoint {
    //頂點着色代碼
    final String vsh = "#version 300 es\n" +
            "layout (location = 0) in vec3 aPosition; \n" +
            "layout (location = 1) in vec4 aColor;\n" +
            "\n" +
            "out vec4 color2frag;\n" +
            "\n" +
            "void main(){\n" +
            " gl_Position = vec4(aPosition.x,aPosition.y, aPosition.z, 1.0);\n" +
            " color2frag = aColor;\n" +
            "gl_PointSize=10.0;"+
            "}";
    
    //片斷着色代碼
    final String fsh = "#version 300 es\n" +
            "precision mediump float;\n" +
            "out vec4 outColor;\n" +
            "in vec4 color2frag;\n" +
            "\n" +
            "void main(){\n" +
            " outColor = color2frag;\n" +
            "}";

    //頂點數組
    private final float vertexes[] = {   //以逆時針順序
            0.0f, 0.0f, 0.0f,//原點
    };
    
    // 顏色數組
    private final float colors[] = new float[]{
            1.0f, 1.0f, 1.0f, 1.0f,//白色
    };

    private int program;
    private static final int VERTEX_DIMENSION = 3;
    private static final int COLOR_DIMENSION = 4;
    
    private FloatBuffer vertBuffer;
    private FloatBuffer colorBuffer;

    private  int aPosition =0;//位置的句柄
    private  int aColor =1;//顏色的句柄

    public GLPoint() {
        program = initProgram();
        vertBuffer= GLBuffer.getFloatBuffer(vertexes);
        colorBuffer= GLBuffer.getFloatBuffer(colors);
    }

    private int initProgram() {
        int program;
        ////頂點shader代碼加載
        int vertexShader = GLLoader.loadShader(GLES30.GL_VERTEX_SHADER, vsh);
        //片斷shader代碼加載
        int fragmentShader = GLLoader.loadShader(GLES30.GL_FRAGMENT_SHADER, fsh);
        program = GLES30.glCreateProgram();//建立空的OpenGL ES 程序
        GLES30.glAttachShader(program, vertexShader);//加入頂點着色器
        GLES30.glAttachShader(program, fragmentShader);//加入片元着色器
        GLES30.glLinkProgram(program);//建立可執行的OpenGL ES項目
        return program;
    }

    public void draw(){
        //清除顏色緩存和深度緩存
        GLES30.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        // 將程序添加到OpenGL ES環境中
        GLES30.glUseProgram(program);
        //啓用頂點句柄
        GLES30.glEnableVertexAttribArray(aPosition);
        //啓用顏色句柄
        GLES30.glEnableVertexAttribArray(aColor);
        //準備座標數據
        GLES30.glVertexAttribPointer(
                aPosition, VERTEX_DIMENSION,
                GLES30.GL_FLOAT, false,
                VERTEX_DIMENSION * 4, vertBuffer);

        //準備顏色數據
        GLES30.glVertexAttribPointer(
                aColor, COLOR_DIMENSION,
                GLES30.GL_FLOAT, false,
                COLOR_DIMENSION * 4, colorBuffer);

        //繪製點
        GLES30.glDrawArrays(GLES30.GL_POINTS, 0, vertexes.length / VERTEX_DIMENSION);
        
        //禁用頂點數組
        GLES30.glDisableVertexAttribArray(aPosition);
        GLES30.glDisableVertexAttribArray(aColor);
    }

}
複製代碼

2.2 緩衝數據

float數組須要經過FloatBuffer進行緩衝,以提升效率post

/**
 * float數組緩衝數據
 *
 * @param vertexs 頂點
 * @return 獲取浮點形緩衝數據
 */
public static FloatBuffer getFloatBuffer(float[] vertexs) {
    FloatBuffer buffer;
    ///每一個浮點數:座標個數* 4字節
    ByteBuffer qbb = ByteBuffer.allocateDirect(vertexs.length * 4);
    //使用本機硬件設備的字節順序
    qbb.order(ByteOrder.nativeOrder());
    // 從字節緩衝區建立浮點緩衝區
    buffer = qbb.asFloatBuffer();
    // 將座標添加到FloatBuffer
    buffer.put(vertexs);
    //設置緩衝區以讀取第一個座標
    buffer.position(0);
    return buffer;
}
複製代碼

2.3 使用

在咱們的GLWorld中建立GLPoint對象,在onDrawFrame中繪製便可測試

public class GLWorld extends GLSurfaceView implements GLSurfaceView.Renderer {
    private static final String TAG = "GLWorld";
    private GLPoint glPoint;
    public GLWorld(Context context) {
        this(context,null);
    }
    
    public GLWorld(Context context, AttributeSet attrs) {
        super(context, attrs);
        setEGLContextClientVersion(3);//設置OpenGL ES 3.0 context
        setRenderer(this);//設置渲染器
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
         glPoint = new GLPoint();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, width, height);//設置GL視口
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        glPoint.draw();
    }
}
複製代碼

2.4 多點繪製

如今繪製點,經過更改頂點和顏色便可優化

//頂點數組
    private final float vertexes[] = {   //以逆時針順序
            0.0f, 0.0f,0.0f,//原點
            0.5f,0.0f,0.0f,
            0.5f,0.5f,0.0f,
            0.0f,0.5f,0.0f,

    };

    // 顏色數組
    private final float colors[] = new float[]{
            1.0f, 1.0f, 1.0f, 1.0f,//白色
            1.0f, 0.0f, 0.0f, 1.0f,//紅色
            0.0f, 1.0f, 0.0f, 1.0f,//綠色
            0.0f, 0.0f, 1.0f, 1.0f,//藍色
    };
複製代碼

2.5 畫線

前面處理好了,劃線比較簡單

//繪製
GLES30.glLineWidth(10);
//GLES30.glDrawArrays(GLES30.GL_POINTS, 0, vertexes.length / VERTEX_DIMENSION);
//GLES30.glDrawArrays(GLES30.GL_LINES, 0, vertexes.length / VERTEX_DIMENSION);
//GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, vertexes.length / VERTEX_DIMENSION);
GLES30.glDrawArrays(GLES30.GL_LINE_STRIP, 0, vertexes.length / VERTEX_DIMENSION);
複製代碼

注:着色器的代碼會有單獨一篇進行講解,此處暫不作解釋。


3.線繪製與比例校訂

可能你已經發現了,本應是個正方形,可現實成了矩形
緣由在於視口的比例不對,如今是這樣個座標系:


3.1 GLLine添加頂點變換矩陣

在頂點着色器代碼中添加用於變換的矩陣uMVPMatrix

//頂點着色代碼
final String vsh = "#version 300 es\n" +
        "layout (location = 0) in vec3 aPosition; \n" +
        "layout (location = 1) in vec4 aColor;\n" +
        "uniform mat4 uMVPMatrix;\n" +
        "out vec4 color2frag;\n" +
        "\n" +
        "void main(){\n" +
        " gl_Position = uMVPMatrix*vec4(aPosition.x,aPosition.y, aPosition.z, 1.0);\n" +
        " color2frag = aColor;\n" +
        "gl_PointSize=10.0;"+
        "}";

---->[變換矩陣相關代碼]----
private  int uMVPMatrix ;//頂點變換矩陣句柄

//構造方法中獲取句柄
uMVPMatrix = GLES30.glGetUniformLocation(program, "uMVPMatrix");

public void draw(float[] mvpMatrix){
   //英雄所見
   GLES30.glUseProgram(program);
   GLES30.glUniformMatrix4fv(uMVPMatrix, 1, false,mvpMatrix, 0);
複製代碼

3.2 GLWorld添加變換矩陣邏輯

具體的變換細節,將在第六集第七集講述,此處不作解釋 如今視角就已經校訂了

public class GLWorld extends GLSurfaceView implements GLSurfaceView.Renderer {
    private static final String TAG = "GLWorld";
    private GLLine line;
    //Model View Projection Matrix--模型視圖投影矩陣
    private final float[] mMVPMatrix = new float[16];
    //投影矩陣 mProjectionMatrix
    private final float[] mProjectionMatrix = new float[16];
    //視圖矩陣 mViewMatrix
    private final float[] mViewMatrix = new float[16];
    
    public GLWorld(Context context) {
        this(context, null);
    }
    public GLWorld(Context context, AttributeSet attrs) {
        super(context, attrs);
        setEGLContextClientVersion(3);//設置OpenGL ES 3.0 context
        setRenderer(this);//設置渲染器
    }
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        line = new GLLine();
    }
    
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, width, height);//設置GL視口
        float ratio = (float) width / height;
        //透視投影矩陣--截錐
        Matrix.frustumM(mProjectionMatrix, 0,
                -ratio, ratio, -1, 1,
                3, 7);
        // 設置相機位置(視圖矩陣)
        Matrix.setLookAtM(mViewMatrix, 0,
                0, 0, 4,
                0f, 0f, 0f,
                0f, 1.0f, 0.0f);
    }
    
    @Override
    public void onDrawFrame(GL10 gl) {
        Matrix.multiplyMM(
                mMVPMatrix, 0,
                mProjectionMatrix, 0,
                mViewMatrix, 0);
        line.draw(mMVPMatrix);
    }
}
複製代碼

4.精簡和優化

4.1 着色器shader的獨立文件

着色器shader是OpenGL靈魂般的存在,因此直接寫在代碼裏確定不太好
通常放在assets文件夾裏,另外值得一提的是AS的着色器代碼高亮顯示插件
我的習慣片斷用.fsh的後綴名,頂點用.vsh的後綴名


4.2 讀取資源文件並加載程序方法

這些通用的不變的操做能夠提取出來進行復用

public static  int initProgramByAssets(Context ctx, String vertName, String fragName){
    int program=-1;
    ////頂點着色
    int vertexShader = loadShaderAssets(ctx, GLES30.GL_VERTEX_SHADER, vertName);
    //片元着色
    int fragmentShader = loadShaderAssets(ctx, GLES30.GL_FRAGMENT_SHADER, fragName);
    program = GLES30.glCreateProgram();//建立空的OpenGL ES 程序
    GLES30.glAttachShader(program, vertexShader);//加入頂點着色器
    GLES30.glAttachShader(program, fragmentShader);//加入片元着色器
    GLES30.glLinkProgram(program);//建立可執行的OpenGL ES項目
    return program;
}
//從sh腳本中加載shader內容的方法
private static int loadShaderAssets(Context ctx, int type, String name) {
    byte[] buf = new byte[1024];
    StringBuilder sb = new StringBuilder();
    int len;
    try (InputStream is = ctx.getAssets().open(name)) {
        while ((len = is.read(buf)) != -1) {
            sb.append(new String(buf, 0, len));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return loadShader(type, sb.toString());
}
複製代碼

使用時直接加載便可,這樣GLLine的內容就比較精簡了。

program = GLLoader.initProgramByAssets(context, "base.vsh", "base.fsh");
複製代碼

這樣也能正常表現,之後對着色器代碼的修改就是屢見不鮮。

本篇爲你介紹了OpenGLES的基礎使用,旨在爲你打開一扇OpenGLES的大門,其中不少細節一言蔽之,後面會一一道來。因此這一篇能夠不求甚解,跑出來就算你成功了。下一篇將會爲你介紹面的繪製和貼圖,這是OpenGLES和圖片關聯起來的重要部分。


@張風捷特烈 2020.01.10 未允禁轉
個人公衆號:編程之王
聯繫我--郵箱:1981462002@qq.com --微信:zdl1994328
~ END ~

相關文章
相關標籤/搜索