#3D Metaballs using Raymarching

32 messages · Page 1 of 1 (latest)

primal sentinel
#

(I sent some messages before, but my information wasn't exactly correct. So, i deleted my previous messages and am starting fresh.)

#

I took a look at the shader that you sent. It appears to use an SDF to do the raymarching for the spheres. The SDF (signed distance function) basically answers the question, "Given a point p in 3D space, how far is p from the surface we are trying to render?" This is implemented in the function called get_distance() (which makes use of other functions, such as sdSphere()). Having an exact answer to this question (which you do for spheres) allows you to to very quickly raymarch/render the surface

#

As far as i understand, the issue with metaballs is that you don't have an exact answer for this question. You can't calculate an exact SDF for the surface of a metaball. The best you can do is to use the gradient to get an approximate SDF. I think using the gradient of the metaball might work for this shader, but its going to be a lot more computationally intensive than rendering the spheres. The reason for this is, because the gradient is only an approximation, it's probably going to have to loop a lot more times for each pixel in order to find the surface. Also, i think the equation for a metaball is more complicated to calculate that the equation for a sphere (or even for many spheres), so that's going to be a little more computationally intensive as well

#

I might be wrong about metaballs and SDFs (i haven't studied them formally... i'm just basing this on my own internet research). So if you can find an SDF for metaballs anywhere, then you should be able to just take that and plug it in to the get_distance() function

#

Otherwise, you can still use this shader, but it might not be very performant. You'll need to decide on a metaball equation (you can find a common one on Wikipedia: https://en.wikipedia.org/wiki/Metaballs#Implementation). Then you'll need to find the gradient for that function. Then, i believe you should be able to plug the gradient into the get_distance() function and it will work

#

Anyway, hope that helps! I'd be happy to try and answer any specific questions you might have about this. I attempted something similar a few months ago

gritty nexus
#

@primal sentinel This would make sense, but there seems to be a problem with the actual shader. I assume its supposed to render a single sphere (by cometing p = opRep(p, vec3(3));) but it doesnt, it instead has seemingly random colors and the background is black instead of transparent

#

i am new to 3d shading though, do you have any insight on this?

primal sentinel
#

Let me try it out on my computer and see if i can get it to work

primal sentinel
gritty nexus
#

yeah, this is my layout

#

the extra cull margin is set to the maximum as well

primal sentinel
#

Cool, and you added the shader to that quad? What does it look like?

gritty nexus
#

im also using compatibility mode, but the shader should be able to support it

primal sentinel
#

When i switch to compatibility, it stops working too

gritty nexus
#

ah, i guess its a lost cause

#

unless theres another method to render metaballs?

primal sentinel
#

It looks like depth is calculated differently in the compatibility renderer

#

In my scene, this is what depth looks like on the mobile renderer

#

And this is what it looks like in the compatibility renderer

#

This should be a simple fix, let me see if i can figure it out

#

The docs have this info, which i think influences the depth texture:

#

Okay, i haven't figured it out, but i've got to go right now

gritty nexus
#

Its good, ill post here if I find anything

primal sentinel
#

If you really want to be able to do this in the compatibility renderer, you might ask someone in #shaders-and-vfx how to correctly calculate NDC and view position from the depth texture while in the compatibility renderer

primal sentinel
#

Okay, there were just a couple lines in the fragment() function that needed to be changed. Now it should work with the compatibility renderer

#
shader_type spatial;
render_mode unshaded;

const int MAX_STEPS = 300;
const float MAX_DISTANCE = 1000.0;
const float MIN_DISTANCE = 0.001;

uniform sampler2D DEPTH_TEXTURE : source_color, hint_depth_texture;

void vertex() {
    POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}

float sdSphere (vec3 p, vec3 centrum, float radius) {
    return length(centrum-p) - radius;
}

// infinte repetitions
vec3 opRep(vec3 p, vec3 c) {
    vec3 q = mod(p+0.5*c,c)-0.5*c;
    return q;
}

float get_distance(vec3 p) {
    //p = opRep(p, vec3(3)); // uncomment for repeating spheres
    return sdSphere(p, vec3(0), 1.0);
}

vec3 get_normal(vec3 p) {
    vec2 e = vec2(1e-3, 0);
    
    vec3 n = get_distance(p) - vec3(
        get_distance(p - e.xyy),
        get_distance(p - e.yxy),
        get_distance(p - e.yyx)
    );
    
    return normalize(n);
}

vec4 raymarch(vec3 ray_origin, vec3 ray_dir, float depth) {
    float t = 0.0;
    vec3 p = ray_origin;
    for (int i = 0; i < MAX_STEPS; i++)
    {
        float d = get_distance(p);
        t += d;
        if (t > MAX_DISTANCE || t >= depth)
            break;
        
        p += d * ray_dir;
        if (abs(d)  < MIN_DISTANCE)
            return vec4(get_normal(p), 1);
     }
    //return vec4(float(i) * 0.01); // uncomment for simple glow effect
    return vec4(0.0);
}

void fragment() {
    float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
    vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;        // changed
    vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
    view.xyz /= view.w;
    float linear_depth = -view.z;
    
    vec3 weird_uv = vec3(SCREEN_UV * 2.0 - 1.0, -1.0);    // changed
    vec4 camera = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(weird_uv, 1);
    
    vec3 ray_origin = INV_VIEW_MATRIX[3].xyz;
    vec3 ray_dir = normalize(camera.xyz);
    
    vec4 col = raymarch(ray_origin, ray_dir, linear_depth);
    
    ALPHA = col.a;
    ALBEDO = col.rgb;
}```