I wrote a simple Sphere Tracer in Processing (Java) and am porting it to WebGL / GLSL. When I wrote it in Processing I had a base class Shape
and would extend it for specific shapes such as Box
, Plane
, Sphere
, etc. Each specific shape had members that were relevant to it, for example Sphere
instances had a radius, Box
instances had a length vector, etc. In addition each had a shape specific distance function.
Unfortunately I cannot use classes like this in GLSL and so I made a single struct
that can represent any shape (I refer to it as Object
below):
struct Object {
vec3 pos, len, nDir;
float rad;
} objects[4];
Then I wrote a distance function for each kind of shape:
float boxSignedDist(Object inBox, vec3 inPos) {
vec3 boxDelta = abs(inPos-inBox.pos)-inBox.len;
return min(max(boxDelta.x, max(boxDelta.y, boxDelta.z)), 0.0)+length(max(boxDelta, 0.0));
}
float planeSignedDist(Object inPlane, vec3 inPos) {
return dot(inPos-inPlane.pos, inPlane.nDir);
}
float roundBoxUnsignedDist(Object inRoundBox, vec3 inPos) {
return length(max(abs(inPos-inRoundBox.pos)-inRoundBox.len, 0.0))-inRoundBox.rad;
}
float sphereSignedDist(Object inSphere, vec3 inPos) {
return length(inPos-inSphere.pos)-inSphere.rad;
}
Now I have run into a different problem which is wrapping shape specific distance functions with another function such as a rotation, it is not obvious how to do this efficiently in GLSL. I added a member to Object
, int type
, and then made a few #define
s for each shape I support at the moment:
#define BOX_SIGNED 1
#define PLANE_SIGNED 2
#define ROUNDBOX_UNSIGNED 3
#define SPHERE_SIGNED 4
struct Object {
int type;
vec3 pos, len, nDir;
float rad;
} objects[4];
So that now I can write a rotation wrapper to a distance function like this:
float rotateY(Object inObject, vec3 inPos, float inRadians) {
inPos -= inObject.pos;
inObject.pos = vec3(0.0, 0.0, 0.0);
float cRad = cos(inRadians);
float sRad = sin(inRadians);
if (inObject.type == BOX_SIGNED)
return boxSignedDist(inObject, vec3(cRad*inPos.x-sRad*inPos.z, inPos.y, cRad*inPos.z+sRad*inPos.x));
else if (inObject.type == PLANE_SIGNED)
return planeSignedDist(inObject, vec3(cRad*inPos.x-sRad*inPos.z, inPos.y, cRad*inPos.z+sRad*inPos.x));
else if (inObject.type == ROUNDBOX_UNSIGNED)
return roundBoxUnsignedDist(inObject, vec3(cRad*inPos.x-sRad*inPos.z, inPos.y, cRad*inPos.z+sRad*inPos.x));
else if (inObject.type == SPHERE_SIGNED)
return sphereSignedDist(inObject, vec3(cRad*inPos.x-sRad*inPos.z, inPos.y, cRad*inPos.z+sRad*inPos.x));
else
return 0.0;
}
It seems ridiculous that this would be necessary, is there a better way to do it? It would be nice if rotateY
could receive a function pointer to just call the appropriate function instead of the all the else if
A function in GLSL must be declared before it can be used, a bit like defining a variable. This feature is the same as in C and was deemed unnecessary with the development of Java. Note unlike C or Java a function in GLSL cannot be called recursively. This is the code within a function cannot call the same function.
GLSL does not have pointers. But it does have shader storage buffer objects, which allow you to define arbitrarily large regions of memory that the shader can read from. A "pointer" in this model is just an index into an array of data structures. You have to do the indexing explicitly in GLSL code.
GLSL doesn't have classes or interfaces, but it is possible to mimic object-oriented classes in GLSL by composing structures with overloaded methods.
GLSL does not support anonymous structures (ie: structs without a type name), and structs must have at least one member declaration. Structs cannot be defined within another struct, but one struct can use another previously defined struct as a member.
GLSL is quite a limited language really. The compiler does a great job at optimizing certain things but isn't perfect.
A few things to remember:
int[4]
array takes the same memory as a vec4[4]
array. Your Object
should group vec3
s with float
s.if (type == ...
code could be improved by constructing the rotated inPos
beforehand, but I can't see a way around the if-statements. Perhaps you could write permutations of functions for each object type (or use macros) and trace the types in batches separately?You might get some good ideas looking at what people have written for https://www.shadertoy.com/.
Finally, GLSL subroutines have a similar intent as function pointers, but are used on a global scale for all shader executions and won't help here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With