(Solved) Shader to apply to only one scene/sprite?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Woon

I applied a shader to a flag sprite and it distorts the sprite to make it look wavy(like a flag), but the shader affects all sprites in that area.

Link to problem

Is there a way to apply a shader only to one sprite or, atleast, a scene so it doesn’t interact with the other scenes/sprites?

Edit 1:
Here’s the shader code

shader_type canvas_item;
uniform int OCTAVES = 1;
uniform float SPD_OF_FOG = 0.1;

// "Random" Number Generator
float rand(vec2 coord) {
   return fract(sin(dot(coord,vec2(12.9898, 78.233)))* 43758.5453123);
}

// Noise Generator
 float noise(vec2 coord) {
vec2 i = floor(coord);
vec2 f = fract(coord);

float a = rand(i);
float b = rand(i + vec2(1.0, 0.0));
float c = rand(i + vec2(0.0, 1.0));
float d = rand(i + vec2(1.0, 1.0));

vec2 cubic = f * f * (3.0 - 2.0 * f);

return mix(a, b, cubic.x) + (c - a) * cubic.y * (1.0 - cubic.x) + (d - b) * cubic.x * cubic.y;
}
// Fractional Brownian motion
float fbm(vec2 coord) {
float value = 0.0; // color changer
float scale = 0.01;
//
for(int i = 0; i < OCTAVES; i++) 
{
	value += noise(coord) * scale;
	scale *= 0.0;
	coord *= 2.0;
}
return value;
}

// Fragment Shader
void fragment() {

// Wavy Shader
vec2 sprite_scale = vec2(textureSize(TEXTURE, 0));

float jagged_scale = 40.0; // Amount of Distort bubbles
vec2 noisecoord1 = UV * sprite_scale / jagged_scale;
vec2 noisecoord2 = UV * sprite_scale / jagged_scale + 4.0;

vec2 motion1 = vec2(TIME * 0.3, TIME * -0.4);
vec2 motion2 = vec2(TIME * 0.1, TIME * 0.5);

vec2 distort1 = vec2(noise(noisecoord1 + motion1), noise(noisecoord2 + motion1)) - vec2(0.5);
vec2 distort2 = vec2(noise(noisecoord1 + motion2), noise(noisecoord2 + motion2)) - vec2(0.5);

float powerOfDistort = 25.0; //55 //Higher = Less Distort 
vec2 distort_sum = (distort1 + distort2) / powerOfDistort * UV.y * UV.y; // Wavy at the bottom more
vec4 color = textureLod(SCREEN_TEXTURE, SCREEN_UV + distort_sum, 0.0);

float near_top = (UV.y + distort_sum.y) / (0.2 / (sprite_scale.y / 32.0));
near_top = clamp(near_top, 0.0, 1.0);
near_top = 1.0 - near_top;

color = mix(color, vec4(1.0), near_top);

float edge_lower = 0.0;
float edge_upper = edge_lower + 0.1;

if (near_top > edge_lower) {
	color.a = 0.0;
	if (near_top < edge_upper) {
		color.a = (edge_upper - near_top) / (edge_upper - edge_lower);
		}
}
COLOR = color;

}

What the shader does:

  1. Create Random Number
  2. Turn it into noise
  3. Use Fbm function to provide more detail(I think)
  4. Set the shader to only affect the bottom 90% or NOT affect the top 10%
    (upper_edge = .1)
  5. Apply it to the entire area

Note: I have thought about applying ONLY the area of the shader to the sprite, but it would create 3 problems.

  1. A lot of code for a more unique flag shape
    (like what I did using upper_edge for leaving out the top of the flag)

  2. And even then, if any sprite that were to enter that given area would be distorted too.

Edit 2: (SOLVED)
The code works :). Thank you estebanmolca

 shader_type canvas_item;
//uniform vec3 shadow_color = texture(TEXTURE, UV + vec2(0.5,0.5)).rgb;// 
vec3(0.0,0.5,0.5);
uniform int OCTAVES = 1;
uniform float SPD_OF_FOG = 0.1;

float rand(vec2 coord) {
return fract(sin(dot(coord,vec2(12.9898, 78.233)))* 43758.5453123);
}

float noise(vec2 coord) {
vec2 i = floor(coord);
vec2 f = fract(coord);

float a = rand(i);
float b = rand(i + vec2(1.0, 0.0));
float c = rand(i + vec2(0.0, 1.0));
float d = rand(i + vec2(1.0, 1.0));

vec2 cubic = f * f * (3.0 - 2.0 * f);

return mix(a, b, cubic.x) + (c - a) * cubic.y * (1.0 - cubic.x) + (d - b) * cubic.x * cubic.y;
}


float fbm(vec2 coord) {
float value = 0.0; // color changer
float scale = 0.01;
//
for(int i = 0; i < OCTAVES; i++) 
{
	value += noise(coord) * scale;
	scale *= 0.0;
	coord *= 2.0;
}
return value;
}

