1. 程式人生 > >Android OpenGL ES 開發(二)— 繪製三角形

Android OpenGL ES 開發(二)— 繪製三角形

        在前一篇部落格我們知道了Android中OpenGL ES是什麼,然後知道了怎麼搭建一個OpenGL ES的執行環境,現在我們就來開始繪製我們自己想要的圖形了(繪製圖片會在後面講解,因為繪製圖形是繪製圖片的基礎),我們最先開始繪製一個三角形,因為三角形是很多圖形的基礎。

一、頂點座標系

在繪製之前,我們需要先了解Android中OpenGL ES的頂點座標系是怎樣的,如圖:

頂點座標系

其中:中心座標(0,0)就是我們手機螢幕的中心,然後到最左邊是(-1,0)、最右邊是(1,0)、最上邊是(0,1)、最下邊是(0,-1)這樣就把我們的手機螢幕分成了一箇中心座標為(0,0)上下左右長度分別為1的矩形。不管我們的手機(具體來講是我們的GLSurfaceView)的大小是多少,都會對映到這個矩形中。這也是OpenGL中歸一化

的處理方式,什麼都不管,反正都必須對映到這個範圍內就對了。

二、設定繪製的三角形所需要的三個頂點

因為我們繪製的是三角形,所以我們需要三個頂點來確定我們的三角形的位置,比如我們要繪製如圖的三角形:

由圖我們知道要繪製的三角形的三個頂點座標分別為:(-1,0)、(0,1)和(1,0)

三、本地化三角形頂點

所謂本地化就是跳出java VM(Java虛擬機器)的約束(垃圾回收)範圍,使我們的頂點在程式執行時一直都有自己分配的記憶體地址,不會因為java的GC而把頂點記憶體地址給回收掉,導致頂點不存在,從而引起OpenGL找不到頂點位置等錯誤,所以在OpenGL中我們需要把頂點座標給本地化。

這裡我們就需要分2個步驟來完成頂點的本地化:

3.1、用float陣列來儲存我們的頂點座標,因為頂點座標範圍是在(-1f~1f)之間的所有小數都可以,所以我們先建立頂點陣列:

float[] vertexData = {
            -1.0f, 0.0f,//三角形左下角
            0.0f, 1.0f,//三角形右下角
            1.0f, 0.0f//三角形頂點
    };

3.2、然後根據頂點陣列分配底層記憶體地址,因為需要本地化,所以就和c/c++一樣需要我們手動分配記憶體地址,這裡用到了ByteBuffer這個類:

FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)//分配記憶體空間(單位位元組)
                .order(ByteOrder.nativeOrder())//記憶體bit的排序方式和本地機器一致
                .asFloatBuffer()//轉換成float的buffer,因為我們是放float型別的頂點
                .put(vertexData);//把資料放入記憶體中
        vertexBuffer.position(0);//把索引指標指向開頭位置

首先用allocateDirect分配記憶體大小,其大小為float陣列長度乘以每一個float的大小,而float佔4個位元組,所以就是:vertexData.length * 4;然後設定其在記憶體中的對齊方式(分大端和小端對其)這裡就和本地對齊方式一樣:order(ByteOrder.nativeOrder());然後設定是儲存float型別資料的記憶體空間:asFloatBuffer();最後再用float陣列(vertexData)初始化記憶體中的資料:put(vertexData)。為了能從開頭訪問這塊記憶體地址,還需要設定其position為0:vertexBuffer.position(0);。

這樣我們的三角形的頂點記憶體地址就已經分配好了,並且做了本地持久化。

四、開始頂點著色器的編寫(shader)

OpenGL的操作需要我們自己編寫著色器(shader)程式給它,然後它會用GPU執行這個著色器程式,最終反饋執行結果給我們。我們用glsl語言來編寫著色器程式,其語法方式和c語言類似,這裡就不展開講了,當學會了OpenGL程式設計後,可以自己學習glsl語法,然後就可以根據自己的能力編寫“吊炸天”的效果了。

4.1、編寫頂點著色器(vertex_shader.glsl),位置我們放在:res/raw/路徑下

attribute vec4 av_Position;//用於在java程式碼中獲取的屬性
void main(){
    gl_Position = av_Position;//gl_Position是內建變數,opengl繪製頂點就是根據它的值繪製的,所以我們需要把我們自己的值賦值給它。
}

這段shader很短,但是足夠說明OpenGL中頂點座標的使用方法了:

首先解釋一下attribute vec4 av_Position這句的意思:

attribute是表示頂點屬性的,只能用在頂點座標裡面,然後在應用程式(java程式碼)中可以獲取其變數,然後為其賦值。vec4是一個包含4個值(x,y,z,w)的向量,x和y表示2d平面,加上z就是3d的影象了,最後的w是攝像機的距離,因為我們繪製的是2d圖形,所以最後z和w的值可以不用管,OpenGL會有預設值1。所以這句話的意思就是:聲明瞭一個名字叫av_Position的包含4個向量的attribute型別的變數,用於我們在java程式碼中獲取並把我們的頂點(FloatBuffer vertexBuffer)值賦值給它。這樣OpenGL執行這段著色器程式碼(程式)時,就有了具體的頂點資料,就會在相應的頂點之間繪製圖形(我們定義的三角形)了。

然後void main(){}是程式中函式,和c中是一樣的。

最後是gl_Position = av_Position,這裡的gl_Position是glsl中內建的最終頂點變數,我們要繪製的頂點就是傳遞給它。這段程式碼就是把我們設定的頂點資料傳遞給gl_Position,然後OpenGL就知道在哪裡繪製頂點了。

五、片元著色器程式編寫(shader)

上面我們只是寫了我們的三角形繪製頂點的著色器程式,而三角形是 “頂點+顏色(樣式)” 組成的,所以我們就需要告訴OpenGL我們繪製的三角形是什麼顏色的,這就需要片元著色器程式了。

  1. 編寫片元著色器程式(fragment_shader.glsl)
precision mediump float;//宣告用中等精度的float
uniform vec4 af_Color;//用於在java層傳遞顏色資料
void main(){
    gl_FragColor = af_Color;//gl_FragColor內建變數,opengl渲染的顏色就是獲取的它的值,這裡我們把我們自己的值賦值給它。
}

這裡的precision mediump float 表明用中等精度的float型別來儲存變數,其他還可以設定高精度和低精度,一般中等精度就可以了,精度不同,執行的效率也會有差別。

然後這裡是用了uniform這個型別來宣告變數,uniform是用於應用程式(java程式碼中)向頂點和片元著色器傳遞資料,和attribute的區別在於,attribute是隻能用在頂點著色器程式中,並且它裡面包含的是具體的頂點的資料,每次執行時都需要從頂點記憶體裡面獲取新的值,而uniform始終都是用同一個變數。vec4 af_Color也是宣告一個4個分量的變數af_Color,這個裡面儲存的是顏色的值了(rgba四個分量)。

最後gl_FragColor也是glsl中內建的變數,用於最終渲染顏色的賦值,這裡我們就把我們自己的顏色賦值給gl_FragColor就行,是操作每一個畫素的rgba。

通過第四步和第五步,我們已經設定好了頂點和顏色的著色器程式,接下來就可以讓OpenGL載入這2個著色器程式,然後執行裡面的程式碼,最終繪製出我們想要的圖形(三角形)了。

六、載入並編譯著色器語言

6.1、通過GLES20.glCreateShader(shaderType)建立(頂點或片元)型別的程式碼程式,如:

建立頂點型別的著色器程式碼程式:

int vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)

片元傳入:GLES20.GL_FRAGMENT_SHADER。

6.2、載入shader原始碼並編譯shader

GLES20.glShaderSource(shader, source);//這裡更加我們建立的型別載入相應型別的著色器(如:頂點型別)
GLES20.glCompileShader(shader);//編譯我們自己寫的著色器程式碼程式

6.3、實際建立並返回一個渲染程式(program)

int program = GLES20.glCreateProgram();//建立一個program程式

6.4、將著色器程式新增到渲染程式中

GLES20.glAttachShader(program, vertexShader);//把頂點著色器加入program程式中
GLES20.glAttachShader(program, fragmentShader);//把片元著色器加入program程式中

6.5、連結源程式

GLES20.glLinkProgram(program);//最終連結頂點和片元著色器,後面在program中就可以訪問頂點和片元著色器裡面的屬性了。

通過上面5個步驟,我們就將用glsl寫的著色器程式變成了我們可以在應用程式(java程式碼)中可以獲取裡面的變數並操作變數的具體的程式(program)了。

七、接下來就是傳遞頂點座標和顏色值給著色器程式:

7.1、獲取頂點變數

