当前位置:   article > 正文

用 OpenGL 画一个三角形丨音视频工程示例_使用opengl进行实验:绘制简单的三角形

使用opengl进行实验:绘制简单的三角形

渲染音视频技术栈相关的一个非常重要的方向,视频图像在设备上的展示、各种流行的视频特效都离不开渲染技术的支持。

在 RenderDemo 这个工程示例系列,我们将为大家展示一些渲染相关的 Demo,来向大家介绍如何在 iOS/Android 平台上手一些渲染相关的开发。

这里是第一篇:用 OpenGL 画一个三角形。我们分别在 iOS 和 Android 实现了用 OpenGL 画一个三角形的 Demo。在本文中,包括如下内容:

  • 1)iOS OpenGL 绘制三角形 Demo;
  • 2)Android OpenGL 绘制三角形 Demo;
  • 3)详尽的代码注释,帮你理解代码逻辑和原理。

 本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

如果我们了解了 OpenGL ES 就会知道,虽然它定义了一套移动设备的图像渲染 API,但是并没有定义窗口系统。为了让 GLES 能够适配各种平台,GLES 需要与知道如何通过操作系统创建和访问窗口的库结合使用,这就有了 EGL,EGL 是 OpenGL ES 渲染 API 和本地窗口系统之间的一个中间接口层,它主要由系统制造商实现。EGL 提供如下机制:

  • 与设备的原生窗口系统通信;
  • 查询绘图表面的可用类型和配置;
  • 创建绘图表面;
  • 在 OpenGL ES 和其他图形渲染 API 之间同步渲染;
  • 管理纹理贴图等渲染资源。

EGL 是 OpenGL ES 与设备的桥梁,以实现让 OpenGL ES 能够在当前设备上进行绘制。

1、iOS Demo

iOS 平台对 EGL 的实现是 EAGL(Embedded Apple Graphics Library),其中 CAEAGLLayer 就是一种可以支持 OpenGL ES 绘制的图层类型,我们的 Demo 里会用它作为绘制三角形的图层。

代码比较简单,我们直接上代码:

DMTriangleRenderView.m

  1. #import "DMTriangleRenderView.h"
  2. #import <OpenGLES/ES2/gl.h>
  3. // 定义顶点的数据结构:包括顶点坐标和颜色维度。
  4. #define PositionDimension 3
  5. #define ColorDimension 4
  6. typedef struct {
  7. GLfloat position[PositionDimension]; // { x, y, z }
  8. GLfloat color[ColorDimension]; // {r, g, b, a}
  9. } SceneVertex;
  10. @interface DMTriangleRenderView ()
  11. @property (nonatomic, assign) GLsizei width;
  12. @property (nonatomic, assign) GLsizei height;
  13. @property (nonatomic, strong) CAEAGLLayer *eaglLayer;
  14. @property (nonatomic, strong) EAGLContext *eaglContext;
  15. @property (nonatomic, assign) GLuint simpleProgram;
  16. @property (nonatomic, assign) GLuint renderBuffer;
  17. @property (nonatomic, assign) GLuint frameBuffer;
  18. @end
  19. @implementation DMTriangleRenderView
  20. #pragma mark - Setup
  21. - (instancetype)initWithFrame:(CGRect)frame {
  22. self = [super initWithFrame:frame];
  23. if (self) {
  24. _width = frame.size.width;
  25. _height = frame.size.height;
  26. [self render];
  27. }
  28. return self;
  29. }
  30. #pragma mark - Action
  31. - (void)render {
  32. // 1、设定 layer 的类型。
  33. _eaglLayer = (CAEAGLLayer *) self.layer;
  34. _eaglLayer.opacity = 1.0;
  35. _eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking: @(NO),
  36. kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
  37. // 2、创建 OpenGL 上下文。
  38. EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2; // 使用的 OpenGL API 的版本。
  39. EAGLContext *context = [[EAGLContext alloc] initWithAPI:api];
  40. if (!context) {
  41. NSLog(@"Create context failed!");
  42. return;
  43. }
  44. BOOL r = [EAGLContext setCurrentContext:context];
  45. if (!r) {
  46. NSLog(@"setCurrentContext failed!");
  47. return;
  48. }
  49. _eaglContext = context;
  50. // 3、申请并绑定渲染缓冲区对象 RBO 用来存储即将绘制到屏幕上的图像数据。
  51. glGenRenderbuffers(1, &_renderBuffer); // 创建 RBO。
  52. glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); // 绑定 RBO 到 OpenGL 渲染管线。
  53. [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer]; // 将渲染图层(_eaglLayer)的存储绑定到 RBO。
  54. // 4、申请并绑定帧缓冲区对象 FBO。FBO 本身不能用于渲染,只有绑定了纹理(Texture)或者渲染缓冲区(RBO)等作为附件之后才能作为渲染目标。
  55. glGenFramebuffers(1, &_frameBuffer); // 创建 FBO。
  56. glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer); // 绑定 FBO 到 OpenGL 渲染管线。
  57. // 将 RBO 绑定为 FBO 的一个附件,绑定后,OpenGL 对 FBO 的绘制会同步到 RBO 后再上屏。
  58. glFramebufferRenderbuffer(GL_FRAMEBUFFER,
  59. GL_COLOR_ATTACHMENT0,
  60. GL_RENDERBUFFER,
  61. _renderBuffer);
  62. // 5、清理窗口颜色,并设置渲染窗口。
  63. glClearColor(0.5, 0.5, 0.5, 1.0); // 设置渲染窗口颜色。这里是灰色。
  64. glClear(GL_COLOR_BUFFER_BIT); // 清空旧渲染缓存。
  65. glViewport(0, 0, _width, _height); // 设置渲染窗口区域。
  66. // 6、加载和编译 shader,并链接到着色器程序。
  67. if (_simpleProgram) {
  68. glDeleteProgram(_simpleProgram);
  69. _simpleProgram = 0;
  70. }
  71. // 加载和编译 shader。
  72. NSString *simpleVSH = [[NSBundle mainBundle] pathForResource:@"simple" ofType:@"vsh"];
  73. NSString *simpleFSH = [[NSBundle mainBundle] pathForResource:@"simple" ofType:@"fsh"];
  74. _simpleProgram = [self loadShaderWithVertexShader:simpleVSH fragmentShader:simpleFSH];
  75. // 链接 shader program
  76. glLinkProgram(_simpleProgram);
  77. // 打印链接日志。
  78. GLint linkStatus;
  79. glGetProgramiv(_simpleProgram, GL_LINK_STATUS, &linkStatus);
  80. if (linkStatus == GL_FALSE) {
  81. GLint infoLength;
  82. glGetProgramiv(_simpleProgram, GL_INFO_LOG_LENGTH, &infoLength);
  83. if (infoLength > 0) {
  84. GLchar *infoLog = malloc(sizeof(GLchar) * infoLength);
  85. glGetProgramInfoLog(_simpleProgram, infoLength, NULL, infoLog);
  86. NSLog(@"%s", infoLog);
  87. free(infoLog);
  88. }
  89. }
  90. glUseProgram(_simpleProgram);
  91. // 7、根据三角形顶点信息申请顶点缓冲区对象 VBO 和拷贝顶点数据。
  92. // 设置三角形 3 个顶点数据,包括坐标信息和颜色信息。
  93. const SceneVertex vertices[] = {
  94. {{-0.5, 0.5, 0.0}, { 1.0, 0.0, 0.0, 1.000}}, // 左下 // 红色
  95. {{-0.5, -0.5, 0.0}, { 0.0, 1.0, 0.0, 1.000}}, // 右下 // 绿色
  96. {{ 0.5, -0.5, 0.0}, { 0.0, 0.0, 1.0, 1.000}}, // 左上 // 蓝色
  97. };
  98. // 申请并绑定 VBO。VBO 的作用是在显存中提前开辟好一块内存,用于缓存顶点数据,从而避免每次绘制时的 CPU 与 GPU 之间的内存拷贝,可以提升渲染性能。
  99. GLuint vertexBufferID;
  100. glGenBuffers(1, &vertexBufferID); // 创建 VBO。
  101. glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); // 绑定 VBO 到 OpenGL 渲染管线。
  102. // 将顶点数据 (CPU 内存) 拷贝到 VBO(GPU 显存)。
  103. glBufferData(GL_ARRAY_BUFFER, // 缓存块类型。
  104. sizeof(vertices), // 创建的缓存块尺寸。
  105. vertices, // 要绑定的顶点数据。
  106. GL_STATIC_DRAW); // 缓存块用途。
  107. // 8、绘制三角形。
  108. // 获取与 Shader 中对应的参数信息:
  109. GLuint vertexPositionLocation = glGetAttribLocation(_simpleProgram, "v_position");
  110. GLuint vertexColorLocation = glGetAttribLocation(_simpleProgram, "v_color");
  111. // 顶点位置属性。
  112. glEnableVertexAttribArray(vertexPositionLocation); // 启用顶点位置属性通道。
  113. // 关联顶点位置数据。
  114. glVertexAttribPointer(vertexPositionLocation, // attribute 变量的下标,范围是 [0, GL_MAX_VERTEX_ATTRIBS - 1]。
  115. PositionDimension, // 指顶点数组中,一个 attribute 元素变量的坐标分量是多少(如:position, 程序提供的就是 {x, y, z} 点就是 3 个坐标分量)。
  116. GL_FLOAT, // 数据的类型。
  117. GL_FALSE, // 是否进行数据类型转换。
  118. sizeof(SceneVertex), // 每一个数据在内存中的偏移量,如果填 0 就是每一个数据紧紧相挨着。
  119. (const GLvoid*) offsetof(SceneVertex, position)); // 数据的内存首地址。
  120. // 顶点颜色属性。
  121. glEnableVertexAttribArray(vertexColorLocation);
  122. // 关联顶点颜色数据。
  123. glVertexAttribPointer(vertexColorLocation,
  124. ColorDimension,
  125. GL_FLOAT,
  126. GL_FALSE,
  127. sizeof(SceneVertex),
  128. (const GLvoid*) offsetof(SceneVertex, color));
  129. // 绘制所有图元。
  130. glDrawArrays(GL_TRIANGLES, // 绘制的图元方式。
  131. 0, // 从第几个顶点下标开始绘制。
  132. sizeof(vertices) / sizeof(vertices[0])); // 有多少个顶点下标需要绘制。
  133. // 把 Renderbuffer 的内容显示到窗口系统 (CAEAGLLayer) 中。
  134. [_eaglContext presentRenderbuffer:GL_RENDERBUFFER];
  135. // 9、清理。
  136. glDisableVertexAttribArray(vertexColorLocation); // 关闭顶点颜色属性通道。
  137. glDisableVertexAttribArray(vertexPositionLocation); // 关闭顶点位置属性通道。
  138. glBindBuffer(GL_ARRAY_BUFFER, 0); // 解绑 VBO。
  139. glBindFramebuffer(GL_FRAMEBUFFER, 0); // 解绑 FBO。
  140. glBindRenderbuffer(GL_RENDERBUFFER, 0); // 解绑 RBO。
  141. }
  142. #pragma mark - Utility
  143. - (GLuint)loadShaderWithVertexShader:(NSString *)vert fragmentShader:(NSString *)frag {
  144. GLuint verShader, fragShader;
  145. GLuint program = glCreateProgram(); // 创建 Shader Program 对象。
  146. [self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
  147. [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
  148. // 装载 Vertex Shader 和 Fragment Shader。
  149. glAttachShader(program, verShader);
  150. glAttachShader(program, fragShader);
  151. glDeleteShader(verShader);
  152. glDeleteShader(fragShader);
  153. return program;
  154. }
  155. - (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file {
  156. NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
  157. const GLchar *source = (GLchar *) [content UTF8String];
  158. *shader = glCreateShader(type); // 创建一个着色器对象。
  159. glShaderSource(*shader, 1, &source, NULL); // 关联顶点、片元着色器的代码。
  160. glCompileShader(*shader); // 编译着色器代码。
  161. // 打印编译日志。
  162. GLint compileStatus;
  163. glGetShaderiv(*shader, GL_COMPILE_STATUS, &compileStatus);
  164. if (compileStatus == GL_FALSE) {
  165. GLint infoLength;
  166. glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &infoLength);
  167. if (infoLength > 0) {
  168. GLchar *infoLog = malloc(sizeof(GLchar) * infoLength);
  169. glGetShaderInfoLog(*shader, infoLength, NULL, infoLog);
  170. NSLog(@"%s -> %s", (type == GL_VERTEX_SHADER) ? "vertex shader" : "fragment shader", infoLog);
  171. free(infoLog);
  172. }
  173. }
  174. }
  175. #pragma mark - Override
  176. + (Class)layerClass {
  177. return [CAEAGLLayer class];
  178. }
  179. @end

上面的代码包括了这些过程:

  • 1)定义顶点的数据结构:包括顶点坐标和颜色维度;
  • 2)设定 layer 的类型;
  • 3)创建 OpenGL 上下文;
  • 4)申请并绑定渲染缓冲区对象 RBO 用来存储即将绘制到屏幕上的图像数据;
  • 5)申请并绑定帧缓冲区对象 FBO;
    • 需要注意,FBO 本身不能用于渲染,只有绑定了纹理(Texture)或者渲染缓冲区(RBO)等作为附件之后才能作为渲染目标。
  • 6)清理窗口颜色,并设置渲染窗口;
  • 7)加载和编译 shader,并链接到着色器程序;
  • 8)根据三角形顶点信息申请顶点缓冲区对象 VBO 和拷贝顶点数据;
    • 这里 VBO 的作用是在显存中提前开辟好一块内存,用于缓存顶点数据,从而避免每次绘制时的 CPU 与 GPU 之间的内存拷贝,可以提升渲染性能。
  • 9)绘制三角形;
  • 10)清理状态机。

更具体细节见上述代码及其注释。

最终我们画出的三角形如下图所示:

2、Android Demo

Android 平台自 2.0 版本之后图形系统的底层渲染均由 OpenGL ES 负责,其 EGL 架构实现如下图所示:

  • Display 是对实际显示设备的抽象。在 Android 上的实现类是 EGLDisplay。
  • Surface 是对用来存储图像的内存区域 FrameBuffer 的抽象,包括 Color Buffer、Stencil Buffer、Depth Buffer。在 Android 上的实现类是 EGLSurface。
  • Context 存储 OpenGL ES 绘图的一些状态信息。在 Android 上的实现类是 EGLContext。

Android Demo 的代码由于平台 EGL 实现的原因以及对模块略作了封装,所以看起来对稍微复杂一些,包括了如下几个文件:

  • KFGLContext.java,KFGLContext 负责管理和组装 EGLDisplay、EGLSurface、EGLContext。
  • KFGLProgram.java,KFGLProgram 负责加载和编译着色器,创建着色器程序容器。
  • KFSurfaceView.java,KFSurfaceView 继承自 SurfaceView 来实现渲染。
  • KFTextureView.java,KFTextureView 继承自 TextureView 来实现渲染。
  • KFRenderView.java,KFRenderView 是一个容器,可以选择使用 KFSurfaceView 或 KFTextureView 作为实际的渲染视图。
  • KFRenderListener.java,KFRenderListener 是 KFRenderView 用来监听渲染视图的事件回调。

代码如下:

KFGLContext.java

  1. // ...省略 import 代码...
  2. public class KFGLContext {
  3. private Surface mSurface = null;
  4. private EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; // 实际显示设备的抽象
  5. private EGLContext mEGLContext = EGL_NO_CONTEXT; // 渲染上下文
  6. private EGLSurface mEGLSurface = EGL_NO_SURFACE; // 存储图像的内存区域
  7. private EGLContext mEGLShareContext = EGL_NO_CONTEXT; // 共享渲染上下文
  8. private EGLConfig mEGLConfig = null; // 渲染表面的配置信息
  9. private EGLContext mLastContext = EGL_NO_CONTEXT; // 存储之前系统上下文
  10. private EGLDisplay mLastDisplay = EGL_NO_DISPLAY; // 存储之前系统设备
  11. private EGLSurface mLastSurface = EGL_NO_SURFACE; // 存储之前系统内存区域
  12. private boolean mIsBind = false;
  13. public KFGLContext(EGLContext shareContext) {
  14. mEGLShareContext = shareContext;
  15. // 创建 OpenGL 上下文
  16. _eglSetup();
  17. }
  18. public KFGLContext(EGLContext shareContext, Surface surface) {
  19. mEGLShareContext = shareContext;
  20. mSurface = surface;
  21. // 创建 OpenGL 上下文
  22. _eglSetup();
  23. }
  24. public void setSurface(Surface surface) {
  25. if (surface == null || surface == mSurface) {
  26. return;
  27. }
  28. // 释放渲染表面 Surface
  29. if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {
  30. eglDestroySurface(mEGLDisplay, mEGLSurface);
  31. }
  32. // 创建 Surface
  33. int[] surfaceAttribs = {
  34. EGL14.EGL_NONE
  35. };
  36. mSurface = surface;
  37. mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mSurface, surfaceAttribs, 0);
  38. if (mEGLSurface == null) {
  39. throw new RuntimeException("surface was null");
  40. }
  41. }
  42. public EGLContext getContext() {
  43. return mEGLContext;
  44. }
  45. public Surface getSurface() {
  46. return mSurface;
  47. }
  48. public boolean swapBuffers() {
  49. // 将后台绘制的缓冲显示到前台
  50. if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {
  51. return eglSwapBuffers(mEGLDisplay, mEGLSurface);
  52. } else {
  53. return false;
  54. }
  55. }
  56. @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
  57. public void setPresentationTime(long nsecs) {
  58. // 设置时间戳
  59. if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {
  60. eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);
  61. }
  62. }
  63. public void bind() {
  64. // 绑定当前上下文
  65. mLastSurface = eglGetCurrentSurface(EGL14.EGL_READ);
  66. mLastContext = eglGetCurrentContext();
  67. mLastDisplay = eglGetCurrentDisplay();
  68. if (!eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
  69. throw new RuntimeException("eglMakeCurrent failed");
  70. }
  71. mIsBind = true;
  72. }
  73. public void unbind() {
  74. if (!mIsBind) {
  75. return;
  76. }
  77. // 绑定回系统之前上下文
  78. if (mLastSurface != EGL_NO_SURFACE && mLastContext != EGL_NO_CONTEXT && mLastDisplay != EGL_NO_DISPLAY) {
  79. eglMakeCurrent(mLastDisplay, mLastSurface, mLastSurface, mLastContext);
  80. mLastDisplay = EGL_NO_DISPLAY;
  81. mLastSurface = EGL_NO_SURFACE;
  82. mLastContext = EGL_NO_CONTEXT;
  83. } else {
  84. if (mEGLDisplay != EGL_NO_DISPLAY) {
  85. eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
  86. }
  87. }
  88. mIsBind = false;
  89. }
  90. public void release() {
  91. unbind();
  92. // 释放设备、Surface
  93. if (mEGLDisplay != EGL_NO_DISPLAY && mEGLSurface != EGL_NO_SURFACE) {
  94. eglDestroySurface(mEGLDisplay, mEGLSurface);
  95. }
  96. if (mEGLDisplay != EGL_NO_DISPLAY && mEGLContext != EGL_NO_CONTEXT) {
  97. eglDestroyContext(mEGLDisplay, mEGLContext);
  98. }
  99. if(mEGLDisplay != EGL_NO_DISPLAY){
  100. eglTerminate(mEGLDisplay);
  101. }
  102. mSurface = null;
  103. mEGLShareContext = null;
  104. mEGLDisplay = null;
  105. mEGLContext = null;
  106. mEGLSurface = null;
  107. }
  108. private void _eglSetup() {
  109. // 创建设备
  110. mEGLDisplay = eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
  111. if (mEGLDisplay == EGL_NO_DISPLAY) {
  112. throw new RuntimeException("unable to get EGL14 display");
  113. }
  114. int[] version = new int[2];
  115. // 根据版本初始化设备
  116. if (!eglInitialize(mEGLDisplay, version, 0, version, 1)) {
  117. mEGLDisplay = null;
  118. throw new RuntimeException("unable to initialize EGL14");
  119. }
  120. // 定义 EGLConfig 属性配置,定义红、绿、蓝、透明度、深度、模板缓冲的位数
  121. int[] attribList = {
  122. EGL14.EGL_BUFFER_SIZE, 32,
  123. EGL14.EGL_ALPHA_SIZE, 8, // 颜色缓冲区中透明度用几位来表示
  124. EGL14.EGL_BLUE_SIZE, 8,
  125. EGL14.EGL_GREEN_SIZE, 8,
  126. EGL14.EGL_RED_SIZE, 8,
  127. EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
  128. EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
  129. EGL14.EGL_NONE
  130. };
  131. EGLConfig[] configs = new EGLConfig[1];
  132. int[] numConfigs = new int[1];
  133. // 找到符合要求的 EGLConfig 配置
  134. if (!eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
  135. numConfigs, 0)) {
  136. throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
  137. }
  138. mEGLConfig = configs[0];
  139. // 指定 OpenGL 使用版本
  140. int[] attrib_list = {
  141. EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
  142. EGL14.EGL_NONE
  143. };
  144. // 创建 GL 上下文
  145. mEGLContext = eglCreateContext(mEGLDisplay, mEGLConfig, mEGLShareContext != null ? mEGLShareContext : EGL_NO_CONTEXT, attrib_list, 0);
  146. if (mEGLContext == null) {
  147. throw new RuntimeException("null context");
  148. }
  149. // 创建 Surface 配置信息
  150. int[] surfaceAttribs = {
  151. EGL14.EGL_NONE
  152. };
  153. // 创建 Surface
  154. if (mSurface != null) {
  155. mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mSurface,
  156. surfaceAttribs, 0);
  157. } else {
  158. mEGLSurface = eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0);
  159. }
  160. if (mEGLSurface == null) {
  161. throw new RuntimeException("surface was null");
  162. }
  163. }
  164. }

KFGLProgram.java

  1. // ...省略 import 代码...
  2. public class KFGLProgram {
  3. private static final String TAG = "KFGLProgram";
  4. private int mProgram = 0; // 着色器程序容器
  5. private int mVertexShader = 0; // 顶点着色器
  6. private int mFragmentShader = 0; // 片元着色器
  7. public KFGLProgram(String vertexShader, String fragmentShader) {
  8. _createProgram(vertexShader,fragmentShader);
  9. }
  10. public void release() {
  11. // 释放顶点、片元着色器、着色器容器
  12. if (mVertexShader != 0) {
  13. glDeleteShader(mVertexShader);
  14. mVertexShader = 0;
  15. }
  16. if (mFragmentShader != 0) {
  17. glDeleteShader(mFragmentShader);
  18. mFragmentShader = 0;
  19. }
  20. if (mProgram != 0) {
  21. glDeleteProgram(mProgram);
  22. mProgram = 0;
  23. }
  24. }
  25. public void use() {
  26. // 使用当前的着色器
  27. if (mProgram != 0) {
  28. glUseProgram(mProgram);
  29. }
  30. }
  31. public int getUniformLocation(String uniformName) {
  32. // 获取着色器 uniform 对应下标
  33. return glGetUniformLocation(mProgram, uniformName);
  34. }
  35. public int getAttribLocation(String uniformName) {
  36. // 获取着色器 attribute 变量对应下标
  37. return glGetAttribLocation(mProgram, uniformName);
  38. }
  39. private void _createProgram(String vertexSource, String fragmentSource) {
  40. // 创建着色器容器
  41. // 创建顶点、片元着色器
  42. mVertexShader = _loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
  43. mFragmentShader = _loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
  44. //
  45. if(mVertexShader != 0 && mFragmentShader != 0){
  46. // 创建一个空的着色器容器
  47. mProgram = GLES20.glCreateProgram();
  48. // 将顶点、片元着色器添加至着色器容器
  49. glAttachShader(mProgram, mVertexShader);
  50. glAttachShader(mProgram, mFragmentShader);
  51. // 链接着色器容器
  52. glLinkProgram(mProgram);
  53. int[] linkStatus = new int[1];
  54. glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
  55. // 获取链接状态
  56. if (linkStatus[0] != GLES20.GL_TRUE) {
  57. Log.e(TAG, "Could not link program:");
  58. Log.e(TAG, glGetProgramInfoLog(mProgram));
  59. glDeleteProgram(mProgram);
  60. mProgram = 0;
  61. }
  62. }
  63. }
  64. private int _loadShader(int shaderType, String source) {
  65. // 根据类型创建顶点、片元着色器
  66. int shader = glCreateShader(shaderType);
  67. // 设置着色器中的源代码
  68. glShaderSource(shader, source);
  69. // 编译着色器
  70. glCompileShader(shader);
  71. int[] compiled = new int[1];
  72. glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
  73. // 获取编译后状态
  74. if (compiled[0] != GLES20.GL_TRUE) {
  75. Log.e(TAG, "Could not compile shader(TYPE=" + shaderType + "):");
  76. Log.e(TAG, glGetShaderInfoLog(shader));
  77. glDeleteShader(shader);
  78. shader = 0;
  79. }
  80. return shader;
  81. }
  82. }

KFSurfaceView.java

  1. // ...省略 import 代码...
  2. public class KFSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
  3. private KFRenderListener mListener = null; // 回调
  4. private SurfaceHolder mHolder = null; // Surface 的抽象接口
  5. public KFSurfaceView(Context context, KFRenderListener listener) {
  6. super(context);
  7. mListener = listener;
  8. getHolder().addCallback(this);
  9. }
  10. @Override
  11. public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
  12. // Surface 创建
  13. mHolder = surfaceHolder;
  14. // 根据 SurfaceHolder 创建 Surface
  15. if(mListener != null){
  16. mListener.surfaceCreate(surfaceHolder.getSurface());
  17. }
  18. }
  19. @Override
  20. public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
  21. // Surface 分辨率变更
  22. if(mListener != null){
  23. mListener.surfaceChanged(surfaceHolder.getSurface(),width,height);
  24. }
  25. }
  26. @Override
  27. public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
  28. // Surface 销毁
  29. if (mListener != null) {
  30. mListener.surfaceDestroy(surfaceHolder.getSurface());
  31. }
  32. }
  33. }

KFTextureView.java

  1. // ...省略 import 代码...
  2. public class KFTextureView extends TextureView implements TextureView.SurfaceTextureListener{
  3. private KFRenderListener mListener = null; // 回调
  4. private Surface mSurface = null; // 渲染缓存
  5. private SurfaceTexture mSurfaceTexture = null; // 纹理缓存
  6. public KFTextureView(Context context, KFRenderListener listener) {
  7. super(context);
  8. this.setSurfaceTextureListener(this);
  9. mListener = listener;
  10. }
  11. @Override
  12. public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
  13. // 纹理缓存创建
  14. mSurfaceTexture = surfaceTexture;
  15. // 根据 SurfaceTexture 创建 Surface
  16. mSurface = new Surface(surfaceTexture);
  17. if (mListener != null) {
  18. // 创建时候回调一次分辨率变更,对应 SurfaceView 接口
  19. mListener.surfaceCreate(mSurface);
  20. mListener.surfaceChanged(mSurface,width,height);
  21. }
  22. }
  23. @Override
  24. public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
  25. // 纹理缓存变更分辨率
  26. if (mListener != null) {
  27. mListener.surfaceChanged(mSurface,width,height);
  28. }
  29. }
  30. @Override
  31. public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
  32. }
  33. @Override
  34. public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
  35. // 纹理缓存销毁
  36. if (mListener != null) {
  37. mListener.surfaceDestroy(mSurface);
  38. }
  39. if (mSurface != null) {
  40. mSurface.release();
  41. mSurface = null;
  42. }
  43. return false;
  44. }
  45. }

