先看效果
image因后面所有关于opengl的demo都是基于现有的一套框架的,所以需要在这第一篇文章中简单介绍一下基类
BaseViewController.h
#import <GLKit/GLKit.h>
#import <OpenGLES/ES3/gl.h>
#include "esUtil.h"
NS_ASSUME_NONNULL_BEGIN
@interface BaseViewController : GLKViewController {
ESContext _esContext;
}
@property (strong, nonatomic) EAGLContext *context;
- (void)setupGL;
@end
BaseViewController.m
#import "BaseViewController.h"
@implementation BaseViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!self.context)
{
NSLog(@"Failed to create ES context");
}
GLKView *view = (GLKView *)self.view;
view.context = self.context;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
}
- (void)dealloc
{
[self tearDownGL];
if ([EAGLContext currentContext] == self.context) {
[EAGLContext setCurrentContext:nil];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if ([self isViewLoaded] && ([[self view] window] == nil)) {
self.view = nil;
[self tearDownGL];
if ([EAGLContext currentContext] == self.context) {
[EAGLContext setCurrentContext:nil];
}
self.context = nil;
}
}
- (void)setupGL
{
[EAGLContext setCurrentContext:self.context];
memset( &_esContext, 0, sizeof( _esContext ) );
}
- (void)tearDownGL
{
[EAGLContext setCurrentContext:self.context];
if ( _esContext.shutdownFunc )
{
_esContext.shutdownFunc( &_esContext );
}
}
@end
然后是一个列表页,用来展示所有的demo的列表,ViewController.h
@interface ViewController : UIViewController
@end
接着是ViewController.m
#import "ViewController.h"
@interface ViewController ()<UITableViewDelegate, UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) NSMutableArray<NSString *> *titleArray;
@property (strong, nonatomic) NSMutableArray<NSString *> *vcArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.titleArray = [NSMutableArray arrayWithObjects:@"粒子动画", @"双重纹理", @"图片模糊", nil];
self.vcArray = [NSMutableArray arrayWithObjects:@"ParticleSystemVCViewController", @"DoubleSampleVC", @"BlurViewController", nil];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _titleArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"identifier"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"identifier"];
cell.textLabel.text = _titleArray[indexPath.row];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Class class = NSClassFromString(_vcArray[indexPath.row]);
UIViewController *vc = [class new];
vc.title = _titleArray[indexPath.row];
[self.navigationController pushViewController:vc animated:YES];
}
@end
以上介绍了一下整个项目的大致结构。下面进入今天的正题粒子动画
ParticleSystemVCViewController继承自BaseViewController
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface ParticleSystemVCViewController : BaseViewController
@end
NS_ASSUME_NONNULL_END
#import "ParticleSystemVCViewController.h"
extern void esParticleSystemMain( ESContext *esContext );
@interface ParticleSystemVCViewController ()
@end
@implementation ParticleSystemVCViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupGL];
}
- (void)setupGL
{
[super setupGL];
esParticleSystemMain( &_esContext );
}
- (void)update
{
if ( _esContext.updateFunc )
{
_esContext.updateFunc( &_esContext, self.timeSinceLastUpdate );
}
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
_esContext.width = view.drawableWidth;
_esContext.height = view.drawableHeight;
if ( _esContext.drawFunc )
{
_esContext.drawFunc( &_esContext );
}
}
@end
OC代码准备完成,接下来就是核心部分。
加载shader以及获取句柄
///
// Initialize the shader and program object
//
static int Init ( ESContext *esContext )
{
UserData *userData = esContext->userData;
int i;
char vShaderStr[] =
"#version 300 es \n"
"uniform float u_time; \n"
"uniform vec3 u_centerPosition; \n"
"layout(location = 0) in float a_lifetime; \n"
"layout(location = 1) in vec3 a_startPosition; \n"
"layout(location = 2) in vec3 a_endPosition; \n"
"out float v_lifetime; \n"
"void main() \n"
"{ \n"
" if ( u_time <= a_lifetime ) \n"
" { \n"
" gl_Position.xyz = a_startPosition + \n"
" (u_time * a_endPosition); \n"
" gl_Position.xyz += u_centerPosition; \n"
" gl_Position.w = 1.0; \n"
" } \n"
" else \n"
" { \n"
" gl_Position = vec4( -1000, -1000, 0, 0 ); \n"
" } \n"
" v_lifetime = 1.0 - ( u_time / a_lifetime ); \n"
" v_lifetime = clamp ( v_lifetime, 0.0, 1.0 ); \n"
" gl_PointSize = ( v_lifetime * v_lifetime ) * 40.0; \n"
"}";
char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"uniform vec4 u_color; \n"
"in float v_lifetime; \n"
"layout(location = 0) out vec4 fragColor; \n"
"uniform sampler2D s_texture; \n"
"void main() \n"
"{ \n"
" vec4 texColor; \n"
" texColor = texture( s_texture, gl_PointCoord ); \n"
" fragColor = vec4( u_color ) * texColor; \n"
" fragColor.a *= v_lifetime; \n"
"} \n";
// Load the shaders and get a linked program object
userData->programObject = esLoadProgram ( vShaderStr, fShaderStr );
// Get the uniform locations
userData->timeLoc = glGetUniformLocation ( userData->programObject, "u_time" );
userData->centerPositionLoc = glGetUniformLocation ( userData->programObject, "u_centerPosition" );
userData->colorLoc = glGetUniformLocation ( userData->programObject, "u_color" );
userData->samplerLoc = glGetUniformLocation ( userData->programObject, "s_texture" );
glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
// Fill in particle data array
srand ( 0 );
for ( i = 0; i < NUM_PARTICLES; i++ )
{
float *particleData = &userData->particleData[i * PARTICLE_SIZE];
// Lifetime of particle
//0 - 1
( *particleData++ ) = ( ( float ) ( rand() % 10000 ) / 10000.0f );
//0 - 1
// End position of particle
//-1 - 1
( *particleData++ ) = ( ( float ) ( rand() % 10000 ) / 5000.0f ) - 1.0f;
( *particleData++ ) = ( ( float ) ( rand() % 10000 ) / 5000.0f ) - 1.0f;
( *particleData++ ) = ( ( float ) ( rand() % 10000 ) / 5000.0f ) - 1.0f;
// Start position of particle
//-0.125 - 0.125
( *particleData++ ) = ( ( float ) ( rand() % 10000 ) / 40000.0f ) - 0.125f;
( *particleData++ ) = ( ( float ) ( rand() % 10000 ) / 40000.0f ) - 0.125f;
( *particleData++ ) = ( ( float ) ( rand() % 10000 ) / 40000.0f ) - 0.125f;
}
// Initialize time to cause reset on first update
userData->time = 1.0f;
userData->textureId = LoadTexture ( esContext->platformData, "smoke.tga" );
if ( userData->textureId <= 0 )
{
return FALSE;
}
return TRUE;
}
继承至GLKViewController的类每帧都会执行以下两个方法,在初始化context的时候指定了函数指针,所以每帧都会执行ParticleSystem.c类里边的update和drwa函数
- (void)update
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
函数static void Update ( ESContext *esContext, float deltaTime ),这里设置粒子的中心点以及起始颜色。
///
// Update time-based variables
//
static void Update ( ESContext *esContext, float deltaTime )
{
UserData *userData = esContext->userData;
userData->time += deltaTime;
glUseProgram ( userData->programObject );
if ( userData->time >= 1.0f )
{
float centerPos[3];
float color[4];
userData->time = 0.0f;
// Pick a new start location and color
//-0.5 - 0.5
centerPos[0] = ( ( float ) ( rand() % 10000 ) / 10000.0f ) - 0.5f;
centerPos[1] = ( ( float ) ( rand() % 10000 ) / 10000.0f ) - 0.5f;
centerPos[2] = ( ( float ) ( rand() % 10000 ) / 10000.0f ) - 0.5f;
glUniform3fv ( userData->centerPositionLoc, 1, ¢erPos[0] );
// Random color
//0.5 - 1
color[0] = ( ( float ) ( rand() % 10000 ) / 20000.0f ) + 0.5f;
color[1] = ( ( float ) ( rand() % 10000 ) / 20000.0f ) + 0.5f;
color[2] = ( ( float ) ( rand() % 10000 ) / 20000.0f ) + 0.5f;
color[3] = 0.5;
glUniform4fv ( userData->colorLoc, 1, &color[0] );
}
// Load uniform time variable
glUniform1f ( userData->timeLoc, userData->time );
}
函数static void Draw ( ESContext *esContext )设置窗口大小位置以及每个粒子在当前时间点的起始位置以及颜色。
///
// Draw a triangle using the shader pair created in Init()
//
static void Draw ( ESContext *esContext )
{
UserData *userData = esContext->userData;
// Set the viewport
glViewport ( 0, 0, esContext->width, esContext->height );
// Clear the color buffer
glClear ( GL_COLOR_BUFFER_BIT );
// Use the program object
glUseProgram ( userData->programObject );
// Load the vertex attributes
glVertexAttribPointer ( ATTRIBUTE_LIFETIME_LOCATION, 1, GL_FLOAT,
GL_FALSE, PARTICLE_SIZE * sizeof ( GLfloat ),
userData->particleData );
glVertexAttribPointer ( ATTRIBUTE_ENDPOSITION_LOCATION, 3, GL_FLOAT,
GL_FALSE, PARTICLE_SIZE * sizeof ( GLfloat ),
&userData->particleData[1] );
glVertexAttribPointer ( ATTRIBUTE_STARTPOSITION_LOCATION, 3, GL_FLOAT,
GL_FALSE, PARTICLE_SIZE * sizeof ( GLfloat ),
&userData->particleData[4] );
glEnableVertexAttribArray ( ATTRIBUTE_LIFETIME_LOCATION );
glEnableVertexAttribArray ( ATTRIBUTE_ENDPOSITION_LOCATION );
glEnableVertexAttribArray ( ATTRIBUTE_STARTPOSITION_LOCATION );
// Blend particles
glEnable ( GL_BLEND );
glBlendFunc ( GL_SRC_ALPHA, GL_ONE );
// Bind the texture
glActiveTexture ( GL_TEXTURE0 );
glBindTexture ( GL_TEXTURE_2D, userData->textureId );
// Set the sampler texture unit to 0
glUniform1i ( userData->samplerLoc, 0 );
glDrawArrays ( GL_POINTS, 0, NUM_PARTICLES );
}
可以下载demo运行一下,看看具体效果。
网友评论