pouët.net

Please help me understand this GLSL code.

category: code [glöplog]
Hi!

First things first. If you still have all your wisdom teeth ... be happy. I had one of mine
pulled out two weeks ago, friday night, and it's been a week of pain,
with half my face burning on the inside.

Nooooow that out of the way, the last days I've been busy again, trying to render spheres.
I'm very happy about all the help I got in my last thread,
but for now I have to look at something else.

And as usual I suck. :)

After failing considerably, even though I understand how it works
and even though testing extensively with code from shadertoy,
it seems my biggest obstacle seems to be vector math?

I don't even know.

I've got the below from shadertoy. I added the texture read for my spheres
and changed names of variables to explain their function.
No clue if I am right, tbh, so input's appreciated.
Always.


Code:float MaxStep = 999.9; MinStep = 9999.9; for (int k = 0; k < 100; k++) { Sphere = normalize(texelFetch(Spheres,ivec2(k,0),0).rgb)*5;//vec3(0,0.5,0); MinStep=999.9; vec3 Vector2Sphere = Sphere - ro; float Angle = dot(Forward,Vector2Sphere); float Distance2Sphere = 0.5*0.5 - dot(Vector2Sphere,Vector2Sphere) + Angle*Angle; if (Distance2Sphere > 0.0) MinStep = Angle - sqrt(Distance2Sphere); // hu? float f = sqrt(Distance2Sphere); float t = Angle - f; if ((MinStep > 0.001) && (MaxStep > MinStep)) { MaxStep = MinStep; dist = ro + (MaxStep * Forward); } }

BB Image

This produces the image as it should be.
Assuming I understand it, it runs classic ray-sphere intersection tests and picks the closest, but that's not what I wanted to do.
I consider the ray-marching variant to be faster than this, yet this is the fastest code so far.

My first question:
When I remove the if, nothing changes. As long as "MinStep = Angle - sqrt(Distance2Sphere);" is there, it works the same as with the condition.
It seems kind of nonsensical to me anyway.

Why is the condition needed?


So I've tried implementing how I learned raymarching works.
Code: for (int m=0; m < 10; m++) { rs=RayOrigin; MinStep=1000; for (int k = 0; k < 100; k++) { Sphere = normalize(texelFetch(Spheres,ivec2(k,0),0).rgb)*5; MinStep = min(distance(rs,Sphere)-0.5,MinStep); } if (MinStep < 0.5) // I'm in a sphere of the radius 0.5 ! { vec3 SphereNormal = normalize(Sphere - rs); vec3 L = normalize(Sphere-vec3(0,100,100)); float diffuse = max(dot(L,SphereNormal),0.0); gl_FragColor.rgb = vec3(diffuse);//vec3(1.0-dot(ro,rs)); return; } rs += MinStep * Forward; }

BB Image
My code runs through every sphere, checks the distance and compares to the saved shortest distance.
Then I check if the last step put me inside a sphere and if so I output the color.
If not I take one step forward, aka "normalize(vec3(q, FoV));" from shadertoy.

As you can see, it doesn't work like that. For some reason I don't step onto the edge of any sphere. When moving around, over/mis-steps are clearly visible and the colours are off as a result as well.

But WHY? WHY? I don't get it ......................


And here's my third attempt at it, even simpler.
Code: for (int k = 0; k < 100; k++) { Sphere = normalize(texelFetch(Spheres,ivec2(k,0),0).rgb)*5; l = distance(ro,Sphere)-0.5; test = ro + l * Forward; float l2 = distance(test,Sphere) - 0.5; if (l2 < 0.5) { vec3 SphereNormal = normalize(Sphere - test); vec3 L = normalize(Sphere-vec3(0,0,0)); float diffuse = max(dot(L,SphereNormal),0.0); gl_FragColor.rgb = vec3(diffuse); return; } }

BB Image
When I wrote this, I thought I'm just overlooking something and do it the easiest and failsafest way possible.
So I calculate the distance from RayOrigin to Sphere (-radius ofc),
place the ray at that point, then measure the distance again.

It makes sense in my head. If that sphere is somewhere on this ray,
then when I add the distance, I must either be inside or not.

It works. Somehow. For some reason, it displays circles.
I failed to change it into something different.


So ... what am I really missing here?
What knowledge am I lacking to understand what's going wrong?
Did I do ray-marching right, but somehow implement it the wrong way?

It's really not that hard!
Measure the distances to each center of each sphere, minus it's radius.
The shortest distance wins and is added to the position of the ray!
Then the same repeats until the ray ends within a sphere!


I'm not happy with using the ray-sphere intersection tests. I want to use my own code
and I want to understand what is going on in the above and what I'm doing wrong.

I have no idea where else to ask.
Any advice and input on how I can improve myself and how the above works is greatly appreciated!


Thank you!
No energy to read through the code now, but a general comment on tracing (intersection test) vs. ray marching (SDFs):

- The ray intersection test is much slower than the distance function for spheres, BUT you then jump directly to the surface. With the raymarch you might need 100 iterations to reach the surface. So I'd guess in most cases the straight raytrace will be faster.

- The other side of that: Add some distortion or whatever to the spheres. It's really easy with raymarching, very hard with traditional tracing. It's much easier to build a complex scene with distance functions.
added on the 2015-09-15 23:34:26 by psonice psonice
im not a coder but let me try..

on roland mc-303 the decays are relative.
on some preset sounds you can't do decay to zero.

your "if" looks like coder equivalent of "no can do"

..and "if"'s after that like "why not ?"

..fake it..is my aswers
added on the 2015-09-16 00:02:53 by 1in10 1in10
Quote:
Assuming I understand it, it runs classic ray-sphere intersection tests and picks the closest, but that's not what I wanted to do.
I consider the ray-marching variant to be faster than this, yet this is the fastest code so far.

Your consideration is vastly off. ;)

Classic ray-sphere intersection tests = 100 x ray-sphere tests.
Ray-marching: 100 steps (at least) x 100 sphere distance calculations.

Ray-marching is searching for the object in empty space.
Analytical ray intersection is just finding the object in a single hit. It's bound to be much faster.

Quote:
When I remove the if, nothing changes. As long as "MinStep = Angle - sqrt(Distance2Sphere);" is there, it works the same as with the condition.
It seems kind of nonsensical to me anyway.

You mean the "if(Distance2Sphere>0.0)"? Well, if the "if" is not there, then you start square rooting negative values. The result is undefined by the spec, but modern GPUs comply to IEEE floating point standards and will probably give you a NaN result (Not a Number).

The NaN value has two main traits:
- it propagates through all arithmetics (Angle - NaN = NaN),
- it fails ALL comparison operators (NaN > 0.001 == false)

So the code works as before. But still, I wouldn't count on this behaviour to be portable.

Quote:
As you can see, it doesn't work like that. For some reason I don't step onto the edge of any sphere.

Good luck getting anywhere close to any sphere with only 10 steps. 100 steps would be probably the smallest number I'd consider to be starting with. Maybe it could be lower if visual errors are acceptable, but usually it would be much higher. For reference, in Volumiscope I'm using 400 steps.

Quote:
if (MinStep < 0.5) // I'm in a sphere of the radius 0.5 !

No, you are not. :) You are closer than 0.5 to a sphere (remember, you already subtracted 0.5 during step calculation), but not in the sphere. As long as you compute distances right, raymarching will never step inside a solid object.


Feel free to shoot more questions if something is still unclear. :)
added on the 2015-09-16 08:52:10 by KK KK
Omg thank you psonice and KK!

So what am I doing wrong? The top code is the only one looking right
and I don't understand how to fix the issue.

So I should just drop it and use plain intersection tests?
Nice 8k, KK! :D

And I guess I'll just stick with intersection tests for this.
Searching for my balls makes no sense, when I know they're on screen.
I'd still suggest trying the raymarching. Not for this scene, of course, but to get familiar with the technique. Also when raymarching, consider switching to gradient method to get normal vector for lighting, because then you won't have to compute them by hand (which may be close to impossible for some shapes).
added on the 2015-09-16 11:43:24 by KK KK
I've implemented ray marching (not distance fields) through a 3d simplex noise.
It's not super-fast, but it works. There i use gradients.
It was at a sufficient working stage that I was able to move on to something else.

Btw ... why would anyone use distance fields for spheres,
when it's more efficient to just do intersections?

It confuses me. Using length I should be able to hit the sphere,
but somehow that doesn't work and I don't understand why.
See above code.
Quote:
Btw ... why would anyone use distance fields for spheres,
when it's more efficient to just do intersections?

He will not. As long as they are simple, separate spheres. But make infinite grid of them, add procedural displacement, CSG or make metaballs, and things no longer look so simple.