KFRenderView.java

  1. // ...省略 import 代码...
  2. public class KFRenderView extends ViewGroup {
  3. // 顶点着色器
  4. public static String customVertexShader =
  5. "attribute vec4 v_position;\n" +
  6. "attribute vec4 v_color;\n" +
  7. "varying mediump vec4 f_color;\n" +
  8. "void main() {\n" +
  9. " f_color = v_color;\n" +
  10. " gl_Position = v_position;\n" +
  11. "}\n";
  12. // 片元着色器
  13. public static String customFragmentShader =
  14. "varying mediump vec4 f_color;\n" +
  15. "void main() {\n" +
  16. " gl_FragColor = f_color;\n" +
  17. "}\n";
  18. private KFGLContext mEGLContext = null; // OpenGL 上下文
  19. private EGLContext mShareContext = null; // 共享上下文
  20. private View mRenderView = null; // 渲染视图基类
  21. private int mSurfaceWidth = 0; // 渲染缓存宽
  22. private int mSurfaceHeight = 0; // 渲染缓存高
  23. private boolean mSurfaceChanged = false; // 渲染缓存是否变更
  24. private KFGLProgram mProgram;
  25. private FloatBuffer mVerticesBuffer = null; // 顶点 buffer
  26. private int mPositionAttribute = -1; // 顶点坐标
  27. private int mColorAttribute = -1; // 顶点颜色
  28. public KFRenderView(Context context, EGLContext eglContext) {
  29. super(context);
  30. mShareContext = eglContext; // 共享上下文
  31. // 1、选择实际的渲染视图
  32. boolean isSurfaceView = false; // TextureView 与 SurfaceView 开关
  33. if (isSurfaceView) {
  34. mRenderView = new KFSurfaceView(context, mListener);
  35. } else {
  36. mRenderView = new KFTextureView(context, mListener);
  37. }
  38. this.addView(mRenderView); // 添加视图到父视图
  39. }
  40. public void release() {
  41. // 释放 OpenGL 上下文、特效
  42. if (mEGLContext != null) {
  43. mEGLContext.bind();
  44. mEGLContext.unbind();
  45. mEGLContext.release();
  46. mEGLContext = null;
  47. }
  48. }
  49. public void render() {
  50. mProgram.use();
  51. // 设置帧缓存背景色
  52. glClearColor(0.5f,0.5f,0.5f,1);
  53. // 清空帧缓存颜色
  54. glClear(GLES20.GL_COLOR_BUFFER_BIT);
  55. // 设置渲染窗口区域
  56. GLES20.glViewport(0, 0, mSurfaceWidth, mSurfaceHeight);
  57. // 启用顶点着色器顶点坐标属性
  58. glEnableVertexAttribArray(mPositionAttribute);
  59. mVerticesBuffer.position(0); // 定位到第一个位置分量
  60. glVertexAttribPointer(
  61. mPositionAttribute,
  62. 3, // x, y, z 有 3 个分量
  63. GLES20.GL_FLOAT,
  64. false,
  65. 7 * 4, // 每个顶点有 xyzrgba 7 个分量,每个分量是 4 字节,所以步进为 7 * 4 字节
  66. mVerticesBuffer);
  67. // 启用顶点着色器顶点颜色属性
  68. glEnableVertexAttribArray(mColorAttribute);
  69. mVerticesBuffer.position(3); // 定位到第一个颜色分量
  70. glVertexAttribPointer(
  71. mColorAttribute,
  72. 4, // r, g, b, a 有 4 个分量
  73. GLES20.GL_FLOAT,
  74. false,
  75. 7 * 4, // 每个顶点有 xyzrgba 7 个分量,每个分量是 4 字节,所以步进为 7 * 4 字节
  76. mVerticesBuffer);
  77. // 绘制三角形
  78. glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
  79. // 关闭顶点着色器顶点坐标属性
  80. glDisableVertexAttribArray(mPositionAttribute);
  81. // 关闭顶点着色器顶点颜色属性
  82. glDisableVertexAttribArray(mColorAttribute);
  83. mEGLContext.swapBuffers();
  84. }
  85. private KFRenderListener mListener = new KFRenderListener() {
  86. @Override
  87. // 渲染缓存创建
  88. public void surfaceCreate(@NonNull Surface surface) {
  89. // 2、创建 OpenGL 上下文
  90. mEGLContext = new KFGLContext(mShareContext, surface);
  91. mEGLContext.bind();
  92. // 3、初始化 GL 相关环境:加载和编译 shader、链接到着色器程序、设置顶点数据
  93. _setupGL();
  94. mEGLContext.unbind();
  95. }
  96. @Override
  97. // 渲染缓存变更
  98. public void surfaceChanged(@NonNull Surface surface, int width, int height) {
  99. mSurfaceWidth = width;
  100. mSurfaceHeight = height;
  101. mSurfaceChanged = true;
  102. mEGLContext.bind();
  103. // 4、设置 OpenGL 上下文 Surface
  104. mEGLContext.setSurface(surface);
  105. // 5、绘制三角形
  106. render();
  107. mEGLContext.unbind();
  108. }
  109. @Override
  110. public void surfaceDestroy(@NonNull Surface surface) {
  111. }
  112. };
  113. private void _setupGL() {
  114. // 加载和编译 shader,并链接到着色器程序
  115. mProgram = new KFGLProgram(customVertexShader, customFragmentShader);
  116. // 获取与 shader 中对应的属性信息
  117. mPositionAttribute = mProgram.getAttribLocation("v_position");
  118. mColorAttribute = mProgram.getAttribLocation("v_color");
  119. // 3 个顶点,每个顶点有 7 个分量:x, y, z, r, g, b, a
  120. final float vertices[] = {
  121. -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 左下 // 红色
  122. -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // 右下 // 绿色
  123. 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 左上 // 蓝色
  124. };
  125. ByteBuffer verticesByteBuffer = ByteBuffer.allocateDirect(4 * vertices.length);
  126. verticesByteBuffer.order(ByteOrder.nativeOrder());
  127. mVerticesBuffer = verticesByteBuffer.asFloatBuffer();
  128. mVerticesBuffer.put(vertices);
  129. mVerticesBuffer.position(0);
  130. }
  131. @Override
  132. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  133. // 视图变更 Size
  134. this.mRenderView.layout(left, top, right, bottom);
  135. }
  136. }

KFRenderListener.java

  1. // ...省略 import 代码...
  2. public interface KFRenderListener {
  3. void surfaceCreate(@NonNull Surface surface); // 渲染缓存创建
  4. void surfaceChanged(@NonNull Surface surface, int width, int height); // 渲染缓存变更分辨率
  5. void surfaceDestroy(@NonNull Surface surface); // 渲染缓存销毁
  6. }

其中绘制三角形的代码实现在 KFRenderView.java 中,包括这些过程:

  • 1)选择实际的渲染视图;
  • 2)创建 OpenGL 上下文;
  • 3)初始化 GL 相关环境:加载和编译 shader、链接到着色器程序、设置顶点数据;
  • 4)设置 OpenGL 上下文 Surface;
  • 5)绘制三角形。

具体细节见上述代码及其注释。

最终我们画出的三角形如下图所示:

 本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/744813
推荐阅读
相关标签
  

闽ICP备14008679号