int aPositionHandl  = GLES20.glGetAttribLocation(programId, "av_Position");//獲取頂點屬性,後面會給它賦值(即:把我們的頂點賦值給它)

這裡的av_Position就是頂點著色器中的attribute變數,後續操作就可以用返回值aPositionHandl這個控制代碼了。

7.2、獲取顏色變數

int afColor = GLES20.glGetUniformLocation(program, "af_Color");//獲取片元變數,後面可以通過它設定片元要顯示的顏色。

這裡的af_Color就是片元著色器中的uniform變數。後面可以對它賦值來改變三角形的顏色。

7.3、開始執行著色器程式

GLES20.glUseProgram(programId);//開始繪製之前,先設定使用當前programId這個程式。

7.4、首先啟用頂點屬性

GLES20.glEnableVertexAttribArray(aPositionHandl);//啟用頂點屬性陣列,啟用後才能對它賦值

7.4、向頂點屬性傳遞頂點陣列的值

GLES20.glVertexAttribPointer(aPositionHandl, 2, GLES20.GL_FLOAT, false, 8,
	 vertexBuffer);//現在就是把我們的頂點vertexBuffer賦值給頂點著色器裡面的變數。

第一引數就是我們的頂點屬性的控制代碼

第二個引數是我們用的幾個分量表示的一個點,這裡用的(x,y)2個分量,所以就填入2

第三個引數表示頂點的資料型別,因為我們用的float型別,所以就填入GL_FLOAT型別

第四個引數是是否做歸一化處理,如果我們的座標不在(-1,1)之間,就需要,由於我們的座標是在(-1,1)之間,所以不需要,填入false

第五個引數是每個點所佔空間大小,因為是(x,y)2個點,每個點是4個位元組,所以一個點佔8個空間大小,這個設定好後,OpenGL才知道8個位元組表示一個點,就能按照這個規則,依次取出所有的點的值。

第六個引數就是OpenGL要從哪個記憶體中取出這些點的資料。

7.4、最後繪製這些頂點

GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);//繪製三角形,從我們的頂點數組裡面第0個位置開始,繪製頂點的個數為3(因為三角形只有三個頂點)

這裡是以頂點陣列的方式來繪製圖形

第一個引數表示繪製的方式:GLES20.GL_TRIANGLES,單個三角形的方式,還有其他方式,我們後面會講解。

第二個引數表示從哪個位置開始繪製,因為頂點座標裡面只有3個座標點,所以從0開始繪製。

第三個引數表示繪製多少個點,這裡顯然繪製三個點。

以上就是OpenGL的執行過程:

座標點(頂點或紋理)->編寫著色器程式->載入著色器程式並編譯生成program->獲取program中的變數->program變數賦值->最終繪製。

注:在載入著色器程式的時候還需要檢查是否載入成功等結果,還有繪製圖形時的清屏操作會在例項程式碼中給出完整的例子。

八、核心程式碼

8.1、載入著色器程式生成program(WlShaderUtil.java)

package com.ywl5320.opengldemo;

