Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GLSL, passing functions

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 #defines 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

like image 764
asimes Avatar asked Nov 20 '14 01:11

asimes


People also ask

How do you create a function in GLSL?

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.

Does GLSL support pointers?

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.

Does GLSL have classes?

GLSL doesn't have classes or interfaces, but it is possible to mimic object-oriented classes in GLSL by composing structures with overloaded methods.

Does GLSL have structs?

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.


1 Answers

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:

  • Local memory is expensive, both in just declaring and in access
  • Dynamically indexed arrays are put in local memory.
  • Arrays and objects are padded to align to 16 byte boundaries. An int[4] array takes the same memory as a vec4[4] array. Your Object should group vec3s with floats.
  • There's no such thing as a function call. Everything is inlined.
  • Arguments passed to functions are copied in and copied out. The complier doesn't always optimized out these copies when the functions are inlined. Keep as much global as possible.
  • Switch statements don't have jump operators, they are expanded to nested if-statements.
  • Divergence is a tricky thing to optimize out. Your 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.

like image 192
jozxyqk Avatar answered Oct 11 '22 23:10

jozxyqk