🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

2d point light, how to draw perfect circle (fragment shader)

Started by
1 comment, last by _WeirdCat_ 3 years, 9 months ago

I want to draw a temporary light where i touch the screen. Some simple circle.

It may seem trivial for first, however. The idea is to find distance between touch position and fragment position.

To simplify things for now lets assume we use landscape screen (sw > sh).

So basically i draw a 2d fullscreen quad and check every fragment if its in the light radius then compute light intensity based on distance and light radius value: r=1/16 of the screen height so basically r is 1/16 (we dont use any pixel lengths here)

now when its valid for height, for width its not cause theres a difference which we can compute by float aspect = sh/sw; => rx = r * aspect;

ok we have two radiuses for both x and y dimensions.

so basically i should be able to compute two intensities both for x and y by:

float dstY = abs(fragpos.y - lightpos[i].y);
float dstX = abs(fragpos.x - lightpos[i].x) * aspect;

But now the thing is that i somehow don't believe adding these two and dividing by 2 will give me the desired intensity.

float intensityY = clamp(1.0 - dstY / LRadius, 0.0, 1.0);
float intensityX = clamp(1.0 - dstX / LRadius, 0.0, 1.0);
float final_intensity = (intensityX + intensityY) / 2.0; //doesnt seem right like something tells me i need to use some kind of quadric gradient lol thats why i ask

Maybe someone could help me solving that.

cheers

and a code

precision highp float;

varying highp vec2 vert;

#include "$shaders/shader_math.h" yeah nice huh?
uniform vec3 click_bait[10];

uniform int cb_len;
uniform float sw;
uniform float sh;
vec4 light_color;
//remember to perserve aspect ratio
//let height be ('main') relative ratio
uniform float LRadius;
void main()
{
if (cb_len > 0)
{
float total_intensity = 0.0;
	float aspect = sh/sw;
	if (sh > sw) aspect = sw/sh;

float lradiusX = lradius * aspect;

int cnt = 0;
vec3 frag_pos = vec3(vert.x, vert.y, 0.0);
for (int i=0; i < cb_len; i++)
{
vec3 dst = vec3( abs( frag_pos.x - click_bait[i].x), abs( frag_pos.y - click_bait[i].y), 0.0 );
dst = vec3(clamp(1.0 - dst.x / LRadius, 0.0, 1.0), clamp(1.0 - dst.y / LRadius, 0.0, 1.0), 0.0);
float final_intensity = (dst.x+dst.y) / 2.0;
if (final_intensity > 0.0)
{
cnt = cnt + 1;
total_intensity = total_intensity + final_intensity;
}
}
total_intensity = total_intensity / float(cnt);
gl_FragColor = vec4(light_color.xyz*total_intensity, light_color.w);
}
else
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
}
Advertisement

_WeirdCat_ said:
So basically i draw a 2d fullscreen quad and check every fragment if its in the light radius then compute light intensity based on distance and light radius value:

I guess this is the good way to do it. What I don't understand is why do you need a loop in your shader. Drawing a fullscreen quad ensures that you'll cover all the pixels of the screen, therefore you don't need to have any loop. But maybe I misunderstood the meaning of “cb_len” ?

Also, when you draw a 50x50 pixels square on a screen, you end up with a square, not a rectangle, regardless of the aspect of your screen. Therefore a circle of 25 px radius is a circle of 25 px radius, no matter if you're along x or y. You should consider pixels to be square. This aspect of the screen is defined by the difference in the number of pixels in the width over the height of the screen, not in the width or height of a given pixel. Or did I miss something too here ?

ok even when pixels are the same size they differ in amount along x and y axis. that is what produces unequal quad instead of ‘one length side quad ’which i intend to draw

i just have found that my formula doesnt quite work since theres always an intensity value for Y along any X, so i still need to find proper solution to draw a perfect circle (in that case an ellipsoid that simulates circle) around touch point.

i'm messing with new interactive vcl gui so don't mind alpha test look

precision highp float;

varying highp vec2 verta;

uniform vec3 bait_pos[25];
uniform float bait_alpha[25];
uniform int bait_used[25];

uniform float sw;
uniform float sh;
uniform vec4 light_color;

uniform float LRadius;
vec2 vert;

void main()
{
vert = verta * 0.5 + 0.5;
float total_intensity = 0.0;
	float aspect = sh/sw;
	if (sh > sw) aspect = sw/sh;

float LRadiusX = LRadius * aspect;

int cnt = 0;
vec3 frag_pos = vec3(vert.x, vert.y, 0.0);

for (int i=0; i < 25; i++)
{
if (bait_used[i] == 0) continue;
vec3 dst = vec3(
abs( frag_pos.x - bait_pos[i].x),
abs( frag_pos.y - bait_pos[i].y), 0.0 );

dst = vec3(
clamp(1.0 - dst.x / LRadiusX, 0.0, 1.0),
clamp(1.0 - dst.y / LRadius, 0.0, 1.0), 0.0);

float final_intensity = (dst.x+dst.y) / 2.0;
 // intensity is the same for all dst.x > LRadiusX
if (final_intensity > 0.0)
{
cnt = cnt + 1;
total_intensity = total_intensity + final_intensity*bait_alpha[i];
}
}

if (cnt > 0)
{
total_intensity = total_intensity / float(cnt);
gl_FragColor = vec4(light_color.xyz, total_intensity);
}
else
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
}

OK figuring whats wrong i tried different approach that is to scale the radius in the x direction

however i ended with instead of defining the radius for y component its defining itself as x component: i mean i set LRadius as 1.0/10.0 of screen height but in shader after scaling it happens that this is the 1/10 of the screen width) and i dont quite understand wats going on, another thing is that i used

lights attenuation formula

float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

and i dont know exactly how to scale it to light radius, but moreover how to make that brightest circle smaller.

also i would like to have a full 1,1,1 color at the center dimming into this blueish and make that intense blue circle, smaller….

so when i managed to get it smaller i want a little max brightness at center (full white) it may be calculated as specular but i dunno and have no idea how to caluclate that in 2d.

however now i see android doesnt want me to use more than 4 touch inputs, somehow whenever i try to add 4th touch event it moves it to the first one position (this is wierd i may have some problem with actual java implementation that handles adding touch events to c++ stack,.. They changed alot i see

Worst thing is using this:

    if( (action == MotionEvent.ACTION_DOWN) ||  (action == MotionEvent.ACTION_POINTER_DOWN) )
    {
        PCPHONESHARE.OnMouseDown(event.getX(), event.getY());
        return true;
    }

Gives me some wicked results:

If i manage to touch the screen with only one finger then x and y coords are logged properly (that means correspond to actual real position i touch) However! if i manage to touch the screen with one finger and then decide to touch the screen with another finger (lets say on the opposite side) then logging shows that i touched the screen some nearby like 3 pixels next to the first touch where difference should be like 2000 pixels…. AND! passing these values to c++ show that this doesnt matter since they are mapped where i actually touched the screen.. so what the heck?? log shows i clicked almost at the same spot even when i touched something like a mile away and passing this garbage value to c++ works like it should and yet after hitting 4th time it cracks. seriously ill check upon c++ code anyway but its ridicilous how i am supposed to do anything if even debugging doesnt work i cant just ask phone politely that it show me the numbers ;x

This is how they look like:

and to show you what i additionally want - that white bright effect)

mhm i think i've had too much coffee lol (warning sound - didint realize)

ideally i wouldn't need another pass to draw bright white light

This topic is closed to new replies.

Advertisement