IOS wave effects -OpenGL implementation chapter

The code used in this article is in

Work needs to be done recently to achieve a wave effect. The general practice is to generate the sin curve using UIBezierPath, and to achieve the wave effect by refreshing the phase or amplitude of the CADisplayLink curve. This article introduces another way of using OpenGL to achieve wave effects. Here is the effect diagram, which uses GLKView, and here is CAShapeLayer. I’ve done simple masking in these two ways.

IOS wave effects -OpenGL implementation chapter

Next, I’ll focus on how to implement this effect using OpenGL. GLWaveView and GLContext contain all of the implementation code. The two shaders, fragment.glsl and vertex.glsl, are the core of the whole effect. Let’s take a look at the code for GLWaveView.


GLWaveView inherits from GLKView, so you can easily initialize OpenGL related environments.

Static EAGLContext *eaglContext; if (eaglContext = = Nil) {eaglContext [[EAGLContext = alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2]; [EAGLContext setCurrentContext:eaglContext];} self.context = eaglContext; self.drawableMultisample = GLKViewDrawableMultisample4X;

Because the EAGLContext of the current thread only one set, so the use of static variables, then setup for GLWaveView EAGLContext and multiple sampling rate GLKViewDrawableMultisample4X, multiple sampling can make more smooth tooth. In order to implement animation, you need a loop to run the rendering code, using CADisplayLink.

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector (ticked)]; displayLink.preferredFramesPerSecond = 60; [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; lastTime = [[NSDate date] timeIntervalSince1970];
- (void) ticked now date] timeIntervalSince1970] {NSTimeInterval = [[NSDate; currentTime = now - lastTime; lastTime = now; [self display];}

You can specify the required frame rate preferredFramesPerSecond for CADisplayLink, where I set the 60fps. LastTime is used to hold the timestamp of the last update, and the ticked is called by loop. Each call is calculated by the current total duration currentTime. [self display]; triggered after call. Execute the following code. The following code is primarily to draw a quad that is full of the current View and binds a picture to diffuseMap. This will be used as a mask map in Shader.

- (void) glkView: (GLKView *) view drawInRect: (CGRect) rect {[self.glContext active]; glClearColor (0, 0, 0, 0); glClear (GL_COLOR_BUFFER_BIT); static GLfloat triangleData[] = {-1, 1, 0.5, 0, 0, 1, 0, 0, -1, -1, 0.5, 0. 0, 1, 0, 1, 1, -1, 0.5, 0, 0, 1, 1, 1, 1, -1, 0.5, 0, 0, 1, 1, 1, 1, 1, 0.5, 0, 0, 1, 1, 0, -1, 1, 0.5, 0. 0, 1, 0, 0}; [self.glContext setUniform1f:@ value:currentTime]; [self.glContext "time" bindTexture:self.diffuseMap to:GL_TEXTURE0 uniformName:@ "diffuseMap"]; [self.glContext drawTriangles:triangleData vertexCount:6];}
IOS wave effects -OpenGL implementation chapter
mask map

before then, you also need to set self.delegate = self; – – (void) glkView: (GLKView *) view drawInRect: (CGRect) rect is one of the methods in GLKView’s delegate.
is also necessary to initialize the tool class OpenGL of GLContext at the same time. Together, the initialization code is as follows.

- (instancetype) initWithFrame: (CGRect frame) {self = [super initWithFrame:frame]; if (self) {static EAGLContext *eaglContext; if (eaglContext = = Nil) {eaglContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2] [EAGLContext setCurrentContext:eaglContext]; self.context;} = eaglContext; self.drawableMultisample = GLKViewDrawableMultisample4X; self.delegate = self; self.layer.backgroundColor = [UIColor clearColor].CGColor; self.layer.opaque = NO; NSString *vertexShader = [[NSBundle mainBundle] pathForResource:@ "vertex" ofType:@ "glsl"]; NSString *fragmentShader = [[NSBundle mainBundle] pathForResource:@ fragment "" OfType:@ "glsl"]; NSString *vertexShaderContent = [NSString stringWithContentsOfFile:vertexShader encoding:NSUTF8StringEncoding error:nil]; NSString *fragmentShaderContent = [NSString stringWithContentsOfFile:fragmentShader encoding: NSUTF8StringEncoding error:nil] [[GLContext alloc] initWithVertexShader:vertexShaderContent; self.glContext = fragmentShader:fragmentShaderContent]; self.diffuseMap = [GLKTextureLoader textureWithCGImage:[UIImage imageNamed:@ "mask.png"].CGImage options:nil error:nil] CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self; selector:@selector (ticked)]; displayLink.preferredFramesPerSecond = 60; [displayLink addToRunLoop:[NSRunLoop mainRunLoo P] forMode:NSDefaultRunLoopMode]; lastTime = [[NSDate; date] timeIntervalSince1970];} return, self;}

