Mobile Development 9 min read

Implementing a Generalized Split‑Screen Filter with GLSL Fragment Shaders

This article demonstrates how to create a universal GLSL fragment shader that produces static and delayed dynamic split‑screen effects—similar to TikTok filters—by manipulating texture coordinates, handling arbitrary screen counts, and integrating multiple textures within the iOS GPUImage framework.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Implementing a Generalized Split‑Screen Filter with GLSL Fragment Shaders

The article explains how to write a single, reusable fragment shader that can generate a variety of split‑screen filters (static and delayed dynamic) commonly seen in short‑video apps, and shows how to integrate it into an iOS project using the GPUImage framework.

Static split‑screen means each screen shows the same image; the author argues for a generic shader controlled by two uniform parameters (horizontal and vertical screen counts) instead of separate shaders for each layout. Basic GLSL vector operations (addition, multiplication, division, and mod ) are introduced with concise examples.

Core static shader implementation: precision highp float; uniform sampler2D inputImageTexture; varying vec2 textureCoordinate; uniform float horizontal; // (1) uniform float vertical; void main(void) { float horizontalCount = max(horizontal, 1.0); // (2) float verticalCount = max(vertical, 1.0); float ratio = verticalCount / horizontalCount; // (3) vec2 originSize = vec2(1.0, 1.0); vec2 newSize = originSize; if (ratio > 1.0) { newSize.y = 1.0 / ratio; } else { newSize.x = ratio; } vec2 offset = (originSize - newSize) / 2.0; // (4) vec2 position = offset + mod(textureCoordinate * min(horizontalCount, verticalCount), newSize); // (5) gl_FragColor = texture2D(inputImageTexture, position); // (6) }

Lines (1)–(6) are explained: (1) exposes the number of horizontal and vertical splits as uniforms; (2) clamps the values to at least 1; (3) computes the aspect ratio of a single cell; (4) centers the cropped region; (5) maps the original texture coordinate into the repeated cell using mod ; (6) samples the texture at the computed coordinate.

Dynamic split‑screen requires capturing multiple textures because each cell displays a different image. GPUImage uses GPUImageFramebuffer objects that must be locked/unlocked to prevent automatic caching. Example of capturing and releasing a framebuffer: [firstInputFramebuffer lock]; self.firstFramebuffer = firstInputFramebuffer; [self.firstFramebuffer unlock]; self.firstFramebuffer = nil;

Rendering multiple textures involves overriding -renderToTextureWithVertices:textureCoordinates: and binding each captured texture to a distinct texture unit: // First texture if (self.firstFramebuffer) { glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, [self.firstFramebuffer texture]); glUniform1i(firstTextureUniform, 3); } // Second texture if (self.secondFramebuffer) { glActiveTexture(GL_TEXTURE4); glBindTexture(GL_TEXTURE_2D, [self.secondFramebuffer texture]); glUniform1i(secondTextureUniform, 4); } // ... up to fourth texture glUniform1i(textureCountUniform, (int)self.capturedCount);

The corresponding fragment shader receives up to four texture samplers and selects the appropriate one based on the current texture count and the fragment’s coordinates: precision highp float; uniform sampler2D inputImageTexture; uniform sampler2D inputImageTexture1; uniform sampler2D inputImageTexture2; uniform sampler2D inputImageTexture3; uniform sampler2D inputImageTexture4; uniform int textureCount; varying vec2 textureCoordinate; void main(void) { vec2 position = mod(textureCoordinate * 2.0, 1.0); if (textureCoordinate.x <= 0.5 && textureCoordinate.y <= 0.5) { gl_FragColor = texture2D(textureCount >= 1 ? inputImageTexture1 : inputImageTexture, position); } else if (textureCoordinate.x > 0.5 && textureCoordinate.y <= 0.5) { gl_FragColor = texture2D(textureCount >= 2 ? inputImageTexture2 : inputImageTexture, position); } else if (textureCoordinate.x <= 0.5 && textureCoordinate.y > 0.5) { gl_FragColor = texture2D(textureCount >= 3 ? inputImageTexture3 : inputImageTexture, position); } else { gl_FragColor = texture2D(textureCount >= 4 ? inputImageTexture4 : inputImageTexture, position); } }

With this approach, the same shader can render any integer or non‑integer split configuration (e.g., 1.5 : 2.5) and can be extended to delayed dynamic effects, providing a flexible solution for iOS developers working with GPUImage and GLSL.

mobile developmentiOSGLSLSplit ScreenGPUImageFragment Shader
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.