curv3d / curv

a language for making art using mathematics
Apache License 2.0
1.13k stars 73 forks source link

Normal argument to texturing #19

Open TLC123 opened 6 years ago

TLC123 commented 6 years ago

When creating textures (x,y,z,t) is available to plug in to some function. In Shadertoy there are many examples where, in addition to the (x,y,z,t) coordinate, the normal of the field at that (x,y,z,t) point is made available. This is of course very useful for texture generation I hacked up a thing like this alt text https://www.shadertoy.com/view/ld3BDj the code let

   RGB_normal  shape =

      make_shape {

        dist p : shape.dist(p),
        colour p :  let n= (
         (  normalize(
         (shape.dist(p) - shape.dist(p+(0.001,0,0,0))  ) ,
         (shape.dist(p) - shape.dist(p+(0,0.001,0,0))   )  ,
         (shape.dist(p) - shape.dist(p+(0,0,0.001,0)) ) ))); 
         in  
         max(0,n[X])*[0,1,1]+max(0,n[Y])*[0,1,0]+max(0,n[Z])*[1,1,0]+
         -min(0,n[X])*[1,0,0]+-min(0,n[Y])*[1,0,1]+-min(0,n[Z])*[0,0,1]
         , 
        bbox : shape.bbox,
        is_2d : shape.is_2d,
        is_3d : shape.is_3d,
    };  

  in

 union( 
 smooth 0.2 .union( 
 union(
     ellipsoid (1,1.4,2) >>move(1,1,1)  ,
     cone  {d:1, h:2}>>move(-1,1,0.5), 
     cylinder  {d:1, h:1.5}>>move(1,-1,1), 
     cube 1>>move(-1,-1,1) 
     ), 
   morph 0.75 (  sphere 2 , cube 1.45>>offset 0.05),
   ),
  torus  {major:4, minor:1}>>move(0,0,-1)

 )
 >>   RGB_normal
Automageek commented 6 years ago

Woah!!!!!!!! That is exciting!!!!

On Sat, May 19, 2018 at 5:10 AM, TLC123 notifications@github.com wrote:

When creating textures (x,y,z,t) is available to plug in to some function. In Shadertoy there are many examples where, in addition to the (x,y,z,t) coordinate, the normal of the field at that (x,y,z,t) point is made available. This is of course very useful for texture generation I hacked up a thing like this [image: alt text] https://github.com/TLC123/Curv_stuff/blob/master/rgbn.png?raw=true

the code let

RGB_normal shape =

make_shape {

  dist p : shape.dist(p),
  colour p :  let n= (
   (  normalize(
   (shape.dist(p) - shape.dist(p+(0.001,0,0,0))  ) ,
   (shape.dist(p) - shape.dist(p+(0,0.001,0,0))   )  ,
   (shape.dist(p) - shape.dist(p+(0,0,0.001,0)) ) )));
   in
   max(0,n[X])*[0,1,1]+max(0,n[Y])*[0,1,0]+max(0,n[Z])*[1,1,0]+
   -min(0,n[X])*[1,0,0]+-min(0,n[Y])*[1,0,1]+-min(0,n[Z])*[0,0,1]
   ,
  bbox : shape.bbox,
  is_2d : shape.is_2d,
  is_3d : shape.is_3d,

};

in

union( smooth 0.2 .union( union( ellipsoid (1,1.4,2) >>move(1,1,1) , cone {d:1, h:2}>>move(-1,1,0.5), cylinder {d:1, h:1.5}>>move(1,-1,1), cube 1>>move(-1,-1,1) ), morph 0.75 ( sphere 2 , cube 1.45>>offset 0.05), ), torus {major:4, minor:1}>>move(0,0,-1)

)

RGB_normal

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/doug-moen/curv/issues/19, or mute the thread https://github.com/notifications/unsubscribe-auth/AQWpJX7p9JrgSSMzs4PifdYzZr9ZGL4cks5tz-GFgaJpZM4UFo9J .

-- Do you want to make something? Here are the tools. Go

Diyode is the single most awesome toy under the tree at Christmas that I had to grow up to get

mkeeter commented 6 years ago

That's a cool demo!

Have you heard the good news about automatic differentiation?

