# Simple Spatial Planet

A 3D planet intended for rendering within a cube, tested also on a quad. It writes a minimal amount of builtins as it is designed for extending into more complex planet renderers. It also serves as a nice example of a simple ray-traced sphere.

Features

• Wrapping animated surface texture resembling a gas giant
• Writes to depth during fragment, so as to properly clip with other geometries
• Behaves as you’d expect a perfectly spherical mesh to behave

References/Sources

``````shader_type spatial;
render_mode cull_front;

bool solveQuadratic( float a, float b, float c, inout float x0, inout float x1 )
{ //https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection.html
float discr = b * b - 4.0 * a * c;

if( discr < 0.0 ) return false;
else if( abs(discr) < 0.0001 ) x0 = x1 = -0.5 * b / a;
else
{
float q = (abs(b) > 0.0001) ? -0.5 * (b + sqrt(discr)) : -0.5 * (b - sqrt(discr));
x0 = q / a;
x1 = c / q;
}
if( x0 > x1 )
{
float _x1 = x1;
x1 = x0;
x0 = _x1;
}
return true;
}

float raySphereIntersect( vec3 origin, vec3 normal, vec3 center, float radius )
{ //https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection.html
float t0, t1; // Solution for t if the ray intersects the sphere
vec3 L = origin - center;
float a = dot(normal,normal);
float b = 2.0 * dot(normal,L);
float c = dot(L,L) - radius * radius;
if( !solveQuadratic(a, b, c, t0, t1 ) ) return -1.0;
if( t0 < 0.0 )
{
if( t1 < 0.0 ) return -1.0;
t0 = t1;
}
return t0;
}

vec3 whorley( vec2 uv, float time, vec2 distortion )
for(float i=1.0; i<10.0; i+=1.0)
{
uv.x+=distortion.x*sin(i*uv.y+time);
uv.y+=distortion.y*cos(i*uv.x+time*.2);
}
vec3 n = vec3( cos(uv.x+uv.y+2.),
sin(uv.x+uv.y+2.2),
(sin(uv.x+uv.y+1.)+cos(uv.x+uv.y+1.5)) );
return n;
}

vec2 uvOnSphere( vec3 dir )
{
float uvX = (atan( dir.y, length( vec2(dir.x,dir.z) ) )/PI)+0.5;
float uvY = ((atan(dir.x,dir.z)+PI)/TAU);
return vec2 ( uvX, uvY );
}

vec3 whorleyOnSphere( vec3 dir, float time, vec2 scale, vec2 distortion )
{
float delta = (dot( -normalize(dir*vec3(1,0,1)), vec3(0,0,1) )+1.0)/2.0;
vec3 wosA = whorley(uvOnSphere(dir)*scale, time, distortion );
vec3 wosB = whorley(uvOnSphere(-dir)*scale, time, distortion );
vec3 mixed = wosA+((wosB-wosA)*delta);
return normalize(mixed);
}

vec3 transform( mat4 mtx, vec3 vec ){	return (mtx*vec4(vec,1.0)).rgb - mtx[3].rgb;}

float fresnel( float val, vec3 normal, vec3 view, float str ){ return clamp( mix( val, 0.0, dot(normal,view)*str ), 0.0, 1.0 );}

uniform float radius = 256.0;
uniform float rimRetraction = 4.0;
uniform float rimBrightness = 1.0;
uniform vec3 rimColor : source_color = vec3(0.7,0.5,0.3);
uniform float animationSpeed = 0.25;
uniform float distortionStrength = 0.3;

void fragment()
{
ALPHA_SCISSOR_THRESHOLD = 0.5;
// First get the ray direction in world space
vec3 rayDir = normalize(transform(INV_VIEW_MATRIX,-VIEW));
// t is the distance along that ray at which intersection occurs (or -1 if there was none)
float t = raySphereIntersect( CAMERA_POSITION_WORLD-NODE_POSITION_WORLD, rayDir, vec3(0), radius );

if( t > 0.0 )
{ // t is greater than 0, therefore an intersection has occured along the ray
vec3 worldPos = CAMERA_POSITION_WORLD+(rayDir*t);
vec3 dir = normalize(worldPos-NODE_POSITION_WORLD);
// Depth is calculated using the world position of the intersection
vec4 clipPos = PROJECTION_MATRIX * VIEW_MATRIX * vec4(worldPos, 1);
DEPTH = clipPos.z/clipPos.w;
// The direction from the center is transformed back into view space to serve the normal
NORMAL = transform( VIEW_MATRIX, dir );
// Dir is then moved into local space to respect rotation of the node for texturing
dir = transform( inverse(MODEL_MATRIX), dir );
ALBEDO = whorleyOnSphere( dir, TIME*animationSpeed, vec2(20,5), vec2(distortionStrength) );
EMISSION = fresnel(0.25,NORMAL,VIEW,rimRetraction)*rimColor*rimBrightness;
ALPHA = 1.0;
}
else ALPHA = 0.0;
}``````
###### Tags
jupiter, moon, planet, raytracing, space, Spatial, sphere
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.