Perlin Noise on GPU in GPUImage 1

Perlin noise has all kinds of applications, from realistic looking texutres to fire effects to flow fields. It’s a useful tool to have in the belt.

If you’ve never heard of perlin noise here’s the wikipedia article and here’s a great explanation of how it’s calculated. Better than that, here are some examples of its use (images are links to the original posts they are sourced from):

Natural Textures

Procedural Terrain Generation

Flow Fields

I wanted to have some perlin noise I could use to apply different effects to GPUImage filters. I found a reference to a GPU implementation on stackoverflow.com. The implementation on github here is ready to go. I used the classic 2D implementation provided there. There are also 3D, 4D as well as simplex versions (the simplex noise performs better at higher dimensions).

Here’s that code used to create a filter that generates a texture using perlin noise. The code is just copied from the github implementation:

Header:

#import "GPUImageFilter.h"

@interface GPUImagePerlinNoiseFilter : GPUImageFilter {
    GLint scaleUniform, colorStartUniform, colorFinishUniform;
}

@property (readwrite, nonatomic) GPUVector4 colorStart;
@property (readwrite, nonatomic) GPUVector4 colorFinish;
@property (readwrite, nonatomic) float scale;

@end

And implementation:

#import "GPUImagePerlinNoiseFilter.h"

NSString *const kGPUImagePerlinNoiseFragmentShaderString = SHADER_STRING
(
 precision highp float;
 varying highp vec2 textureCoordinate;
 uniform float scale;
 
 uniform vec4 colorStart;
 uniform vec4 colorFinish;
 
 //
 // Description : Array and textureless GLSL 2D/3D/4D simplex
 // noise functions.
 // Author : Ian McEwan, Ashima Arts.
 // Maintainer : ijm
 // Lastmod : 20110822 (ijm)
 // License : Copyright (C) 2011 Ashima Arts. All rights reserved.
 // Distributed under the MIT License. See LICENSE file.
 // https://github.com/ashima/webgl-noise
 //
 
 vec4 mod289(vec4 x)
{
    return x - floor(x * (1.0 / 289.0)) * 289.0;
}
 
 vec4 permute(vec4 x)
{
    return mod289(((x*34.0)+1.0)*x);
}
 
 vec4 taylorInvSqrt(vec4 r)
{
    return 1.79284291400159 - 0.85373472095314 * r;
}
 
 vec2 fade(vec2 t) {
     return t*t*t*(t*(t*6.0-15.0)+10.0);
 }
 
 // Classic Perlin noise
 float cnoise(vec2 P)
{
    vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
    vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
    Pi = mod289(Pi); // To avoid truncation effects in permutation
    vec4 ix = Pi.xzxz;
    vec4 iy = Pi.yyww;
    vec4 fx = Pf.xzxz;
    vec4 fy = Pf.yyww;
    
    vec4 i = permute(permute(ix) + iy);
    
    vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;
    vec4 gy = abs(gx) - 0.5 ;
    vec4 tx = floor(gx + 0.5);
    gx = gx - tx;
    
    vec2 g00 = vec2(gx.x,gy.x);
    vec2 g10 = vec2(gx.y,gy.y);
    vec2 g01 = vec2(gx.z,gy.z);
    vec2 g11 = vec2(gx.w,gy.w);
    
    vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));
    g00 *= norm.x;  
    g01 *= norm.y;  
    g10 *= norm.z;  
    g11 *= norm.w;  
    
    float n00 = dot(g00, vec2(fx.x, fy.x));
    float n10 = dot(g10, vec2(fx.y, fy.y));
    float n01 = dot(g01, vec2(fx.z, fy.z));
    float n11 = dot(g11, vec2(fx.w, fy.w));
    
    vec2 fade_xy = fade(Pf.xy);
    vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
    float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
    return 2.3 * n_xy;
}
 
 
 void main()
 {
     
     float n1 = (cnoise(textureCoordinate * scale) + 1.0) / 2.0;
     
     vec4 colorDiff = colorFinish - colorStart;
     vec4 color = colorStart + colorDiff * n1;
     
     gl_FragColor = color;
 }
 );


