這個公眾號會路線圖 式的遍歷分享音視頻技術(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é)見上述代碼及其注釋。
最終我們畫出的三角形如下圖所示:
2穴豫、Android Demo
Android 平臺自 2.0 版本之后圖形系統(tǒng)的底層渲染均由 OpenGL ES 負責凡简,其 EGL 架構(gòu)實現(xiàn)如下圖所示:
-
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é)見上述代碼及其注釋秧廉。
最終我們畫出的三角形如下圖所示: