We have a Steam curator now. You should be following it. https://store.steampowered.com/curator/44994899-RPGHQ/

gamedev megathread

Game development hub. Projects, modding, and resources.
Post Reply
User avatar
twig
The Only Good Commie
Posts: 77
Joined: Feb 2, '23

gamedev megathread

Post by twig »

This thread is for discussing implementation details of your own game development projects (rendering APIs, pathfinding, algorithms, AI...) as well as for bragging about completed milestones.
User avatar
twig
The Only Good Commie
Posts: 77
Joined: Feb 2, '23

Post by twig »

I'd like to ask @rusty_shackleford to help me with getting light attenuation to work when light strength is expressed as distance where brightness of the light is 0%. Expressing light intensity as percentile distance makes it easier to operate for level design purposes.

Let D_max be the distance in pixels where the light has no effect anymore. This is set up by the level designer. For quadratic lights, this is the distance where the light brightens the image by less than 1/255th of a pixel. For linear light falloff, this is (linearly) extrapolated point where brightness becomes 0 or below.

Let pos be the position of the current pixel that we're determining the color for.

Let center be the point where the light originates.

Something like that comes to mind:

Code: Select all

vec2 tmp = pos - center;
float D = sqrt(tmp.x*tmp.x + tmp.y*tmp.y);
float alpha = 1 - min(1, D / D_max);
color = vec4(light_color.xyz, alpha);
I'm having trouble reasoning about this algebraically due to this being an inverse function, having to manage multiple attenuation types, and having to visualize it as a radial gradient.

Code: Select all

# one-liner for testing
>>> I = 255; dist=128; attenuation=1 - dist / I; print(attenuation)
0.4980392156862745
If you find this less counterintuitive than it is for me then I'd be grateful for help.

-twig
User avatar
Maggot
Posts: 69
Joined: Feb 9, '23

Post by Maggot »

twig wrote: June 10th, 2023, 14:19
I'd like to ask @rusty_shackleford to help me with getting light attenuation to work when light strength is expressed as distance where brightness of the light is 0%. Expressing light intensity as percentile distance makes it easier to operate for level design purposes.

Let D_max be the distance in pixels where the light has no effect anymore. This is set up by the level designer. For quadratic lights, this is the distance where the light brightens the image by less than 1/255th of a pixel. For linear light falloff, this is (linearly) extrapolated point where brightness becomes 0 or below.

Let pos be the position of the current pixel that we're determining the color for.

Let center be the point where the light originates.

Something like that comes to mind:

Code: Select all

vec2 tmp = pos - center;
float D = sqrt(tmp.x*tmp.x + tmp.y*tmp.y);
float alpha = 1 - min(1, D / D_max);
color = vec4(light_color.xyz, alpha);
I'm having trouble reasoning about this algebraically due to this being an inverse function, having to manage multiple attenuation types, and having to visualize it as a radial gradient.

Code: Select all

# one-liner for testing
>>> I = 255; dist=128; attenuation=1 - dist / I; print(attenuation)
0.4980392156862745
If you find this less counterintuitive than it is for me then I'd be grateful for help.

-twig
Could you give some context to this? I'm guessing it's a 2D game since you're calculating distance as a vec2. I only have any experience doing standard phong shading for a class in OpenGL/GLSL so keep that in mind, but I do find it strange that you're messing with the alpha for light falloff. I'm not sure why this couldn't be done with a normal diffuse calculation multiplied by your special attenuation for lighting.
User avatar
Maggot
Posts: 69
Joined: Feb 9, '23

Post by Maggot »

I'm personally working on an SDL port of JA2 without all the BS of Stracciatella. It's very slow going since I don't have a firm understanding of either SDL or DirectDraw so a lot of time is spent figuring out what has been more or less automated with SDL compared to DirectDraw's bookkeeping as well as the fact that JA2 does not do a great job of decoupling all the screen drawing code from game logic so there's tons of files to go through before it will even be able to compile. My butthurt at Stracciatella being a contributor toybox where nobody actually plays the game and instead just make it worse for people trying to play the standard game keeps me going.
User avatar
rusty_shackleford
Site Admin
Posts: 10253
Joined: Feb 2, '23
Contact:

Post by rusty_shackleford »

twig wrote: June 10th, 2023, 14:19
I'd like to ask @rusty_shackleford to help me with getting light attenuation to work when light strength is expressed as distance where brightness of the light is 0%. Expressing light intensity as percentile distance makes it easier to operate for level design purposes.

Let D_max be the distance in pixels where the light has no effect anymore. This is set up by the level designer. For quadratic lights, this is the distance where the light brightens the image by less than 1/255th of a pixel. For linear light falloff, this is (linearly) extrapolated point where brightness becomes 0 or below.

Let pos be the position of the current pixel that we're determining the color for.

Let center be the point where the light originates.

Something like that comes to mind:

Code: Select all

vec2 tmp = pos - center;
float D = sqrt(tmp.x*tmp.x + tmp.y*tmp.y);
float alpha = 1 - min(1, D / D_max);
color = vec4(light_color.xyz, alpha);
I'm having trouble reasoning about this algebraically due to this being an inverse function, having to manage multiple attenuation types, and having to visualize it as a radial gradient.

Code: Select all

# one-liner for testing
>>> I = 255; dist=128; attenuation=1 - dist / I; print(attenuation)
0.4980392156862745
If you find this less counterintuitive than it is for me then I'd be grateful for help.

-twig
Linear:
A = (d₂-d₁)÷d₂
Where d₁ is the distance the current fragment is from the lightsource, and d₂ is the distance of the light where the intensity is 0.

Quadratic:
A = ((d₂-d₁)÷d₂)²

Technically linear is just a special case where the exponent of the attenuation is 1.

Code: Select all

import math
from PIL import Image
import matplotlib.pyplot as plt

w, h = 1024, 1024
light_pos = (512, 512)
light_dist = 512  # d₂


def compute_light(img, exponent):
    for x in range(img.size[0]):
        for y in range(img.size[1]):
            dist = math.sqrt((x - light_pos[0])**2 + (y - light_pos[1])**2)  # d₁
            attenuation = (light_dist - dist / light_dist) ** exponent
            if attenuation < 0 or dist > light_dist:
                continue
            # map to 8-bit rgb
            color = 255 * attenuation
            pixels[x, y] = (int(color), int(color), int(color))


img = Image.new('RGB', (w, h), "green")
pixels = img.load()
compute_light(img, 1)
img_2 = Image.new('RGB', (w, h), "green")
pixels = img_2.load()
compute_light(img_2, 2)

# display
f = plt.figure()
f.add_subplot(1, 2, 1)
plt.imshow(img)
f.add_subplot(1, 2, 2)
plt.imshow(img_2)
plt.show(block=True)
Image
Technically wouldn't look like that in a game due to gamma correction:
Image

Does this help?

[edit]
woops, had the light inverse, fixed
Last edited by rusty_shackleford on June 11th, 2023, 00:33, edited 1 time in total.
Reason: assume dist ≤ light_dist, can simplify expression
User avatar
twig
The Only Good Commie
Posts: 77
Joined: Feb 2, '23

Post by twig »

Yes. Thank you.

Maggot wrote: June 10th, 2023, 17:13
but I do find it strange that you're messing with the alpha for light falloff. I'm not sure why this couldn't be done with a normal diffuse calculation multiplied by your special attenuation for lighting
vec3 uses the same amount of bytes as vec4. It's useful from the level design perspective (edit color brightness). Because light is always blended onto blackness and a constant blend mode is used, it's guaranteed to be equivalent to

Code: Select all

color = vec3(color_intensity.rgb * A);
Maggot wrote: June 10th, 2023, 17:31
My butthurt at Stracciatella being a contributor toybox where nobody actually plays the game and instead just make it worse for people trying to play the standard game keeps me going.
Seems like a lot more work than making a hostile fork from a known good revision and cherry-picking the following commits.
User avatar
twig
The Only Good Commie
Posts: 77
Joined: Feb 2, '23

Post by twig »

Here's another problem I'm having:

The idea is to output lighting and shadows without tanking performance by repeated calls to glNamedReadBuffer. Two color attachments are used, let's call them 'scratch' (0) and 'accum' (1).

glNamedReadBuffer is set up for the above.

Code: Select all

    framebuffer.fb.mapForDraw({
        { 0u, GL::Framebuffer::ColorAttachment{0} },
        { 1u, GL::Framebuffer::ColorAttachment{1} },
    });
    // src_C, dst_C src_A, dst_A
    GL::Renderer::setBlendFunction(0, BlendFunction::One, BlendFunction::Zero, BlendFunction::One, BlendFunction::Zero);
    GL::Renderer::setBlendFunction(1, BlendFunction::One, BlendFunction::One, BlendFunction::One, BlendFunction::One);
The shader is conditionally outputing depending on a uniform value.

Code: Select all

#if 1
    setUniform(ModeUniform, DrawLightmapMode); // == 1
    AbstractShaderProgram::draw(light_mesh);
#endif
#if 0
    setUniform(ModeUniform, DrawShadowsMode); // == 0
[...]
    AbstractShaderProgram::draw(mesh_view);
#endif

#if 1
[...]
    setUniform(ModeUniform, BlendLightmapMode); // == 2
    AbstractShaderProgram::draw(light_mesh);
#endif

Code: Select all

layout (location = 8) uniform uint mode;
layout (location = 0) out vec4 color0;
layout (location = 1) out vec4 color1;
[...]
void main() {
    if (mode == 1)
    {
[...]
        color0 = vec4(light_color.rgb * A, 1);
    }
[...]
    else if (mode == 2) // blend
    {
        color1 = texture(sampler0, (gl_FragCoord.xy + vec2(0.5)) * scale);
    } [...]
}
Now the whole thing has big artifacts that are flickering all the time.

Image

If this approach is wrong and a completely different solution is needed, what can be done to prevent the need to bind buffers each time 'scratch' is to be generated and blended into 'accum'?
User avatar
rusty_shackleford
Site Admin
Posts: 10253
Joined: Feb 2, '23
Contact:

Post by rusty_shackleford »

twig wrote: August 24th, 2023, 18:30
glNamedReadBuffer
This isn't an opengl function, did you mean something else?
User avatar
twig
The Only Good Commie
Posts: 77
Joined: Feb 2, '23

Post by twig »

rusty_shackleford wrote: August 24th, 2023, 18:39
twig wrote: August 24th, 2023, 18:30
glNamedReadBuffer
This isn't an opengl function, did you mean something else?
glNamedFramebufferReadBuffer.

This might be a better visualization of what's wrong with it. It's with the distance metric removed. Lights are green and red.

Image
User avatar
rusty_shackleford
Site Admin
Posts: 10253
Joined: Feb 2, '23
Contact:

Post by rusty_shackleford »

twig wrote: August 24th, 2023, 19:33
rusty_shackleford wrote: August 24th, 2023, 18:39
twig wrote: August 24th, 2023, 18:30
glNamedReadBuffer
This isn't an opengl function, did you mean something else?
glNamedFramebufferReadBuffer.

This might be a better visualization of what's wrong with it. It's with the distance metric removed. Lights are green and red.

Image
I honestly don't know, I think I need to see more of the code.
User avatar
twig
The Only Good Commie
Posts: 77
Joined: Feb 2, '23

Post by twig »

rusty_shackleford wrote: August 24th, 2023, 19:44
I honestly don't know, I think I need to see more of the code.
I posted the clone URL on IRC. You can do basically:

Code: Select all

git clone --recurse-submodules [url from IRC]
cd project-name
mkdir build
cd build
# note: there's a hack for save location that won't work 'if '..' of the install directory isn't the build directory.
mkdir save
cd save
wget [save-url from IRC]
cd ..
cmake ..
make install # will install into an 'install' subdir
install/bin/project-name-editor
# press f9
# right-click on a light icon and press 'Test'
# now look at shaders/lightmap.{cpp,hpp,frag,vert}
User avatar
rusty_shackleford
Site Admin
Posts: 10253
Joined: Feb 2, '23
Contact:

Post by rusty_shackleford »

twig wrote: August 24th, 2023, 21:24
rusty_shackleford wrote: August 24th, 2023, 19:44
I honestly don't know, I think I need to see more of the code.
I posted the clone URL on IRC. You can do basically:

Code: Select all

git clone --recurse-submodules [url from IRC]
cd project-name
mkdir build
cd build
# note: there's a hack for save location that won't work 'if '..' of the install directory isn't the build directory.
mkdir save
cd save
wget [save-url from IRC]
cd ..
cmake ..
make install # will install into an 'install' subdir
install/bin/project-name-editor
# press f9
# right-click on a light icon and press 'Test'
# now look at shaders/lightmap.{cpp,hpp,frag,vert}
cmake fails at trying to find sdl2(it is installed)

I see no issues with the code tbh
User avatar
Shillitron
Turtle
Turtle
Posts: 1632
Joined: Feb 6, '23
Location: ADL Head Office

Post by Shillitron »

Redaxium 3 sneak peak? Looks ready to ship IMO.
User avatar
Tweed
Turtle
Turtle
Posts: 1635
Joined: Feb 2, '23

Post by Tweed »

twig wrote: August 24th, 2023, 19:33
rusty_shackleford wrote: August 24th, 2023, 18:39
twig wrote: August 24th, 2023, 18:30
glNamedReadBuffer
This isn't an opengl function, did you mean something else?
glNamedFramebufferReadBuffer.

This might be a better visualization of what's wrong with it. It's with the distance metric removed. Lights are green and red.

Image
The watermelon dimension.
User avatar
twig
The Only Good Commie
Posts: 77
Joined: Feb 2, '23

Post by twig »

It was a glNamedFramebufferDrawBuffer call missing in between the two draws in order to map only one of the buffers for reading purposes, rather than both. It's cheaper to map them than bind them, anyway.
Post Reply