My attempt at writing a Perlin Noise function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Improved Noise - Copyright 2002 Ken Perlin. | |
// Adapted and updated for iOS by Joshua Sullivan, 2016.01.12 | |
// Apply the function 6t^5 - 15t^4 + 10t^3 | |
float fade(float t) | |
{ | |
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); | |
} | |
// I'm keeping this around for reference. | |
float gradOld(float hash, float x, float y, float z) | |
{ | |
float modHash = mod(floor(hash), 16.0); | |
int h = int(modHash); | |
float u = h < 8 ? x : y, | |
v = h < 4 ? y : h == 12 ? x : (h == 14 ? x : z); | |
bool h1 = mod(modHash, 2.0) != 0.0; | |
bool h2 = mod(modHash, 4.0) > 1.0; | |
return (h1 ? -u : u) + (h2 ? -v : v); | |
} | |
// A bunch of articles on the internet claim a simple if or switch based approach to selecting the | |
// gradients can be over 2x as fast as Ken Perlin's original grad() method. | |
float grad(float hash, float x, float y, float z) { | |
int h = int(mod(hash, 16.0)); | |
if (h == 0) { | |
return x + y; | |
} else if (h == 1) { | |
return -x + y; | |
} else if (h == 2) { | |
return x - y; | |
} else if (h == 3) { | |
return -x - y; | |
} else if (h == 4) { | |
return x + z; | |
} else if (h == 5) { | |
return -x + z; | |
} else if (h == 6) { | |
return x - z; | |
} else if (h == 7) { | |
return -x - z; | |
} else if (h == 8) { | |
return y + z; | |
} else if (h == 9) { | |
return -y + z; | |
} else if (h == 10) { | |
return y - z; | |
} else if (h == 11) { | |
return -y - z; | |
} else if (h == 12) { | |
return y + x; | |
} else if (h == 13) { | |
return -y + z; | |
} else if (h == 14) { | |
return y - x; | |
} else if (h == 15) { | |
return -y - z; | |
} | |
return 0.0; | |
} | |
float permute(sampler permutation, float offset) { | |
return sample(permutation, vec2(offset / 512.0, 0.5)).r * 255.0; | |
} | |
kernel vec4 perlin(sampler p, float xScale, float yScale, float time) | |
{ | |
vec2 dc = destCoord(); | |
// Apply the scaling and convert names to x, y, z. | |
float x = dc.x * xScale, | |
y = dc.y * yScale, | |
z = time; | |
// Normalize x, y, z to the 0 - 255 range and trim them to integers. | |
float xf = floor(mod(x, 256.0)), | |
yf = floor(mod(y, 256.0)), | |
zf = floor(mod(x, 256.0)); | |
// Convert the original x, y, z to scalars representing the fractional part unit cube. | |
x -= floor(x); | |
y -= floor(y); | |
z -= floor(z); | |
// Fade the values | |
float u = fade(x), | |
v = fade(y), | |
w = fade(z); | |
// Calculate the initial permutations. | |
float A = permute(p, xf ) + yf, | |
AA = permute(p, A ) + zf, | |
AB = permute(p, A + 1.0 ) + zf, | |
B = permute(p, xf + 1.0) + yf, | |
BA = permute(p, B ) + zf, | |
BB = permute(p, B + 1.0 ) + zf; | |
// Calculate the second permutations. | |
float aa1 = grad(permute(p, AA ), x , y , z ), | |
ba1 = grad(permute(p, BA ), x - 1.0, y , z ), | |
ab1 = grad(permute(p, AB ), x , y - 1.0, z ), | |
bb1 = grad(permute(p, BB ), x - 1.0, y - 1.0, z ), | |
aa2 = grad(permute(p, AA + 1.0), x , y , z - 1.0), | |
ba2 = grad(permute(p, BA + 1.0), x - 1.0, y , z - 1.0), | |
ab2 = grad(permute(p, AB + 1.0), x , y - 1.0, z - 1.0), | |
bb2 = grad(permute(p, BB + 1.0), x - 1.0, y - 1.0, z - 1.0); | |
// Calculate the perlin result by mixing along all 3 axes. | |
float perlin = mix(w, mix(v, mix(u, aa1, ba1), mix(u, ab1, bb1)), mix(v, mix(u, aa2, ba2), mix(u, ab2, bb2))); | |
// Return it as a grayscale color. | |
return vec4(perlin, perlin, perlin, 1.0); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// PerlinNoiseGenerator.swift | |
// CustomCIFilterAttempt | |
// | |
// Created by Joshua Sullivan on 1/16/16. | |
// Copyright © 2016 Joshua Sullivan. All rights reserved. | |
// | |
import UIKit | |
import CoreImage | |
public class PerlinNoiseGenerator: CIFilter { | |
/// Returns a 512x1 image suitable for use as permutation data in a Perlin Noise function. | |
private static let permutationImage: CIImage = { | |
let encodedPermutationString = "iVBORw0KGgoAAAANSUhEUgAAAgAAAAABCAAAAACN0EmqAAABGklEQVQoFWOYvqAzOoq/mfdkfILpoZfsD3tU0uVc+ziSVT+IconvY5vyveKVN4PUUbu4P7dPlypzK1huVIx4O9UifJ1IbcfqFS7rvZa6t3UbSC/znTTveXD+0yqby63P7mTGaJrrfdX4ktZv5ihpv5DxRoDnRZ+W3RcihVaeONLeVLJnftiSlNxja3cxO5jcfPSrppr1lNrksrr/QaFXzp+zfqwvYCW4ba+M1v3tq66W/5jBpDNrsdvdmamzl2uv4WwUU/8rnJST51/44MWmnQUZt74lPvmt9Ongu0sTePZvXvQx0Hji659877MND1yT33o8a+6OkDMbiiuNdOtZpv3renM29l6Rs6yEx+fehsN+Ttdt52wZ6f4HABfw/wFO+rwIAAAAAElFTkSuQmCC" | |
guard let encodedPermutationData = encodedPermutationString.dataUsingEncoding(NSUTF8StringEncoding), | |
permutationData = NSData(base64EncodedData: encodedPermutationData, options: []) else { | |
assertionFailure("Error decoding Base64 image data.") | |
return CIImage() | |
} | |
guard let image = CIImage(data: permutationData) else { | |
assertionFailure("Unable to create CIImage.") | |
return CIImage() | |
} | |
return image | |
}() | |
/// The horizontal scaling factor of the perlin noise. | |
public var scaleX: Float = 1.0 | |
/// The vertical scaling factor of the perlin noise. | |
public var scaleY: Float = 1.0 | |
/// Allows animation/variation of the noise. | |
public var time: Float = 0.0 | |
/// The computation kernel. | |
private let kernel: CIKernel = { | |
// Find the kernel text file. | |
guard let kernelURL = NSBundle.mainBundle().URLForResource("PerlinNoise", withExtension: "cikernel") else { | |
assertionFailure("Couldn't locate kernel source.") | |
return CIKernel() | |
} | |
// Read the kernel file to a string. | |
guard let kernelSource = try? String(contentsOfURL: kernelURL) else { | |
assertionFailure("Couldn't load kernel source!") | |
return CIKernel() | |
} | |
// Compile the kernel. | |
guard let krn = CIKernel(string: kernelSource) else { | |
assertionFailure("Unable to compile kernel.") | |
return CIKernel() | |
} | |
return krn | |
}() | |
override public var outputImage: CIImage { | |
// The Perlin Noise function is unbounded. | |
let extent = CGRectInfinite | |
// Load the permutation data image. | |
let image = PerlinNoiseGenerator.permutationImage | |
// Invoke the kernel. | |
guard let img = kernel.applyWithExtent(extent, roiCallback:self.roiCallback, arguments: [image, scaleX, scaleY, time]) else { | |
assertionFailure("Failed to invoke kernel.applyWithExtent()") | |
return CIImage() | |
} | |
// Return the generated image. | |
return img | |
} | |
/// The Perlin Noise method is homogenous across the entire xy plane, so it affects the entire target rect. | |
private func roiCallback(imageIndex: Int32, targetRect: CGRect) -> CGRect { | |
return targetRect | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment