0

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

Community
  • 1
  • 1
loremIpsum1771
  • 2,497
  • 5
  • 40
  • 87
  • where are the details? how do you pass sphere x,y,z,r to shader? what kind of ray tracing are you using (forward,back,what are the properties of rays...)? we do not know what is behind your partial code and doubt anyone will try to decode it ... usually is a good idea to add some text describing what the code does and how (comments in code are sufficient only for those who now what the code should do and how)... – Spektre Sep 29 '15 at 07:49
  • @Spektre Yes, sorry about that. I added some additional information to the post. If any additional clarification is needed, let me know. – loremIpsum1771 Sep 29 '15 at 22:03

1 Answers1

2

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

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Thanks for the answer! Right now it seems like I'm getting the different spheres to at least be calculated using the iterative solution (i.e. #1) ; however, it seems that when they are being rendered, they are being superimposed on top of one another. Do you know how render the spheres in different positions (without modifying the vertex shader)? I tried shifting the Vertex origin of the ray but that doesn't seem to have an effect. – loremIpsum1771 Oct 05 '15 at 18:51
  • 1
    @loremIpsum1771 both approaches need a slight change in shaders so show the code you have after the change ... and some image of output would be OK too. How do you pass the spheres (texture,uniform,?)? – Spektre Oct 06 '15 at 06:46
  • I just updated the post with the code for the vertex and fragment shaders (except the definition of the noise function). I have a vec3 of varying variables that are passed to the fragment shader in order to calculate the vertex position of the ray used to shade the sphere. – loremIpsum1771 Oct 06 '15 at 13:29
  • @loremIpsum1771 you do not have any uniform array with sphere `x,y,r` nor any texture with such info needed for #1 approach. The only thing I see is hard-coded `spheres[]` array but the usage looks suspicious if I get it right all the spheres are initialized with the same center `(x,y)` so when you do not use any matrices to rotate view (the axises are the same as screens) they are rendered on top of each other ... – Spektre Oct 06 '15 at 14:28
  • Ok, I'm actually figuring this stuff out as I go along lol. I tried manipulating the vPosition.x and vPosition.y for each sphere; however nothing seems to be working. Is there a way to make multiple spheres just within the fragment shader? – loremIpsum1771 Oct 06 '15 at 15:07
  • @loremIpsum1771 1. you set list of spheres `varying vec4 spheres[]={ {x0,y0,z0,r0},{x1,y1,z1,r1},.... ,{0.0,0.0,0.0,-1.0} }` ideally in Vertex shader so it does not initialize on each pixel ... and pass the vPosition as you already have 2. in fragment compute color for example `vec3 col=vec3(0.0,0.0,0.0); for (i=0;spheres[i].w>=0.0;i++) if length(vPosition.xyz-spheres.xyz)<=spheres[i].w) col=vec3(1.0,1.0,1.0); gl_FragColor.xyz=col;` when working then add shading and move the spheres to texture or uniforms ... – Spektre Oct 06 '15 at 15:19
  • Thanks for the code. I'm just curious why is there the check for ```if length(vPosition.xyz-spheres.xyz)<=spheres[i].w)``` ? Does checking the value of i not work? Also, I was checking before to see if the value of z is greater than 0 (which creates the shape of the sphere out of the square). Would that cause a problem when adding the code for multiple spheres. – loremIpsum1771 Oct 06 '15 at 16:28
  • 1
    @loremIpsum1771 that if is just a termination if you read my `spheres[]` list I ended it with negative radius ... that is where the for stops ... but you can also have a variable/constant how many spheres are there then it would be `i – Spektre Oct 06 '15 at 19:20