Shaders How do you make a Shader draw outside of a sprite without a surface?

Ido-f

Member
So the way I take it from guides so far, the common way to enable a shader to draw outside of a sprite's texture (like drawing an outline for it) is to simply increase the size of that texture and disable auto-cropping in its settings.

I don't like this solution. It feels awkward to me to resize sprites like that, and I heard that it's more efficient to keep texture pages compact. A desired shader effect could be several times the sprite's original size.

Another possible solution is to draw that sprite to a surface large enough and then draw that surface using the shader.
I don't like this solution either because it adds a draw call and requires creating and handling a surface.

It seems to me to have something to do with the vertex shader and the gl_Position value, but I don't understand the process well enough to know if it's even possible.
Does anyone know a good way to do this?
 

Juju

Member
Use a primitive to define a quad (2x triangle) larger than your base image and write your shader to correct for this. You cannot draw outside of the triangles defined in your vertex buffer so you simply need to make the area your triangles cover bigger.
 

Bart

WiseBart
It should also be possible to use a quad the size of the sprite as the contents of the vertex buffer. In the vertex shader you can then modify gl_Position so that the surrounding pixels are also covered by the fragment shader.
That way, you don't have to 'hard-code' the border into the vertex buffer.
It would require that you figure out a way to find out the direction in which to extend the vertices in the vertex shader, though. That's probably easiest using corner IDs.
 

Juju

Member
That's a fair point Bart, though I would like to point out that corner IDs are a bit messed up at the moment and are inconsistent across various native draw functions (I think - @Ariak knows more).

In lieu of corner IDs, one could use normals.
 

Ido-f

Member
Thanks for the answers!
I'll try to implement juju's method. @Bart 's method does seem preferable but I have no idea how to approach it.
If I change the gl_Position value, doesn't it just take that pixel in the original draw area and draws it somewhere else? So for each pixel in the additional area there would be a hole in the original area?
Would very much appreciate more detail about this, maybe some example code?
 
Last edited:

Bart

WiseBart
If I change the gl_Position value, doesn't it just take that pixel in the original draw area and draws it somewhere else? So for each pixel in the additional area there would be a hole in the original area?
Would very much appreciate more detail about this, maybe some example code?
No, not exactly. The code in the fragment shader is run for each pixel that lies in the draw area. But you can change the shape of that draw area before the fragment shader is run by modifying gl_Position of vertices in the vertex shader.

Here's a small working example that shows the idea: test_sprite_border_shader
It uses the texture coordinates, rather than normals, to find out which corner we're in, assuming that top-left is uv (0,0) and bottom-right uv (1,1) (so this works when sprites are on a separate texture page).

The most important code is in the vertex shader:
Code:
//
// Vertex shader
//
attribute vec3 in_Position;                  // (x,y,z)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform float u_fBorderWidth;
uniform vec2 u_vSpriteSize;

void main()
{
    // Find out the direction we need to go in order to add a border to this side
    // In this code, we'll quite happily assume that the top-left and bottom-right uvs are (0,0) and (1,1) respectively
    // -1 + 2 * 0 = -1 => A value of 0 returns -1
    // -1 + 2 * 1 =  1 => A value of 1 returns 1
    vec2 offset = vec2(-1.0,-1.0) + 2.0 * in_TextureCoord;
   
    // Now scale the current vertex to account for the border
    vec4 object_space_pos = vec4( in_Position + vec3(offset, 0.0) * u_fBorderWidth, 1.0);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
   
    v_vColour = in_Colour;
   
    // Modify the texture coordinate as well
    v_vTexcoord = vec2(in_TextureCoord + offset * u_fBorderWidth / u_vSpriteSize);
}
First thing is to move the vertices outward in the right direction, depending on the corner we're currently at.
The uvs then have to be moved inward, so the sprite's pixels end up in the same position as before.

Then, in the fragment shader, all uvs that are outside of the (0,0)-(1,1) range belong to the border.
In this way, you can execute different code in the fragment shader on those pixels.
 
Top