void fragment() {

// Wavy Shader
vec2 sprite_scale = vec2(textureSize(TEXTURE, 0));

float jagged_scale = 40.0;
vec2 noisecoord1 = UV * sprite_scale / jagged_scale;
vec2 noisecoord2 = UV * sprite_scale / jagged_scale + 4.0;

vec2 motion1 = vec2(TIME * 1.3, TIME * -0.4); // vec2(TIME * 0.3, TIME * -0.4)
vec2 motion2 = vec2(TIME * 0.1, TIME * 1.5); // vec2(TIME * 0.1, TIME * 0.5);

vec2 distort1 = vec2(noise(noisecoord1 + motion1), noise(noisecoord2 + motion1)) - vec2(0.5);
vec2 distort2 = vec2(noise(noisecoord1 + motion2), noise(noisecoord2 + motion2)) - vec2(0.5);

float powerOfDistort = 15.0; //Higher = Less Distort 
vec2 distort_sum = (distort1 + distort2) / powerOfDistort * UV.y * UV.y; // Wavy at 
the bottom more
vec4 color = textureLod(TEXTURE, UV + distort_sum, 0.0);


float flag_sat = 1.0;
color.rgb = mix(vec3(0.5), color.rgb, flag_sat);

float near_top = (UV.y + distort_sum.y) / (0.2 / (sprite_scale.y / 30.0));
near_top = clamp(near_top, 0.0, 1.0);
near_top = 1.0 - near_top;

color = mix(color, vec4(1.0), near_top);

float edge_lower = 0.0;
float edge_upper = edge_lower + 0.1;

if (near_top > edge_lower) {
	color.a = 0.0;
	if (near_top < edge_upper) {
		color.a = 0.0;//(edge_upper - near_top) / (edge_upper - edge_lower);
		}
}

COLOR = color;
}

Can you put the shader code here?

estebanmolca | 2020-01-23 05:56

The problem is that you are using the screen texture (SCREEN_TEXTURE) to generate the shader and not the only sprite texture (TEXTURE), this line:
vec4 color = textureLod (SCREEN_TEXTURE, SCREEN_UV + distort_sum, 0.0);
It should be like this:
vec4 color = textureLod (TEXTURE, UV + distort_sum, 0.0);
but you would have to modify the shader parameters again if the effect differs greatly. From what I read in your edit, you know this?

Other problem is: shadow_noise (coord) that function does not exist, but I guess it was an error copying the script and you were referring to noise ()

estebanmolca | 2020-01-23 21:48

when you say do you mean what I said? Using sprite texture instead of screen texture?
In order to assign the same shader to several sprites, you can create an empty node and configure the material there, then you would make children of that node the sprites you want to be affected and use the use_parent_material property in each sprite (without assigning an individual shader to each).
The problem with this is that all sprites would move the same wherever they are.
To solve that I think maybe there you could do some use of the screen coordinates (SCREEN_UV) along with the coordinates of each sprite (UV) to add randomness according to where the sprites are.

EDIT: (so that my idea is clear)

changue this line:
vec4 color = textureLod (SCREEN_TEXTURE, SCREEN_UV + distort_sum, 0.0);
by:
vec4 color = textureLod (TEXTURE, UV + distort_sum, 0.0);

Scene
—OtherNodes
—ShaderNode (Apply material this)
------FlagSprite1 (check use_parent_material to true)
------FlagSprite2 (same above)

after using some of the screen coordinates to randomize, a basic example would be to multiply, like this: float powerOfDistort = 15.0 * SCREEN_UV.x; (You will have to use better math here for better results)

estebanmolca | 2020-01-23 22:19

Thank you for your reply. I’ll see if I can get it to work. Also the shadownoise() func was something of a test I was working on. It should be just noise()

float fbm(vec2 coord) {
float value = 0.0; // color changer
float scale = 0.01;
//
for(int i = 0; i < OCTAVES; i++) 
{
	value += noise(coord) * scale;
	scale *= 0.0;
	coord *= 2.0;
}
return value;
}

I’ll see if I can get it to work and post my results. thanks again

Woon | 2020-01-24 16:27

:bust_in_silhouette: Reply From: Woon

Credit to estebanmolca:

Change the SCREEN_TEXTURE to TEXTURE to only get the sprite, not the screen
From:
vec4 color = textureLod (SCREEN_TEXTURE, SCREEN_UV + distort_sum, 0.0);
To:
vec4 color = textureLod (TEXTURE, UV + distort_sum, 0.0);

Additionally its easier to apply a shader to one node and all child nodes use the “UseParentMaterial” udner the CanvasItem of a node.

Scene
—OtherNodes
—ShaderNode (Apply material this)
------FlagSprite1 (check useparentmaterial to true)
------FlagSprite2 (same above)

Add When creating random distortion, you can use SCREEN_UV to get different distortions depending on the location of the sprite.

Example:
float powerOfDistort = 15.0 * SCREEN_UV.x; (You will have to use better math here for better results)