深入浅出:OpenGL ES 初学者指南

更新:11-03 名人轶事 我要投稿 纠错 投诉

大家好,关于深入浅出:OpenGL ES 初学者指南很多朋友都还不太明白,今天小编就来为大家分享关于的知识,希望对各位有所帮助!

在研究案例之前,我们先来看看什么是FrameBuffer和RenderBuffer?

帧缓冲区对象FrameBuffer(FBO)

在OpenGL渲染管线中,几何数据和纹理经过多次转换和多次测试,最终以二维像素的形式显示在屏幕上。 OpenGL管道的最终渲染目的地称为帧缓冲区。帧缓冲区是OpenG使用的二维数组和存储区域的集合:颜色缓冲区、深度缓冲区、模板缓冲区和累积缓冲区。默认情况下,OpenGL 使用帧缓冲区作为渲染的最终目的地。该帧缓冲区完全由系统生成和管理。

我们知道,应用程序调用任何OpenGL ES命令之前,需要首先创建渲染上下文和绘图表面,并使其成为当前上下文和表面。在渲染之前,实际上使用了本机窗口系统(例如EAGL)。GLFW)提供渲染上下文和绘图表面(即帧缓冲区)。

一般情况下,我们只需要系统提供的帧缓冲区作为绘图表面,但也有一些特殊情况,比如阴影贴图、动态反射、后处理特效等需要渲染到纹理操作。如果使用系统提供的帧缓冲区,效率会比较低,所以需要定制自己的帧缓冲区。

帧缓冲区对象API 支持以下操作:

·仅使用OpenGL ES 命令创建帧缓冲区对象

·在单个EGL上下文中创建和使用多个缓冲区对象,也就是说,不需要每个帧缓冲区都有一个渲染上下文。

· 创建离屏颜色、深度或模板渲染缓冲区和纹理,并将它们链接到帧缓冲区对象

· 在多个帧缓冲区之间共享颜色、深度或模板缓冲区

将纹理作为颜色或深度直接链接到帧缓冲区,从而避免复制操作

·在帧缓冲区之间复制并使帧缓冲区内容无效。

创建帧缓冲区对象//定义缓冲区ID

GLuint 缓冲区;

//申请缓存区标志

glGenFramebuffers(1, 缓冲区);

//然后绑定

glBindFramebuffer(GL_FRAMEBUFFER, buffer);glGenFramebuffers (GLsizei n, GLuint* 帧缓冲区) :

第一个参数是要创建的帧缓冲区的数量,

第二个参数是指向存储一个或多个ID 的变量或数组的指针。

它返回未使用的帧缓冲区对象的ID。 ID为0表示默认帧缓冲区,即系统提供的帧缓冲区。

一旦一个FBO被创建,在使用它之前必须绑定glBindFramebuffer(GLenum目标,GLuint帧缓冲区):

第一个参数target是GL_FRAMEBUFFER,

第二个参数是帧缓冲区对象的ID。

一旦framebuffer对象被绑定,所有后续的OpenGL操作都会影响当前绑定的framebuffer对象。 ID为0表示默认帧缓冲区,是系统提供的默认帧缓冲区。因此,在glBindFramebuffer() 中将ID 设置为0 会解除当前帧缓冲区对象的绑定。

绑定到GL_FRAMEBUFFER 目标后,所有读取和写入帧缓冲区的操作都会影响当前绑定的帧缓冲区。我们还可以使用GL_READ_FRAMEBUFFER 或GL_DRAW_FRAMEBUFFER 分别将帧缓冲区绑定到读取目标或写入目标。绑定到GL_READ_FRAMEBUFFER 的帧缓冲区将用于所有读取操作(例如glReadPixels),而绑定到GL_DRAW_FRAMEBUFFER 的帧缓冲区将用作渲染、清除和其他写入操作的目标。大多数情况下,不需要区分它们,通常使用GL_FRAMEBUFFER。

删除缓冲区对象当不再使用缓冲对象时,可以通过调用glDeleteFramebuffers(GLsizei n, const GLuint*framebuffers)来删除缓冲对象。

glDeleteFramebuffers(1, 缓冲区);与系统帧缓冲区一样,帧缓冲区对象也包括颜色缓冲区、深度缓冲区和模板缓冲区。这些逻辑缓冲区在帧缓冲区对象中称为可附加图像。它们是可以附加到帧缓冲区对象的二维像素数组。

FBO 包含两种类型的附加图像:纹理图像和渲染缓冲区图像。如果纹理对象的图像数据与帧缓冲区关联,则OpenGL 会执行“渲染到纹理”操作。如果渲染缓冲区的图像数据与帧缓冲区关联,则OpenGL执行离屏渲染。

渲染缓存区对象RenderBuffer(RBO)

为离线渲染新引入了渲染缓存。它允许将场景直接渲染到渲染缓存对象中,而不是渲染到纹理对象中。渲染缓存对象是用于存储单个图像的数据存储区域。图像以可渲染的内部格式存储。它用于存储没有关联纹理格式的OpenGL 逻辑缓冲区,例如模板缓冲区或深度缓冲区。

渲染缓冲区对象是由应用程序分配的2D 图像缓冲区。渲染缓冲区可用于分配和存储颜色、深度或模板值。它还可以用作颜色、深度和模板附件的帧缓冲区。渲染缓冲区是一个类似屏幕的对象。外部窗口系统提供的可绘制表面。但renderbuffer不能直接用作GL纹理。

创建渲染缓冲区//1.定义缓冲区ID

GLuint 缓冲区;

//2.申请缓存区标志

glGenRenderbuffers(1, 缓冲区);

//3.将标识符绑定到GL_RENDERBUFFER

glBindRenderbuffer(GL_RENDERBUFFER, 缓冲区);与帧缓冲区对象一样,在引用渲染缓冲区对象之前必须先绑定当前渲染缓冲区对象。调用函数glBindRenderbuffer(GLenum target, GLuint renderbuffer)进行绑定。

第一个参数target是GL_RENDERBUFFER,

第二个参数是渲染缓冲区对象的ID。

删除缓冲区对象当缓冲区对象不再使用时,可以通过调用glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers)删除缓冲区对象。

glDeleteRenderbuffers(1, 缓冲区);当渲染缓冲区创建时,它没有任何数据存储区域,因此必须为其分配空间。这可以通过使用glRenderbufferStorage(GLenum 目标、GLenum 内部格式、GLsizei 宽度、GLsizei 高度)来实现。

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 600, 800);第一个参数必须是GL_RENDERBUFFER,

第二个参数是颜色、深度、模板可用的格式,

宽度和高度是渲染缓存图像的像素尺寸

附加渲染缓冲对象最后,生成framebuffer后,需要将renderbuffer绑定到framebuffer上,并调用glFramebufferRenderbuffer函数将其绑定到对应的附着点上,以便后续的绘制工作。

//通过glFramebufferRenderbuffer函数将渲染缓冲区myColorRenderBuffer绑定到GL_COLOR_ATTACHMENT0。

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);下图展示了framebuffer对象、渲染缓冲区对象和纹理之间的关系。帧缓冲区对象只能附加一种颜色、深度和模板。 Buffer关系.png 简单来说,framebuffer只是一个管理器。它没有任何存储区域(它不存储纹理、顶点、颜色等数据)。它只有颜色、深度和模板附着点,并且它实际上存储这些数据。是渲染缓冲区。

GLSL渲染图片

这个案例大概实现了以下目的:

·使用EAGL创建屏幕渲染表面

·加载顶点/片段着色器

·创建程序对象并链接顶点/元素着色器并链接程序对象

·设置视口

·清除颜色缓冲区

·渲染简单的图元

·使颜色缓冲区的内容能够显示在EAGL窗口中

1.创建顶点/片元着色器文件

着色器文件通常具有文件后缀.vsh/.fsh/.gsl。

顶点着色器shaderv.vsh//顶点坐标

属性highp vec4 位置;

//纹理坐标

