Skip to content

Instantly share code, notes, and snippets.

@JoshuaSullivan
Last active July 10, 2023 22:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshuaSullivan/c9a37aa4d6b2767e79d4 to your computer and use it in GitHub Desktop.
Save JoshuaSullivan/c9a37aa4d6b2767e79d4 to your computer and use it in GitHub Desktop.
My attempt at writing a Perlin Noise function.
// 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);
}
//
// 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