版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.01.10 |
前言
OpenGL ES图形库项目中一直也没用过,最近也想学着使用这个图形库,感觉还是很有意思,也就自然想着好好的总结一下,希望对大家能有所帮助。下面就开始进行实践,写一些小程序。感兴趣的可以看上面几篇文章。
1. OpenGL ES实践(一)—— 一个简单的小程序
2. OpenGL ES实践(二)—— 一个简单的三角形
3. OpenGL ES实践(三)—— 一个三色三角形的绘制
4. OpenGL ES实践(四)—— 简单图形的旋转
5. OpenGL ES实践(五)—— 使用一个纹理来给几何图形对象着色(一)
功能要求
使用一个纹理来给几何图形对象着色。
功能实现
还是直接看代码。
1. JJOpenGLESVC.h
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
@class JJVertexAttribArrayBuffer;
@interface JJOpenGLESVC : GLKViewController
@property (strong, nonatomic) GLKBaseEffect *baseEffect;
@property (strong, nonatomic) JJVertexAttribArrayBuffer *vertexBuffer;
@end
2. JJOpenGLESVC.m
#import "JJOpenGLESVC.h"
#import "JJVertexAttribArrayBuffer.h"
#import "JJContext.h"
#import "JJGLKTextureLoader.h"
/////////////////////////////////////////////////////////////////
// This data type is used to store information for each vertex
typedef struct {
GLKVector3 positionCoords;
GLKVector2 textureCoords;
}SceneVertex;
/////////////////////////////////////////////////////////////////
// Define vertex data for a triangle to use in example
static const SceneVertex vertices[] =
{
{{-0.5f, -0.5f, 0.0f}, {0.0f, 0.0f}}, // lower left corner
{{ 0.5f, -0.5f, 0.0f}, {1.0f, 0.0f}}, // lower right corner
{{-0.5f, 0.5f, 0.0f}, {0.0f, 1.0f}}, // upper left corner
};
@implementation JJOpenGLESVC
@synthesize baseEffect;
@synthesize vertexBuffer;
#pragma mark - Override Base Function
/////////////////////////////////////////////////////////////////
// Called when the view controller's view is loaded
// Perform initialization before the view is asked to draw
- (void)viewDidLoad
{
[super viewDidLoad];
// Verify the type of view created automatically by the
// Interface Builder storyboard
GLKView *view = (GLKView *)self.view;
NSAssert([view isKindOfClass:[GLKView class]],
@"View controller's view is not a GLKView");
// Create an OpenGL ES 2.0 context and provide it to the
// view
view.context = [[JJContext alloc]
initWithAPI:kEAGLRenderingAPIOpenGLES2];
// Make the new context current
[JJContext setCurrentContext:view.context];
// Create a base effect that provides standard OpenGL ES 2.0
// shading language programs and set constants to be used for
// all subsequent rendering
self.baseEffect = [[GLKBaseEffect alloc] init];
self.baseEffect.useConstantColor = GL_TRUE;
self.baseEffect.constantColor = GLKVector4Make(
1.0f, // Red
1.0f, // Green
1.0f, // Blue
1.0f);// Alpha
// Set the background color stored in the current context
((JJContext *)view.context).clearColor = GLKVector4Make(
0.0f, // Red
0.0f, // Green
0.0f, // Blue
1.0f);// Alpha
// Create vertex buffer containing vertices to draw
self.vertexBuffer = [[JJVertexAttribArrayBuffer alloc]
initWithAttribStride:sizeof(SceneVertex)
numberOfVertices:sizeof(vertices) / sizeof(SceneVertex)
bytes:vertices
usage:GL_STATIC_DRAW];
// Setup texture
CGImageRef imageRef = [[UIImage imageNamed:@"mountain"] CGImage];
AGLKTextureInfo *textureInfo = [JJGLKTextureLoader
textureWithCGImage:imageRef
options:nil
error:NULL];
self.baseEffect.texture2d0.name = textureInfo.name;
self.baseEffect.texture2d0.target = textureInfo.target;
}
/////////////////////////////////////////////////////////////////
// Called when the view controller's view has been unloaded
// Perform clean-up that is possible when you know the view
// controller's view won't be asked to draw again soon.
- (void)viewDidUnload
{
[super viewDidUnload];
// Make the view's context current
GLKView *view = (GLKView *)self.view;
[JJContext setCurrentContext:view.context];
// Delete buffers that aren't needed when view is unloaded
self.vertexBuffer = nil;
// Stop using the context created in -viewDidLoad
((GLKView *)self.view).context = nil;
[EAGLContext setCurrentContext:nil];
}
#pragma mark - GLKViewDelegate
/////////////////////////////////////////////////////////////////
// GLKView delegate method: Called by the view controller's view
// whenever Cocoa Touch asks the view controller's view to
// draw itself. (In this case, render into a frame buffer that
// shares memory with a Core Animation Layer)
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
[self.baseEffect prepareToDraw];
// Clear back frame buffer (erase previous drawing)
[(JJContext *)view.context clear:GL_COLOR_BUFFER_BIT];
[self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition
numberOfCoordinates:3
attribOffset:offsetof(SceneVertex, positionCoords)
shouldEnable:YES];
[self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribTexCoord0
numberOfCoordinates:2
attribOffset:offsetof(SceneVertex, textureCoords)
shouldEnable:YES];
// Draw triangles using the first three vertices in the
// currently bound vertex buffer
[self.vertexBuffer drawArrayWithMode:GL_TRIANGLES
startVertexIndex:0
numberOfVertices:3];
}
@end
3. JJContext.h
#import <GLKit/GLKit.h>
@interface JJContext : EAGLContext
{
GLKVector4 clearColor;
}
@property (nonatomic, assign) GLKVector4 clearColor;
- (void)clear:(GLbitfield)mask;
- (void)enable:(GLenum)capability;
- (void)disable:(GLenum)capability;
- (void)setBlendSourceFunction:(GLenum)sfactor
destinationFunction:(GLenum)dfactor;
@end
4. JJContext.m
#import "JJContext.h"
@implementation JJContext
/////////////////////////////////////////////////////////////////
// This method sets the clear (background) RGBA color.
// The clear color is undefined until this method is called.
- (void)setClearColor:(GLKVector4)clearColorRGBA
{
clearColor = clearColorRGBA;
NSAssert(self == [[self class] currentContext],
@"Receiving context required to be current context");
glClearColor(
clearColorRGBA.r,
clearColorRGBA.g,
clearColorRGBA.b,
clearColorRGBA.a);
}
/////////////////////////////////////////////////////////////////
// Returns the clear (background) color set via -setClearColor:.
// If no clear color has been set via -setClearColor:, the
// return clear color is undefined.
- (GLKVector4)clearColor
{
return clearColor;
}
/////////////////////////////////////////////////////////////////
// This method instructs OpenGL ES to set all data in the
// current Context's Render Buffer(s) identified by mask to
// colors (values) specified via -setClearColor: and/or
// OpenGL ES functions for each Render Buffer type.
- (void)clear:(GLbitfield)mask
{
NSAssert(self == [[self class] currentContext],
@"Receiving context required to be current context");
glClear(mask);
}
/////////////////////////////////////////////////////////////////
//
- (void)enable:(GLenum)capability;
{
NSAssert(self == [[self class] currentContext],
@"Receiving context required to be current context");
glEnable(capability);
}
/////////////////////////////////////////////////////////////////
//
- (void)disable:(GLenum)capability;
{
NSAssert(self == [[self class] currentContext],
@"Receiving context required to be current context");
glDisable(capability);
}
/////////////////////////////////////////////////////////////////
//
- (void)setBlendSourceFunction:(GLenum)sfactor
destinationFunction:(GLenum)dfactor;
{
glBlendFunc(sfactor, dfactor);
}
@end
5. JJGLKTextureLoader.h
#import <Foundation/Foundation.h>
#import <GLKit/GLKit.h>
@interface AGLKTextureInfo : NSObject
{
@private
GLuint name;
GLenum target;
GLuint width;
GLuint height;
}
@property (readonly) GLuint name;
@property (readonly) GLenum target;
@property (readonly) GLuint width;
@property (readonly) GLuint height;
@end
@interface JJGLKTextureLoader : NSObject
+ (AGLKTextureInfo *)textureWithCGImage:(CGImageRef)cgImage
options:(NSDictionary *)options
error:(NSError **)outError;
@end
6. JJGLKTextureLoader.m
#import "JJGLKTextureLoader.h"
/////////////////////////////////////////////////////////////////
// This data type is used specify power of 2 values. OpenGL ES
// best supports texture images that have power of 2 dimensions.
typedef enum
{
AGLK1 = 1,
AGLK2 = 2,
AGLK4 = 4,
AGLK8 = 8,
AGLK16 = 16,
AGLK32 = 32,
AGLK64 = 64,
AGLK128 = 128,
AGLK256 = 256,
AGLK512 = 512,
AGLK1024 = 1024,
}
AGLKPowerOf2;
/////////////////////////////////////////////////////////////////
// Forward declaration of function
static AGLKPowerOf2 AGLKCalculatePowerOf2ForDimension(
GLuint dimension);
/////////////////////////////////////////////////////////////////
// Forward declaration of function
static NSData *AGLKDataWithResizedCGImageBytes(
CGImageRef cgImage,
size_t *widthPtr,
size_t *heightPtr);
/////////////////////////////////////////////////////////////////
// Instances of AGLKTextureInfo are immutable once initialized
@interface AGLKTextureInfo (AGLKTextureLoader)
- (id)initWithName:(GLuint)aName
target:(GLenum)aTarget
width:(GLuint)aWidth
height:(GLuint)aHeight;
@end
@implementation AGLKTextureInfo (AGLKTextureLoader)
/////////////////////////////////////////////////////////////////
// This method is the designated initializer.
- (id)initWithName:(GLuint)aName
target:(GLenum)aTarget
width:(GLuint)aWidth
height:(GLuint)aHeight
{
if (nil != (self = [super init]))
{
name = aName;
target = aTarget;
width = aWidth;
height = aHeight;
}
return self;
}
@end
@implementation AGLKTextureInfo
@synthesize name;
@synthesize target;
@synthesize width;
@synthesize height;
@end
@implementation JJGLKTextureLoader
/////////////////////////////////////////////////////////////////
// This method generates a new OpenGL ES texture buffer and
// initializes the buffer contents using pixel data from the
// specified Core Graphics image, cgImage. This method returns an
// immutable AGLKTextureInfo instance initialized with
// information about the newly generated texture buffer.
// The generated texture buffer has power of 2 dimensions. The
// provided image data is scaled (re-sampled) by Core Graphics as
// necessary to fit within the generated texture buffer.
+ (AGLKTextureInfo *)textureWithCGImage:(CGImageRef)cgImage options:(NSDictionary *)options
error:(NSError **)outError;
{
// Get the bytes to be used when copying data into new texture
// buffer
size_t width;
size_t height;
NSData *imageData = AGLKDataWithResizedCGImageBytes(
cgImage,
&width,
&height);
// Generation, bind, and copy data into a new texture buffer
GLuint textureBufferID;
glGenTextures(1, &textureBufferID); // Step 1
glBindTexture(GL_TEXTURE_2D, textureBufferID); // Step 2
glTexImage2D( // Step 3
GL_TEXTURE_2D,
0,
GL_RGBA,
(GLuint)width,
(GLuint)height,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
[imageData bytes]);
// Set parameters that control texture sampling for the bound
// texture
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER,
GL_LINEAR);
// Allocate and initialize the AGLKTextureInfo instance to be
// returned
AGLKTextureInfo *result = [[AGLKTextureInfo alloc]
initWithName:textureBufferID
target:GL_TEXTURE_2D
width:(GLuint)width
height:(GLuint)height];
return result;
}
@end
/////////////////////////////////////////////////////////////////
// This function returns an NSData object that contains bytes
// loaded from the specified Core Graphics image, cgImage. This
// function also returns (by reference) the power of 2 width and
// height to be used when initializing an OpenGL ES texture buffer
// with the bytes in the returned NSData instance. The widthPtr
// and heightPtr arguments must be valid pointers.
static NSData *AGLKDataWithResizedCGImageBytes(
CGImageRef cgImage,
size_t *widthPtr,
size_t *heightPtr)
{
NSCParameterAssert(NULL != cgImage);
NSCParameterAssert(NULL != widthPtr);
NSCParameterAssert(NULL != heightPtr);
GLuint originalWidth = (GLuint)CGImageGetWidth(cgImage);
GLuint originalHeight = (GLuint)CGImageGetWidth(cgImage);
NSCAssert(0 < originalWidth, @"Invalid image width");
NSCAssert(0 < originalHeight, @"Invalid image width");
// Calculate the width and height of the new texture buffer
// The new texture buffer will have power of 2 dimensions.
GLuint width = AGLKCalculatePowerOf2ForDimension(
originalWidth);
GLuint height = AGLKCalculatePowerOf2ForDimension(
originalHeight);
// Allocate sufficient storage for RGBA pixel color data with
// the power of 2 sizes specified
NSMutableData *imageData = [NSMutableData dataWithLength:
height * width * 4]; // 4 bytes per RGBA pixel
NSCAssert(nil != imageData,
@"Unable to allocate image storage");
// Create a Core Graphics context that draws into the
// allocated bytes
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef cgContext = CGBitmapContextCreate(
[imageData mutableBytes], width, height, 8,
4 * width, colorSpace,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
// Flip the Core Graphics Y-axis for future drawing
CGContextTranslateCTM (cgContext, 0, height);
CGContextScaleCTM (cgContext, 1.0, -1.0);
// Draw the loaded image into the Core Graphics context
// resizing as necessary
CGContextDrawImage(cgContext, CGRectMake(0, 0, width, height),
cgImage);
CGContextRelease(cgContext);
*widthPtr = width;
*heightPtr = height;
return imageData;
}
/////////////////////////////////////////////////////////////////
// This function calculates and returns the nearest power of 2
// that is greater than or equal to the dimension argument and
// less than or equal to 1024.
static AGLKPowerOf2 AGLKCalculatePowerOf2ForDimension(
GLuint dimension)
{
AGLKPowerOf2 result = AGLK1;
if(dimension > (GLuint)AGLK512)
{
result = AGLK1024;
}
else if(dimension > (GLuint)AGLK256)
{
result = AGLK512;
}
else if(dimension > (GLuint)AGLK128)
{
result = AGLK256;
}
else if(dimension > (GLuint)AGLK64)
{
result = AGLK128;
}
else if(dimension > (GLuint)AGLK32)
{
result = AGLK64;
}
else if(dimension > (GLuint)AGLK16)
{
result = AGLK32;
}
else if(dimension > (GLuint)AGLK8)
{
result = AGLK16;
}
else if(dimension > (GLuint)AGLK4)
{
result = AGLK8;
}
else if(dimension > (GLuint)AGLK2)
{
result = AGLK4;
}
else if(dimension > (GLuint)AGLK1)
{
result = AGLK2;
}
return result;
}
7. JJVertexAttribArrayBuffer.h
#import <Foundation/Foundation.h>
#import <GLKit/GLKit.h>
@class AGLKElementIndexArrayBuffer;
/////////////////////////////////////////////////////////////////
//
typedef enum {
AGLKVertexAttribPosition = GLKVertexAttribPosition,
AGLKVertexAttribNormal = GLKVertexAttribNormal,
AGLKVertexAttribColor = GLKVertexAttribColor,
AGLKVertexAttribTexCoord0 = GLKVertexAttribTexCoord0,
AGLKVertexAttribTexCoord1 = GLKVertexAttribTexCoord1,
} AGLKVertexAttrib;
@interface JJVertexAttribArrayBuffer : NSObject
{
GLsizei stride;
GLsizeiptr bufferSizeBytes;
GLuint name;
}
@property (nonatomic, readonly) GLuint name;
@property (nonatomic, readonly) GLsizeiptr bufferSizeBytes;
@property (nonatomic, readonly) GLsizei stride;
+ (void)drawPreparedArraysWithMode:(GLenum)mode
startVertexIndex:(GLint)first
numberOfVertices:(GLsizei)count;
- (id)initWithAttribStride:(GLsizei)stride
numberOfVertices:(GLsizei)count
bytes:(const GLvoid *)dataPtr
usage:(GLenum)usage;
- (void)prepareToDrawWithAttrib:(GLuint)index
numberOfCoordinates:(GLint)count
attribOffset:(GLsizeiptr)offset
shouldEnable:(BOOL)shouldEnable;
- (void)drawArrayWithMode:(GLenum)mode
startVertexIndex:(GLint)first
numberOfVertices:(GLsizei)count;
- (void)reinitWithAttribStride:(GLsizei)stride
numberOfVertices:(GLsizei)count
bytes:(const GLvoid *)dataPtr;
@end
8. JJVertexAttribArrayBuffer.m
#import "JJVertexAttribArrayBuffer.h"
@interface JJVertexAttribArrayBuffer ()
@property (nonatomic, assign) GLsizeiptr bufferSizeBytes;
@property (nonatomic, assign) GLsizei stride;
@end
@implementation JJVertexAttribArrayBuffer
@synthesize name;
@synthesize bufferSizeBytes;
@synthesize stride;
/////////////////////////////////////////////////////////////////
// This method creates a vertex attribute array buffer in
// the current OpenGL ES context for the thread upon which this
// method is called.
- (id)initWithAttribStride:(GLsizei)aStride
numberOfVertices:(GLsizei)count
bytes:(const GLvoid *)dataPtr
usage:(GLenum)usage;
{
NSParameterAssert(0 < aStride);
NSAssert((0 < count && NULL != dataPtr) ||
(0 == count && NULL == dataPtr),
@"data must not be NULL or count > 0");
if(nil != (self = [super init]))
{
stride = aStride;
bufferSizeBytes = stride * count;
glGenBuffers(1, // STEP 1
&name);
glBindBuffer(GL_ARRAY_BUFFER, // STEP 2
self.name);
glBufferData( // STEP 3
GL_ARRAY_BUFFER, // Initialize buffer contents
bufferSizeBytes, // Number of bytes to copy
dataPtr, // Address of bytes to copy
usage); // Hint: cache in GPU memory
NSAssert(0 != name, @"Failed to generate name");
}
return self;
}
/////////////////////////////////////////////////////////////////
// This method loads the data stored by the receiver.
- (void)reinitWithAttribStride:(GLsizei)aStride
numberOfVertices:(GLsizei)count
bytes:(const GLvoid *)dataPtr;
{
NSParameterAssert(0 < aStride);
NSParameterAssert(0 < count);
NSParameterAssert(NULL != dataPtr);
NSAssert(0 != name, @"Invalid name");
self.stride = aStride;
self.bufferSizeBytes = aStride * count;
glBindBuffer(GL_ARRAY_BUFFER, // STEP 2
self.name);
glBufferData( // STEP 3
GL_ARRAY_BUFFER, // Initialize buffer contents
bufferSizeBytes, // Number of bytes to copy
dataPtr, // Address of bytes to copy
GL_DYNAMIC_DRAW);
}
/////////////////////////////////////////////////////////////////
// A vertex attribute array buffer must be prepared when your
// application wants to use the buffer to render any geometry.
// When your application prepares an buffer, some OpenGL ES state
// is altered to allow bind the buffer and configure pointers.
- (void)prepareToDrawWithAttrib:(GLuint)index
numberOfCoordinates:(GLint)count
attribOffset:(GLsizeiptr)offset
shouldEnable:(BOOL)shouldEnable
{
NSParameterAssert((0 < count) && (count < 4));
NSParameterAssert(offset < self.stride);
NSAssert(0 != name, @"Invalid name");
glBindBuffer(GL_ARRAY_BUFFER, // STEP 2
self.name);
if(shouldEnable)
{
glEnableVertexAttribArray( // Step 4
index);
}
glVertexAttribPointer( // Step 5
index, // Identifies the attribute to use
count, // number of coordinates for attribute
GL_FLOAT, // data is floating point
GL_FALSE, // no fixed point scaling
(self.stride), // total num bytes stored per vertex
NULL + offset); // offset from start of each vertex to
// first coord for attribute
#ifdef DEBUG
{ // Report any errors
GLenum error = glGetError();
if(GL_NO_ERROR != error)
{
NSLog(@"GL Error: 0x%x", error);
}
}
#endif
}
/////////////////////////////////////////////////////////////////
// Submits the drawing command identified by mode and instructs
// OpenGL ES to use count vertices from the buffer starting from
// the vertex at index first. Vertex indices start at 0.
- (void)drawArrayWithMode:(GLenum)mode
startVertexIndex:(GLint)first
numberOfVertices:(GLsizei)count
{
NSAssert(self.bufferSizeBytes >=
((first + count) * self.stride),
@"Attempt to draw more vertex data than available.");
glDrawArrays(mode, first, count); // Step 6
}
/////////////////////////////////////////////////////////////////
// Submits the drawing command identified by mode and instructs
// OpenGL ES to use count vertices from previously prepared
// buffers starting from the vertex at index first in the
// prepared buffers
+ (void)drawPreparedArraysWithMode:(GLenum)mode
startVertexIndex:(GLint)first
numberOfVertices:(GLsizei)count;
{
glDrawArrays(mode, first, count); // Step 6
}
/////////////////////////////////////////////////////////////////
// This method deletes the receiver's buffer from the current
// Context when the receiver is deallocated.
- (void)dealloc
{
// Delete buffer from current context
if (0 != name)
{
glDeleteBuffers (1, &name); // Step 7
name = 0;
}
}
@end
功能效果
下面我们看一下功能效果
这个和上一篇那个纹理有什么区别呢?对了,图像纹理是正向的了。
后记
未完,待续~~~