Since Curv has full control over the evaluation and rendering pipeline, I'm curious if there are plans to get the normals at a language level, without needing to manually evaluate partial differences.

doug-moen commented 6 years ago

@mkeeter: Thanks for the link, Matt. I did not know about automatic differentiation.

Based on the article, I'd consider implementing forward accumulation, and also allow the user to directly specify the gradient of the distance function within a shape.

doug-moen commented 6 years ago

(I did have plans to provide normals at the language level, but that was years ago, and higher priority work kept getting in the way. Automatic differentiation looks easier than the symbolic differentiation that I had previously considered.)

doug-moen commented 6 years ago

Hi @TLC123, I decided to speed up your code.

First, in RGB_normal, in make_shape, in colour, I cached the value of shape.dist(p) in the variable d, and that got me from 12 to 15 FPS on my 2010 MacBook Air. This shows that the Curv compiler ought to be doing common subexpression optimization (future enhancement).

Next, I fixed a performance bug in the Curv GLSL compiler, and that got me from 15 to 30 FPS. See: https://github.com/doug-moen/curv/commit/878cf2b43825c7c873f03298ff48de4a76857839

Finally, the colour function returns linear RGB, which isn't as nice to work with as the more familiar sRGB colour space. So I added a call to sRGB to your colour function, just to see if the colours look any better. sRGB converts from sRGB coordinates to linear RGB coordinates.

Here's my revision of your code:

let
RGB_normal  shape =
    make_shape {
        dist : shape.dist,
        colour p :
            let d = shape.dist p;
                n = normalize(
                        d - shape.dist(p+(0.001,0,0,0)),
                        d - shape.dist(p+(0,0.001,0,0)),
                        d - shape.dist(p+(0,0,0.001,0)));
            in
                sRGB(max(0,n[X])*[0,1,1]+max(0,n[Y])*[0,1,0]+max(0,n[Z])*[1,1,0]+
                -min(0,n[X])*[1,0,0]+-min(0,n[Y])*[1,0,1]+-min(0,n[Z])*[0,0,1]),
        bbox : shape.bbox,
        is_2d : shape.is_2d,
        is_3d : shape.is_3d,
    };

in
union(
    smooth 0.2 .union(
        union(
            ellipsoid (1,1.4,2) >>move(1,1,1)  ,
            cone  {d:1, h:2}>>move(-1,1,0.5),
            cylinder  {d:1, h:1.5}>>move(1,-1,1),
            cube 1>>move(-1,-1,1)),
        morph 0.75 (  sphere 2 , cube 1.45>>offset 0.05)),
    torus {major:4, minor:1} >> move(0,0,-1))
>> RGB_normal
TLC123 commented 6 years ago

Thanks. Assuming Automatic differentiation is cheap shouldn't that gradient slope help determine a truer step-size in sphere tracing of unruly distance fields? There might be more benefits than mere textures.

doug-moen commented 6 years ago

I compute multiple gradients per pixel in the lighting model, so this could speed up rendering.

Sphere tracing works on distance fields that aren't differentiable. For something like a cube, the distance field isn't differentiable at edges and corners. For something like the Mandelbulb, the DF is not differentiable anywhere.

However, Sphere tracing requires the distance field to be Lipschitz(1) continuous. There are some distance fields that are not Lipschitz continuous, but which are C1-continuous (differentiable everywhere, and the derivative is continuous). For these distance fields, I could use a root finding technique that uses the derivative for ray-casting. The problem is the limited applicability. What if I union a C1-continuous DF with another shape? The output of union is not C1-continuous, so how do I render it?

What I would like is a ray-casting method that works on any continuous distance field, even if it is not Lipschitz-continuous. This method does not require differentiability, so it works on Mandelbulb. It's expected to be slower than sphere tracing. Then I can use this alternate method instead of sphere tracing if the DF is not Lipschitz.

doug-moen commented 6 years ago

I think this started as a feature request for a high level interface for making textures that have access to the normal. Okay, here's my initial idea for this.

let
gtexture f shape =
    make_shape {
        dist : shape.dist,
        colour p :
            let d = shape.dist p;
                n = normalize(
                        d - shape.dist(p+(0.001,0,0,0)),
                        d - shape.dist(p+(0,0.001,0,0)),
                        d - shape.dist(p+(0,0,0.001,0)));
            in f(p, n),
        bbox : shape.bbox,
        is_2d : shape.is_2d,
        is_3d : shape.is_3d,
    };
RGB_normal (p, n) = sRGB(
      max(0,n[X])*[0,1,1]
    + max(0,n[Y])*[0,1,0]
    + max(0,n[Z])*[1,1,0]
    - min(0,n[X])*[1,0,0]
    - min(0,n[Y])*[1,0,1]
    - min(0,n[Z])*[0,0,1]);

in
union(
    smooth 0.2 .union(
        union(
            ellipsoid (1,1.4,2) >>move(1,1,1)  ,
            cone  {d:1, h:2}>>move(-1,1,0.5),
            cylinder  {d:1, h:1.5}>>move(1,-1,1),
            cube 1>>move(-1,-1,1)),
        morph 0.75 (  sphere 2 , cube 1.45>>offset 0.05)),
    torus {major:4, minor:1} >> move(0,0,-1))
>> gtexture RGB_normal
doug-moen commented 6 years ago

I was trying to get rid of the sharp colour banding. I realized that the output of min and max in RGB_normal is not smooth, so I tried this instead:

let
gtexture f shape =
    make_shape {
        dist : shape.dist,
        colour p :
            let d = shape.dist p;
                n = normalize(
                        d - shape.dist(p+(0.001,0,0,0)),
                        d - shape.dist(p+(0,0.001,0,0)),
                        d - shape.dist(p+(0,0,0.001,0)));
            in f(p, n),
        bbox : shape.bbox,
        is_2d : shape.is_2d,
        is_3d : shape.is_3d,
    };
RGB_normal (p, n) = sRGB(
      max(0,n[X])*[0,1,1]
    + max(0,n[Y])*[0,1,0]
    + max(0,n[Z])*[1,1,0]
    - min(0,n[X])*[1,0,0]
    - min(0,n[Y])*[1,0,1]
    - min(0,n[Z])*[0,0,1]);
smooth_rgbn (p, n) = sRGB(
      smooth_max(0,n[X],1)*[0,1,1]
    + smooth_max(0,n[Y],1)*[0,1,0]
    + smooth_max(0,n[Z],1)*[1,1,0]
    - smooth_min(0,n[X],1)*[1,0,0]
    - smooth_min(0,n[Y],1)*[1,0,1]
    - smooth_min(0,n[Z],1)*[0,0,1]);

in
union(
    smooth 0.2 .union(
        union(
            ellipsoid (1,1.4,2) >>move(1,1,1)  ,
            cone  {d:1, h:2}>>move(-1,1,0.5),
            cylinder  {d:1, h:1.5}>>move(1,-1,1),
            cube 1>>move(-1,-1,1)),
        morph 0.75 (  sphere 2 , cube 1.45>>offset 0.05)),
    torus {major:4, minor:1} >> move(0,0,-1))
>> gtexture smooth_rgbn
TLC123 commented 6 years ago

Brilliant. Works like a charm.
alt

Just one question. I tried and failed. What if i need to pass say two arguments a and b or maybe a list to my f function. Some times i do, sometimes i don't RGB_normal a b (p, n) = sRGB(... ... ... ...>> gtexture RGB_normal 10 .7

doug-moen commented 6 years ago

@TLC123 The short answer is: use gtexture (RGB_normal 10 .7).

The reason that gtexture RGB_normal 10 .7 doesn't work is: you are passing 3 arguments to gtexture. The first argument is RGB_normal, the second argument is 10, the third argument is .7. But gtexture takes 2 arguments, a function and a shape.

A more detailed answer is: if you call a function, passing fewer arguments than are required, then this is legal, and what you get back is another function that consumes the remaining arguments. This is called 'Currying', and Curv functions with 2 or more arguments are called 'Curried functions'.

In the example, you define

RGB_normal a b (p,n) = ...;

So RGB_normal takes 3 arguments: the third argument is the pair (p,n). If you write (RGB_normal 10 .7), this is called partial application, and you get back another function that consumes a single argument (p,n).

More generally, if f takes 3 arguments, then you call it by writing f a b c. This is equivalent to writing

((f a) b) c