I've been attempting to convert this shader over to Godot: https://www.shadertoy.com/view/NlGcDh
The original shader uses its own lighting calculations, but I'm trying to make it work with Godot's built-in lighting. As far as I can tell all I really need is to give Godot the proper normals and it should "just work". But I'm having issues with that. I'm close, but I've been banging up against the wall for a while now trying to get it fully working.
Here is the code I have at the moment:
shader_type spatial;
#define INF 1e5f
uniform vec4 albedo : source_color = vec4( 1.0f, 0.0f, 0.0f );
uniform float specular : hint_range( 0.0f, 1.0f ) = 1.0f;
uniform float metallic : hint_range( 0.0f, 1.0f ) = 0.0f;
uniform float roughness : hint_range( 0.0f, 1.0f ) = 0.2f;
uniform vec3 positions[5];
uniform int amount = 1;
uniform float radius : hint_range( 0.0f, 100.0f ) = 1.0f;
uniform float max_distance : hint_range( 0.0f, 1000.0f ) = 20.0f;
uniform int max_iterations : hint_range( 0, 5000 ) = 200;
uniform float step_size : hint_range( 0.0f, 1.0f ) = 0.005f;
struct Hit
{
float t;
vec3 point;
vec3 normal;
vec3 color;
int objId;
};
void swap( inout float x0, inout float x1 )
{
float tmp = x0;
x0 = x1;
x1 = tmp;
}
bool solveQuadratic( float a, float b, float c, out float x0, out float x1 )
{
float delta = b * b - 4.0f * a * c;
if ( delta < 0.0f )
{
return false;
}
else if ( delta == 0.0f )
{
x0 = x1 = -0.5f * b / a;
}
else
{
float q = ( b > 0.0f ) ? -0.5f * ( b + sqrt( delta ) ) : -0.5f * ( b - sqrt( delta ) );
x0 = q / a;
x1 = c / q;
}
return true;
}
bool traceSphere( vec3 eye, vec3 ray, vec3 center, out Hit hit )
{
float t0, t1;
vec3 L = eye - center;
float a = dot( ray, ray );
float b = 2.0f * dot( ray, L );
float rad = radius * 0.9f;
float c = dot( L, L ) - ( rad * rad );
if ( ! solveQuadratic( a, b, c, t0, t1 ) ) return false;
if ( t0 > t1 ) swap( t0, t1 );
if ( t0 < 0.0f )
{
t0 = t1;
if ( t0 < 0.0f ) return false;
}
hit.t = t0;
hit.point = eye + t0 * ray;
hit.normal = normalize( hit.point - center );
return true;
}
void getFalloff( float x, out float f )
{
f = 0.0f;
if ( x < 0.01f || x > 1.0f ) return;
f = 1.0f -( x * x * x * ( 6.0f * x * x -15.0f * x + 10.0f ) );
}
void getFalloffDerivative( float x, out float df )
{
df = 0.0f;
if ( x < 0.01f || x > 1.0f ) return;
df = -( x * x * ( 30.0f * x * x - 60.0f * x + 30.0f ) );
}
bool getFieldValues( vec3 p, float threshold, out float value, out vec3 normal, out vec3 color )
{
normal = vec3( 0.0f );
float f, df;
value = 0.0f;
float rad = radius * 0.9f;
for ( int i = 0; i < amount; i++ )
{
float r = length( p - positions[ i ] );
if ( length( p - positions[ i ] ) > rad ) continue;
float d = length( ( p - positions[ i ] ) / rad );
getFalloff( d, f );
value += f;
color = vec3( 1.0f );
}
if ( value >= threshold )
{
float df = 0.0f;
for( int i = 0; i < amount; i++ )
{
float d = length( ( p - positions[ i ] ) / rad );
getFalloffDerivative( d, df );
normal += df * normalize( positions[ i ] - p );
}
normal = normalize( normal );
return true;
}
return false;
}
Hit rayTraceFieldBoudingSpheres( vec3 origin, vec3 dir )
{
float minT = INF;
float point;
Hit hit, result;
result.t = INF;
for ( int i = 0; i < amount; i++ )
{
if ( traceSphere( origin, dir, positions[ i ], hit ) && hit.t < result.t )
{
result.t = hit.t;
result.point = origin + result.t * dir;
result.objId = 1;
}
}
return result;
}
vec4 castRay( vec3 origin, vec3 dir, out Hit hit )
{
vec3 fieldNormal;
hit = rayTraceFieldBoudingSpheres( origin, dir );
float t = hit.t;
if ( t == INF ) return vec4( 0.0f );
vec3 p;
vec3 color;
float value = 0.0f;
float threshold = 0.4f;
for ( int i = 0; i < max_iterations; i++ )
{
if ( t > max_distance ) break;
t += step_size;
p = origin + t * dir;
if ( getFieldValues( p, threshold, value, fieldNormal, color ) )
{
hit.t = t;
hit.point = p;
hit.normal = fieldNormal;
hit.objId = 1;
hit.color = color;
break;
}
}
if ( value < 0.4f ) return vec4( 0.0f );
return vec4( 1.0f );
}
void vertex()
{
POSITION = vec4( VERTEX, 1.0f );
}
void fragment()
{
vec3 ndc = vec3( SCREEN_UV, 0.0f ) * 2.0f - 1.0f;
vec4 view_coords = INV_PROJECTION_MATRIX * vec4( ndc, 1.0f );
view_coords.xyz /= view_coords.w;
vec3 world_cam_pos = ( INV_VIEW_MATRIX * vec4( 0.0f, 0.0f, 0.0f, 1.0f ) ).xyz;
vec4 world_coords = INV_VIEW_MATRIX * vec4( view_coords.xyz, 1.0f );
vec3 ray_origin = world_coords.xyz;
vec3 ray_dir = normalize( world_coords.xyz - world_cam_pos );
Hit ray_hit;
vec4 ray_result = castRay( ray_origin, ray_dir, ray_hit );
ALBEDO = albedo.rgb;
ALPHA = ray_result.a * albedo.a;
vec4 sdf_ndc = PROJECTION_MATRIX * VIEW_MATRIX * vec4( ray_origin + ray_dir * ray_hit.t, 1.0f );
DEPTH = ( sdf_ndc.z / sdf_ndc.w );
NORMAL = ( VIEW_MATRIX * vec4( ray_hit.normal, 0.0f ) ).xyz;
SPECULAR = specular;
METALLIC = metallic;
ROUGHNESS = roughness;
}
As I said, it's mostly working, but there is some sort of issue with the normals (or something). It is visible when the specular is turned way up and roughness way down. Basically, it does have specular on it, but it's not quite correctly positioned on the ball and it has some odd distortions when you move the camera around to look at the ball(s) from different angles. Putting a standard sphere mesh into the scene and using the standard shader with specular turned way up shows correct results, so that is what I've been comparing to.
It's a metaballS shader, but for testing I've just been setting it to 1 ball to keep things simple.
If needed, I could maybe try to get a video of the issue, but I guess someone who is more adept at shaders will probably be able to see issues just looking at the code.
Also if any other info would help, just let me know.
I would greatly appreciate any help. Thanks! 🙂