Updating API and documentation.
This commit is contained in:
parent
71edce1b4f
commit
6fe4a2b851
25
main.lua
25
main.lua
@ -1,26 +1,21 @@
|
|||||||
lg = love.graphics
|
local Histogram = require 'histogram'
|
||||||
W,H = lg.getDimensions()
|
|
||||||
Histogram = require 'histogram'
|
|
||||||
local Noise = require "noise"
|
local Noise = require "noise"
|
||||||
local DRAW_RES = love.window.toPixels(4)
|
local DRAW_RES = love.window.toPixels(4)
|
||||||
local SCALE = 100
|
local SCALE = 100
|
||||||
local font = lg.newFont(love.window.toPixels(24))
|
|
||||||
|
|
||||||
local decay = function(x) return .15^x end
|
local decay = function(x) return .15^x end
|
||||||
local function rng()
|
local n1,g1 = Noise.make1d{resolution=5, distribution=decay, seed=1}
|
||||||
local r = love.math.newRandomGenerator(1)
|
local n2,g2 = Noise.make2d{resolution=7, distribution=decay, seed=1}
|
||||||
return function() return r:random() end
|
local n3,g3 = Noise.make3d{resolution=9, distribution=decay, seed=1}
|
||||||
end
|
local s1,s1g = Noise.make1dShader{resolution=10, distribution=decay, seed=1}
|
||||||
local makeAmps = Noise.makeAmplitudes
|
local s2,s2g = Noise.make2dShader{resolution=15, distribution=decay, seed=1}
|
||||||
local n1,g1 = Noise.make1d(makeAmps(8, decay), rng())
|
local s3,s3g = Noise.make3dShader{resolution=20, distribution=decay, seed=1}
|
||||||
local n2,g2 = Noise.make2d(makeAmps(9, decay), rng())
|
|
||||||
local n3,g3 = Noise.make3d(makeAmps(11, decay), rng())
|
|
||||||
local s1,s1g = Noise.make1dShader(makeAmps(9, decay), rng())
|
|
||||||
local s2,s2g = Noise.make2dShader(makeAmps(15, decay), rng())
|
|
||||||
local s3,s3g = Noise.make3dShader(makeAmps(19, decay), rng())
|
|
||||||
|
|
||||||
|
lg = love.graphics
|
||||||
|
W,H = lg.getDimensions()
|
||||||
local cw,ch = W/2,H/3
|
local cw,ch = W/2,H/3
|
||||||
local canv = lg.newCanvas(cw,ch)
|
local canv = lg.newCanvas(cw,ch)
|
||||||
|
local font = lg.newFont(love.window.toPixels(24))
|
||||||
|
|
||||||
function love.load()
|
function love.load()
|
||||||
xOffset = 0
|
xOffset = 0
|
||||||
|
569
noise.lua
569
noise.lua
@ -1,3 +1,64 @@
|
|||||||
|
-- Hill Noise library
|
||||||
|
-- Copyright 2017 Bruce Hill
|
||||||
|
--
|
||||||
|
-- This library provides functions for continuous pseudorandom 1D, 2D, and 3D noise functions
|
||||||
|
-- with approximately uniform distribution on the interval (0,1) and customizable resolution,
|
||||||
|
-- levels of detail, and shape characteristics.
|
||||||
|
--
|
||||||
|
-- Usage:
|
||||||
|
-- local Noise = require 'hill_noise'
|
||||||
|
-- local noise,noise_gradient = Noise.make3d{resolution=9}
|
||||||
|
-- local x,y,z = 1,2,3
|
||||||
|
-- local n = noise(x,y,z)
|
||||||
|
-- local dndx,dndy,dndz = noise_gradient(x,y,z)
|
||||||
|
--
|
||||||
|
-- The noise construction functions all take the following options:
|
||||||
|
-- * random: a random number function (default: math.random)
|
||||||
|
-- * seed: if "random" is not provided, and the love.math module is loaded, a new
|
||||||
|
-- pseudorandom number generator will be used with the specified seed
|
||||||
|
-- * amplitudes: a list of sine wave amplitudes to use
|
||||||
|
-- * resolution: if "amplitudes" is not provided, the number of sine waves to use
|
||||||
|
-- (default: 5,7,9 for 1d,2d,3d functions, and 10,15,20 for 1d,2d,3d shaders)
|
||||||
|
-- * distribution: if "amplitudes" is not provided, a function to evenly sample
|
||||||
|
-- amplitudes from on the interval (0,1) (default: 0.1^x)
|
||||||
|
--
|
||||||
|
-- The noise and noise gradient functions all run in O(resolution), and more resolution
|
||||||
|
-- may be needed for higher dimensional noise and additional detail.
|
||||||
|
--
|
||||||
|
-- For the LOVE game engine, there are versions that produce 1D, 2D, and 3D noise shaders
|
||||||
|
-- as well, which are much faster and run on the GPU.
|
||||||
|
-- local noise_shader, noise_gradient_shader = Noise.make3dShader{resolution=11}
|
||||||
|
-- local canvas = love.graphics.newCanvas()
|
||||||
|
-- -- At each location on the texture, the noise function is sampled at a linear
|
||||||
|
-- -- interpolation between range_min and range_max, using the texture coordinates.
|
||||||
|
-- -- For 3D noise, the "z" value is passed as a parameter.
|
||||||
|
-- noise_shader:send("range_min", {0,20})
|
||||||
|
-- noise_shader:send("range_max", {0,20})
|
||||||
|
-- noise_shader:send('z', love.timer.getTime())
|
||||||
|
-- love.graphics.setShader(noise_shader)
|
||||||
|
-- love.graphics.draw(canvas)
|
||||||
|
-- love.graphics.setShader()
|
||||||
|
-- The noise gradient shaders populate the RGB channels with dn/dx, dn/dy, dn/dz partial
|
||||||
|
-- derivatives, where [-1,0,1] is remapped to [0,0.5,1].
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
-- this software and associated documentation files (the "Software"), to deal in
|
||||||
|
-- the Software without restriction, including without limitation the rights to
|
||||||
|
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
-- so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in all
|
||||||
|
-- copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
-- SOFTWARE.
|
||||||
|
|
||||||
local noise = {}
|
local noise = {}
|
||||||
local exp,sin,cos,floor,log,acos,sqrt,abs = math.exp,math.sin,math.cos,math.floor,math.log,math.acos,math.sqrt,math.abs
|
local exp,sin,cos,floor,log,acos,sqrt,abs = math.exp,math.sin,math.cos,math.floor,math.log,math.acos,math.sqrt,math.abs
|
||||||
local GR, PI, TAU, SQRT5, LOG_GR = (sqrt(5)+1)/2, math.pi, 2*math.pi, sqrt(5), log((sqrt(5)+1)/2)
|
local GR, PI, TAU, SQRT5, LOG_GR = (sqrt(5)+1)/2, math.pi, 2*math.pi, sqrt(5), log((sqrt(5)+1)/2)
|
||||||
@ -10,12 +71,28 @@ local function cdf_prime(x, dx)
|
|||||||
return (0.31831 * exp(-2/PI * x*x) * abs(x)*dx)/sqrt(1-exp(-2/PI*x*x))
|
return (0.31831 * exp(-2/PI * x*x) * abs(x)*dx)/sqrt(1-exp(-2/PI*x*x))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _defaultArgs(amplitudes,random)
|
local default_resolutions = {["1d"]=5, ["2d"]=7, ["3d"]=9, ["1dShader"]=10, ["2dShader"]=15, ["3dShader"]=20}
|
||||||
if amplitudes == nil then amplitudes = 5 end
|
local function _defaultArgs(opts, kind)
|
||||||
if type(amplitudes) == 'number' then
|
opts = opts or {}
|
||||||
amplitudes = noise.makeAmplitudes(amplitudes, function(x) return .1^x end)
|
local amplitudes, random = opts.amplitudes, opts.random
|
||||||
|
if random then
|
||||||
|
assert(type(random) == 'function', "Random number function must be a function.")
|
||||||
|
elseif not random then
|
||||||
|
if opts.seed and love and love.math then
|
||||||
|
local rng = love.math.newRandomGenerator(opts.seed)
|
||||||
|
random = function(...) return rng:random(...) end
|
||||||
|
else
|
||||||
|
random = math.random
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not amplitudes then
|
||||||
|
local resolution = opts.resolution or default_resolutions[kind]
|
||||||
|
local distribution = opts.distribution or (function(x) return .1^x end)
|
||||||
|
amplitudes = {}
|
||||||
|
for i=1,resolution do
|
||||||
|
amplitudes[i] = distribution((i-.5)/resolution)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if not random then random = math.random end
|
|
||||||
return amplitudes, random
|
return amplitudes, random
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -35,17 +112,8 @@ local function makeOffsets(count, random)
|
|||||||
return offsets
|
return offsets
|
||||||
end
|
end
|
||||||
|
|
||||||
noise.makeAmplitudes = function(count, fn)
|
noise.make1d = function(opts)
|
||||||
local amplitudes = {}
|
local amplitudes, random = _defaultArgs(opts, "1d")
|
||||||
for i=1,count do
|
|
||||||
amplitudes[i] = fn((i-.5)/count)
|
|
||||||
end
|
|
||||||
return amplitudes
|
|
||||||
end
|
|
||||||
|
|
||||||
noise.make1d = function(amplitudes,random)
|
|
||||||
amplitudes, random = _defaultArgs(amplitudes, random)
|
|
||||||
local resolution = #amplitudes
|
|
||||||
local sigma = calculateSigma(amplitudes)
|
local sigma = calculateSigma(amplitudes)
|
||||||
local offsets = makeOffsets(#amplitudes, random)
|
local offsets = makeOffsets(#amplitudes, random)
|
||||||
local function noise(x)
|
local function noise(x)
|
||||||
@ -70,9 +138,8 @@ noise.make1d = function(amplitudes,random)
|
|||||||
return noise, gradient
|
return noise, gradient
|
||||||
end
|
end
|
||||||
|
|
||||||
noise.make2d = function(amplitudes, random)
|
noise.make2d = function(opts)
|
||||||
amplitudes, random = _defaultArgs(amplitudes, random)
|
local amplitudes, random = _defaultArgs(opts, "2d")
|
||||||
local resolution = #amplitudes
|
|
||||||
local sigma = calculateSigma(amplitudes)
|
local sigma = calculateSigma(amplitudes)
|
||||||
local offsets = makeOffsets(2*#amplitudes, random)
|
local offsets = makeOffsets(2*#amplitudes, random)
|
||||||
sigma = sigma/sqrt(2)
|
sigma = sigma/sqrt(2)
|
||||||
@ -109,8 +176,8 @@ noise.make2d = function(amplitudes, random)
|
|||||||
return noise, gradient
|
return noise, gradient
|
||||||
end
|
end
|
||||||
|
|
||||||
noise.make3d = function(amplitudes, random)
|
noise.make3d = function(opts)
|
||||||
amplitudes, random = _defaultArgs(amplitudes, random)
|
local amplitudes, random = _defaultArgs(opts, "3d")
|
||||||
local resolution = #amplitudes
|
local resolution = #amplitudes
|
||||||
local sigma = calculateSigma(amplitudes)
|
local sigma = calculateSigma(amplitudes)
|
||||||
local offsets = makeOffsets(3*#amplitudes, random)
|
local offsets = makeOffsets(3*#amplitudes, random)
|
||||||
@ -203,242 +270,244 @@ noise.make3d = function(amplitudes, random)
|
|||||||
return noise, gradient
|
return noise, gradient
|
||||||
end
|
end
|
||||||
|
|
||||||
local shader1d = [[
|
if love and love.graphics then
|
||||||
#define TAU 6.283185307179586476925286766559005768394338798750211641949
|
local shader1d = [[
|
||||||
#define MAX_RESOLUTION 64
|
#define TAU 6.283185307179586476925286766559005768394338798750211641949
|
||||||
extern int resolution;
|
#define MAX_RESOLUTION 64
|
||||||
extern float sigma, range_min, range_max;
|
extern int resolution;
|
||||||
extern float amplitudes[MAX_RESOLUTION];
|
extern float sigma, range_min, range_max;
|
||||||
extern float offsets[MAX_RESOLUTION];
|
extern float amplitudes[MAX_RESOLUTION];
|
||||||
|
extern float offsets[MAX_RESOLUTION];
|
||||||
|
|
||||||
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords)
|
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords)
|
||||||
{
|
{
|
||||||
float x = mix(range_min,range_max,texture_coords.x);
|
float x = mix(range_min,range_max,texture_coords.x);
|
||||||
float noise = 0.;
|
float noise = 0.;
|
||||||
#ifdef GRADIENT
|
#ifdef GRADIENT
|
||||||
float dndx = 0.;
|
float dndx = 0.;
|
||||||
#endif
|
#endif
|
||||||
for (int i=0; i < resolution; i++) {
|
for (int i=0; i < resolution; i++) {
|
||||||
float a = amplitudes[i];
|
float a = amplitudes[i];
|
||||||
noise += a*cos(x/a + offsets[i]);
|
noise += a*cos(x/a + offsets[i]);
|
||||||
#ifdef GRADIENT
|
#ifdef GRADIENT
|
||||||
dndx -= sin(x/a + offsets[i]);
|
dndx -= sin(x/a + offsets[i]);
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
noise /= sigma;
|
|
||||||
#ifdef GRADIENT
|
|
||||||
dndx /= sigma;
|
|
||||||
dndx = (0.31831 * exp(-4./TAU * noise*noise) * abs(noise)*dndx)/sqrt(1.0-exp(-4./TAU * noise*noise));
|
|
||||||
// TODO: normalize properly
|
|
||||||
dndx = .5 + .5*dndx;
|
|
||||||
return vec4(dndx,dndx,dndx, 1.);
|
|
||||||
#else
|
|
||||||
noise = .5 + .5*sign(noise)*sqrt(1.-exp(-4./TAU * noise*noise));
|
|
||||||
return vec4(noise,noise,noise, 1.);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
]]
|
|
||||||
|
|
||||||
noise.make1dShader = function(amplitudes, random)
|
|
||||||
amplitudes, random = _defaultArgs(amplitudes, random)
|
|
||||||
local resolution = #amplitudes
|
|
||||||
local sigma = calculateSigma(amplitudes)
|
|
||||||
local offsets = makeOffsets(#amplitudes, random)
|
|
||||||
local shaderCode = "#define RESOLUTION "..tostring(resolution).."\n"..shader1d
|
|
||||||
local noiseShader = lg.newShader(shaderCode)
|
|
||||||
local gradShader = lg.newShader("#define GRADIENT\n"..shaderCode)
|
|
||||||
do -- Dumb hack to work around a bug in Love 0.10.2 not sending the last value
|
|
||||||
table.insert(amplitudes, 1.)
|
|
||||||
table.insert(offsets, 1.)
|
|
||||||
end
|
|
||||||
for _,shader in ipairs{noiseShader, gradShader} do
|
|
||||||
shader:send("sigma",sigma)
|
|
||||||
shader:send("resolution",resolution)
|
|
||||||
shader:send("offsets",unpack(offsets))
|
|
||||||
shader:send("amplitudes",unpack(amplitudes))
|
|
||||||
shader:send("range_min", 0)
|
|
||||||
shader:send("range_max", 0)
|
|
||||||
end
|
|
||||||
return noiseShader,gradShader
|
|
||||||
end
|
|
||||||
|
|
||||||
local shader2d = [[
|
|
||||||
#define TAU 6.283185307179586476925286766559005768394338798750211641949
|
|
||||||
#define PHI 1.618033988749894848204586834365638117720309179805762862135
|
|
||||||
extern float sigma;
|
|
||||||
extern float amplitudes[RESOLUTION];
|
|
||||||
extern vec2 offsets[RESOLUTION];
|
|
||||||
extern vec2 range_min, range_max;
|
|
||||||
|
|
||||||
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords)
|
|
||||||
{
|
|
||||||
vec2 pos = mix(range_min,range_max,texture_coords);
|
|
||||||
float noise = 0.;
|
|
||||||
#ifdef GRADIENT
|
|
||||||
float dndx = 0., dndy = 0.;
|
|
||||||
#endif
|
|
||||||
for (int i=0; i < RESOLUTION; i++) {
|
|
||||||
float angle = mod(float(i)*PHI, 1.)*TAU;
|
|
||||||
float cosa = cos(angle), sina = sin(angle);
|
|
||||||
float u = pos.x*cosa - pos.y*sina;
|
|
||||||
float v = -pos.x*sina - pos.y*cosa;
|
|
||||||
float a = amplitudes[i];
|
|
||||||
float k = offsets[i].x, w = offsets[i].y;
|
|
||||||
noise += a*(cos(u/a + k) + cos(v/a + w));
|
|
||||||
#ifdef GRADIENT
|
|
||||||
dndx += -cosa*sin(u/a + k) + sina*sin(v/a + w);
|
|
||||||
dndy += sina*sin(u/a + k) + cosa*sin(v/a + w);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
noise /= 2.*sigma;
|
|
||||||
|
|
||||||
#ifdef GRADIENT
|
|
||||||
dndx /= 2.*sigma;
|
|
||||||
dndx = (0.31831 * exp(-4./TAU * noise*noise) * abs(noise)*dndx)/sqrt(1.0-exp(-4./TAU * noise*noise));
|
|
||||||
dndx = .5 + .5*dndx;
|
|
||||||
|
|
||||||
dndy /= 2.*sigma;
|
|
||||||
dndy = (0.31831 * exp(-4./TAU * noise*noise) * abs(noise)*dndy)/sqrt(1.0-exp(-4./TAU * noise*noise));
|
|
||||||
dndy = .5 + .5*dndy;
|
|
||||||
|
|
||||||
return vec4(dndx,dndy,0.,1.);
|
|
||||||
#else
|
|
||||||
noise = .5 + .5*sign(noise)*sqrt(1.-exp(-4./TAU * noise*noise));
|
|
||||||
return vec4(noise,noise,noise,1.);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
]]
|
|
||||||
|
|
||||||
noise.make2dShader = function(amplitudes, random)
|
|
||||||
amplitudes, random = _defaultArgs(amplitudes, random)
|
|
||||||
local resolution = #amplitudes
|
|
||||||
local sigma = calculateSigma(amplitudes)
|
|
||||||
local offsets = makeOffsets(2*#amplitudes, random)
|
|
||||||
local shaderCode = "#define RESOLUTION "..tostring(resolution).."\n"..shader2d
|
|
||||||
local noiseShader = lg.newShader(shaderCode)
|
|
||||||
local gradShader = lg.newShader("#define GRADIENT\n"..shaderCode)
|
|
||||||
sigma = sigma/sqrt(2)
|
|
||||||
local offsets2 = {}
|
|
||||||
for i=1,#offsets-1,2 do
|
|
||||||
table.insert(offsets2, {offsets[i],offsets[i+1]})
|
|
||||||
end
|
|
||||||
do -- Dumb hack to work around a bug in Love 0.10.2 not sending the last value
|
|
||||||
table.insert(amplitudes, 1.)
|
|
||||||
table.insert(offsets2, {1,1})
|
|
||||||
end
|
|
||||||
for _,shader in ipairs{noiseShader, gradShader} do
|
|
||||||
shader:send("sigma",sigma)
|
|
||||||
shader:send("offsets",unpack(offsets2))
|
|
||||||
shader:send("amplitudes",unpack(amplitudes))
|
|
||||||
shader:send("range_min", {0,0})
|
|
||||||
shader:send("range_max", {1,1})
|
|
||||||
end
|
|
||||||
return noiseShader,gradShader
|
|
||||||
end
|
|
||||||
|
|
||||||
local shader3d = [[
|
|
||||||
#define TAU 6.283185307179586476925286766559005768394338798750211641949
|
|
||||||
#define PHI 1.618033988749894848204586834365638117720309179805762862135
|
|
||||||
#define LOG_PHI 0.481211825059603447497758913424368423135184334385660519660
|
|
||||||
#define SQRT5 2.236067977499789696409173668731276235440618359611525724270
|
|
||||||
extern float sigma, z;
|
|
||||||
extern float amplitudes[RESOLUTION];
|
|
||||||
extern vec3 offsets[RESOLUTION];
|
|
||||||
extern vec2 range_min, range_max;
|
|
||||||
|
|
||||||
// https://www.graphics.rwth-aachen.de/media/papers/jgt.pdf
|
|
||||||
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords)
|
|
||||||
{
|
|
||||||
vec3 pos = vec3(mix(range_min,range_max,texture_coords), z);
|
|
||||||
// Find the biggest fibonacci number F_n such that F_n < RESOLUTION
|
|
||||||
int fib_n = int(log((float(RESOLUTION)-1.)*SQRT5 + .5)/LOG_PHI);
|
|
||||||
int dec = int(.5 + pow(PHI,fib_n)/SQRT5); // F_n, using closed form Fibonacci
|
|
||||||
int inc = int(.5 + dec/PHI); // F_(fib_n-1)
|
|
||||||
|
|
||||||
float n = 0.;
|
|
||||||
#ifdef GRADIENT
|
|
||||||
float dndx = 0., dndy = 0., dndz = 0.;
|
|
||||||
#endif
|
|
||||||
for (int i=0, j=0; i<RESOLUTION; ++i) {
|
|
||||||
if (j >= dec) {
|
|
||||||
j -= dec;
|
|
||||||
} else {
|
|
||||||
j += inc;
|
|
||||||
if (j >= RESOLUTION)
|
|
||||||
j -= dec;
|
|
||||||
}
|
}
|
||||||
// Convert golden ratio sequence into polar coordinate unit vector
|
noise /= sigma;
|
||||||
float phi = mod(float(i)*PHI,1.)*TAU;
|
#ifdef GRADIENT
|
||||||
float theta = acos(mix(-1.,1.,mod(float(j)*PHI,1.)));
|
dndx /= sigma;
|
||||||
// Make an orthonormal basis, where n1 is from polar phi/theta,
|
dndx = (0.31831 * exp(-4./TAU * noise*noise) * abs(noise)*dndx)/sqrt(1.0-exp(-4./TAU * noise*noise));
|
||||||
// n2 is roated 90 degrees along phi, and n3 is the cross product of the two
|
// TODO: normalize properly
|
||||||
vec3 n1 = vec3(sin(phi)*cos(theta), sin(phi)*sin(theta), cos(phi));
|
dndx = .5 + .5*dndx;
|
||||||
vec3 n2 = vec3(sin(phi+TAU/4.)*cos(theta), sin(phi+TAU/4.)*sin(theta), cos(phi+TAU/4.));
|
return vec4(dndx,dndx,dndx, 1.);
|
||||||
vec3 n3 = cross(n1,n2);
|
#else
|
||||||
// Convert pos from x/y/z coordinates to n1/n2/n3 coordinates
|
noise = .5 + .5*sign(noise)*sqrt(1.-exp(-4./TAU * noise*noise));
|
||||||
float u = dot(n1, pos);
|
return vec4(noise,noise,noise, 1.);
|
||||||
float v = dot(n2, pos);
|
#endif
|
||||||
float w = dot(n3, pos);
|
|
||||||
// Pull the amplitude from the shuffled array index ("j"), not "i",
|
|
||||||
// otherwise neighboring unit vectors will have similar amplitudes!
|
|
||||||
float a = amplitudes[j];
|
|
||||||
//float a = pow(mod(float(i+1)*(PHI-1.), 1.), .3);
|
|
||||||
// Noise is the average of cosine of distance along each axis, shifted by offsets and scaled by amplitude.
|
|
||||||
n += a*(cos(u/a + offsets[i].x) + cos(v/a + offsets[i].y) + cos(w/a + offsets[i].z));
|
|
||||||
#ifdef GRADIENT
|
|
||||||
vec3 k = vec3(-sin(u/a+offsets[i].x),-sin(v/a+offsets[i].y),-sin(w/a + offsets[i].z));
|
|
||||||
dndx += (n1.x*k.x + n2.x*k.y + n3.x*k.z)/3.;
|
|
||||||
dndy += (n1.y*k.x + n2.y*k.y + n3.y*k.z)/3.;
|
|
||||||
dndz += (n1.z*k.x + n2.z*k.y + n3.z*k.z)/3.;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
n /= 3.*sigma;
|
]]
|
||||||
#ifdef GRADIENT
|
|
||||||
dndx /= sigma;
|
|
||||||
dndx = (0.31831 * exp(-4./TAU * n*n) * abs(n)*dndx)/sqrt(1.0-exp(-4./TAU * n*n));
|
|
||||||
dndx = .5 + .5*dndx;
|
|
||||||
|
|
||||||
dndy /= sigma;
|
noise.make1dShader = function(opts)
|
||||||
dndy = (0.31831 * exp(-4./TAU * n*n) * abs(n)*dndy)/sqrt(1.0-exp(-4./TAU * n*n));
|
local amplitudes, random = _defaultArgs(opts, "1dShader")
|
||||||
dndy = .5 + .5*dndy;
|
local resolution = #amplitudes
|
||||||
|
local sigma = calculateSigma(amplitudes)
|
||||||
dndz /= sigma;
|
local offsets = makeOffsets(#amplitudes, random)
|
||||||
dndz = (0.31831 * exp(-4./TAU * n*n) * abs(n)*dndz)/sqrt(1.0-exp(-4./TAU * n*n));
|
local shaderCode = "#define RESOLUTION "..tostring(resolution).."\n"..shader1d
|
||||||
dndz = .5 + .5*dndz;
|
local noiseShader = love.graphics.newShader(shaderCode)
|
||||||
|
local gradShader = love.graphics.newShader("#define GRADIENT\n"..shaderCode)
|
||||||
return vec4(dndx,dndy,dndz, 1.);
|
do -- Dumb hack to work around a bug in Love 0.10.2 not sending the last value
|
||||||
#else
|
table.insert(amplitudes, 1.)
|
||||||
n = .5 + .5*sign(n)*sqrt(1.-exp(-4./TAU * n*n));
|
table.insert(offsets, 1.)
|
||||||
return vec4(n,n,n,1.);
|
end
|
||||||
#endif
|
for _,shader in ipairs{noiseShader, gradShader} do
|
||||||
}
|
shader:send("sigma",sigma)
|
||||||
]]
|
shader:send("resolution",resolution)
|
||||||
|
shader:send("offsets",unpack(offsets))
|
||||||
noise.make3dShader = function(amplitudes, random)
|
shader:send("amplitudes",unpack(amplitudes))
|
||||||
amplitudes, random = _defaultArgs(amplitudes, random)
|
shader:send("range_min", 0)
|
||||||
local resolution = #amplitudes
|
shader:send("range_max", 0)
|
||||||
local sigma = calculateSigma(amplitudes)
|
end
|
||||||
local offsets = makeOffsets(3*#amplitudes, random)
|
return noiseShader,gradShader
|
||||||
local shaderCode = "#define RESOLUTION "..tostring(resolution).."\n"..shader3d
|
|
||||||
local noiseShader = lg.newShader(shaderCode)
|
|
||||||
local gradShader = lg.newShader("#define GRADIENT\n"..shaderCode)
|
|
||||||
sigma = sigma/sqrt(3)
|
|
||||||
local offsets2 = {}
|
|
||||||
for i=1,#offsets-1,3 do
|
|
||||||
table.insert(offsets2, {offsets[i],offsets[i+1],offsets[i+2]})
|
|
||||||
end
|
end
|
||||||
do -- Dumb hack to work around a bug in Love 0.10.2 not sending the last value
|
|
||||||
table.insert(amplitudes, 1.)
|
local shader2d = [[
|
||||||
table.insert(offsets, {1,1,1})
|
#define TAU 6.283185307179586476925286766559005768394338798750211641949
|
||||||
|
#define PHI 1.618033988749894848204586834365638117720309179805762862135
|
||||||
|
extern float sigma;
|
||||||
|
extern float amplitudes[RESOLUTION];
|
||||||
|
extern vec2 offsets[RESOLUTION];
|
||||||
|
extern vec2 range_min, range_max;
|
||||||
|
|
||||||
|
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords)
|
||||||
|
{
|
||||||
|
vec2 pos = mix(range_min,range_max,texture_coords);
|
||||||
|
float noise = 0.;
|
||||||
|
#ifdef GRADIENT
|
||||||
|
float dndx = 0., dndy = 0.;
|
||||||
|
#endif
|
||||||
|
for (int i=0; i < RESOLUTION; i++) {
|
||||||
|
float angle = mod(float(i)*PHI, 1.)*TAU;
|
||||||
|
float cosa = cos(angle), sina = sin(angle);
|
||||||
|
float u = pos.x*cosa - pos.y*sina;
|
||||||
|
float v = -pos.x*sina - pos.y*cosa;
|
||||||
|
float a = amplitudes[i];
|
||||||
|
float k = offsets[i].x, w = offsets[i].y;
|
||||||
|
noise += a*(cos(u/a + k) + cos(v/a + w));
|
||||||
|
#ifdef GRADIENT
|
||||||
|
dndx += -cosa*sin(u/a + k) + sina*sin(v/a + w);
|
||||||
|
dndy += sina*sin(u/a + k) + cosa*sin(v/a + w);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
noise /= 2.*sigma;
|
||||||
|
|
||||||
|
#ifdef GRADIENT
|
||||||
|
dndx /= 2.*sigma;
|
||||||
|
dndx = (0.31831 * exp(-4./TAU * noise*noise) * abs(noise)*dndx)/sqrt(1.0-exp(-4./TAU * noise*noise));
|
||||||
|
dndx = .5 + .5*dndx;
|
||||||
|
|
||||||
|
dndy /= 2.*sigma;
|
||||||
|
dndy = (0.31831 * exp(-4./TAU * noise*noise) * abs(noise)*dndy)/sqrt(1.0-exp(-4./TAU * noise*noise));
|
||||||
|
dndy = .5 + .5*dndy;
|
||||||
|
|
||||||
|
return vec4(dndx,dndy,0.,1.);
|
||||||
|
#else
|
||||||
|
noise = .5 + .5*sign(noise)*sqrt(1.-exp(-4./TAU * noise*noise));
|
||||||
|
return vec4(noise,noise,noise,1.);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
|
||||||
|
noise.make2dShader = function(opts)
|
||||||
|
local amplitudes, random = _defaultArgs(opts, "2dShader")
|
||||||
|
local resolution = #amplitudes
|
||||||
|
local sigma = calculateSigma(amplitudes)
|
||||||
|
local offsets = makeOffsets(2*#amplitudes, random)
|
||||||
|
local shaderCode = "#define RESOLUTION "..tostring(resolution).."\n"..shader2d
|
||||||
|
local noiseShader = love.graphics.newShader(shaderCode)
|
||||||
|
local gradShader = love.graphics.newShader("#define GRADIENT\n"..shaderCode)
|
||||||
|
sigma = sigma/sqrt(2)
|
||||||
|
local offsets2 = {}
|
||||||
|
for i=1,#offsets-1,2 do
|
||||||
|
table.insert(offsets2, {offsets[i],offsets[i+1]})
|
||||||
|
end
|
||||||
|
do -- Dumb hack to work around a bug in Love 0.10.2 not sending the last value
|
||||||
|
table.insert(amplitudes, 1.)
|
||||||
|
table.insert(offsets2, {1,1})
|
||||||
|
end
|
||||||
|
for _,shader in ipairs{noiseShader, gradShader} do
|
||||||
|
shader:send("sigma",sigma)
|
||||||
|
shader:send("offsets",unpack(offsets2))
|
||||||
|
shader:send("amplitudes",unpack(amplitudes))
|
||||||
|
shader:send("range_min", {0,0})
|
||||||
|
shader:send("range_max", {1,1})
|
||||||
|
end
|
||||||
|
return noiseShader,gradShader
|
||||||
end
|
end
|
||||||
for _,shader in ipairs{noiseShader, gradShader} do
|
|
||||||
shader:send("sigma",sigma)
|
local shader3d = [[
|
||||||
shader:send("offsets",unpack(offsets2))
|
#define TAU 6.283185307179586476925286766559005768394338798750211641949
|
||||||
shader:send("amplitudes",unpack(amplitudes))
|
#define PHI 1.618033988749894848204586834365638117720309179805762862135
|
||||||
shader:send("range_min", {0,0})
|
#define LOG_PHI 0.481211825059603447497758913424368423135184334385660519660
|
||||||
shader:send("range_max", {1,1})
|
#define SQRT5 2.236067977499789696409173668731276235440618359611525724270
|
||||||
|
extern float sigma, z;
|
||||||
|
extern float amplitudes[RESOLUTION];
|
||||||
|
extern vec3 offsets[RESOLUTION];
|
||||||
|
extern vec2 range_min, range_max;
|
||||||
|
|
||||||
|
// https://www.graphics.rwth-aachen.de/media/papers/jgt.pdf
|
||||||
|
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords)
|
||||||
|
{
|
||||||
|
vec3 pos = vec3(mix(range_min,range_max,texture_coords), z);
|
||||||
|
// Find the biggest fibonacci number F_n such that F_n < RESOLUTION
|
||||||
|
int fib_n = int(log((float(RESOLUTION)-1.)*SQRT5 + .5)/LOG_PHI);
|
||||||
|
int dec = int(.5 + pow(PHI,fib_n)/SQRT5); // F_n, using closed form Fibonacci
|
||||||
|
int inc = int(.5 + dec/PHI); // F_(fib_n-1)
|
||||||
|
|
||||||
|
float n = 0.;
|
||||||
|
#ifdef GRADIENT
|
||||||
|
float dndx = 0., dndy = 0., dndz = 0.;
|
||||||
|
#endif
|
||||||
|
for (int i=0, j=0; i<RESOLUTION; ++i) {
|
||||||
|
if (j >= dec) {
|
||||||
|
j -= dec;
|
||||||
|
} else {
|
||||||
|
j += inc;
|
||||||
|
if (j >= RESOLUTION)
|
||||||
|
j -= dec;
|
||||||
|
}
|
||||||
|
// Convert golden ratio sequence into polar coordinate unit vector
|
||||||
|
float phi = mod(float(i)*PHI,1.)*TAU;
|
||||||
|
float theta = acos(mix(-1.,1.,mod(float(j)*PHI,1.)));
|
||||||
|
// Make an orthonormal basis, where n1 is from polar phi/theta,
|
||||||
|
// n2 is roated 90 degrees along phi, and n3 is the cross product of the two
|
||||||
|
vec3 n1 = vec3(sin(phi)*cos(theta), sin(phi)*sin(theta), cos(phi));
|
||||||
|
vec3 n2 = vec3(sin(phi+TAU/4.)*cos(theta), sin(phi+TAU/4.)*sin(theta), cos(phi+TAU/4.));
|
||||||
|
vec3 n3 = cross(n1,n2);
|
||||||
|
// Convert pos from x/y/z coordinates to n1/n2/n3 coordinates
|
||||||
|
float u = dot(n1, pos);
|
||||||
|
float v = dot(n2, pos);
|
||||||
|
float w = dot(n3, pos);
|
||||||
|
// Pull the amplitude from the shuffled array index ("j"), not "i",
|
||||||
|
// otherwise neighboring unit vectors will have similar amplitudes!
|
||||||
|
float a = amplitudes[j];
|
||||||
|
//float a = pow(mod(float(i+1)*(PHI-1.), 1.), .3);
|
||||||
|
// Noise is the average of cosine of distance along each axis, shifted by offsets and scaled by amplitude.
|
||||||
|
n += a*(cos(u/a + offsets[i].x) + cos(v/a + offsets[i].y) + cos(w/a + offsets[i].z));
|
||||||
|
#ifdef GRADIENT
|
||||||
|
vec3 k = vec3(-sin(u/a+offsets[i].x),-sin(v/a+offsets[i].y),-sin(w/a + offsets[i].z));
|
||||||
|
dndx += (n1.x*k.x + n2.x*k.y + n3.x*k.z)/3.;
|
||||||
|
dndy += (n1.y*k.x + n2.y*k.y + n3.y*k.z)/3.;
|
||||||
|
dndz += (n1.z*k.x + n2.z*k.y + n3.z*k.z)/3.;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
n /= 3.*sigma;
|
||||||
|
#ifdef GRADIENT
|
||||||
|
dndx /= sigma;
|
||||||
|
dndx = (0.31831 * exp(-4./TAU * n*n) * abs(n)*dndx)/sqrt(1.0-exp(-4./TAU * n*n));
|
||||||
|
dndx = .5 + .5*dndx;
|
||||||
|
|
||||||
|
dndy /= sigma;
|
||||||
|
dndy = (0.31831 * exp(-4./TAU * n*n) * abs(n)*dndy)/sqrt(1.0-exp(-4./TAU * n*n));
|
||||||
|
dndy = .5 + .5*dndy;
|
||||||
|
|
||||||
|
dndz /= sigma;
|
||||||
|
dndz = (0.31831 * exp(-4./TAU * n*n) * abs(n)*dndz)/sqrt(1.0-exp(-4./TAU * n*n));
|
||||||
|
dndz = .5 + .5*dndz;
|
||||||
|
|
||||||
|
return vec4(dndx,dndy,dndz, 1.);
|
||||||
|
#else
|
||||||
|
n = .5 + .5*sign(n)*sqrt(1.-exp(-4./TAU * n*n));
|
||||||
|
return vec4(n,n,n,1.);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
|
||||||
|
noise.make3dShader = function(opts)
|
||||||
|
local amplitudes, random = _defaultArgs(opts, "3dShader")
|
||||||
|
local resolution = #amplitudes
|
||||||
|
local sigma = calculateSigma(amplitudes)
|
||||||
|
local offsets = makeOffsets(3*#amplitudes, random)
|
||||||
|
local shaderCode = "#define RESOLUTION "..tostring(resolution).."\n"..shader3d
|
||||||
|
local noiseShader = love.graphics.newShader(shaderCode)
|
||||||
|
local gradShader = love.graphics.newShader("#define GRADIENT\n"..shaderCode)
|
||||||
|
sigma = sigma/sqrt(3)
|
||||||
|
local offsets2 = {}
|
||||||
|
for i=1,#offsets-1,3 do
|
||||||
|
table.insert(offsets2, {offsets[i],offsets[i+1],offsets[i+2]})
|
||||||
|
end
|
||||||
|
do -- Dumb hack to work around a bug in Love 0.10.2 not sending the last value
|
||||||
|
table.insert(amplitudes, 1.)
|
||||||
|
table.insert(offsets, {1,1,1})
|
||||||
|
end
|
||||||
|
for _,shader in ipairs{noiseShader, gradShader} do
|
||||||
|
shader:send("sigma",sigma)
|
||||||
|
shader:send("offsets",unpack(offsets2))
|
||||||
|
shader:send("amplitudes",unpack(amplitudes))
|
||||||
|
shader:send("range_min", {0,0})
|
||||||
|
shader:send("range_max", {1,1})
|
||||||
|
end
|
||||||
|
return noiseShader, gradShader
|
||||||
end
|
end
|
||||||
return noiseShader, gradShader
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return noise
|
return noise
|
||||||
|
Loading…
Reference in New Issue
Block a user