As long as you need just spheres, analytical intersections are more than perfect. But I'm sure that you will want to move to more complex objects soon enough, so learning how raymarching/spheretracing works will be the next step anyway.

But do use intersections if all you need is spheres for this particular demo/intro. Of course you can mix the two techniques (analytics for simple stuff, raymarching for more complex) just fine.


Quote:
It confuses me. Using length I should be able to hit the sphere,
but somehow that doesn't work and I don't understand why.
See above code.

The third code fragment shows just a loop out of context, so I can't really say what it does. If you could show something more (at least including whole stepping loop), that would help me understand what you did.
added on the 2015-09-16 12:04:54 by KK KK
Hm. I thought it was self explanatory.

There's no stepping involved.
I read a sphere's coordinates from the texture and spread it out a bit with *5.
I measure the distance to that sphere, minus the radius, to hit its surface.
Then I extend a ray from origin using this distance.
After that I check the distance to the sphere again.

That seemed a failsafe way to test if the ray, with its extended length, was in the sphere.
And it is, basically, as the third screenshot shows. What confuses me is that it displays shaded circles instead of actual spheres. I fully expected this to result in the same as the first one.

It's more or less a weird way to do intersection tests and it's actually a bit faster right now.
The distance you compute is shortest distance from camera to sphere center. But that distance is not the distance to sphere surface along the ray. So by moving by this distance you won't reach the surface unless the ray points directly at the sphere. In effect you are shading a point in 3D space, not on the sphere. To do it properly you really have to do proper test, like in first case.

Also as you don't compare sphere distances, the first one that is "hit" will be displayed even if there is something closer to camera.
added on the 2015-09-16 13:55:14 by KK KK
I don't get it.

l = distance(ro,Sphere)-0.5;

If this doesn't give me the distance from origin to sphere surface,
what does it give me then?
Oh and ofc 0.5 would be the radius.
Yes, but this is shortest distance. You need the distance along the current ray.
added on the 2015-09-16 14:47:02 by KK KK
Yes. Which I was doing.

I take the distance l and then extend the ray that goes through the current pixel,
by ray = l * forward. I described it above...
And from that position I again measure the distance to the sphere's surface.

if the ray is inside, I color the pixel.

Is the issue that the ray is indeed inside, instead of at the surface?

So I'd have to correct the position? But that's weird, because all I do
is measuring towards its surface.....
Quote:
if the ray is inside, I color the pixel.

Is the issue that the ray is indeed inside, instead of at the surface?


No, you can't step inside the geometry that way in sphere tracing (well, perhaps by floating point aliasing but I doubt even that would be a problem in practice). Assuming the case we're only interested in positive values of the SDF, you'll only get smaller and smaller values until you reach a surface of the geometry. That's when the SDF returns 0 and your marching will stop. I practice though you'll want to stop the marching at some value epsilon that is just some small value, say 0.001 that is "close enough" to the surface so that it affects the visuals minimally and doesn't cause artifacts.

It doesn't matter where do you the shading since the distance function is (should be) continuous and defined everywhere, so it will look good as long as your epsilon is reasonably small (0.5 most likely isn't, even close).
added on the 2015-09-16 17:19:04 by noby noby
I have no idea what this has to do with it now...

The third one isn't raymarching.

------------------
There's no stepping involved.
I read a sphere's coordinates from the texture and spread it out a bit with *5.
I measure the distance to that sphere, minus the radius, to hit its surface.
Then I extend a ray from origin using this distance.
After that I check the distance to the sphere again.
------------------

I feel like we're not talking about the same thing.
I'm questioning why the third code produces circles instead of spheres.

The code doesn't march at all.

Code:for (int k = 0; k < 100; k++) { Sphere = normalize(texelFetch(Spheres,ivec2(k,0),0).rgb)*5; l = distance(ro,Sphere)-0.5; test = ro + l * Forward; float l2 = distance(test,Sphere) - 0.5; if (l2 < 0.5) { vec3 SphereNormal = normalize(Sphere - test); vec3 L = normalize(Sphere-vec3(0,0,0)); float diffuse = max(dot(L,SphereNormal),0.0); gl_FragColor.rgb = vec3(diffuse); return; } }

It extends the ray with the length to one sphere of the list
and then checks that extended ray if it's in the sphere.
If it's not, then there's no hit and it checks for the next sphere.

It's basically the ELI5 for ray-sphere intersection and not ray marching.

Yet for some reason it draws circles instead of spheres.

