Creating the Mandelbulb fractal using raymarching in GhGL.

GHGL isn’t working with Rhino 8 yet. I’ve been waiting for an update, but I decided to share my code for Rhino 7 instead. Hopefully, we’ll get a new version of GHGL for Rhino 8 soon.


I just published a new version of GhGL that runs in Rhino 8.6 and above (Windows only). No new features except that code editing now uses the same editing control we use in other parts of Rhino.

1 Like

Thank you so much for the update. I’ve tested the new version and am pleased to report that the code editor is excellent. However, I did notice a small issue: when I turn off the preview for all Grasshopper components, the GL shader preview also vanishes. Could you please take a look at this? Thank you for your attention to this matter.


moved to Grasshopper channel.

I probably won’t be able to look at this right away. I remember getting the preview stuff to work was a bit twisted code since I wanted to make sure that downstream shader components would properly get textures from upstream shader components.

1 Like

Hi @Mahdiyar thanks, and have been enjoying playing with it and modifying the adjust the embedded formula’s and variables on the fly to get the full diversity and see what can be done with this code.

i have 3 questions which i hope you can answer:

  1. sometime the shader displayed geometry goes missing, stops rendering. this also happens in your original example, Say you try zoom in from Top view to get really close, and then the object stops being rendered, while still visible in the other views Perspective and Front

  2. I am looking for a way you get change dynamicly change the code from gh, without changing it in the editor

  3. I wanted to add a clipping, so slicing the geometry and moving the slce plane in all xyz directions

Innitially I want to externalize internal code line like this

z = zr*vec3(sin(theta) * cos(phi), cos(theta), sin(theta) * sin(phi));

But it seems externalizing specific lines of shader code or mathematical expressions to be dynamically loaded or modified from an external source like a text file is not directly supported within the standard GLSL shader compilation process. So I now externalized parameters like zr , theta , and phi as uniforms to allow additional external control.

Defining Uniforms

// External variables 
uniform float initialDistanceRate; // Initially 'dr = 1.0'
uniform float initialRadius; // Initially 'r = 0.0'
uniform int maxIterations; // Loop control, originally 'for (int i = 0; i < 32 ; i++)'
uniform float breakRadius; // Condition to break loop, originally 'if (r > 4.0) break;'

// Externalizing constants in mathematical operations
uniform float powerAdjustment; // To adjust 'power' dynamically
uniform float additionAfterMultiplication; // Originally "+ 1.0" in the 'dr' calculation

// Individual trigonometric function control for each operation
uniform int trigFuncThetaX; // Controls the function used for 'theta' calculation (x component)
uniform int trigFuncThetaY; // Controls the function used for 'theta' calculation (y component)
uniform int trigFuncPhiX; // Controls the function used for 'phi' calculation (x component)
uniform int trigFuncPhiZ; // Controls the function used for 'phi' calculation (z component)

uniform int trigFuncZx; // Controls trig function for z.x component
uniform int trigFuncZy; // Controls trig function for z.y component
uniform int trigFuncZz; // Controls trig function for z.z component

Select Trigonometric Function

float applyTrigFunction(int funcId, float value) {
    if(funcId == 0) return sin(value);
    else if(funcId == 1) return cos(value);
    else if(funcId == 2) return tan(value);
    else if(funcId == 3) return asin(clamp(value, -1.0, 1.0));
    else if(funcId == 4) return acos(clamp(value, -1.0, 1.0)); 
    else if(funcId == 5) return atan(value); // 
    else return value; //, should not happen

main loop

float dr = initialDistanceRate;
float r = initialRadius;
for (int i = 0; i < maxIterations; i++) {
    r = length(z);
    steps = i;
    if (r > breakRadius) break;
    // Apply individual trigonometric functions
    float theta = applyTrigFunction(trigFuncThetaY, z.y / r);
    float phi = applyTrigFunction(trigFuncPhiZ, z.z / r);
    dr = pow(r, powerAdjustment) * power * dr + additionAfterMultiplication;
    float zr = pow(r, power);
    theta = applyTrigFunction(trigFuncThetaX, theta * power);
    phi = applyTrigFunction(trigFuncPhiX, phi * power);
    // Construct 'z' using individually controlled trig functions
    z = zr * vec3(
        applyTrigFunction(trigFuncZx, theta) * applyTrigFunction(trigFuncZx, phi),
        applyTrigFunction(trigFuncZy, cos(theta)), // Example adjustment
        applyTrigFunction(trigFuncZz, sin(theta)) * applyTrigFunction(trigFuncZz, phi)
    z += pos;
return 0.5 * log(r) * r / dr;
  1. I also tried to get some type op clipping/slicing the geometry, but this seems to be restricted to the initial size of the 4 vertex points
# version 330

layout(location = 0) in vec3 vertex;

uniform mat4 _worldToClip;
out vec4 vertex_color;
uniform float _vertexDisplacement;
uniform vec3 _displacementDirection;
uniform float displacementScale;

void main() {
    vec3 displacedVertex = vertex + (_displacementDirection * _vertexDisplacement * displacementScale);
    gl_Position = _worldToClip * vec4(displacedVertex, 1.0);