Skip to content
This repository has been archived by the owner on Sep 27, 2023. It is now read-only.

Normal argument to texturing #19

Open
TLC123 opened this issue May 19, 2018 · 11 comments
Open

Normal argument to texturing #19

TLC123 opened this issue May 19, 2018 · 11 comments

Comments

@TLC123
Copy link
Contributor

TLC123 commented May 19, 2018

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
Copy link

Automageek commented May 19, 2018 via email

@mkeeter
Copy link

mkeeter commented May 19, 2018

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
Copy link
Member

@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
Copy link
Member

(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
Copy link
Member

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: 878cf2b

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
Copy link
Contributor Author

TLC123 commented May 20, 2018

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
Copy link
Member

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
Copy link
Member

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
Copy link
Member

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
Copy link
Contributor Author

TLC123 commented May 20, 2018

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
Copy link
Member

@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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants