Reputation: 73
I am trying to replicate something similar to this from Unity in Godot Engine with shaders, however, I am not able to find a solution. Calculating the position of the effect is the problem. How can I get the position in Godot, where I don't have access to the worlPos
variable used in the video? A full code snippet of the shader would be really appreciated.
Currently, my shader code looks like this. ob_position
is the position passed from the node.
shader_type spatial;
uniform vec2 ob_position = vec2(1, 0.68);
uniform float ob_radius = 0.01;
float circle(vec2 position, float radius, float feather)
{
return smoothstep(radius, radius + feather, length(position - vec2(0.5)));
}
void fragment() {
ALBEDO.rgb = vec3(circle(UV * (ob_position), ob_radius, 0.001) );
}
Upvotes: 0
Views: 1073
Reputation: 73
The answer from Theraot was a lifesaver for me however, I also needed support for multiple positions, using arrays, uniform vec3 sphere_position[];
So I came up with this:
shader_type spatial;
uniform uint ob_position_size;
uniform vec3 sphere_position[2];
uniform sampler2D noise_texture;
uniform sampler2D tex1;
uniform float radius;
uniform float edge;
void fragment()
{
vec3 pixel_world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
float noise_value = texture(noise_texture, pixel_world_pos.xy + vec2(TIME)).r;
ALBEDO = texture(SCREEN_TEXTURE, SCREEN_UV).rgb;
for(int i = 0; i < sphere_position.length(); i++) {
float dist = distance(sphere_position[i], pixel_world_pos) + noise_value;
float threshold = step(radius, dist);
ALBEDO.rgb = mix(texture(tex1, UV).rgb, ALBEDO.rgb, threshold);
//EMISSION = vec3(step(dist, edge + radius) * step(radius, dist));
}
}
Upvotes: 0
Reputation: 40315
The video says:
Send the sphere position to the shader in script.
We can do that. First define an uniform:
uniform vec3 sphere_position;
And we can set it from code:
material.set_shader_param("sphere_position", global_transform.origin)
Since you need to set this every time the sphere moves, you can use NOTIFICATION_TRANSFORM_CHANGED
which you enable by calling set_notify_local_transform(true)
.
Get the distance between the sphere and World Position.
To do that we need to figure out the world position of the fragment. Let us start by looking at the Fragment Build-ins. We find that:
VERTEX
is the position of the fragment in view space.CAMERA_MATRIX
is the transform from view space to world space.Yes, the naming is confusing.
So we can do this (in fragment
):
vec3 pixel_world_pos = (CAMERA_MATRIX * vec4(VERTEX, 1.0)).xyz;
You can use this to debug: ALBEDO.rgb = pixel_world_pos;
. In general, output whatever variable you want to visualize for debugging to ALBEDO
.
And now the distance is:
float dist = distance(sphere_position, pixel_world_pos);
Control the size by dividing by radius.
While we don't have direct translation for the code in the video… sure, we can divide by radius (dist / radius
). Where radius
would be a uniform float
.
Create a cutoff with Step.
That would be something like this: step(0.5, dist / radius)
.
Honestly, I would rather do this: step(radius, dist)
.
Your mileage may vary.
Lerp two different textures over the cutoff.
For that we can use mix
. But first, define your textures as uniform sampler2D
. Then you can something like this:
float threshold = step(radius, dist);
ALBEDO.rgb = mix(texture(tex1, UV).rgb, texture(tex2, UV).rgb, threshold);
Moving worldspace noise.
Add one more uniform sampler2D
and set a NoiseTexture
(make sure to set its noise
and make seamless
to true), and then we can query it with the world coordinates we already have.
float noise_value = texture(noise_texture, pixel_world_pos.xy + vec2(TIME)).r;
Add worldspace to noise.
I'm not sure what they mean. But from the visual, they use the noise to distort the cutoff. I'm not sure if this yields the same result, but it looks good to me:
vec3 pixel_world_pos = (CAMERA_MATRIX * vec4(VERTEX, 1.0)).xyz;
float noise_value = texture(noise_texture, pixel_world_pos.xy + vec2(TIME)).r;
float dist = distance(sphere_position, pixel_world_pos) + noise_value;
float threshold = step(radius, dist);
ALBEDO.rgb = mix(texture(tex1, UV).rgb, texture(tex2, UV).rgb, threshold);
Add a line to Emission (glow).
I don't understand what they did originally, so I came up with my own solution:
EMISSION = vec3(step(dist, edge + radius) * step(radius, dist));
What is going on here is that we will have a white EMISSION
when dist < edge + radius
and radius < dist
. To reiterate, we will have white EMISSION
when the distance is greater than the radius (radius < dist
) and lesser than the radius plus some edge (dist < edge + radius
). The comparisons become step
functions, which return 0.0
or 1.0
, and the AND operation is a multiplication.
Reveal object by clipping instead of adding a second texture.
I suppose that means there is another version of the shader that either uses discard
or ALPHA
and it is used for other objects.
This is the shader I wrote to test this:
shader_type spatial;
uniform vec3 sphere_position;
uniform sampler2D noise_texture;
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform float radius;
uniform float edge;
void fragment()
{
vec3 pixel_world_pos = (CAMERA_MATRIX * vec4(VERTEX, 1.0)).xyz;
float noise_value = texture(noise_texture, pixel_world_pos.xy + vec2(TIME)).r;
float dist = distance(sphere_position, pixel_world_pos) + noise_value;
float threshold = step(radius, dist);
ALBEDO.rgb = mix(texture(tex1, UV).rgb, texture(tex2, UV).rgb, threshold);
EMISSION = vec3(step(dist, edge + radius) * step(radius, dist));
}
Upvotes: 1