用 OpenGL 畫一個三角形丨音視頻工程示例

這個公眾號會路線圖 式的遍歷分享音視頻技術(shù)音視頻基礎(chǔ)音視頻工具音視頻工程示例音視頻工業(yè)實戰(zhàn)苍在。關(guān)注一下成本不高讨永,錯過干貨損失不小 ↓↓↓

渲染是音視頻技術(shù)棧相關(guān)的一個非常重要的方向,視頻圖像在設(shè)備上的展示、各種流行的視頻特效都離不開渲染技術(shù)的支持。

在 RenderDemo 這個工程示例系列,我們將為大家展示一些渲染相關(guān)的 Demo剖踊,來向大家介紹如何在 iOS/Android 平臺上手一些渲染相關(guān)的開發(fā)庶弃。

這里是第一篇:用 OpenGL 畫一個三角形衫贬。我們分別在 iOS 和 Android 實現(xiàn)了用 OpenGL 畫一個三角形的 Demo。在本文中歇攻,包括如下內(nèi)容:

  • 1)iOS OpenGL 繪制三角形 Demo固惯;
  • 2)Android OpenGL 繪制三角形 Demo;
  • 3)詳盡的代碼注釋缴守,幫你理解代碼邏輯和原理葬毫。

如果你想要獲得我們所有 Demo 的工程源碼镇辉,可以在關(guān)注本公眾號后,在公眾號發(fā)送消息『AVDemo』來咨詢贴捡。

在繼續(xù)閱讀下文前忽肛,你可能需要對 OpenGL 的基礎(chǔ)知識有一些了解,你可以看看這篇文章:OpenGL 基礎(chǔ)知識烂斋。

如果我們了解了 OpenGL ES 就會知道屹逛,雖然它定義了一套移動設(shè)備的圖像渲染 API,但是并沒有定義窗口系統(tǒng)汛骂。為了讓 GLES 能夠適配各種平臺罕模,GLES 需要與知道如何通過操作系統(tǒng)創(chuàng)建和訪問窗口的庫結(jié)合使用,這就有了 EGL帘瞭,EGL 是 OpenGL ES 渲染 API 和本地窗口系統(tǒng)之間的一個中間接口層淑掌,它主要由系統(tǒng)制造商實現(xiàn)。EGL 提供如下機制:

  • 與設(shè)備的原生窗口系統(tǒng)通信蝶念;
  • 查詢繪圖表面的可用類型和配置抛腕;
  • 創(chuàng)建繪圖表面;
  • 在 OpenGL ES 和其他圖形渲染 API 之間同步渲染媒殉;
  • 管理紋理貼圖等渲染資源兽埃。

EGL 是 OpenGL ES 與設(shè)備的橋梁,以實現(xiàn)讓 OpenGL ES 能夠在當前設(shè)備上進行繪制适袜。

1柄错、iOS Demo

iOS 平臺對 EGL 的實現(xiàn)是 EAGL(Embedded Apple Graphics Library) ,其中 CAEAGLLayer 就是一種可以支持 OpenGL ES 繪制的圖層類型苦酱,我們的 Demo 里會用它作為繪制三角形的圖層售貌。

代碼比較簡單,我們直接上代碼:

DMTriangleRenderView.m


#import "DMTriangleRenderView.h"  
#import <OpenGLES/ES2/gl.h>  
  
// 定義頂點的數(shù)據(jù)結(jié)構(gòu):包括頂點坐標和顏色維度疫萤。  
#define PositionDimension 3  
#define ColorDimension 4  
typedef struct {  
    GLfloat position[PositionDimension]; // { x, y, z }  
    GLfloat color[ColorDimension]; // {r, g, b, a}  
} SceneVertex;  
  
  
@interface DMTriangleRenderView ()  
@property (nonatomic, assign) GLsizei width;  
@property (nonatomic, assign) GLsizei height;  
  
@property (nonatomic, strong) CAEAGLLayer *eaglLayer;  
@property (nonatomic, strong) EAGLContext *eaglContext;  
  
@property (nonatomic, assign) GLuint simpleProgram;  
  
@property (nonatomic, assign) GLuint renderBuffer;  
@property (nonatomic, assign) GLuint frameBuffer;  
  
@end  
  
@implementation DMTriangleRenderView  
  
#pragma mark - Setup  
- (instancetype)initWithFrame:(CGRect)frame {  
    self = [super initWithFrame:frame];  
    if (self) {  
        _width = frame.size.width;  
        _height = frame.size.height;  
        [self render];  
    }  
    return self;  
}  
  
#pragma mark - Action  
- (void)render {  
    // 1颂跨、設(shè)定 layer 的類型。  
    _eaglLayer = (CAEAGLLayer *) self.layer;  
    _eaglLayer.opacity = 1.0;  
    _eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking: @(NO),  
                                      kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};  
  
      
    // 2扯饶、創(chuàng)建 OpenGL 上下文恒削。  
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2; // 使用的 OpenGL API 的版本。  
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:api];  
    if (!context) {  
        NSLog(@"Create context failed!");  
        return;  
    }  
    BOOL r = [EAGLContext setCurrentContext:context];  
    if (!r) {  
        NSLog(@"setCurrentContext failed!");  
        return;  
    }  
    _eaglContext = context;  
  
      
    // 3尾序、申請并綁定渲染緩沖區(qū)對象 RBO 用來存儲即將繪制到屏幕上的圖像數(shù)據(jù)钓丰。  
    glGenRenderbuffers(1, &_renderBuffer); // 創(chuàng)建 RBO。  
    glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); // 綁定 RBO 到 OpenGL 渲染管線每币。  
    [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer]; // 將渲染圖層(_eaglLayer)的存儲綁定到 RBO携丁。  
  
      
    // 4、申請并綁定幀緩沖區(qū)對象 FBO兰怠。FBO 本身不能用于渲染梦鉴,只有綁定了紋理(Texture)或者渲染緩沖區(qū)(RBO)等作為附件之后才能作為渲染目標李茫。  
    glGenFramebuffers(1, &_frameBuffer); // 創(chuàng)建 FBO。  
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer); // 綁定 FBO 到 OpenGL 渲染管線肥橙。  
    // 將 RBO 綁定為 FBO 的一個附件魄宏,綁定后,OpenGL 對 FBO 的繪制會同步到 RBO 后再上屏存筏。  
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,  
                              GL_COLOR_ATTACHMENT0,  
                              GL_RENDERBUFFER,  
                              _renderBuffer);  
  
      
    // 5娜庇、清理窗口顏色,并設(shè)置渲染窗口方篮。  
    glClearColor(0.5, 0.5, 0.5, 1.0); // 設(shè)置渲染窗口顏色名秀。這里是灰色。  
    glClear(GL_COLOR_BUFFER_BIT); // 清空舊渲染緩存藕溅。  
    glViewport(0, 0, _width, _height); // 設(shè)置渲染窗口區(qū)域匕得。  
  
  
    // 6、加載和編譯 shader巾表,并鏈接到著色器程序汁掠。  
    if (_simpleProgram) {  
        glDeleteProgram(_simpleProgram);  
        _simpleProgram = 0;  
    }  
    // 加載和編譯 shader。  
    NSString *simpleVSH = [[NSBundle mainBundle] pathForResource:@"simple" ofType:@"vsh"];  
    NSString *simpleFSH = [[NSBundle mainBundle] pathForResource:@"simple" ofType:@"fsh"];  
    _simpleProgram = [self loadShaderWithVertexShader:simpleVSH fragmentShader:simpleFSH];  
    // 鏈接 shader program集币。  
    glLinkProgram(_simpleProgram);  
    // 打印鏈接日志考阱。  
    GLint linkStatus;  
    glGetProgramiv(_simpleProgram, GL_LINK_STATUS, &linkStatus);  
    if (linkStatus == GL_FALSE) {  
        GLint infoLength;  
        glGetProgramiv(_simpleProgram, GL_INFO_LOG_LENGTH, &infoLength);  
        if (infoLength > 0) {  
            GLchar *infoLog = malloc(sizeof(GLchar) * infoLength);  
            glGetProgramInfoLog(_simpleProgram, infoLength, NULL, infoLog);  
            NSLog(@"%s", infoLog);  
            free(infoLog);  
        }  
    }  
    glUseProgram(_simpleProgram);  
      
      
    // 7、根據(jù)三角形頂點信息申請頂點緩沖區(qū)對象 VBO 和拷貝頂點數(shù)據(jù)鞠苟。  
    // 設(shè)置三角形 3 個頂點數(shù)據(jù)乞榨,包括坐標信息和顏色信息。  
    const SceneVertex vertices[] = {  
        {{-0.5,  0.5, 0.0}, { 1.0, 0.0, 0.0, 1.000}}, // 左下 // 紅色  
        {{-0.5, -0.5, 0.0}, { 0.0, 1.0, 0.0, 1.000}}, // 右下 // 綠色  
        {{ 0.5, -0.5, 0.0}, { 0.0, 0.0, 1.0, 1.000}}, // 左上 // 藍色  
    };  
    // 申請并綁定 VBO当娱。VBO 的作用是在顯存中提前開辟好一塊內(nèi)存吃既,用于緩存頂點數(shù)據(jù),從而避免每次繪制時的 CPU 與 GPU 之間的內(nèi)存拷貝跨细,可以提升渲染性能鹦倚。  
    GLuint vertexBufferID;  
    glGenBuffers(1, &vertexBufferID); // 創(chuàng)建 VBO。  
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); // 綁定 VBO 到 OpenGL 渲染管線冀惭。  
    // 將頂點數(shù)據(jù) (CPU 內(nèi)存) 拷貝到 VBO(GPU 顯存)震叙。  
    glBufferData(GL_ARRAY_BUFFER, // 緩存塊類型。  
                 sizeof(vertices), // 創(chuàng)建的緩存塊尺寸散休。  
                 vertices, // 要綁定的頂點數(shù)據(jù)媒楼。  
                 GL_STATIC_DRAW); // 緩存塊用途。  
      
    // 8溃槐、繪制三角形匣砖。  
    // 獲取與 Shader 中對應(yīng)的參數(shù)信息:  
    GLuint vertexPositionLocation = glGetAttribLocation(_simpleProgram, "v_position");  
    GLuint vertexColorLocation = glGetAttribLocation(_simpleProgram, "v_color");  
    // 頂點位置屬性。  
    glEnableVertexAttribArray(vertexPositionLocation); // 啟用頂點位置屬性通道昏滴。  
    // 關(guān)聯(lián)頂點位置數(shù)據(jù)猴鲫。  
    glVertexAttribPointer(vertexPositionLocation, // attribute 變量的下標,范圍是 [0, GL_MAX_VERTEX_ATTRIBS - 1]谣殊。  
                          PositionDimension, // 指頂點數(shù)組中拂共,一個 attribute 元素變量的坐標分量是多少(如:position, 程序提供的就是 {x, y, z} 點就是 3 個坐標分量)。  
                          GL_FLOAT, // 數(shù)據(jù)的類型姻几。  
                          GL_FALSE, // 是否進行數(shù)據(jù)類型轉(zhuǎn)換宜狐。  
                          sizeof(SceneVertex), // 每一個數(shù)據(jù)在內(nèi)存中的偏移量,如果填 0 就是每一個數(shù)據(jù)緊緊相挨著蛇捌。  
                          (const GLvoid*) offsetof(SceneVertex, position)); // 數(shù)據(jù)的內(nèi)存首地址抚恒。  
    // 頂點顏色屬性。  
    glEnableVertexAttribArray(vertexColorLocation);  
    // 關(guān)聯(lián)頂點顏色數(shù)據(jù)络拌。  
    glVertexAttribPointer(vertexColorLocation,  
                          ColorDimension,  
                          GL_FLOAT,  
                          GL_FALSE,  
                          sizeof(SceneVertex),  
                          (const GLvoid*) offsetof(SceneVertex, color));  
    // 繪制所有圖元俭驮。  
    glDrawArrays(GL_TRIANGLES, // 繪制的圖元方式。  
                 0, // 從第幾個頂點下標開始繪制春贸。  
                 sizeof(vertices) / sizeof(vertices[0])); // 有多少個頂點下標需要繪制混萝。  
    // 把 Renderbuffer 的內(nèi)容顯示到窗口系統(tǒng) (CAEAGLLayer) 中。  
    [_eaglContext presentRenderbuffer:GL_RENDERBUFFER];  
      
      
    // 9萍恕、清理逸嘀。  
    glDisableVertexAttribArray(vertexColorLocation); // 關(guān)閉頂點顏色屬性通道。  
    glDisableVertexAttribArray(vertexPositionLocation); // 關(guān)閉頂點位置屬性通道允粤。  
    glBindBuffer(GL_ARRAY_BUFFER, 0); // 解綁 VBO崭倘。  
    glBindFramebuffer(GL_FRAMEBUFFER, 0); // 解綁 FBO。  
    glBindRenderbuffer(GL_RENDERBUFFER, 0); // 解綁 RBO类垫。  
}  
  
#pragma mark - Utility  
- (GLuint)loadShaderWithVertexShader:(NSString *)vert fragmentShader:(NSString *)frag {  
    GLuint verShader, fragShader;  
    GLuint program = glCreateProgram(); // 創(chuàng)建 Shader Program 對象绳姨。  
      
    [self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];  
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];  
      
    // 裝載 Vertex Shader 和 Fragment Shader。  
    glAttachShader(program, verShader);  
    glAttachShader(program, fragShader);  
      
    glDeleteShader(verShader);  
    glDeleteShader(fragShader);  
      
    return program;  
}  
  
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file {  
    NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];  
    const GLchar *source = (GLchar *) [content UTF8String];  
    *shader = glCreateShader(type); // 創(chuàng)建一個著色器對象阔挠。  
    glShaderSource(*shader, 1, &source, NULL); // 關(guān)聯(lián)頂點飘庄、片元著色器的代碼。  
    glCompileShader(*shader); // 編譯著色器代碼购撼。  
      
    // 打印編譯日志跪削。  
    GLint compileStatus;  
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &compileStatus);  
    if (compileStatus == GL_FALSE) {  
        GLint infoLength;  
        glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &infoLength);  
        if (infoLength > 0) {  
            GLchar *infoLog = malloc(sizeof(GLchar) * infoLength);  
            glGetShaderInfoLog(*shader, infoLength, NULL, infoLog);  
            NSLog(@"%s -> %s", (type == GL_VERTEX_SHADER) ? "vertex shader" : "fragment shader", infoLog);  
            free(infoLog);  
        }  
    }  
}  
  
#pragma mark - Override  
+ (Class)layerClass {  
    return [CAEAGLLayer class];  
}  
  
@end


上面的代碼包括了這些過程:

  • 1)定義頂點的數(shù)據(jù)結(jié)構(gòu):包括頂點坐標和顏色維度;
  • 2)設(shè)定 layer 的類型迂求;
  • 3)創(chuàng)建 OpenGL 上下文碾盐;
  • 4)申請并綁定渲染緩沖區(qū)對象 RBO 用來存儲即將繪制到屏幕上的圖像數(shù)據(jù);
  • 5)申請并綁定幀緩沖區(qū)對象 FBO揩局;
    • 需要注意毫玖,F(xiàn)BO 本身不能用于渲染,只有綁定了紋理(Texture)或者渲染緩沖區(qū)(RBO)等作為附件之后才能作為渲染目標。
  • 6)清理窗口顏色付枫,并設(shè)置渲染窗口烹玉;
  • 7)加載和編譯 shader,并鏈接到著色器程序阐滩;
  • 8)根據(jù)三角形頂點信息申請頂點緩沖區(qū)對象 VBO 和拷貝頂點數(shù)據(jù)二打;
    • 這里 VBO 的作用是在顯存中提前開辟好一塊內(nèi)存,用于緩存頂點數(shù)據(jù)掂榔,從而避免每次繪制時的 CPU 與 GPU 之間的內(nèi)存拷貝继效,可以提升渲染性能。
  • 9)繪制三角形装获;
  • 10)清理狀態(tài)機瑞信。

更具體細節(jié)見上述代碼及其注釋。

最終我們畫出的三角形如下圖所示:

OpenGL 繪制三角形(iOS)

2穴豫、Android Demo

Android 平臺自 2.0 版本之后圖形系統(tǒng)的底層渲染均由 OpenGL ES 負責凡简,其 EGL 架構(gòu)實現(xiàn)如下圖所示:

Android EGL 架構(gòu)
  • Display 是對實際顯示設(shè)備的抽象。在 Android 上的實現(xiàn)類是 EGLDisplay绩郎。
  • Surface 是對用來存儲圖像的內(nèi)存區(qū)域 FrameBuffer 的抽象潘鲫,包括 Color Buffer、Stencil Buffer肋杖、Depth Buffer溉仑。在 Android 上的實現(xiàn)類是 EGLSurface
  • Context 存儲 OpenGL ES 繪圖的一些狀態(tài)信息状植。在 Android 上的實現(xiàn)類是 EGLContext浊竟。

Android Demo 的代碼由于平臺 EGL 實現(xiàn)的原因以及對模塊略作了封裝,所以看起來對稍微復雜一些津畸,包括了如下幾個文件:

  • KFGLContext.java振定,KFGLContext 負責管理和組裝 EGLDisplay、EGLSurface肉拓、EGLContext后频。
  • KFGLProgram.java,KFGLProgram 負責加載和編譯著色器暖途,創(chuàng)建著色器程序容器卑惜。
  • KFSurfaceView.java,KFSurfaceView 繼承自 SurfaceView 來實現(xiàn)渲染驻售。
  • KFTextureView.java露久,KFTextureView 繼承自 TextureView 來實現(xiàn)渲染。
  • KFRenderView.java欺栗,KFRenderView 是一個容器毫痕,可以選擇使用 KFSurfaceView 或 KFTextureView 作為實際的渲染視圖征峦。
  • KFRenderListener.java,KFRenderListener 是 KFRenderView 用來監(jiān)聽渲染視圖的事件回調(diào)消请。

代碼如下:

KFGLContext.java


// ...省略 import 代碼...  
public class KFGLContext {  
    private Surface mSurface = null;  
    private EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; // 實際顯示設(shè)備的抽象  
    private EGLContext mEGLContext = EGL_NO_CONTEXT; // 渲染上下文  
    private EGLSurface mEGLSurface = EGL_NO_SURFACE; // 存儲圖像的內(nèi)存區(qū)域  
    private EGLContext mEGLShareContext = EGL_NO_CONTEXT; // 共享渲染上下文  
    private EGLConfig mEGLConfig = null; // 渲染表面的配置信息  
  
    private EGLContext mLastContext = EGL_NO_CONTEXT; // 存儲之前系統(tǒng)上下文  
    private EGLDisplay mLastDisplay = EGL_NO_DISPLAY; // 存儲之前系統(tǒng)設(shè)備  
    private EGLSurface mLastSurface = EGL_NO_SURFACE; // 存儲之前系統(tǒng)內(nèi)存區(qū)域  
    private boolean mIsBind = false;  
  
    public KFGLContext(EGLContext shareContext) {  
        mEGLShareContext = shareContext;  
        // 創(chuàng)建 OpenGL 上下文  
        _eglSetup();  
    }  
  
    public KFGLContext(EGLContext shareContext, Surface surface) {  
        mEGLShareContext = shareContext;  
        mSurface = surface;  
        // 創(chuàng)建 OpenGL 上下文  
        _eglSetup();  
    }  
  
    public void setSurface(Surface surface) {  
        if (surface == null || surface == mSurface) {  
            return;  
        }  
  
        // 釋放渲染表面 Surface  
        if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {  
            eglDestroySurface(mEGLDisplay, mEGLSurface);  
        }  
        // 創(chuàng)建 Surface  
        int[] surfaceAttribs = {  
                EGL14.EGL_NONE  
        };  
        mSurface = surface;  
        mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mSurface, surfaceAttribs, 0);  
        if (mEGLSurface == null) {  
            throw new RuntimeException("surface was null");  
        }  
    }  
  
    public EGLContext getContext() {  
        return mEGLContext;  
    }  
  
    public Surface getSurface() {  
        return mSurface;  
    }  
  
    public boolean swapBuffers() {  
        // 將后臺繪制的緩沖顯示到前臺  
        if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {  
            return eglSwapBuffers(mEGLDisplay, mEGLSurface);  
        } else {  
            return false;  
        }  
    }  
  
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)  
    public void setPresentationTime(long nsecs) {  
        // 設(shè)置時間戳  
        if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {  
            eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);  
        }  
    }  
  
    public void bind() {  
        // 綁定當前上下文  
        mLastSurface = eglGetCurrentSurface(EGL14.EGL_READ);  
        mLastContext = eglGetCurrentContext();  
        mLastDisplay = eglGetCurrentDisplay();  
        if (!eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {  
            throw new RuntimeException("eglMakeCurrent failed");  
        }  
        mIsBind = true;  
    }  
  
    public void unbind() {  
        if (!mIsBind) {  
            return;  
        }  
  
        // 綁定回系統(tǒng)之前上下文  
        if (mLastSurface != EGL_NO_SURFACE && mLastContext != EGL_NO_CONTEXT  && mLastDisplay != EGL_NO_DISPLAY) {  
            eglMakeCurrent(mLastDisplay, mLastSurface, mLastSurface, mLastContext);  
            mLastDisplay = EGL_NO_DISPLAY;  
            mLastSurface = EGL_NO_SURFACE;  
            mLastContext = EGL_NO_CONTEXT;  
        } else {  
            if (mEGLDisplay != EGL_NO_DISPLAY) {  
                eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);  
            }  
        }  
        mIsBind = false;  
    }  
  
    public void release() {  
        unbind();  
  
        // 釋放設(shè)備栏笆、Surface  
        if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {  
            eglDestroySurface(mEGLDisplay, mEGLSurface);  
        }  
  
        if (mEGLDisplay != EGL_NO_DISPLAY && mEGLContext != EGL_NO_CONTEXT) {  
            eglDestroyContext(mEGLDisplay, mEGLContext);  
        }  
  
        if(mEGLDisplay != EGL_NO_DISPLAY){  
            eglTerminate(mEGLDisplay);  
        }  
  
        mSurface = null;  
        mEGLShareContext = null;  
  
        mEGLDisplay = null;  
        mEGLContext = null;  
        mEGLSurface = null;  
    }  
  
    private void _eglSetup() {  
        // 創(chuàng)建設(shè)備  
        mEGLDisplay = eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);  
        if (mEGLDisplay == EGL_NO_DISPLAY) {  
            throw new RuntimeException("unable to get EGL14 display");  
        }  
  
        int[] version = new int[2];  
        // 根據(jù)版本初始化設(shè)備  
        if (!eglInitialize(mEGLDisplay, version, 0, version, 1)) {  
            mEGLDisplay = null;  
            throw new RuntimeException("unable to initialize EGL14");  
        }  
  
        // 定義 EGLConfig 屬性配置,定義紅梯啤、綠竖伯、藍存哲、透明度因宇、深度、模板緩沖的位數(shù)  
        int[] attribList = {  
                EGL14.EGL_BUFFER_SIZE, 32,  
                EGL14.EGL_ALPHA_SIZE, 8, // 顏色緩沖區(qū)中透明度用幾位來表示  
                EGL14.EGL_BLUE_SIZE, 8,  
                EGL14.EGL_GREEN_SIZE, 8,  
                EGL14.EGL_RED_SIZE, 8,  
                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,  
                EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,  
                EGL14.EGL_NONE  
        };  
        EGLConfig[] configs = new EGLConfig[1];  
        int[] numConfigs = new int[1];  
        // 找到符合要求的 EGLConfig 配置  
        if (!eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,  
                numConfigs, 0)) {  
            throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");  
        }  
        mEGLConfig = configs[0];  
  
        // 指定 OpenGL 使用版本  
        int[] attrib_list = {  
                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,  
                EGL14.EGL_NONE  
        };  
        // 創(chuàng)建 GL 上下文  
        mEGLContext = eglCreateContext(mEGLDisplay, mEGLConfig, mEGLShareContext != null ? mEGLShareContext : EGL_NO_CONTEXT, attrib_list, 0);  
        if (mEGLContext == null) {  
            throw new RuntimeException("null context");  
        }  
  
        // 創(chuàng)建 Surface 配置信息  
        int[] surfaceAttribs = {  
                EGL14.EGL_NONE  
        };  
  
        // 創(chuàng)建 Surface  
        if (mSurface != null) {  
            mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mSurface,  
                    surfaceAttribs, 0);  
        } else {  
            mEGLSurface = eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0);  
        }  
  
        if (mEGLSurface == null) {  
            throw new RuntimeException("surface was null");  
        }  
    }  
}

KFGLProgram.java


// ...省略 import 代碼...  
public class KFGLProgram {  
    private static final String TAG = "KFGLProgram";  
    private int mProgram = 0; // 著色器程序容器  
    private int mVertexShader = 0; // 頂點著色器  
    private int mFragmentShader = 0; // 片元著色器  
  
    public KFGLProgram(String vertexShader, String fragmentShader) {  
        _createProgram(vertexShader,fragmentShader);  
    }  
  
    public void release() {  
        // 釋放頂點祟偷、片元著色器察滑、著色器容器  
        if (mVertexShader != 0) {  
            glDeleteShader(mVertexShader);  
            mVertexShader = 0;  
        }  
  
        if (mFragmentShader != 0) {  
            glDeleteShader(mFragmentShader);  
            mFragmentShader = 0;  
        }  
  
        if (mProgram != 0) {  
            glDeleteProgram(mProgram);  
            mProgram = 0;  
        }  
    }  
  
    public void use() {  
        // 使用當前的著色器  
        if (mProgram != 0) {  
            glUseProgram(mProgram);  
        }  
    }  
  
    public int getUniformLocation(String uniformName) {  
        // 獲取著色器 uniform 對應(yīng)下標  
        return glGetUniformLocation(mProgram, uniformName);  
    }  
  
    public int getAttribLocation(String uniformName) {  
        // 獲取著色器 attribute 變量對應(yīng)下標  
        return glGetAttribLocation(mProgram, uniformName);  
    }  
  
    private void  _createProgram(String vertexSource, String fragmentSource) {  
        // 創(chuàng)建著色器容器  
        // 創(chuàng)建頂點、片元著色器  
        mVertexShader = _loadShader(GLES20.GL_VERTEX_SHADER,   vertexSource);  
        mFragmentShader = _loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);  
  
        //  
        if(mVertexShader != 0 && mFragmentShader != 0){  
            // 創(chuàng)建一個空的著色器容器  
            mProgram = GLES20.glCreateProgram();  
            // 將頂點修肠、片元著色器添加至著色器容器  
            glAttachShader(mProgram, mVertexShader);  
            glAttachShader(mProgram, mFragmentShader);  
  
            // 鏈接著色器容器  
            glLinkProgram(mProgram);  
            int[] linkStatus = new int[1];  
            glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);  
            // 獲取鏈接狀態(tài)  
            if (linkStatus[0] != GLES20.GL_TRUE) {  
                Log.e(TAG, "Could not link program:");  
                Log.e(TAG, glGetProgramInfoLog(mProgram));  
                glDeleteProgram(mProgram);  
                mProgram = 0;  
            }  
        }  
    }  
  
    private int _loadShader(int shaderType, String source) {  
        // 根據(jù)類型創(chuàng)建頂點贺辰、片元著色器  
        int shader = glCreateShader(shaderType);  
        // 設(shè)置著色器中的源代碼  
        glShaderSource(shader, source);  
        // 編譯著色器  
        glCompileShader(shader);  
  
        int[] compiled = new int[1];  
        glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);  
        // 獲取編譯后狀態(tài)  
        if (compiled[0] != GLES20.GL_TRUE) {  
            Log.e(TAG, "Could not compile shader(TYPE=" + shaderType + "):");  
            Log.e(TAG, glGetShaderInfoLog(shader));  
            glDeleteShader(shader);  
            shader = 0;  
        }  
  
        return shader;  
    }  
}

KFSurfaceView.java

// ...省略 import 代碼...  
public class KFSurfaceView extends SurfaceView implements SurfaceHolder.Callback {  
    private KFRenderListener mListener = null; // 回調(diào)  
    private SurfaceHolder mHolder = null; // Surface 的抽象接口  
  
    public KFSurfaceView(Context context, KFRenderListener listener) {  
        super(context);  
        mListener = listener;  
        getHolder().addCallback(this);  
    }  
  
    @Override  
    public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {  
        // Surface 創(chuàng)建  
        mHolder = surfaceHolder;  
        // 根據(jù) SurfaceHolder 創(chuàng)建 Surface  
        if(mListener != null){  
            mListener.surfaceCreate(surfaceHolder.getSurface());  
        }  
    }  
  
    @Override  
    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {  
        // Surface 分辨率變更  
        if(mListener != null){  
            mListener.surfaceChanged(surfaceHolder.getSurface(),width,height);  
        }  
    }  
  
    @Override  
    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {  
        // Surface 銷毀  
        if (mListener != null) {  
            mListener.surfaceDestroy(surfaceHolder.getSurface());  
        }  
    }  
}

KFTextureView.java

// ...省略 import 代碼...
public class KFTextureView extends TextureView implements TextureView.SurfaceTextureListener{
    private KFRenderListener mListener = null; // 回調(diào)
    private Surface mSurface = null; // 渲染緩存
    private SurfaceTexture mSurfaceTexture = null; // 紋理緩存

