2
\$\begingroup\$

I have some sprites in my game, mostly though not entirely UI related, that I would like to be able to scale to different sizes without warping the borders of the sprite.

Looking around I was able to find the following two threads

How do i set up 9-slicing in Opengl?

https://www.gamedev.net/forums/topic/696879-glsl-9-slicing/

And from those, was able to get the following fragment shader

#version 330 core

in vec2 TextureCoordinates;

uniform sampler2D image;

uniform vec2 u_dimensions;
uniform vec2 u_border;
uniform vec4 spriteColor;
uniform vec4 bounds;


float map(float value, float originalMin, float originalMax, float newMin, float newMax) {
    return (value - originalMin) / (originalMax - originalMin) * (newMax - newMin) + newMin;
}

// Helper function, because WET code is bad code
// Takes in the coordinate on the current axis and the borders 
float processAxis(float coord, float textureBorder, float windowBorder, float x, float z) 
{
    if (coord < windowBorder)
        return map(coord, 0, windowBorder, 0, textureBorder) ;
    if (coord < 1 - windowBorder) 
        return map(coord,  windowBorder, 1 - windowBorder, textureBorder, 1 - textureBorder);
    return map(coord, 1 - windowBorder, 1, 1 - textureBorder, 1);
} 

vec2 subspriteMap(vec2 inner) 
{
    return mix(vec2(bounds.x, bounds.z), vec2(bounds.y, bounds.w), inner.xy);
}

void main(void) 
{
    vec2 newUV = vec2(
        processAxis(TextureCoordinates.x, u_border.x, u_dimensions.x, bounds.x, bounds.y),
        processAxis(TextureCoordinates.y, u_border.y, u_dimensions.y, bounds.z, bounds.w)
    );
    
    newUV = subspriteMap(newUV);

    gl_FragColor = vec4(spriteColor) * texture2D(image, newUV);

}

Where u_dimensions is the border size divided by what I am scaling the sprite to, u_border is the border size divided by the original size of the sprite, and bounds are the uv coordinates of the sprite within the sprite sheet.

This works perfectly fine when the interior of my textures is featureless, and allows those textures to be part of a larger sprite sheet/atlas.

enter image description here enter image description here

However, if I want to have some sort of pattern in the interior, that is still stretched out.

enter image description here enter image description here

What I would like is for the interior of the pattern to repeat, similar to an example image of something that one can do in Gamemaker, and indeed most engines that I have seen.

enter image description here

While it seems like it is just a simple option in most game engines, I am at a loss for how I could set it up myself. Any ideas? Using GL_REPEAT as a texture parameter doesn't really work, as the sprites I am using are part of a sheet, and I don't want to repeat most of them, just the interiors of these ones.

Making the following edits from @Blindman67:

float map(float val, float oMin, float oMax, float nMin, float nMax) {
    return (val - oMin) / (oMax - oMin) * (nMax - nMin) + nMin;
}
float mapInside(float val, float min, float innerStart, float innerSize) {
    return mod(val - min, innerSize) + innerStart;
}
float processAxis(float val, float tBorder, float wBorder) {
    if (val < wBorder) {   // check if in near border
        return map(val, 0.0, wBorder, 0.0, tBorder);
    }
    float farEdge = 1.0 - wBorder;
    if (val >= farEdge) {  // check if in far border
        return map(val, farEdge, 1.0, 1.0 - tBorder, 1.0);
    }
    // must be in inside section of sprite
    return mapInside(val, wBorder, tBorder, 1.0 - tBorder - tBorder);
}

I get the following result. enter image description here This is repeating the pattern, but I am still getting very noticeable stretching. What I would like is for the result to look more similar to this, which I created manually. enter image description here

\$\endgroup\$
2
  • 1
    \$\begingroup\$ You should not change a question once an answer is given. I don't have a platform to test at the moment, i will remove my answer and fix it if I get a chance. The line return mod(val - min, innerSize) + innerStart; needs to change the scale of val - min eg return mod((val - min) * scale, innerSize) + innerStart; where scale is passed as a argument and is the ratio of tBorder and wBorder which way round?not sure eg scale = (1.0 - wBorder * 2.0) / (1.0 - tBorder * 2.0) or scale = (1.0 - tBorder * 2.0) / (1.0 - wBorder * 2.0) \$\endgroup\$
    – Blindman67
    Commented Jul 15 at 17:52
  • \$\begingroup\$ Sorry, I can't attach images to comments and I was under the impression I'm not supposed to "answer" with additional questions or clarifications. Attempting the suggested update, we're definitely getting closer, but there is still some odd stretching imgur.com/RR6J6ri \$\endgroup\$
    – starrider
    Commented Jul 15 at 22:48

1 Answer 1

1
\$\begingroup\$

I was able to make the following changes to the mapInside function provided by @blindman67.

float mapInside(float val, float min, float innerStart, float innerSize, float uDimensions, float uBorder) {
    float scale = uBorder / uDimensions;
    if(fract(val * scale) < uBorder)
    {
        return fract(min*scale);
    }
    if(fract(val*scale) > innerStart + innerSize)
    {
        return fract(min*scale);
    }
    return mod((val - min) * scale, innerSize) + innerStart; 
}

I noticed by dividing the original size by the new size I could get the scale. When I did that it was repeating the center section, but it was repeating it at the edge of my central section. So, if my central section was 18 pixels, this would repeat it every 18 pixels. When resizing to 96, this meant it was drawn 5 times, instead of the three that I wanted. If I account for values outside of the range I want and just draw the value I want at those places, it seems to fix my problem. Here is my example sprite resized to 96x96

enter image description here

And here it is resized to 56x32. You can see the interior pattern is cut off as it cannot be completed. enter image description here

This works well enough for me. If anyone comes across this, note my use of fract(min*scale) for the values outside of the central parts I want is fairly arbitrary, I just happen to know that the results there are going to be the colors I want. Were that not the case, if my shape instead looked like this enter image description here you end up with something that looks like this enter image description here

There's probably a simpler way to do this, but it works for me, so I'll call this solved.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .