(541 lines)
1 -- Hill Noise library2 -- Copyright 2017 Bruce Hill3 --4 -- This library provides functions for continuous pseudorandom 1D, 2D, and 3D noise functions5 -- with approximately uniform distribution on the interval (0,1) and customizable resolution,6 -- levels of detail, and shape characteristics.7 --8 -- Functions: make1d([opts]), make2d([opts]), make3d([opts])9 -- each of these returns a noise function, and a gradient function of the noise function10 -- Shader functions: make1dShader([opts]), make2dShader([opts]), make3dShader([opts])11 -- each of these returns a noise shader, and a gradient shader of the noise function12 --13 -- Usage:14 -- local Noise = require 'hill_noise'15 -- local noise,noise_gradient = Noise.make3d{resolution=9}16 -- local x,y,z = 1,2,317 -- local n = noise(x,y,z)18 -- local dndx,dndy,dndz = noise_gradient(x,y,z)19 --20 -- The noise construction functions all take the following options:21 -- * random: a random number function (default: math.random)22 -- * seed: if "random" is not provided, and the love.math module is loaded, a new23 -- pseudorandom number generator will be used with the specified seed24 -- * amplitudes: a list of sine wave amplitudes to use25 -- * resolution: if "amplitudes" is not provided, the number of sine waves to use26 -- (default: 5,7,9 for 1d,2d,3d functions, and 10,15,20 for 1d,2d,3d shaders)27 -- * distribution: if "amplitudes" is not provided, a function to evenly sample28 -- amplitudes from on the interval (0,1) (default: 0.1^x)29 --30 -- The noise and noise gradient functions all run in O(resolution), and more resolution31 -- may be needed for higher dimensional noise and additional detail.32 --33 -- For the LOVE game engine, there are versions that produce 1D, 2D, and 3D noise shaders34 -- as well, which are much faster and run on the GPU.35 -- local noise_shader, noise_gradient_shader = Noise.make3dShader{resolution=11}36 -- local canvas = love.graphics.newCanvas()37 -- -- At each location on the texture, the noise function is sampled at a linear38 -- -- interpolation between range_min and range_max, using the texture coordinates.39 -- -- For 3D noise, the "z" value is passed as a parameter.40 -- noise_shader:send("range_min", {0,20})41 -- noise_shader:send("range_max", {0,20})42 -- noise_shader:send('z', love.timer.getTime())43 -- love.graphics.setShader(noise_shader)44 -- love.graphics.draw(canvas)45 -- love.graphics.setShader()46 -- The noise gradient shaders populate the RGB channels with:47 -- * for 1D: sigmoid(2*dn/dx), sigmoid(2*dn/dx), sigmoid(2*dn/dx)48 -- * for 2D: sigmoid(1.5*dn/dx), sigmoid(1.5*dn/dy), 049 -- * for 3D: sigmoid(4*dn/dx), sigmoid(4*dn/dy), sigmoid(4*dn/dz)50 -- (coefficients were arbitrarily chosen to best squash typical gradients for the default51 -- parameters evenly into (0,1))52 -- Additionally, the 1D noise shader has an extra option "draw_outline" that, if set to53 -- true, makes the 1D shader render output as white or black if the y-coordinate is54 -- above/below the noise value for a given x-coordinate.55 --56 -- Permission is hereby granted, free of charge, to any person obtaining a copy of57 -- this software and associated documentation files (the "Software"), to deal in58 -- the Software without restriction, including without limitation the rights to59 -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies60 -- of the Software, and to permit persons to whom the Software is furnished to do61 -- so, subject to the following conditions:62 --63 -- The above copyright notice and this permission notice shall be included in all64 -- copies or substantial portions of the Software.65 --66 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR67 -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,68 -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE69 -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER70 -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,71 -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE72 -- SOFTWARE.75 local exp,sin,cos,floor,log,acos,sqrt,abs = math.exp,math.sin,math.cos,math.floor,math.log,math.acos,math.sqrt,math.abs76 local GR, PI, TAU, SQRT5, LOG_GR = (sqrt(5)+1)/2, math.pi, 2*math.pi, sqrt(5), log((sqrt(5)+1)/2)80 end84 end86 local default_resolutions = {["1d"]=5, ["2d"]=7, ["3d"]=9, ["1dShader"]=10, ["2dShader"]=15, ["3dShader"]=20}88 opts = opts or {}97 random = math.random103 amplitudes = {}109 end114 sigma = sigma + a*a117 end122 offsets[i] = random()*TAU125 end134 n = n + a*cos(x/a + offsets[i])136 n = n/sigma143 n = n + a*cos(x/a + offsets[i])144 dndx = dndx - sin(x/a + offsets[i])146 dndx = dndx/sigma147 n = n/sigma151 end166 n = n + a*(cos(u/a + k) + cos(v/a + w))179 n = n + a*(cos(u/a + k) + cos(v/a + w))180 dx = dx + (sina*sin(v/a + w) - cosa*sin(u/a + k))181 dy = dy + (sina*sin(u/a + k) + cosa*sin(v/a + w))189 end206 j = j - dec208 j = j + inc210 j = j - dec222 n1z*n2x - n1x*n2z,223 n1x*n2y - n1y*n2x231 -- Noise is the average of cosine of distance along each axis, shifted by offsets and scaled by amplitude.246 j = j - dec248 j = j + inc250 j = j - dec262 n1z*n2x - n1x*n2z,263 n1x*n2y - n1y*n2x271 -- Noise is the average of cosine of distance along each axis, shifted by offsets and scaled by amplitude.275 dndx = dndx + (n1x*kx + n2x*ky + n3x*kz)276 dndy = dndy + (n1y*kx + n2y*ky + n3y*kz)277 dndz = dndz + (n1z*kx + n2z*ky + n3z*kz)283 end287 #define TAU 6.283185307179586476925286766559005768394338798750211641949288 #define MAX_RESOLUTION 64289 #define SIGMOID(x) (1./(1.+exp(-(x))))290 #define CDF(x) (.5 + .5*sign(x)*sqrt(1.-exp(-4./TAU * (x)*(x))))291 #define CDF_PRIME(x, dx) ((0.31831 * exp(-4./TAU * (x)*(x)) * abs(x)*(dx))/sqrt(1.0-exp(-4./TAU * (x)*(x))))292 extern int resolution;293 extern float sigma, range_min, range_max;294 extern float amplitudes[MAX_RESOLUTION];295 extern float offsets[MAX_RESOLUTION];296 extern bool draw_outline = false;298 vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords)299 {300 float x = mix(range_min,range_max,texture_coords.x);301 float noise = 0.;302 #ifdef GRADIENT303 float dndx = 0.;304 #endif305 for (int i=0; i < resolution; i++) {306 float a = amplitudes[i];307 noise += a*cos(x/a + offsets[i]);308 #ifdef GRADIENT309 dndx -= sin(x/a + offsets[i]);310 #endif311 }312 noise /= sigma;313 #ifdef GRADIENT314 dndx = CDF_PRIME(noise, dndx/sigma);315 dndx = SIGMOID(dndx*2.);316 if (draw_outline) {317 return texture_coords.y > (1.-dndx) ? vec4(1.,1.,1.,1.) : vec4(0.,0.,0.,1.);318 } else {319 return vec4(dndx,dndx,dndx,1.);320 }321 #else322 noise = CDF(noise);323 if (draw_outline) {324 return texture_coords.y > (1.-noise) ? vec4(1.,1.,1.,1.) : vec4(0.,0.,0.,1.);325 } else {326 return vec4(noise,noise,noise,1.);327 }328 #endif329 }359 #define TAU 6.283185307179586476925286766559005768394338798750211641949360 #define PHI 1.618033988749894848204586834365638117720309179805762862135361 #define CDF(x) (.5 + .5*sign(x)*sqrt(1.-exp(-4./TAU * (x)*(x))))362 #define CDF_PRIME(x, dx) ((0.31831 * exp(-4./TAU * (x)*(x)) * abs(x)*(dx))/sqrt(1.0-exp(-4./TAU * (x)*(x))))363 #define SIGMOID(x) (1./(1.+exp(-2.*(x))))364 extern float sigma;365 extern float amplitudes[RESOLUTION];366 extern vec2 offsets[RESOLUTION];367 extern vec2 range_min, range_max;369 vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords)370 {371 vec2 pos = mix(range_min,range_max,texture_coords);372 float noise = 0.;373 #ifdef GRADIENT374 float dndx = 0., dndy = 0.;375 #endif376 for (int i=0; i < RESOLUTION; i++) {377 float angle = mod(float(i)*PHI, 1.)*TAU;378 float cosa = cos(angle), sina = sin(angle);379 float u = pos.x*cosa - pos.y*sina;380 float v = -pos.x*sina - pos.y*cosa;381 float a = amplitudes[i];382 float k = offsets[i].x, w = offsets[i].y;383 noise += a*(cos(u/a + k) + cos(v/a + w));384 #ifdef GRADIENT385 dndx += -cosa*sin(u/a + k) + sina*sin(v/a + w);386 dndy += sina*sin(u/a + k) + cosa*sin(v/a + w);387 #endif388 }390 float _sigma = 2.*sigma;391 noise /= _sigma;392 #ifdef GRADIENT393 dndx = CDF_PRIME(noise, dndx/_sigma);394 dndx = SIGMOID(dndx*1.5);396 dndy = CDF_PRIME(noise, dndy/_sigma);397 dndy = SIGMOID(dndy*1.5);399 return vec4(dndx,dndy,0.,1.);400 #else401 noise = CDF(noise);402 return vec4(noise,noise,noise,1.);403 #endif404 }435 #define TAU 6.283185307179586476925286766559005768394338798750211641949436 #define PHI 1.618033988749894848204586834365638117720309179805762862135437 #define LOG_PHI 0.481211825059603447497758913424368423135184334385660519660438 #define SQRT5 2.236067977499789696409173668731276235440618359611525724270439 #define CDF(x) (.5 + .5*sign(x)*sqrt(1.-exp(-4./TAU * (x)*(x))))440 #define CDF_PRIME(x, dx) ((0.31831 * exp(-4./TAU * (x)*(x)) * abs(x)*(dx))/sqrt(1.0-exp(-4./TAU * (x)*(x))))441 #define SIGMOID(x) (1./(1.+exp(-2.*(x))))442 extern float sigma, z;443 extern float amplitudes[RESOLUTION];444 extern vec3 offsets[RESOLUTION];445 extern vec2 range_min, range_max;447 // https://www.graphics.rwth-aachen.de/media/papers/jgt.pdf448 vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords)449 {450 vec3 pos = vec3(mix(range_min,range_max,texture_coords), z);451 // Find the biggest fibonacci number F_n such that F_n < RESOLUTION452 int fib_n = int(log((float(RESOLUTION)-1.)*SQRT5 + .5)/LOG_PHI);453 int dec = int(.5 + pow(PHI,fib_n)/SQRT5); // F_n, using closed form Fibonacci454 int inc = int(.5 + dec/PHI); // F_(fib_n-1)456 float n = 0.;457 #ifdef GRADIENT458 float dndx = 0., dndy = 0., dndz = 0.;459 #endif460 for (int i=0, j=0; i<RESOLUTION; ++i) {461 if (j >= dec) {462 j -= dec;463 } else {464 j += inc;465 if (j >= RESOLUTION)466 j -= dec;467 }468 // Convert golden ratio sequence into polar coordinate unit vector469 float phi = mod(float(i)*PHI,1.)*TAU;470 float theta = acos(mix(-1.,1.,mod(float(j)*PHI,1.)));471 // Make an orthonormal basis, where n1 is from polar phi/theta,472 // n2 is roated 90 degrees along phi, and n3 is the cross product of the two473 vec3 n1 = vec3(sin(phi)*cos(theta), sin(phi)*sin(theta), cos(phi));474 vec3 n2 = vec3(sin(phi+TAU/4.)*cos(theta), sin(phi+TAU/4.)*sin(theta), cos(phi+TAU/4.));475 vec3 n3 = cross(n1,n2);476 // Convert pos from x/y/z coordinates to n1/n2/n3 coordinates477 float u = dot(n1, pos);478 float v = dot(n2, pos);479 float w = dot(n3, pos);480 // Pull the amplitude from the shuffled array index ("j"), not "i",481 // otherwise neighboring unit vectors will have similar amplitudes!482 float a = amplitudes[j];483 //float a = pow(mod(float(i+1)*(PHI-1.), 1.), .3);484 // Noise is the average of cosine of distance along each axis, shifted by offsets and scaled by amplitude.485 n += a*(cos(u/a + offsets[i].x) + cos(v/a + offsets[i].y) + cos(w/a + offsets[i].z));486 #ifdef GRADIENT487 vec3 k = vec3(-sin(u/a+offsets[i].x),-sin(v/a+offsets[i].y),-sin(w/a + offsets[i].z));488 dndx += (n1.x*k.x + n2.x*k.y + n3.x*k.z)/3.;489 dndy += (n1.y*k.x + n2.y*k.y + n3.y*k.z)/3.;490 dndz += (n1.z*k.x + n2.z*k.y + n3.z*k.z)/3.;491 #endif492 }493 float _sigma = 3.*sigma;494 n /= _sigma;495 #ifdef GRADIENT496 dndx = CDF_PRIME(n, dndx/_sigma);497 dndx = SIGMOID(dndx*4.);499 dndy = CDF_PRIME(n, dndy/_sigma);500 dndy = SIGMOID(dndy*4.);502 dndz = CDF_PRIME(n, dndz);503 dndz = SIGMOID(dndz*4.);505 return vec4(dndx,dndy,dndz, 1.);506 #else507 n = CDF(n);508 return vec4(n,n,n,1.);509 #endif510 }539 end