Single Screen pixel fragment shader not working as expected
Asked Answered
S

3

0

I want to draw a line across a sprite that is consistently 1 screen pixel wide, here is a simplified version of the shader code (just use this on a sprite):

shader_type canvas_item;
render_mode blend_mix ;

void fragment() {
	float topedge = (0.5); // top edge of line
	float bottomedge = topedge + SCREEN_PIXEL_SIZE.y; // bottom edge of line
	
	if ((UV.y >= topedge) && (UV.y < bottomedge)) {COLOR = vec4(1.,0.,0.,1.);}
	else {COLOR = texture(TEXTURE, UV);} // default colour
}

The code just detects if the UV y coord is between the centre of the sprite or the centre of the sprite + 1 screen pixel and changes the colour to red if it is. Scaling the sprite makes the line appear and disappear. Oddly I also noticed that, when the project is run, changing the window size also makes it appear and disappear. Any ideas what I am doing wrong here?

Saval answered 10/9, 2021 at 17:37 Comment(0)
D
0

You want 1 screen pixel line across the sprite even if sprite is scaled? I'm assuming that you do. In which case you'll need a little help from the vertex shader:

shader_type canvas_item;
render_mode blend_mix;

uniform float lineWidth = 1.0; // line width in screen pixels
uniform float lineOffset = 0.0; // line offset in screen pixels
uniform vec4 lineColor = vec4(1.0, 0.0, 0.0 ,1.0);

varying vec2 pivotWorld;
varying vec2 pixelWorld;

void vertex(){
	pivotWorld = (WORLD_MATRIX * vec4(0.0, 0.0, 0.0, 1.0) ).xy;	
	pixelWorld = (WORLD_MATRIX * vec4(VERTEX, 0.0, 1.0) ).xy;	
}

void fragment() {
    float s = step(lineWidth/2.0, abs(pivotWorld.y - pixelWorld.y + lineOffset));
	COLOR = mix(lineColor, texture(TEXTURE, UV), s);
}
Denise answered 10/9, 2021 at 18:30 Comment(0)
S
0

Thanks xyz. I'm probably being dim but this does not seem to work for me. I dropped this on a sprite and have tried fiddling about with the uniforms but I see no line. I think the issue is with pivotWorld, could you explain what pivotWorld is meant to be - a bit of experimenting (I used it to colour the sprite) suggests it is in the 1000s, it was full bright until I divided it's value by over 5000. Any idea what I might be doing wrong?

  • Update - removing pivotWorld and just setting lineoffset to a large -ve number does make the line appear at an absolute screen pos, so I assume pivotWorld is mean to be the sprite position?
  • Update Update - Seems my sprite had ended up with some pretty wacky values for offset and scale, after adjusting these your code works fine xyz (some work needed by me to get it to work correctly with offset), thanks again!
Saval answered 11/9, 2021 at 15:11 Comment(0)
D
0

pivotWorld is sprite's centerpoint (its pivot) transformed back to the world space. Checking for the distance in this space will give us that value in native pixels. The line will pass through that point when lineOffset uniform is kept at its default zero value. If your spirte's Y offset is large then this point could be out of the sprite's image. In that case the line won't be visible unless you adjust lineOffset in the shader, or bring sprite's center back into its image via sprite's offset properties.

Note that you can easily adapt the shader to show vertical or even oblique line.

Btw if you want this line's width to be measured in sprite's pixels then the whole vertex shader transformation isn't needed.

Denise answered 11/9, 2021 at 18:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.