Reputation: 2527
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.
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?
*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
Upvotes: 0
Views: 1701
Reputation: 51845
So if I get it right:
r=1.0
float z = computeZ(vPosition.xy, 1.0);
vPosition
is passed from vertex shader
Unknown things needed to be cleared:
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:
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
x=B; y=G; r=R;
x,y,r
Then in each fragment call loop through all spheres ...
pass single quad per each sphere
So you can render 2D Quad with endpoints (x0,y0),(x1,y1)
in place of each sphere.
(x,y) = ( 0.5*(x0+x1) , 0.5*(y0+y1) )
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
Look here Draw Quadratic Curve on GPU it is very similar task
Upvotes: 2