If you’re not familiar with OpenGL, you can read the OpenGL ES series, and the GLContext class is taken directly from there. The main package of some of the basic operation of OpenGL.

With these, you can draw a quad with OpenGL. Next, Fragment Shader is on the stage.

Fragment Shader

Let’s start with the idea of using UV as the coordinate value to calculate the current uv.x corresponding to the sine value sin (uv.x), and if uv.y is under sin (uv.x), gl_FragColor is colored. This gives you a basic wave. The code that determines whether the current point is colored is as follows.

Bool shouldBeColored (float waveAmplitude, float waveHeight, float phase, float period) {float x = fragUV.x * period; / / X axis range of 0 ~ period - fragUV.y float y = 1; float = topY (sin (x + phase) + 1) / 2 * waveAmplitude - waveAmplitude / 2 + waveHeight; return y < = topY;}

WaveAmplitude is the distance between the crest and trough, waveHeight is the position of the crest, phase is the initial phase for calculating the sine, and period is the visible range of the cycle. Here is the sketch map. Since the UV of Y is from 0 to 1, the float y = 1 – fragUV.y is flipped first, and finally, if the flip y is below topY, it can be colored.

IOS wave effects -OpenGL implementation chapter

Here is the complete code.

Precision highp float; varying vec3 fragNormal; varying vec2 fragUV; uniform sampler2D diffuseMap; uniform float time; bool shouldBeColored (float waveAmplitude, float waveHeight, float phase, float period) {float x = fragUV.x * period; / / X axis range of 0 ~ period - fragUV.y float y = 1; float = topY (sin (x + phase) + 1) / 2 * waveAmplitude - waveAmplitude / 2 + waveHeight; return y < void = topY;} main (void) {vec4 color = texture2D (diffuseMap, fragUV); float (baseFactor = sin (time / 4.5) + 1) / 2 float; heightFactor = baseFactor; float phaseFactor time * float = 3.14; period = 3.14 * 1.4 = vec4; / / vec4 finalColor cycle (0.2, 0.2, 0.2, 1); if (shouldBeColored (0.07, heightFac Tor, phaseFactor, period)) {finalColor = finalColor * 0 + vec4 (1, 0.4, 0.4, 1);} if (shouldBeColored (0.05, heightFactor - 0.02, phaseFactor - 0.25, period)) {finalColor = finalColor * 0.4 + vec4 (1, 0.1, 0.1, 1) * 0.6}; gl_FragColor = finalColor * color.a;}

A total of two waves were plotted, and the second waves gave 0.6 of the Alpha values, using the SrcColor SrcAlpha + DstColor (1 – SrcAlpha) mixing algorithm. Two waves have a 0.02 amplitude difference and a 0.02 height difference, with a phase difference of 0.25. Height and phase change with time. The cycle is 1.4Pi.
the last finalColor * color.a; will make the part of the alpha on the mask 0 invisible, so as to achieve the mask effect.

The code on GitHub also includes the version UIBezierPath implemented by CAWaveView, which is interesting for you to view by yourself clone.