    public KFTextureView(Context context, KFRenderListener listener) {
        super(context);
        this.setSurfaceTextureListener(this);
        mListener = listener;
    }

    @Override
    public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
        // 紋理緩存創(chuàng)建
        mSurfaceTexture = surfaceTexture;
        // 根據(jù) SurfaceTexture 創(chuàng)建 Surface
        mSurface = new Surface(surfaceTexture);
        if (mListener != null) {
            // 創(chuàng)建時候回調(diào)一次分辨率變更,對應(yīng) SurfaceView 接口
            mListener.surfaceCreate(mSurface);
            mListener.surfaceChanged(mSurface,width,height);
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
        // 紋理緩存變更分辨率
        if (mListener != null) {
            mListener.surfaceChanged(mSurface,width,height);
        }
    }

    @Override
    public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
        // 紋理緩存銷毀
        if (mListener != null) {
            mListener.surfaceDestroy(mSurface);
        }
        if (mSurface != null) {
            mSurface.release();
            mSurface = null;
        }
        return false;
    }
}

KFRenderView.java

// ...省略 import 代碼...
public class KFRenderView extends ViewGroup {
    // 頂點著色器
    public static String customVertexShader =
                    "attribute vec4 v_position;\n" +
                    "attribute vec4 v_color;\n" +
                    "varying mediump vec4 f_color;\n" +
                    "void main() {\n" +
                    "  f_color = v_color;\n" +
                    "  gl_Position = v_position;\n" +
                    "}\n";

    // 片元著色器
    public static String customFragmentShader =
                    "varying mediump vec4 f_color;\n" +
                    "void main() {\n" +
                    "  gl_FragColor = f_color;\n" +
                    "}\n";

    private KFGLContext mEGLContext = null; // OpenGL 上下文
    private EGLContext mShareContext = null; // 共享上下文
    private View mRenderView = null; // 渲染視圖基類
    private int mSurfaceWidth = 0; // 渲染緩存寬
    private int mSurfaceHeight = 0; // 渲染緩存高
    private boolean mSurfaceChanged = false; // 渲染緩存是否變更
    private KFGLProgram mProgram;
    private FloatBuffer mVerticesBuffer = null; // 頂點 buffer
    private int mPositionAttribute = -1; // 頂點坐標
    private int mColorAttribute = -1; // 頂點顏色

    public KFRenderView(Context context, EGLContext eglContext) {
        super(context);
        mShareContext = eglContext; // 共享上下文

        // 1嵌施、選擇實際的渲染視圖
        boolean isSurfaceView  = false; // TextureView 與 SurfaceView 開關(guān)
        if (isSurfaceView) {
            mRenderView = new KFSurfaceView(context, mListener);
        } else {
            mRenderView = new KFTextureView(context, mListener);
        }

        this.addView(mRenderView); // 添加視圖到父視圖
    }

    public void release() {
        // 釋放 OpenGL 上下文饲化、特效
        if (mEGLContext != null) {
            mEGLContext.bind();
            mEGLContext.unbind();

            mEGLContext.release();
            mEGLContext = null;
        }
    }

    public void render() {
        mProgram.use();

        // 設(shè)置幀緩存背景色
        glClearColor(0.5f,0.5f,0.5f,1);
        // 清空幀緩存顏色
        glClear(GLES20.GL_COLOR_BUFFER_BIT);
        // 設(shè)置渲染窗口區(qū)域
        GLES20.glViewport(0, 0, mSurfaceWidth, mSurfaceHeight);

        // 啟用頂點著色器頂點坐標屬性
        glEnableVertexAttribArray(mPositionAttribute);
        mVerticesBuffer.position(0); // 定位到第一個位置分量
        glVertexAttribPointer(
                mPositionAttribute,
                3, // x, y, z 有 3 個分量
                GLES20.GL_FLOAT,
                false,
                7 * 4, // 每個頂點有 xyzrgba 7 個分量,每個分量是 4 字節(jié)吗伤,所以步進為 7 * 4 字節(jié)
                mVerticesBuffer);

        // 啟用頂點著色器頂點顏色屬性
        glEnableVertexAttribArray(mColorAttribute);
        mVerticesBuffer.position(3); // 定位到第一個顏色分量
        glVertexAttribPointer(
                mColorAttribute,
                4, // r, g, b, a 有 4 個分量
                GLES20.GL_FLOAT,
                false,
                7 * 4, // 每個頂點有 xyzrgba 7 個分量吃靠,每個分量是 4 字節(jié),所以步進為 7 * 4 字節(jié)
                mVerticesBuffer);

        // 繪制三角形
        glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);

        // 關(guān)閉頂點著色器頂點坐標屬性
        glDisableVertexAttribArray(mPositionAttribute);

        // 關(guān)閉頂點著色器頂點顏色屬性
        glDisableVertexAttribArray(mColorAttribute);

        mEGLContext.swapBuffers();
    }

    private KFRenderListener mListener = new KFRenderListener() {
        @Override
        // 渲染緩存創(chuàng)建
        public void surfaceCreate(@NonNull Surface surface) {
            // 2足淆、創(chuàng)建 OpenGL 上下文
            mEGLContext = new KFGLContext(mShareContext, surface);
            mEGLContext.bind();
            // 3巢块、初始化 GL 相關(guān)環(huán)境:加載和編譯 shader、鏈接到著色器程序巧号、設(shè)置頂點數(shù)據(jù)
            _setupGL();
            mEGLContext.unbind();
        }

        @Override
        // 渲染緩存變更
        public void surfaceChanged(@NonNull Surface surface, int width, int height) {
            mSurfaceWidth = width;
            mSurfaceHeight = height;
            mSurfaceChanged = true;
            mEGLContext.bind();
            // 4族奢、設(shè)置 OpenGL 上下文 Surface
            mEGLContext.setSurface(surface);
            // 5、繪制三角形
            render();
            mEGLContext.unbind();
        }

        @Override
        public void surfaceDestroy(@NonNull Surface surface) {

        }
    };

    private void _setupGL() {
        // 加載和編譯 shader丹鸿,并鏈接到著色器程序
        mProgram = new KFGLProgram(customVertexShader, customFragmentShader);

        // 獲取與 shader 中對應(yīng)的屬性信息
        mPositionAttribute = mProgram.getAttribLocation("v_position");
        mColorAttribute = mProgram.getAttribLocation("v_color");

        // 3 個頂點越走,每個頂點有 7 個分量:x, y, z, r, g, b, a
        final float vertices[] = {
                -0.5f,  0.5f, 0.0f,  1.0f, 0.0f, 0.0f, 1.0f, // 左下 // 紅色
                -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f, 1.0f, // 右下 // 綠色
                 0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f, 1.0f, // 左上 // 藍色
        };
        ByteBuffer verticesByteBuffer = ByteBuffer.allocateDirect(4 * vertices.length);
        verticesByteBuffer.order(ByteOrder.nativeOrder());
        mVerticesBuffer = verticesByteBuffer.asFloatBuffer();
        mVerticesBuffer.put(vertices);
        mVerticesBuffer.position(0);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        // 視圖變更 Size
        this.mRenderView.layout(left, top, right, bottom);
    }
}

KFRenderListener.java

// ...省略 import 代碼...
public interface KFRenderListener {
    void surfaceCreate(@NonNull Surface surface); // 渲染緩存創(chuàng)建
    void surfaceChanged(@NonNull Surface surface, int width, int height); // 渲染緩存變更分辨率
    void surfaceDestroy(@NonNull Surface surface); // 渲染緩存銷毀
}

其中繪制三角形的代碼實現(xiàn)在 KFRenderView.java 中,包括這些過程:

  • 1)選擇實際的渲染視圖靠欢;
  • 2)創(chuàng)建 OpenGL 上下文廊敌;
  • 3)初始化 GL 相關(guān)環(huán)境:加載和編譯 shader、鏈接到著色器程序掺涛、設(shè)置頂點數(shù)據(jù)庭敦;
  • 4)設(shè)置 OpenGL 上下文 Surface;
  • 5)繪制三角形薪缆。

具體細節(jié)見上述代碼及其注釋秧廉。

最終我們畫出的三角形如下圖所示:

OpenGL 繪制三角形(Android)

- 完 -

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伞广,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子疼电,更是在濱河造成了極大的恐慌嚼锄,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔽豺,死亡現(xiàn)場離奇詭異区丑,居然都是意外死亡,警方通過查閱死者的電腦和手機修陡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門沧侥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人魄鸦,你說我怎么就攤上這事宴杀。” “怎么了拾因?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵旺罢,是天一觀的道長。 經(jīng)常有香客問我绢记,道長扁达,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任蠢熄,我火速辦了婚禮跪解,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘护赊。我一直安慰自己惠遏,他們只是感情好,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布骏啰。 她就那樣靜靜地躺著节吮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪判耕。 梳的紋絲不亂的頭發(fā)上透绩,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音壁熄,去河邊找鬼帚豪。 笑死,一個胖子當著我的面吹牛草丧,可吹牛的內(nèi)容都是我干的狸臣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昌执,長吁一口氣:“原來是場噩夢啊……” “哼烛亦!你這毒婦竟也來了诈泼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤煤禽,失蹤者是張志新(化名)和其女友劉穎铐达,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體檬果,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡瓮孙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了选脊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杭抠。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖知牌,靈堂內(nèi)的尸體忽然破棺而出祈争,到底是詐尸還是另有隱情斤程,我是刑警寧澤角寸,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站忿墅,受9級特大地震影響扁藕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疚脐,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一亿柑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棍弄,春花似錦望薄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蛮原,卻和暖如春卧须,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背儒陨。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工花嘶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹦漠。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓椭员,卻偏偏與公主長得像,于是被迫代替她去往敵國和親笛园。 傳聞我的和親對象是個殘疾皇子隘击,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容