import android.content.Context;
import android.opengl.GLES20;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class WlShaderUtil {


    public static String readRawTxt(Context context, int rawId) {
        InputStream inputStream = context.getResources().openRawResource(rawId);
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuffer sb = new StringBuffer();
        String line;
        try
        {
            while((line = reader.readLine()) != null)
            {
                sb.append(line).append("\n");
            }
            reader.close();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return sb.toString();
    }

    public static int loadShader(int shaderType, String source)
    {
        int shader = GLES20.glCreateShader(shaderType);
        if(shader != 0)
        {
            GLES20.glShaderSource(shader, source);
            GLES20.glCompileShader(shader);
            int[] compile = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compile, 0);
            if(compile[0] != GLES20.GL_TRUE)
            {
                Log.d("ywl5320", "shader compile error");
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }

    public static int createProgram(String vertexSource, String fragmentSource)
    {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if(vertexShader == 0)
        {
            return 0;
        }
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if(fragmentShader == 0)
        {
            return 0;
        }
        int program = GLES20.glCreateProgram();
        if(program != 0)
        {
            GLES20.glAttachShader(program, vertexShader);
            GLES20.glAttachShader(program, fragmentShader);
            GLES20.glLinkProgram(program);
            int[] linsStatus = new int[1];
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linsStatus, 0);
            if(linsStatus[0] != GLES20.GL_TRUE)
            {
                Log.d("ywl5320", "link program error");
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        return  program;

    }

}

8.2、WlRender.java

package com.ywl5320.opengldemo;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class WlRender implements GLSurfaceView.Renderer{


    private Context context;

    private final float[] vertexData ={
            -1f, 0f,
            0f, 1f,
            1f, 0f
    };
    private FloatBuffer vertexBuffer;
    private int program;
    private int avPosition;
    private int afColor;



    public WlRender(Context context)
    {
        this.context = context;
        vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertexData);
        vertexBuffer.position(0);
    }


    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        String vertexSource = WlShaderUtil.readRawTxt(context, R.raw.vertex_shader);
        String fragmentSource = WlShaderUtil.readRawTxt(context, R.raw.fragment_shader);
        program = WlShaderUtil.createProgram(vertexSource, fragmentSource);
        if(program > 0)
        {
            avPosition = GLES20.glGetAttribLocation(program, "av_Position");
            afColor = GLES20.glGetUniformLocation(program, "af_Color");
        }
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        GLES20.glUseProgram(program);
        GLES20.glUniform4f(afColor, 1f, 0f, 0f, 1f);
        GLES20.glEnableVertexAttribArray(avPosition);
        GLES20.glVertexAttribPointer(avPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);

    }
}

這裡用到了通過uniform型別變數傳值的方式:

GLES20.glUniform4f(afColor, 1f, 0f, 0f, 1f);//分別設定片元變數的rgba四個值(前面的glUniform4f:表示這是uniform型別的變數的4個float型別的值)

給片元著色器中的顏色變數afColor設定argb的值為:(1f, 0f, 0f,1f)——紅色

然後其他的程式碼和上一篇部落格一樣。

九、最終效果如下:

十、總結

通過本篇文章,我們瞭解了Android中OpenGL ES的頂點座標載入過程,在OpenGL ES中最複雜的影象就是三角形,其他任意影象都可以通過三角形來組合出來,這也為我們後續的功能打下了基礎,務必好好理解裡面的流程和邏輯。

相關推薦

Android OpenGL ES 開發繪製三角形

        在前一篇部落格我們知道了Android中OpenGL ES是什麼,然後知道了怎麼搭建一個OpenGL ES的執行環境,現在我們就來開始繪製我們自己想要的圖形了(繪製圖片會在後面講解,因為繪製圖形是繪製圖片的基礎),我們最先開始繪製一個三角形

一步一步學習Android TV/盒子開發

TV、機頂盒開發除錯不能像手機一樣通過USB線連線除錯,可通過ADB連線除錯 連線電視 adb connect 10.74.84.199 1 2 連線後就可以開始開發除錯了! 斷開連線 // 斷開某個裝置 adb disconnect 10.74.84

OpenGL ES 光照

散射光(Diffuse): 從物體表面向全方位360度均勻反射的光,反射後的散射光在各個方向是均勻的,但反射光的反射強度與入射光的強度以及入射角度密切相關。垂直地照射到物體表面時比斜照時要亮。 散射光具體計算公式: 其中設光照入射角為α. 散射光照射結果=材質的反射係數×散射

新版OpenGL學習入門——繪製圖形

教程連結:你好,三角形 這一章學的東西超級多,學完也算基本入門啦 那就從最基礎的開始吧   頂點輸入 首先是座標軸,它是高中數學學的直角座標系的座標軸,理解特別簡單。 對應的數值需要在-1和1之間,大概類似百分比吧,最後的f代表浮點數。 和頂點對應的是頂點緩衝物

Android Studio Jni開發實現Native呼叫java方法和Native呼叫Android API

這一篇主要內容是Native呼叫java方法和Native呼叫Android API,以及External Tools快速生成.h檔案,依然是使用NDK方式編譯,如果是複製貼上黨,建議跟本文用一樣的工程名,本文後面會提供demo連結 一、建立工程 1.建立名為Jnites

Android藍芽開發 BLE4.0低功耗藍芽

一、BLE4.0低功耗藍芽 Bluetooth Low Energy,藍芽低功耗,是從藍芽4.0開始支援的技術。相較傳統藍芽,傳輸速度更快、覆蓋範圍廣、安全性高、延時短、耗電低等特點。 二、關鍵術語 1.GATT(通用屬性配置):通用屬性配置檔案,用於ble鏈路上傳送和接

Android微信開發:程式碼分析

package com.example.teststudyshare.wxapi; import java.io.ObjectOutputStream.PutField; import android.R; import android.app.Activity; import android.content

Android快樂貪吃蛇遊戲實戰專案開發教程-03虛擬方向鍵繪製一個三角形

一、繪製三角形 在上一篇文章中,我們已經新建了虛擬方向鍵的自定義控制元件DirectionKeys類,下面我們繼續。 本專案中的虛擬方向鍵的背景是4個三角形組成的矩形,其實是4個三角形的按鈕。 系統自帶的按鈕是矩形的,怎麼做一個三角形按鈕呢? 首先我需要了解,所有控制元件的外觀都是畫出來的,當然不

android的百度地圖開發 定位

頻率 update 殺死 一次 ddr animate 語義 pri des 參考:http://blog.csdn.net/mr_wzc/article/details/51590485 第一步,初始化LocationClient類 //獲取地圖控件引用

Android OpenGL ES 開發教程 22 繪製一個球體

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android開發SQLite視覺化工具SQLiteStudio的用法

一、SQLiteStudio下載地址:  http://www.pc6.com/softview/SoftView_86552.html#download二、SQLiteStudio使用教程第一步:建庫    開啟SQLite Studio,點選“新增資料庫”按鈕第二步:資料

Android 百萬級視訊應用開發

在完成可之前的思考之後,我感覺我也是絞盡腦汁了。 7月25號的時候出了效果圖,和切圖,我26號下午完成了整個軟體的頁面搭建。歷史8.5小時26個頁面。但是我想我應該可以做的更好,因為,我的封裝做的還不徹底,既然複用那麼複用率就應該達到100%!既然封裝,那麼就

通俗易懂的 OpenGL ES 3.0渲染三角形

前言 學習了OpenGL有一段時間,在繪製出屬於自己的三角形之前,會接觸許多理論上的知識。用簡單的方式寫下自己對OpenGL的一些見解。望大家取其精華去其糟粕 最終效果:改變背景色,並且繪製渲染一個暗紅色的三角形 必備知識 OpenGL需要我們至少設定一個

自學Android開發 AndroidStudio 匯入Github上下載的專案,以及使用Genymotion所遇到的問題

因為是自學Android自然是想找一些原始碼,自己跑著試試,然後自己再去解讀這些程式碼~~本著這樣的想法,於是就在Github上找了一個Android的小遊戲https://github.com/HurTeng/StormPlane    連線奉上~~   -。-高高興興的下

Android Gallery3d原始碼學習總結——繪製流程drawThumbnails

此函式控制相簿表格頁、相片表格頁、時間分類表格頁的展示,非常重要。以下以相簿表格頁為例進行講解,其他的就舉一反三吧。準備輸入引數 final GridDrawables drawables = mDrawables;         final DisplayList d

Android NDK 開發JNI 傳遞引數和返回值

前言 我們在使用 JNI 時最常問到的是 JAVA 和 C/C++之間如何傳遞資料,以及資料型別之間如何 互相對映。我們從整數等基本型別和陣列、字串等普通的物件型別開始講述。至於如何傳遞任意物件,將在後面會更新。 正文 繼JNI簡介及呼叫流程這

Android 自定義View,點,線的繪製

public class PointLine extends View { Paint mLinePaint; Paint mPointPaint; float width; float height; float pointAddress

【學習OpenGL——繪製矩形視口與裁剪區

繪製矩形 在前面第一個建立視窗的程式的基礎之上,新增繪製矩形的函式 #include <gl/glut.h> void RenderScene(void) { // 用當前的清除顏色清除視窗 glClear(GL_COLOR

android Bluetooth 開發:開啟、關閉、搜尋、允許搜尋、檢視

相關專案的下載連結繼本專案之後實現了語音識別:點選開啟連結1.承接上一篇文章,本篇文章主要實現了藍芽的開啟 關閉 允許搜尋 檢視配對裝置2. BluetoothInit,主要實現了部件的初始化,按鈕的點選事件,通過ListVIew顯示本地配對的藍芽裝置,ListView的點選

Android OpenGL ES 開發繪製圖形

# OpenGL 繪製圖形步驟 上一篇介紹了 OpenGL 的相關概念,今天來實際操作,使用 OpenGL 繪製出圖形,對其過程有一個初步的瞭解。 OpenGL 繪製圖形主要概括成以下幾個步驟: 1. 建立程式 2. 初始化著色器 3. 將著色器加入程式 4. 連結並使用程式 6. 繪製圖形 上述每個