Particle effects

This attempt to achieve some simple iOS particle effect implementation. Project key

Uniform sampler2D u_sampler[10]; // Particle transparency VARYING lowp float v_particleAlpha; // Select the texture unit index VARYING LOWp float v_textureIndex; Void main() {//gl_PointCoord is a built-in read-only variable of the slice shader. Its value is the two-dimensional coordinate of the current slice. Point range is 0.0 to 1.0 lowp int indx = int(floor(v_textureIndex)); If (v_textureIndex-floor (v_textureIndex) > 0.0) {indx = int(ceil(v_textureIndex)); } lowp vec4 textureColor = texture2D(u_sampler[indx], gl_PointCoord); textureColor.a = textureColor.a * v_particleAlpha; gl_FragColor = textureColor; }Copy the code

The texture is passed in as an array, and the index of the texture used is passed in to select the texture. OpenGL ES 2.0 does not support ints, so we pass in float as index, but float and int conversion error, sometimes smaller, sometimes larger, so to ensure that the same particle every time when rendering the same texture, indX is judged more than once.

// Attribute vec3 a_position; // Attribute vec3 a_initialSpeed; // Attribute acceleration vec3a_acceleration; // Texture index attribute lowp float a_textureIndex; // Launch time attribute highp float a_launchTime; Attribute highp float a_duration; // Fade time attribute highp float a_disappearDuration; // Particle size attribute highp float a_size; // Current time uniform highp float u_runTime; Uniform highp mat4u_mvpmatrix; // Particle transparency VARYING lowp float v_particleAlpha; // Select the texture unit index VARYING LOWp float v_textureIndex; Void main() {// end: CurrentPoint = (u_runtime_a_launchTime) * a_initialSpeed + pow(u_runTime -a_launchTime), 2.0) * a_acceleration + a_position; Gl_Position = u_mvpMatrix * vec4(currentPoint, 1.0); gl_Position = u_mvpMatrix * vec4(currentPoint, 1.0); gl_PointSize = a_size; If (u_runTime < a_launchTime | | (a_launchTime + a_duration) < u_runTime) {v_particleAlpha = 0.0; }else {if ((a_launchTime + a_duration - a_disappearDuration) > u_runTime) {v_particleAlpha = 1.0; }else { v_particleAlpha = (a_launchTime + a_duration - u_runTime)/a_disappearDuration; } } v_textureIndex = a_textureIndex; }Copy the code

In the vertex shader, because it is a uniform linear motion, the motion formula is used to calculate the position of the particle at the current time. According to the current time, particle appearance time, particle duration, particle extinction time to calculate the particle transparency under the previous time.

(void)setTexturesWithPathList:(NSArray <NSString *> *)pathList {int index = 0; for (int i = 0; i < pathList.count; i ++) { NSString *path = pathList[i]; UIImage *image = [UIImage imageWithContentsOfFile:path]; CGImageRef imageRef = image.CGImage; if (! imageRef) { continue; } size_t width = CGImageGetWidth(imageRef); size_t height = CGImageGetHeight(imageRef); GLbyte *imageData = calloc(width * height * 4, sizeof(GLbyte)); /// Since the particles do not distinguish between up and down, CGContextRef contextRef = CGBitmapContextCreate(imageData, width, height, 8, width * 4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast); CGRect rect = CGRectMake(0, 0, width, height); CGContextDrawImage(contextRef, rect, imageRef); CGContextRelease(contextRef); GlActiveTexture (GL_TEXTURE0 + index); glBindTexture(GL_TEXTURE_2D, index); 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); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); Free (imageData); free(imageData); index ++; } _textureCount = index; }Copy the code

Since this method will be called frequently in this project, the texture ID will not be allocated through glGenTextures method, which will create a new memory to store texture data each time it is called. Since the texture data can be released automatically by OpenGL, we cannot manually release the texture data, and the texture data will be stored multiple times during multiple calls. Memory accumulates. If we assign a fixed ID each time, memory doesn’t accumulate, it just overwrites.

Typedef NS_ENUM (NSInteger AttributeKey) {positionAttributeKey, / / / < initialSpeedAttributeKey source location, / / / < initial velocity TextureIndexAttributeKey accelerationAttributeKey, / / / < acceleration, / / / < texture index launchTimeAttributeKey, / / / < launch time DurationAttributeKey, / / / < duration disappearDurationAttributeKey, / / / < sizeAttributeKey fading time, / / / particle size}; glAttachShader(program, vShader); glAttachShader(program, fShader); GlBindAttribLocation (Program, positionAttributeKey, "a_position"); glBindAttribLocation(program, initialSpeedAttributeKey, "a_initialSpeed"); glBindAttribLocation(program, accelerationAttributeKey, "a_acceleration"); glBindAttribLocation(program, textureIndexAttributeKey, "a_textureIndex"); glBindAttribLocation(program, launchTimeAttributeKey, "a_launchTime"); glBindAttribLocation(program, durationAttributeKey, "a_duration"); glBindAttribLocation(program, disappearDurationAttributeKey, "a_disappearDuration"); glBindAttribLocation(program, sizeAttributeKey, "a_size"); glLinkProgram(program); glEnableVertexAttribArray(positionAttributeKey); glVertexAttribPointer(positionAttributeKey, size, type, normalized, stride, ptr);Copy the code

Before link Program, we can bind attributes to the local index and use it according to the bound local index, but this function is only supported by attributes.

Typedef NS_ENUM(NSInteger,UniformKey) {runTimeUniformKey,// < runtime mvpMatrixUniformKey,///< samplerUniformKey,///< Texture array}; @interface MCustomParticleManager () {/// the total number of textures int _textureCount; NSMutableData *_particleAttributesData; / / / whether need to update particle attribute data BOOL _needUpdateParticleAttributesData; GLuint program; GLint uniforms[3]; GLuint buffer; } uniforms[runTimeUniformKey] = glGetUniformLocation(program, "u_runTime"); uniforms[mvpMatrixUniformKey] = glGetUniformLocation(program, "u_mvpMatrix"); uniforms[samplerUniformKey] = glGetUniformLocation(program, "u_sampler"); GLint sampler[_textureCount]; for (int i = 0; i < _textureCount; i ++) { sampler[i] = i; } glUniform1iv(uniforms[samplerUniformKey], _textureCount, sampler);Copy the code

After link Program, we can also bind Uniform to local indexes, similar to property operations, but we are not provided with methods. We need to manage these mappings ourselves

- (void)draw {/// render bug glDepthMask(GL_FALSE); NSUInteger count = _particleAttributesData.length / sizeof(MCustomParticle); glDrawArrays(GL_POINTS, 0, (GLsizei)count); glDepthMask(GL_TRUE); }Copy the code

Be sure to disable depth buffer writing before you start drawing particles, otherwise you will see the transparent part of the particle texture for depth comparison reasons, as shown below

- (void)glkView:(glkView *)view drawInRect:(CGRect)rect {glClearColor(0.5, 0.5, 0.5, 1.0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); // Enable glEnable(GL_BLEND); // Set the blending factor glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); [self.manager start]; // NSLog(@"glkView drawInRect", self.manager.runTime); }Copy the code

To render particles with a particle texture with transparent parts, be sure to set the color blend and blend factor, otherwise the transparent parts will be filled with black, as shown below

- (void)update {
    self.manager.runTime = self.timeSinceFirstResume;
//    NSLog(@"update %lf", self.manager.runTime);
}
Copy the code

Calculate the current running time, can be read by rewriting the update to the self. The timeSinceFirstResume, send you updates (1) this is the first time since the view controller to restore time elapsed since the event.

/ / / @ property transformation matrix (strong, nonatomic) GLKEffectPropertyTransform * transform; float aspect = CGRectGetWidth(self.view.bounds) / CGRectGetHeight(self.view.bounds); The self. The manager. The transform. ProjectionMatrix = GLKMatrix4MakePerspective (GLKMathDegreesToRadians (85.0 f), the aspect, 0.1 f, 20.0 f); The self. The manager. The transform. ModelviewMatrix = GLKMatrix4MakeLookAt (0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); /* GLKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, Float upZ is equivalent to void gluLookAt(GLdouble Eyex,GLdouble Eyey,GLdouble Eyez,GLdouble Centerx,GLdouble) in OpenGL centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz); Purpose: Return the world coordinate coordinates of a 4x4 matrix transformation based on your Settings. Parameter 1: the x coordinate of the eye position parameter 2: the y coordinate of the eye position parameter 3: the Z coordinate of the eye position Parameter 1: the position of the head parameter 4: the X coordinate of the point being observed parameter 5: the y coordinate of the point being observed parameter 6: the Z coordinate of the point being observed Parameter 2: the position of the object being observed Parameter 7: x coordinate of the vector on the camera parameter 8: Y coordinate of the vector on the camera parameter 9: Z coordinate of the vector on the cameraCopy the code

There are still some shortcomings in the project:

  1. The adjustment area of adjustable parameters, this time using a single floating point value to control, namely, (-f,+ F) area, if using two values to control will be more flexible, namely, (-A,+b), can use 2-dimensional vector storage
  2. This time, acceleration and so on various regulating parameters configuration is fixed, if can be more free configuration of each particle support effect will be better, or more particles that might need to configure the calculation function to, how to configure the specific function has not thought, but through configuration function to control the particles more reasonable, Perhaps you can add a block or proxy to the Manager to control the implementation of each particle, so that the implementation of the particle can be calculated at the time of use, and the Manager does not need as many configuration parameters. Well, two solutions can be provided, a simple version configured through properties and a custom version based on blocks or proxies.
  3. The current particle position calculation is based on uniform motion, which cannot be supported if variable acceleration motion is to be achieved.
  4. I actually want to implement a fireworks effect, but the current project does not support it, if there is a custom version based on block or proxy, it should be able to implement it.

See Github for a code example