美文网首页
OpenGL ES - 粒子动画

OpenGL ES - 粒子动画

作者: 再好一点点 | 来源:发表于2021-08-18 09:55 被阅读0次

先看效果

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, &centerPos[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运行一下,看看具体效果。

所有demo地址

本人原文博客地址

相关文章

网友评论

      本文标题:OpenGL ES - 粒子动画

      本文链接:https://www.haomeiwen.com/subject/dsfubltx.html