CCCPaste

Mandelbrot Fractal Shader

// Minimal Mandelbulb fractal shader - test version

#define MARCH_MAX_STEPS 200
#define MARCH_MAX_DISTANCE 500.0
#define DE_MAX_ITER 25
#define DE_BAILOUT 10.0

#define HIT_STATUS_HIT 1
#define HIT_STATUS_MISS_DISTANCE 2
#define HIT_STATUS_MISS_STEPS 3

struct MarchResult
{
float distance;
int steps;
int hitStatus;
int totalDEIterations;
};

struct FractalFunctions
{
// Computes the world-space radius that a sphere must have to appear 1 pixel tall on screen at a given distance.
static float GetPixelWorldRadius(float distance)
{
return distance * ResolvedView.ClipToView._22 / ResolvedView.ViewSizeAndInvSize.y;
}

// Mandelbulb distance estimator
static float MandelbulbDE(float3 pos, float power, out int iterations)
{
float3 z = pos;
float dr = 1.0;
float r = 0.0;

for (iterations = 0; iterations < DE_MAX_ITER; iterations++)
{
r = length(z);
if (r > DE_BAILOUT)
break;

// Convert to spherical coordinates
float theta = acos(z.z / r);
float phi = atan2(z.y, z.x);
dr = pow(r, power - 1.0) * power * dr + 1.0;

// Scale and rotate the point
float zr = pow(r, power);
theta = theta * power;
phi = phi * power;

// Convert back to Cartesian coordinates
float sinTheta = sin(theta);
z = zr * float3(sinTheta * cos(phi), sinTheta * sin(phi), cos(theta));
z += pos;
}

return 0.5 * log(r) * r / dr;
}

// Ray marching
static MarchResult MarchFractal(float3 rayOrigin, float3 rayDir, float power)
{
float totalDist = 0.0;
int steps = 0;
int totalDEIterations = 0;

while (totalDist < MARCH_MAX_DISTANCE && steps < MARCH_MAX_STEPS)
{
steps++;
float3 pos = rayOrigin + rayDir * totalDist;

int deIterations;
float distance = MandelbulbDE(pos, power, deIterations);
totalDEIterations += deIterations;

float pixelSize = GetPixelWorldRadius(totalDist);

if (distance <= pixelSize)
{
MarchResult result;
result.distance = totalDist;
result.steps = steps;
result.hitStatus = HIT_STATUS_HIT;
result.totalDEIterations = totalDEIterations;
return result;
}

float stepSize = max(distance, pixelSize * 0.5);
totalDist += stepSize;
}

MarchResult result;
result.distance = totalDist;
result.steps = steps;
result.hitStatus = (totalDist >= MARCH_MAX_DISTANCE) ? HIT_STATUS_MISS_DISTANCE : HIT_STATUS_MISS_STEPS;
result.totalDEIterations = totalDEIterations;
return result;
}

static float3 RenderFractal(float2 screenUV, float3 sceneColor, float power)
{
// Get camera parameters
float3 cameraOrigin = LWCHackToFloat(ResolvedView.WorldCameraOrigin);

// Convert screen UV to clip space
float2 clipXY = screenUV * 2.0 - 1.0;
clipXY.y = -clipXY.y;

float4 clipPos = float4(clipXY, 1.0, 1.0);
float4 viewPos = mul(clipPos, ResolvedView.ClipToView);
viewPos.xyz /= viewPos.w;

// Transform view direction to world space
float3 worldDir = normalize(mul(viewPos.xyz, (float3x3)ResolvedView.ViewToTranslatedWorld));

// Scale down the camera origin for better framing
float3 rayOrigin = cameraOrigin * 0.001;
float3 rayDir = worldDir;

// Ray march
MarchResult finalResult = MarchFractal(rayOrigin, rayDir, power);

// Calculate color based on total steps taken
float stepFactor = saturate(finalResult.steps / float(MARCH_MAX_STEPS));
float t = sqrt(stepFactor);

// Gradient color stops
float3 almostBlack = float3(0.0, 0.0, 0.05);
float3 deepBlue = float3(0.05, 0.15, 0.3);
float3 vibrantBlue = float3(0.2, 0.4, 0.8);
float3 brightBlue = float3(0.6, 0.8, 1.0);

// Blend through gradient
float range1 = saturate(t / 0.15);
float range2 = saturate((t - 0.15) / 0.25);
float range3 = saturate((t - 0.4) / 0.6);
float3 fractalColor = lerp(lerp(lerp(almostBlack, deepBlue, range1), vibrantBlue, range2), brightBlue, range3);

// Add green tint based on total DE iterations
float iterFactor = saturate(finalResult.totalDEIterations / float(DE_MAX_ITER * finalResult.steps));

if (iterFactor > 0.1)
{
float greenAmount = (iterFactor - 0.1) / 0.9;
greenAmount = pow(greenAmount, 2.0);
fractalColor = lerp(fractalColor, float3(0.3, 0.5, 0.6), greenAmount);
}

if (iterFactor > 0.9)
{
float greenAmount = (iterFactor - 0.9) / 0.1;
greenAmount = pow(greenAmount, 5.0);
fractalColor = lerp(fractalColor, float3(0.3, 0.9, 0.7), greenAmount);
}

if (finalResult.hitStatus == HIT_STATUS_HIT)
{
return fractalColor;
}
else if (finalResult.hitStatus == HIT_STATUS_MISS_DISTANCE)
{
float fogAmount = pow(stepFactor, 0.1);
return lerp(sceneColor, fractalColor, fogAmount);
}
else
{
return fractalColor;
}
}
};

// Usage in material custom node:
// #include "/Project/FractalMaterial.usf"
// FractalFunctions FF;
// return FF.RenderFractal(ScreenPosition, SceneColor, Power).rgb;