@implementation GPUImagePerlinNoiseFilter

@synthesize scale = _scale, colorStart = _colorStart, colorFinish = _colorFinish;

- (id)init;
{
    if (!(self = [super initWithFragmentShaderFromString:kGPUImagePerlinNoiseFragmentShaderString]))
    {
		return nil;
    }
    
    scaleUniform = [filterProgram uniformIndex:@"scale"];
    
    colorStartUniform = [filterProgram uniformIndex:@"colorStart"];
    colorFinishUniform = [filterProgram uniformIndex:@"colorFinish"];
    
    [self setScale:8.0];
    
    [self setColorStart:(GPUVector4){0.0, 0.0, 0.0, 1.0}];
    [self setColorFinish:(GPUVector4){1.0, 1.0, 1.0, 1.0}];    
    
    return self;
}

-(void)setScale:(float)scale {
    _scale = scale;
    
    [GPUImageOpenGLESContext useImageProcessingContext];
    [filterProgram use];
    glUniform1f(scaleUniform, _scale);
}

-(void)setColorStart:(GPUVector4)colorStart {
    _colorStart = colorStart;
    
    [GPUImageOpenGLESContext useImageProcessingContext];
    [filterProgram use];
    glUniform4f(colorStartUniform, _colorStart.one, _colorStart.two, _colorStart.three, _colorStart.four);
}

-(void)setColorFinish:(GPUVector4)colorFinish {
    _colorFinish = colorFinish;
    
    [GPUImageOpenGLESContext useImageProcessingContext];
    [filterProgram use];
    glUniform4f(colorFinishUniform, _colorFinish.one, _colorFinish.two, _colorFinish.three, _colorFinish.four);
}

@end

If you read my previous tutorial on how to create a custom GPUImage filter there shouldn’t be any surprises here. I’m not going to go over how the code creates the noise, because I haven’t fully investigated how it works myself. I don’t really care how it works, I’m only really interested in how to use it.

The usage lies in the cnoise(vec2) function. You give it an x and y coordinate, and it returns a number between -1 and 1. In the filter, I’m scaling up the input coordinate (because textureCoordinate is between 0.0 and 1.0 so that won’t give us very interesting noise). Then I’m adding 1 and dividing by 2 to convert from a -1 to 1 range into a 0 to 1 range (which is the range of our color values).

Finally, I use the start and finish color values supplied to the uniforms to create a noise texture that ranges between those two values. Here’s what the result looks like (I’m using white and black as start and finish color values, but you can set it to any two colors):

You can use this filter with a blend filter to create interesting texture. You could, for example, combine it with a brightness filter, and then put a sepia filter on it. This would create a natural faded look, something like this:

However, for me, the more interesting usage of this perlin noise function is to combine it with other functions. You can use this function in any of your shader code to create interesting, natural randomness. A modified Pixellate filter that doesn’t create square pixels, but uses perlin noise to determine the size of the pixel cutoff. Here’s a video of the result:

The code is mostly the pixellate filter. I’ve added a scale uniform and of course the perlin noise function to the shader code:

#import "GPUImageFilter.h"

@interface GPUImagePixellateCrazyFilter : GPUImageFilter

{
    GLint fractionalWidthOfAPixelUniform, scaleUniform;
}

// The fractional width of the image to use as a size for the pixels in the resulting image. Values below one pixel width in the source image are ignored.
@property(readwrite, nonatomic) CGFloat fractionalWidthOfAPixel;
@property(readwrite, nonatomic) CGFloat scale;

@end

Implementation:

#import "GPUImagePixellateCrazyFilter.h"

NSString *const kGPUImagePixellateCrazyFragmentShaderString = SHADER_STRING
(
 precision highp float;
 varying vec2 textureCoordinate;
 
 uniform sampler2D inputImageTexture;
 
 uniform float fractionalWidthOfPixel;
 uniform float scale;
 //
 // Description : Array and textureless GLSL 2D/3D/4D simplex
 // noise functions.
 // Author : Ian McEwan, Ashima Arts.
 // Maintainer : ijm
 // Lastmod : 20110822 (ijm)
 // License : Copyright (C) 2011 Ashima Arts. All rights reserved.
 // Distributed under the MIT License. See LICENSE file.
 // https://github.com/ashima/webgl-noise
 //
 
 vec4 mod289(vec4 x)
{
    return x - floor(x * (1.0 / 289.0)) * 289.0;
}
 
 vec4 permute(vec4 x)
{
    return mod289(((x*34.0)+1.0)*x);
}
 
 vec4 taylorInvSqrt(vec4 r)
{
    return 1.79284291400159 - 0.85373472095314 * r;
}
 
 vec2 fade(vec2 t) {
     return t*t*t*(t*(t*6.0-15.0)+10.0);
 }
 
 // Classic Perlin noise
 float cnoise(vec2 P)
{
    vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
    vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
    Pi = mod289(Pi); // To avoid truncation effects in permutation
    vec4 ix = Pi.xzxz;
    vec4 iy = Pi.yyww;
    vec4 fx = Pf.xzxz;
    vec4 fy = Pf.yyww;
    
    vec4 i = permute(permute(ix) + iy);
    
    vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;
    vec4 gy = abs(gx) - 0.5 ;
    vec4 tx = floor(gx + 0.5);
    gx = gx - tx;
    
    vec2 g00 = vec2(gx.x,gy.x);
    vec2 g10 = vec2(gx.y,gy.y);
    vec2 g01 = vec2(gx.z,gy.z);
    vec2 g11 = vec2(gx.w,gy.w);
    
    vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));
    g00 *= norm.x;  
    g01 *= norm.y;  
    g10 *= norm.z;  
    g11 *= norm.w;  
    
    float n00 = dot(g00, vec2(fx.x, fy.x));
    float n10 = dot(g10, vec2(fx.y, fy.y));
    float n01 = dot(g01, vec2(fx.z, fy.z));
    float n11 = dot(g11, vec2(fx.w, fy.w));
    
    vec2 fade_xy = fade(Pf.xy);
    vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
    float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
    return 2.3 * n_xy;
}

 
 void main()
 {
     float n = cnoise(textureCoordinate * scale);
     vec2 sampleDivisor = vec2((((n + 1.0) / 2.0) + 0.5) * fractionalWidthOfPixel);
     
     vec2 samplePos = textureCoordinate - mod(textureCoordinate, sampleDivisor);
     gl_FragColor = texture2D(inputImageTexture, samplePos );
 }
 );


@implementation GPUImagePixellateCrazyFilter

@synthesize fractionalWidthOfAPixel = _fractionalWidthOfAPixel, scale = _scale;

#pragma mark -
#pragma mark Initialization and teardown

- (id)init;
{
    if (!(self = [super initWithFragmentShaderFromString:kGPUImagePixellateCrazyFragmentShaderString]))
    {
		return nil;
    }
    
    fractionalWidthOfAPixelUniform = [filterProgram uniformIndex:@"fractionalWidthOfPixel"];
    scaleUniform = [filterProgram uniformIndex:@"scale"];
    
    self.fractionalWidthOfAPixel = 0.05;
    self.scale = 4.0;
    
    return self;
}

#pragma mark -
#pragma mark Accessors

- (void)setFractionalWidthOfAPixel:(CGFloat)newValue;
{

    _fractionalWidthOfAPixel = newValue;
    
    [GPUImageOpenGLESContext useImageProcessingContext];
    [filterProgram use];
    glUniform1f(fractionalWidthOfAPixelUniform, _fractionalWidthOfAPixel);
}

-(void)setScale:(CGFloat)scale {
    _scale = scale;
    [GPUImageOpenGLESContext useImageProcessingContext];
    [filterProgram use];
    glUniform1f(scaleUniform, _scale);
}

@end

The applications of perlin noise are endless. I just wanted to throw a couple things out there to spark some imagination. Here is another great creative coding applications (by @inspirit) that uses perlin noise to guide a particle system to paint an image with ‘strings’, this stuff is awesome:

Click on the above to go to the site and play with the demo.

One comment on “Perlin Noise on GPU in GPUImage

  1. Pingback: Pic-stagram | Make Things Talk

Leave a Reply