Glad I'm at my computer again so I can find out why it does.
Quick comments on the ray marching code:

Code:for (int m=0; m < 10; m++)


Only 10 marching steps, not much for a ray marcher!

Code:{ rs=RayOrigin;


So rs is the ray origin. This is where it all goes wrong :)

Code: MinStep=1000; for (int k = 0; k < 100; k++)


100 texture samples per step is a *lot*. You probably want less for realtime!

Code: { Sphere = normalize(texelFetch(Spheres,ivec2(k,0),0).rgb)*5; MinStep = min(distance(rs,Sphere)-0.5,MinStep);


You're calculating the closest distance from rs, *which you set to the ray origin* at the start of every iteration.

Code: } if (MinStep < 0.5) // I'm in a sphere of the radius 0.5 !


This will only be true if the ray origin is inside the sphere. Also, MinStep is the distance from the centre of the sphere minus 0.5. So if MinStep is <0.5, it implies the point is within 1.0 distance, not 0.5.

Code: { vec3 SphereNormal = normalize(Sphere - rs);


Normal = sphere centre minus ray origin. This is why it looks wrong, it should be the point on the surface of the sphere, not the ray origin.

Code: vec3 L = normalize(Sphere-vec3(0,100,100)); float diffuse = max(dot(L,SphereNormal),0.0); gl_FragColor.rgb = vec3(diffuse);//vec3(1.0-dot(ro,rs)); return; } rs += MinStep * Forward;


This does nothing, since you set "rs=RayOrigin" at the start of the next iteration.

Code: }
added on the 2015-09-16 18:55:36 by psonice psonice
Haha, that was the wrong piece of code...
... but yes, damn, rs is wrong.

And the 100 work fine at 70fps ... I'll go over this again.

And damn, the normal is wrong!

Thanks! I thought it works from the center,
as it's the same direction anyway... I thought.
That rs=RayOrigin was added accidentially by me in this post,
I thought it makes sense to write it so it's known what rs is.

I checked, it's not there.

I'm confused.
I've changed the texture to a proper samplerbuffer ...
... and now none, but the proper ray-intersection code give me an output anymore.


I hate this shit so much ...
Ha!

Code: for (int m=0; m < 10; m++) { MinStep=1000; for (int k = 0; k < 100; k++) { Sphere = texelFetch(Spheres,k).rgb;//vec3(0,0.5,0); MinStep = min(distance(rs,Sphere)-0.5,MinStep); } if (MinStep < 0.001) // I'm in a sphere of the radius 0.5 ! { vec3 SphereNormal = normalize(Sphere - rs); vec3 L = normalize(Sphere-vec3(0,0,-100)); float diffuse = max(dot(L,SphereNormal),0.0); gl_FragColor.rgb = vec3(diffuse);//vec3(1.0-dot(ro,rs)); return; } rs += MinStep * Forward; }

This works! It produces distances between the spheres, (they're tightly packed together)
but it works. It's also horribly slow, but it works!

Okay I get this now. First I was checking for "< 0.5" and now I understand how that made no sense.
Though why does 0.001 give me gaps between the spheres... sheesh.

I really just need to stick to the basic intersection test.

I'll never understand this stuff. -.-
Quote:
Though why does 0.001 give me gaps between the spheres... sheesh.

Raymarching has severe problems when passing close to the objects, but not hitting them.

Let's say a ray passes 0.002 from the first sphere. It will not trigger a "hit" and will continue stepping. But at the same time the nearest distance is only 0.002 so it will step conservatively by 0.002 (because larger step could miss something). You have only 10 steps here, so a ray stuck in small steps won't have time to hit anything.

Raymarching usually hits surface quite fast, except that near miss cases when it's very slow (but fortunately, that's only a small number of edge pixels). That's why I suggested to start from 100 steps, not 10.
added on the 2015-09-16 22:08:45 by KK KK
Ah! Now I get that one too! Thanks!
I didn't realize that the amount of steps is relevant in regards to closeness and accuracy!

In the mean time I've implemented floating point buffer textures,
saving sphere.xyz and radius as w. Using the standard intersection and
mowing through 128 spheres I get constant 80+ fps.

Next step is to implement some grid to reduce the number of intersections
per section of the viewport. Still wrapping my mind around that.

I'm still wondering when marching would actually pay off performance wise...


Errr I know I'm derailing my own thread...
... but I'm learning from you guys and just keep going. ^_^

login