loremIpsum1771
loremIpsum1771

Reputation: 2527

How to render multiple spheres in a glsl fragment shader

I've written a fragment shader that uses simple ray tracing to render a sphere inside of a fragment shader that diffusely shades the shape. I can get this to work for one sphere; however, when I try to make multiple spheres, only one can be seen. In the main() function of the shader, I basically am doing the same process I did to render the one sphere for each additional sphere that I want to make, but this doesn't seem to be working currently.

Additional Info:

The vertex of the sphere (x,y,z,r) are found by using: the vertex positions for x and y (i.e. float x = vPosition.x; and float y = vPosition.y;). The vertex position for z is computed using the computeZ() function found in the code below. Lastly, for sphere.r, I used the ray's origin vertex (V) + the ray's direction (W). The rays themselves are shooting from an origin point into the screen and determining the color of of sphere based on whether or not it has hit the sphere (I have the code for the functionality that does this in raySphere().

I think I just need to make an array in main to store the spheres and then to make a for loop to call the raySphere() and shadeSphere() functions for each sphere in the array (this is what I tried originally but it didn't work).

In main, after initializing the one sphere's vertex positions and the V and W for the ray, I have an if statement that checks if z is greater than 0 meaning that the shader is currently inside of the sphere so it can proceed with the shading, and otherwise do nothing. Since defining the bounds of the sphere this way, I wasn't sure how to have this same logic apply for multiple spheres using the for loop solution I mentioned previously. Also in main, I check to see whether the t values (for each sphere) is less than 10000 and if so, call the shadeSphere() function to shade the sphere; however, my current logic also isn't working for multiple spheres. The turbulence and fractal functions are just to add procedural texturing and they are long so I won't add the code for them.

What is the correct way to make multiple spheres?

Edit:

*It seems that the spheres are being rendered on top of each other based on the fact that I have an if statement that sets the gl_FragColor for each sphere and the only color actually being rendered is the one set in the last statement (see the end of the code)

I have the relevant code below:

<script src=lib1.js></script>

<body bgcolor=black>
<center>
<td><canvas id='canvas1' width=550 height=550></canvas></td>
</center>
</body>

<script id='my_vertex_shader' type='x-shader/x-vertex'>
   attribute vec3 aPosition;
   varying   vec3 vPosition;
   void main() {
      gl_Position = vec4(aPosition, 1.0);
      vPosition = aPosition;
   }
</script>

<script id='my_fragment_shader' type='x-shader/x-fragment'>
   precision mediump float;
   uniform float uTime;
   uniform vec3  uCursor;
   varying vec3  vPosition;
   vec4 sphere;
   vec4 sphere2;
   vec3 material;
   vec3 Lrgb;
   vec3 Ldir;
    
    float computeZ(vec2 xy, float r) {
      float zz = (r * r - xy.x * xy.x - xy.y * xy.y)/.5;
      if (zz < 0.)
         return -1.;
      else
         return sqrt(zz);
   }
   // Compute intersection of a ray with a sphere, if any.  Return t.
   // If there is no intersection, return 10000.
   float raySphere(vec3 V, vec3 W, vec4 sph) {
        //float r = 1.0;
        //float b = 2.0* dot(V,W);
        //float c = dot(V, V) - (sph.w * sph.w);
        //float h = b*b - 4.0*c;
        //float t = (-b - sqrt(h))/2.0;
        //if(h <0.0  || t < 0.0 ) return 10000.;
        //return t;
        float b = 2.0 * dot(V -= sph.xyz, W);
        float c = dot(V, V) - sph.w * sph.w;
        float d = b * b - 4.0 * c;
        return d < 0.0 ? 10000. : (-b - sqrt(d)) / 2.0;
   }
   // Diffusely shade a sphere.
   //    point is the x,y,z position of the surface point.
   //    sphere is the x,y,z,r definition of the sphere.
   //    material is the r,g,b color of the sphere.
   //vec3 shadeSphere(vec3 point, vec4 sphere, vec3 material, float s) {
   vec3 shadeSphere(vec3 point, vec4 sphere, vec3 material) {
      vec3 color = vec3(1.,2.,4.);
      vec3 N = (point - sphere.xyz) / sphere.w;
      float diffuse = max(dot(Ldir, N), 0.0);
      vec3 ambient = material/5.0;
      //color = ambient + Lrgb *s *diffuse *  max(0.0, dot(N , Ldir));
      color = ambient + Lrgb * diffuse *  max(0.0, dot(N , Ldir));
      return color;
   }

void main(void) {
      vec2 c = uCursor.xy;
      Lrgb = vec3(1.,.5,0.);
      Ldir = normalize(vec3(c.x, c.y, 1. - 2. * dot(c, c)));
      float x = vPosition.x;
      float y = vPosition.y;
      float z = computeZ(vPosition.xy, 1.0);
      // COMPUTE V AND W TO CREATE THE RAY FOR THIS PIXEL,
      // USING vPosition.x AND vPosition.y.
      vec3 V, W;
      W = normalize(vec3( 2.0,0.0,1.0 ));
      vec4 spheres[3];
      if(z > 0.){
      //sphere = vec4(x,y,z,V + dot(W,vec3(1.,1.,1.)));
      //sphere2 = vec4(x+10.,y+10.,z+10.,V + dot(W,vec3(1.,1.,1.)));
      vec2 uv = vPosition.xy/uCursor.xy;
      //generate a ray 
      //V = vec3(0.0, 1.0, 3.0);
      //W = normalize(vec3((-1.0 + 2.0   )*vec2(1.78,1.0), -1.0));
      //SET x,y,z AND r FOR sphere.
      //SET r,g,b FOR material.
      vec3 material = vec3(4., 1., 3.);
      vec3 color = vec3(0., 0., 0.);
      float t = 0.;
      
      for(int i = 0; i < 3; i++){
        
        if(i == 0){
            V  = vec3(2.0,1.0,.0);
            spheres[i] = vec4(x,y,z/2.,V + dot(W,vec3(1.,1.,1.)));
            float t = raySphere(V, W, spheres[i] );
        }
        if(i == 1){
            V  = vec3(100.0,500.0,.0);//attempt to move the vertex of the ray for the second sphere
            spheres[i] = vec4(x,y,z/5.,V + dot(W,vec3(1.,1.,1.)));
            vec3 shift1 = vec3(30.,30.,30.);
            vec3 newPoint = shift1 + V;
            float t = raySphere(newPoint, W, spheres[i] );
        }
        if(i == 2){
            V  = vec3(500.0,1.0,.0); //attempt to move the vertex of the ray for the third sphere
            spheres[i] = vec4(x,y,z/7.,V + dot(W,vec3(1.,1.,1.)));
            vec3 shift2 = vec3(50.,50.,50.);
            vec3 newPoint2 = shift2 + V;
            float t = raySphere(newPoint2, W, spheres[i] );
        }
        //float t2 = raySphere(V, W, sphere2);
        
        //float s = sin((uTime));
        vec3 time = vec3(uTime*2., 1.,1.);
        //float s = tan((tan(sphere.z)/tan((time)*.90+200.0)));
        if (t < 10000.)
            //float s = (sin(sphere.x)/cos(uTime*1.123+200.0));
            //if(i == 0)
                color = shadeSphere(V + t * W, spheres[i], material);
            //if(i == 1)
                //color = shadeSphere(V + t1 * W, sphere[i], material);
            //if(i == 2)
                //color = shadeSphere(V + t1 * W, sphere[i], material);
            //color = shadeSphere(V + t1 * W, sphere, material,s);
        
            //if (t2 < 10000.)
            //color = shadeSphere(V + t2 * W, sphere, material,s);
        
      color.r = 0.5;
      color = pow(color, vec3(.45,.45,.45)); // Do Gamma correction.
      //float d = dot(vec3(x,y,z), vec3(1.,1.,1.));
      //if (d > 0.)
        //    s += 0.6 * d;
      //gl_FragColor = vec4(color, 1.);        // Set opa   city to 1.
      if(i == 0)
        gl_FragColor = vec4((color) * vec3(5.0, 1.0, 4.5), 1.);
      if(i == 1)
        gl_FragColor = vec4((color) * vec3(1.0, 3.0, 7.5), 1.);
      if(i == 2)
        gl_FragColor = vec4((color) * vec3(3.0, 4.0, 8.5), 1.);
      
      }
      } //Close brace for z-check
   }
   
   
</script>

Image of sphere 3 appearing to be rendered on top of the other two spheres

enter image description here

Upvotes: 0

Views: 1701

Answers (1)

Spektre
Spektre

Reputation: 51845

So if I get it right:

  1. all of your spheres are on the same plane coplanar with projection plane
  2. all spheres are at radius r=1.0
    • deduced from: float z = computeZ(vPosition.xy, 1.0);
  3. vPosition is passed from vertex shader
    • and contains interpolated screen position of fragment

Unknown things needed to be cleared:

  1. What and how is passed to GL?

    I got the impression you pass single Vertex per sphere (as which primitive?) that is clearly wrong unless you have geometry shader present. But it is hard to say because your Question has no info about that. No uniforms, no attributes, no interpolators what so ever and also no code for connection to GLSL

How to do it?

I see 2 basic options:

  1. pass single quad covering the whole screen

    This way the fragment shader will loop through all pixels of screen so you need some kind of array with x,y,r per sphere to be present per each fragment call. The only reliable way I know of is to use textures

    • you can create 1D RGB texture where x=B; y=G; r=R;
    • or 3x 1D float textures one for x,y,r
    • or 1x 1D float texture with sphere packed into 3 texels...

    Then in each fragment call loop through all spheres ...

  2. pass single quad per each sphere

    So you can render 2D Quad with endpoints (x0,y0),(x1,y1) in place of each sphere.

    • the mid of the Quad is sphere center (x,y) = ( 0.5*(x0+x1) , 0.5*(y0+y1) )
    • and the radius of the sphere is r = 0.5*|x1-x0| = 0.5*|y1-y0|

    So compute x,y,r inside vertex shader and pass it to fragment shader

[Notes]

These two ways have their pros/cons so chose which one is better for you

  • taking into account your performance needs
  • and what is closer to your way of doing things ...

Look here Draw Quadratic Curve on GPU it is very similar task

Upvotes: 2

Related Questions