Refraction light pattern

Hi ! i would like to do code in processing, that look like refraction light pattern .
Someone have any suggest how to start ?
download

1 Like

if you want to fake it there’s a good guide here it’s not processing but you could port it over.

1 Like

Hi @netasoreq,

I can’t help but to see Voronoi pattern in your picture.

Do you want your final output to be a static image ? If so, pixel displacement based on Worley noise (or even ridged noise) could be a solution. See this thread for more info on noise-based pixel displacement.

1 Like

Hi @solub !
thank you very much, your link is very cool !!
may you send me the full code of the 2D displacement mapping for water effect
it’s could be very helpful to learn from this…

TNX again !!!
Neta

It IS the full code but because it is written in Python you need to port it to Java.

Note that this example sketch is implementing Fractal Brownian Noise.

def setup():
    size(400, 300, P2D)
        
    loadPixels()
    for y in range(height):
        for x in range(width):
            i = x + y * width
            n = warp(x, y, .01, 255)
            pixels[i] = color(-n, n, n)
    updatePixels()
            
def warp(_x, _y, factor, n_range):
    n1 = noise((_x+0.0) * factor, (_y+0.0) * factor) * n_range
    n2 = noise((_x+5.2) * factor, (_y+1.3) * factor) * n_range
    q = PVector(n1, n2)
            
    n3 = noise(((_x + q.x * 4) + 1.7) * factor, ((_y + q.y * 4) + 9.2) * factor) * n_range
    n4 = noise(((_x + q.x * 4) + 8.3) * factor, ((_y + q.y * 4) + 2.8) * factor) * n_range
    r = PVector(n3, n4)
                
    return noise((_x + r.x * 4) * factor, (_y + r.y * 4) * factor) * n_range

But for what you’re trying to achieve I was suggesting Ridged Noise instead …

EDIT: just raised ridged noise to the power of 4 and decreased NoiseDetail() to 3 for better results

def setup():
    size(400, 300, P2D)
        
    noiseDetail(3)
    loadPixels()
    for y in range(height):
        for x in range(width):
            i = x + y * width
            n = ridgedNoise(x, y, .007, 80, 120)
            pixels[i] = color(0, n, n)
    updatePixels()
            
def ridgedNoise(_x, _y, factor, minimum, maximum):
    n = noise(_x * factor, _y * factor)
    return minimum + pow((2 * (.5 - abs(.5 - n))), 4) * maximum

… or even Worley Noise (similar to Voronoi Noise)

def setup():
    size(400, 300, P2D)
    
    points = [PVector(random(width), random(height)) for i in range(40)]
    maxDist = 0.0
    
    loadPixels()
    for y in range(height):
        for x in range(width):
            dists = [dist(x, y, p.x, p.y) for p in points]
            minDist = min(dists)
            if minDist > maxDist: maxDist = minDist
            c = map(pow((minDist / maxDist), 2), 0, 1, 55, 255)
            i = x + y * width
            pixels[i] = color(10, 40 + c, 40 + c)
    updatePixels()

Now, for a more realistic output you can try to:

  • fiddle with the noiseDetail() function (for the first 2 examples)
  • mix different kind of noises / algorithm

If you want to make an animation then I would suggest:

  • to port the algorithm of your choice to GLSL and increment the noise value as the skecth is iterating
  • to use Spatial Hashing for efficient distances computation (for Worley Noise)
  • or to implement the Navier-Stokes equations for fluid simulation (preferably in a shader). Daniel Shiffman did it in Processing Java in a recent coding challenge.
4 Likes

super cool thank you very much !!! you relay help me , many tnx !

Hi @solub
may I ask you question ?
maybe you some idea why it’s not wrap the width stripe of the pic’s?

I’m sorry, I’m unable to reproduce your output.

Maybe try to replace the following lines

n = warp(x, y, .003, 1) #Be sure to keep n_range (last value) at 1
c = img.pixels[int(round(i * n))] 
pixels[i] = c

Edit: Just tried with a random image with black and white stripes, it should work:

I have recently stumbled upon this video tutorial on “caustics” in Houdini that reminded me of this question. I realize now that it was what the OP was actually looking for, so I am posting a more appropriate answer for future readers.

As explained in the video linked above caustics are obtained by simulating light refraction from one medium (air for example) to another (water in this case). The algorithm consists in the following steps:

  • Shoot vertical rays of light PVector(0,0,-1) on a water surface (yellow noised grid)
  • Compute refracted rays (red) using Snell’s law
  • Find the intersection points (blue) of these rays with the plane underneath

Caustics become apparent where the point-density is high (rays pointing in the same direction)

ezgif.com-optimize(2)

Computing refraction for thousands of rays in 3D being quite expensive a workaround could consist in lowering the grid resolution and applying a blur filter on the projected points.

Here below and example with a 128*72 grid:

add_library('hemesh')

W, H = 128, 72            #dimension of grid ex: 1024/576
nFaces = W*H              #number of Faces
nVerts = (W+1)*(H+1)      #number of Vertices
F = .018                  #Factor of noise
Z = 100                   #Z-coordinate of plane

dir = WB_Vector(0, 0, -1) #direction of light
pln = WB_Plane(WB_Point(-100, -100, -Z), WB_Vector(0, -1, -Z)) #plane(origin, direction)

def setup():
    size(1280, 720, P3D)
    translate(width>>1, height>>1)
    background('#003638')
    noiseSeed(2)
    
    # compute noise value for each vertex
    nvalues = [noise(i%(W+1) * F, i//(W+1) * F) * 100 for i in xrange(nVerts)]
    
    # create grid with chosen dimensions and feed it with height values (noise values)
    grid = HEC_Grid().setU(W).setV(H).setUSize(width).setVSize(height).setWValues(nvalues)
    
    # construct mesh
    mesh = HE_Mesh(grid) 
    
    # computes refracted rays + render intersection points
    for i in xrange(nFaces):
        
        n = mesh.getFaceNormal(i)
        c = mesh.getFaceCenter(i)
        
        ray = refract(dir, n, c, 1.0, 1.33) # 1.0 / 1.33 = refraction indices of air and water
        sec = WB_GeometryOp.getIntersection3D(ray, pln) # find if ray intersects the plane
        ipt = sec.object # intersection point
        
        d = ipt.getDistance(c.sub(WB_Point(0, 0, Z))) # how far is itp from its face's normal
        
        cl = map(d, .7, 57, 255, 36) # map distance to color value        
        sw = map(d, .7, 57, 6, 0) # map distance to stroke weight
        
        strokeWeight(sw)
        stroke(0, cl, cl)
        point(ipt.xf(), ipt.yf())
      
    filter(DILATE)  
    filter(BLUR, 6)  

    noLoop()
    
    
def refract(k, n, o, i1, i2):
    
    # Based on -> https://github.com/pmocherla/OOP-RayTracer-/blob/master/raytracer.py
    
    """
    Returns a WB_Vector.
    Computes the 3D direction vector of a refracted ray through a surface using Snell's law.
        
    Args:
        k (WB_Vector) - incident ray direction vector
        n (WB_Vector) - vector normal to refracting surface
        o (WB_Point) - origin point of refracted ray / intersection point of the incident ray with surface
        i1 (float) - refractive index of media 1
        i2 (float) - refractive index of media 2
    """
    
    #Calculate the angle between the ray and the surface normal
    theta = acos(k.dot(n) / n.getSqLength())
    
    #Condition for total internal reflection must not be satisfied.
    if sin(theta) < i2/i1:
        
        k.normalizeSelf()
        n.normalizeSelf()
        cprod = n.cross(k)
        ior = i1/i2 #index of reflection

        #Calculation of Snells law broken into two parts and combined
        one = n.cross(cprod).mul(ior)
        two = n.mul(sqrt(1 - ior**2 * cprod.dot(cprod)))
        dir = one.sub(two)
        
        return WB_Ray(o, dir)
2 Likes