属性highp vec2 文本坐标;

//纹理坐标

变化lowp vec2 变化文本坐标;

无效主(){

变化文本坐标=文本坐标;

gl_Position=位置;

Shader文件中最好不要添加中文注释,以免编译失败。这里的中文注释仅作为理解注释。

片元着色器shaderf.fsh//纹理坐标

变化lowp vec2 变化文本坐标;

//纹理采样器(获取对应的纹理ID)

统一采样器二维颜色图;

无效主(){

gl_FragColor=texture2D(colorMap,varieTextCoord);

}创建一个UIView并导入头文件#import.这次,所有使用GLSL渲染图像的代码都写在这个UIView中了。

//用于在iOS和tvOS上绘制OpenGL ES内容的层,继承自CALayer

@property(非原子,强)CAEAGLLayer *zhEagLayer;

@property(非原子,强)EAGLContext *zhContext;

@property(非原子,分配)GLuint zhColorRenderBuffer;

@property(非原子,分配)GLuint zhColorFrameBuffer;

@property(非原子,分配)GLuint zhPrograme;

2.设置图层setupLayer

//1.创建一个特殊层

//这里需要重写layerClass,将ZHView返回的layer从CALayer替换为CAEAGLLayer

self.zhEagLayer=(CAEAGLLayer *)self.layer;

//2.设置比例

[self setContentScaleFactor:[[UIScreen mainScreen]scale]];

//3.设置描述属性。这里不维护渲染内容,颜色格式为RGBA8。

self.zhEagLayer.drawableProperties=[NSDictionary DictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];设置drawableProperties的描述属性,

kEAGLDrawablePropertyRetainedBacking:表示绘图表面的内容显示后是否保留。

kEAGLDrawablePropertyColorFormat:表示可绘制表面的内部颜色缓冲区格式。该键对应的值是一个指定特定颜色缓冲区对象的NSString。默认为kEAGLColorFormatRGBA8;

· kEAGLColorFormatRGBA8:32位RGBA颜色

· kEAGLColorFormatRGB565:16位RGB颜色

· kEAGLColorFormatSRGBA8:sRGB 代表标准红、绿和蓝,这三种基本颜料用于CRT 显示器、LCD 显示器、投影仪、打印机和其他设备中的色彩再现。 sRGB色彩空间基于独立的色彩坐标,它允许颜色在不同设备使用和传输时对应相同的色彩坐标系,而不受这些设备不同色彩坐标的影响。

覆盖图层类

+(类)层类

{

返回[CAEAGLLayer类];

}

3.设置渲染上下文setupContext

//1.指定OpenGL ES渲染API版本

EAGLRenderingAPI api=kEAGLRenderingAPIOpenGLES2;

//2.创建图形上下文

EAGLContext *context=[[EAGLContext alloc]initWithAPI:api];

//3.判断是否创建成功

如果(!上下文){

NSLog(@"创建失败!");

返回;

}

//4.设置图形上下文

if (![EAGLContext setCurrentContext:context]) {

NSLog(@"设置失败!");

返回;

}

//5.将本地上下文分配给全局上下文

self.zhContext=上下文;

4.清空缓冲区deleteRenderAndFrameBuffer

//清空帧缓冲区

glDeleteBuffers(1, _zhColorFrameBuffer);

self.zhColorFrameBuffer=0;

//清空渲染缓冲区

glDeleteBuffers(1, _zhColorRenderBuffer);

self.zhColorRenderBuffer=0;清除缓冲区的代码也可以写成:

glDeleteFramebuffers(1, _zhColorFrameBuffer);

self.zhColorFrameBuffer=0;

glDeleteRenderbuffers(1, _zhColorRenderBuffer);

self.zhColorRenderBuffer=0;

5.设置RenderBuffer

//1.定义缓冲区ID

GLuint 缓冲区;

//2.申请缓存区标志

glGenRenderbuffers(1, 缓冲区);

//3.使当前应用的缓冲区全局化

self.zhColorRenderBuffer=缓冲区;

//4.将标识符绑定到GL_RENDERBUFFER

glBindRenderbuffer(GL_RENDERBUFFER, self.zhColorRenderBuffer);

//5.将drawable对象的CAEAGLLayer的存储绑定到OpenGL ES renderBuffer对象

[self.zhContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.zhEagLayer];

6.设置FrameBuffer

//1.定义缓冲区ID

GLuint 缓冲区;

//2.申请缓存区标志

glGenRenderbuffers(1, 缓冲区);

//3.使缓冲区全局化

self.zhColorFrameBuffer=缓冲区;

//4.将标识符绑定到GL_FRAMEBUFFER

glBindFramebuffer(GL_FRAMEBUFFER, self.zhColorFrameBuffer);

/*生成帧缓冲区后,需要将renderbuffer绑定到帧缓冲区。

调用glFramebufferRenderbuffer函数绑定对应的附着点,以便后续的绘制工作*/

//5.通过glFramebufferRenderbuffer函数将渲染缓冲区myColorRenderBuffer绑定到GL_COLOR_ATTACHMENT0。

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.zhColorRenderBuffer);

7.开始绘制renderLayer

1.设置清屏颜色glClearColor(0.3f, 0.45f, 0.5f, 1.0f);2.清除屏幕glClear(GL_ COLOR_BUFFER_BIT);3.设置视口大小CGFloat 比例=[[ UIScreen 主屏幕]比例];

glViewport(self.frame.origin.x * 缩放比例, self.frame.origin.y * 缩放比例, self.frame.size.width * 缩放比例, self.frame.size.height * 缩放比例);4.读取顶点着色程序、片元着色程序NSString *vertFile=[[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];

NSString *fragFile=[[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];5.加载着色器shaderself.zhPrograme=[self loadShaders:vertFile Withfrag:fragFile];

//加载着色器

-(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag

{

//1.定义2个临时着色器对象

GLuint verShader、fragShader;

//创建程序

GLint 程序=glCreateProgram();

//2.编译顶点着色器程序和片段着色器程序

[自编译Shader:verShader类型:GL_VERTEX_SHADER文件:vert];

[自编译Shader:fragShader类型:GL_FRAGMENT_SHADER文件:frag];

/*

关于这个compileShader:type:file:方法传入的三个参数:

参数1:编译后存储的底层地址

参数2:编译的shader的类型,GL_VERTEX_SHADER(顶点),GL_FRAGMENT_SHADER(片段)

参数3:文件路径

*/

//3.链接着色器对象和程序对象

glAttachShader(程序, verShader);

glAttachShader(程序, fragShader);

//4.释放不必要的着色器

glDeleteShader(verShader);

glDeleteShader(fragShader);

退货计划;

}

//编译着色器

- (void)compileShader:(GLuint *)着色器类型:(GLenum)类型文件:(NSString *)文件{

//1.读取文件路径字符串

NSString* 内容=[NSString stringWithContentsOfFile:文件编码:NSUTF8StringEncoding error:nil];

const GLchar* 源=(GLchar *)[内容UTF8String];

//2.根据类型创建着色器

*着色器=glCreateShader(类型);

//3.将着色器源代码附加到着色器对象。

glShaderSource(*着色器, 1, 源,NULL);

/*

参数1:shader,要编译的shader对象*shader

参数2:numOfStrings,传递的源字符串数量为1

参数3:字符串,shader程序的源码(真正的shader程序源码)

参数4:lenOfStrings,长度,每个字符串长度的数组,或者NULL,表示字符串以NULL结尾

*/

//4.将shader源代码编译为目标代码

glCompileShader(*着色器);

}6.链接程序对象glLinkProgram(self.zhPrograme);

//检查链接是否成功

GLint 链接状态;

//获取链接状态

glGetProgramiv(self.myPrograme, GL_LINK_STATUS, linkStatus);

if (linkStatus==GL_FALSE) {

GLchar 消息[512];

glGetProgramInfoLog(self.zhPrograme, sizeof(message), 0, message[0]);

NSString *messageString=[NSString stringWithUTF8String:message];

NSLog(@"程序链接错误:%@",messageString);

返回;

}

NSLog(@"程序链接成功!");7.使用程序对象glUseProgram(self.zhPrograme);8.设置顶点坐标和纹理坐标//坐标数组

GLfloat attrArr[]=

{

1.0f、-1.0f、-1.0f、1.0f、0.0f、

-1.0f、1.0f、-1.0f、0.0f、1.0f、

-1.0f、-1.0f、-1.0f、0.0f、0.0f、

1.0f、1.0f、-1.0f、

1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, }; //每一行的前3位为顶点坐标,后两位为纹理坐标9.处理顶点数据//(1)顶点缓存区 GLuint attrBuffer; //(2)申请一个缓存区标识符 glGenBuffers(1, &attrBuffer); //(3)将attrBuffer绑定到GL_ARRAY_BUFFER标识符上 glBindBuffer(GL_ARRAY_BUFFER, attrBuffer); //(4)把顶点数据从CPU内存复制到GPU上 glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);10.将顶点数据传入到顶点着色器对象将顶点数据通过self.zhPrograme中的传递到顶点着色器程序的position,通过以下三个函数处理顶点数据 ·glGetAttribLocation,用来获取vertex attribute的入口。 ·告诉OpenGL ES,通过glEnableVertexAttribArray从buffer读取数据(打开通道) ·glVertexAttribPointer设置读取数据的方式 //第二参数字符串必须和shaderv.vsh中的输入变量:position保持一致 GLuint position = glGetAttribLocation(self.zhPrograme, "position"); //设置合适的格式从buffer里面读取数据 glEnableVertexAttribArray(position); //设置读取方式 glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr) 参数1:index,顶点数据的索引 参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4. 参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT 参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE) 参数5:stride,连续顶点属性之间的偏移量,默认为0; 参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为011.处理纹理数据//第二参数字符串必须和shaderv.vsh中的输入变量:textCoordinate保持一致 GLuint textCoor = glGetAttribLocation(self.zhPrograme, "textCoordinate"); //设置合适的格式从buffer里面读取数据 glEnableVertexAttribArray(textCoor); //设置读取方式 glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);12.加载纹理//1、将 UIImage 转换为 CGImageRef CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage; //判断图片是否获取成功 if (!spriteImage) { NSLog(@"load image failed: %@", fileName); return; } //2、读取图片的大小,宽和高 size_t width = CGImageGetWidth(spriteImage); size_t height = CGImageGetHeight(spriteImage); //3.获取图片字节数 宽*高*4(RGBA) GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte)); //4.创建上下文 /* 参数1:data,指向要渲染的绘制图像的内存地址 参数2:width,bitmap的宽度,单位为像素 参数3:height,bitmap的高度,单位为像素 参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8 参数5:bytesPerRow,bitmap的没一行的内存所占的比特数 参数6:colorSpace,bitmap上使用的颜色空间 kCGImageAlphaPremultipliedLast:RGBA */ CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast); //5、在CGContextRef上-->将图片绘制出来 /* CGContextDrawImage 使用的是Core Graphics框架,坐标系与UIKit 不一样。UIKit框架的原点在屏幕的左上角,Core Graphics框架的原点在屏幕的左下角。 CGContextDrawImage 参数1:绘图上下文 参数2:rect坐标 参数3:绘制的图片 */ CGRect rect = CGRectMake(0, 0, width, height); //6.使用默认方式绘制 CGContextTranslateCTM(spriteContext, 0, rect.size.height); CGContextScaleCTM(spriteContext, 1.0, -1.0); CGContextDrawImage(spriteContext, rect, spriteImage); //7、画图完毕就释放上下文 CGContextRelease(spriteContext); //8、绑定纹理到默认的纹理ID( glBindTexture(GL_TEXTURE_2D, 0); //9.设置纹理属性 /* 参数1:纹理维度 参数2:线性过滤、为s,t坐标设置模式 参数3:wrapMode,环绕模式 */ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); float fw = width, fh = height; //10.载入纹理2D数据 /* 参数1:纹理模式(绑定纹理对象的种类),GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 参数2:加载的层次,一般设置为0, 0表示没有进行缩小的原始图片等级。 参数3:纹理的颜色值GL_RGBA, 表示了纹理所采用的内部格式,内部格式是我们的像素数据在显卡中存储的格式,这里的GL_RGB显然就表示纹理中像素的颜色值是以RGB的格式存储的。 参数4:纹理的宽 参数5:纹理的高 参数6:border,边界宽度,通常为0. 参数7:format(描述了像素在内存中的存储格式) 参数8:type(描述了像素在内存中的数据类型) 参数9:纹理数据 */ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData); //11.释放spriteData free(spriteData);13.设置纹理采样器 sampler2DglUniform1i(glGetUniformLocation(self.zhPrograme, "colorMap"), 0);14.绘图glDrawArrays(GL_TRIANGLES, 0, 6);15.从渲染缓冲区显示到屏幕上

深入浅出:OpenGL ES 初学者指南和的问题分享结束啦,以上的文章解决了您的问题吗?欢迎您下次再来哦!

用户评论

冷青裳

想学习做手机游戏的渲染,看到这文章,感觉入门点就近了!

    有11位网友表示赞同!

念旧是个瘾。

我一直想了解OpenGLES是什么,现在终于有时间进阶一下了。

    有13位网友表示赞同!

水波映月

这个标题很有吸引力,我已经开始期待这篇教程了。

    有12位网友表示赞同!

看我发功喷飞你

学习新的3D渲染系统一直是我的目标,希望这篇文章能给我一些指导

    有15位网友表示赞同!

寻鱼水之欢

最近在尝试做一些移动端的图形,也许OpenGLES能够派上用场。

    有13位网友表示赞同!

琴断朱弦

虽然我不太熟悉programming,但还是想了解一下游戏开发的原理,这篇教程看起来不错。

    有5位网友表示赞同!

此生一诺

手机游戏越来越受人喜爱,掌握OpenGL ES技术很有价值!

    有6位网友表示赞同!

你与清晨阳光

这个入门之旅听起来很轻松愉快,不会太难啃吧?

    有8位网友表示赞同!

半梦半醒半疯癫

想要制作更炫酷的游戏画面,OpenGLES应该是个强力的工具。

    有18位网友表示赞同!

走过海棠暮

之前看过一些关于图形渲染的课程,希望这篇文章能进一步拓宽我的知识面。

    有12位网友表示赞同!

伱德柔情是我的痛。

学习新技术总是令人兴奋,期待这篇教程能带领我踏上这个旅程!

    有8位网友表示赞同!

米兰

作为一名程序员,掌握OpenGL ES会让我在游戏开发方面更专业.

    有12位网友表示赞同!

暮光薄凉

听说OpenGLES是比较通用的图形渲染API,这篇文章正好可以帮我入门。

    有13位网友表示赞同!

丢了爱情i

我的朋友都在学习3D渲染技术,我也想跟上脚步尝试一下!

    有14位网友表示赞同!

冷眼旁观i

这篇教程看起来很有针对性,适合想要学习OpenGLES的新手。

    有9位网友表示赞同!

墨染年华

学习OpenGL ES对未来从事游戏开发工作很有帮助。

    有15位网友表示赞同!

凝残月

这篇文章应该能解答我关于OpenGLES的一些疑问。

    有13位网友表示赞同!

话少情在

希望这篇文章能够用通俗易懂的语言讲解OpenGL ES,方便我理解.

    有17位网友表示赞同!

从此我爱的人都像你

感谢作者分享这篇入门指南, 能让我更快学习到OpenGLES

    有16位网友表示赞同!

墨染天下

期待阅读这篇教程,从中获得学习OpenGLES的宝贵知识!

    有19位网友表示赞同!

【深入浅出:OpenGL ES 初学者指南】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:深入解析与优化:CSS 盒模型最佳实践 下一篇:30分钟掌握双拼打字技巧,实际操作体验分享