Sprite outline shader

General discussion of game creation, including ai, shaders, and other topics.

Moderators: naturally, ezraanderson

Sprite outline shader

Postby Galdred » Fri Mar 02, 2018 8:20 am

As discussed on slack, there are basically 2 ways to outline sprites:
The first one is to create an outline texture for each sprite or spritesheet.
It is a bit cumbersome, but it is faster (that said, sprite outline is seldom something you need very high performance for).
The second one is to use shader.

I took inspiration from here.

The following uses moai 1.75
So here is the vertex shader, outline.vsh:

Code: Select all
  1.  

  2. attribute vec4 position;

  3. attribute vec2 uv;

  4. attribute vec4 color;

  5. varying vec2 uvVarying;

  6.  

  7. void main () {

  8.   gl_Position = position;

  9.   uvVarying = uv;

  10. }

  11.  

  12.  



It basically only transfer the uv coordinates to the fragment shader.

and now the fragment shader, outline.fsh:


Code: Select all
  1.  

  2. uniform float stepX;

  3. uniform float stepY;

  4. uniform float r;

  5. uniform float g;

  6. uniform float b;

  7. // We will retrieve the texture from whatever prop the shader is attached to. No need to feed it in the code.

  8. uniform sampler2D texture;

  9.  

  10. varying vec2 uvVarying;

  11.  

  12. void main() {

  13.     // alpha will be the value we compute for each fragment

  14.     float alpha;

  15.     // we recover texture position from the vertex shader

  16.     vec2 texturePos = uvVarying;

  17.  

  18.     // we want the pixel to be drawn if its alpha value is different from one of the neighboring ones, so we sum:

  19.     // texture2D( texture, texturePos ).a-texture2D( texture, texturePos + vec2( i, j ) ).a ;

  20.     // for i,j in [-1,1], excluding (0,0)

  21.     // summing the absolute values would be more accurate if alpha is not always 1 or 0

  22.     alpha = 8*texture2D( texture, texturePos ).a;

  23.     alpha -= texture2D( texture, texturePos + vec2( stepX, 0.0 ) ).a;

  24.     alpha -= texture2D( texture, texturePos + vec2( -stepX, 0.0 ) ).a;

  25.     alpha -= texture2D( texture, texturePos + vec2( 0.0, stepY ) ).a;

  26.     alpha -= texture2D( texture, texturePos + vec2( 0.0, -stepY ) ).a;

  27.     alpha -= texture2D( texture, texturePos + vec2( stepX, stepY ) ).a;

  28.     alpha -= texture2D( texture, texturePos + vec2( stepX, -stepY ) ).a;

  29.     alpha -= texture2D( texture, texturePos + vec2( -stepX, stepY ) ).a;

  30.     alpha -= texture2D( texture, texturePos + vec2( -stepX, -stepY ) ).a;

  31.  

  32.     // we want the pixel to be displayed only if it was transparent in the first place, so we take the opposite of alpha,

  33.     // otherwise, we would get the inner outline (the line of outline pixels that are on the sprite, while we want the

  34.     // pixels around).

  35.     alpha = -alpha;

  36.  

  37.     gl_FragColor = vec4( r, g, b, alpha);

  38.  

  39. }

  40.  

  41.  



And the lua code, main.lua:
Code: Select all
  1.  

  2. -- moai 1.75

  3. local init

  4. local initShader

  5.  

  6. -- init the shader program

  7. function initShader()

  8.   local shaderProgram = MOAIShaderProgram.new()

  9.   -- transmits vertex attribute values to shader program

  10.   shaderProgram:setVertexAttribute ( 1, 'position' )

  11.   shaderProgram:setVertexAttribute ( 2, 'uv' )

  12.   shaderProgram:setVertexAttribute ( 3, 'color' )

  13.   -- uniform float variable declaration

  14.   shaderProgram:reserveUniforms(5)

  15. -- we associate an attibute index to each glsl variable

  16.   shaderProgram:declareUniform(1,"stepX",  MOAIShaderProgram.UNIFORM_FLOAT)

  17.   shaderProgram:declareUniform(2,"stepY",  MOAIShaderProgram.UNIFORM_FLOAT)

  18.   shaderProgram:declareUniform(3,"r",  MOAIShaderProgram.UNIFORM_FLOAT)  

  19.   shaderProgram:declareUniform(4,"g",  MOAIShaderProgram.UNIFORM_FLOAT)  

  20.   shaderProgram:declareUniform(5,"b",  MOAIShaderProgram.UNIFORM_FLOAT)  

  21.  

  22.   -- Note that we have declared the variables, but not loaded the program yet

  23.   return shaderProgram

  24. end

  25.  

  26. -- width and height are the size of the sprite to outline

  27. function init(width, height)

  28.   width = width or 128

  29.   height = height or 128

  30.  

  31. -- we read and load both shader programs

  32.   local file = assert(io.open("outline.fsh"))

  33.   local file2 = assert(io.open("outline.vsh"))

  34.   local fsh = file:read('*all')

  35.   local vsh = file2:read('*all')

  36.   file:close()

  37.   file2:close()

  38.  

  39. -- the shader is the program instance

  40.   local shader = MOAIShader.new()

  41.  

  42.   local gfxQuad = MOAIGfxQuad2D.new ()

  43.   gfxQuad:setTexture ( "moai.png" )

  44.   gfxQuad:setRect ( -width/2, -height/2, width/2, height/2 )

  45.  

  46.   local prop = MOAIProp.new()

  47.   prop:setDeck ( gfxQuad )

  48.   prop:setLoc(0,0)

  49.   layer:insertProp(prop)

  50.  

  51. -- we declare the variables for the shader program, and load the code that was in our string buffers.

  52.   local shaderProgram = initShader()

  53.   shaderProgram:load(vsh, fsh)

  54.  

  55. -- We already declared the shader variables, and made the association between the attr id and the glsl variable names

  56. -- We initialize the shaders with the shaderProgram, and pass the values

  57.   shader:setProgram(shaderProgram)

  58.   shader:setAttr(1, 1.0/width) --texture 1 pixel increment on x axis

  59.   shader:setAttr(2, 1.0/height) -- texture 1 pixel increment on y axis

  60.   shader:setAttr(3, 1.0) -- value for r

  61.   shader:setAttr(4, 0.0) -- g

  62.   shader:setAttr(5, 0.0) -- b of the outline color

  63.  

  64.   prop:setBlendMode(MOAIProp2D.GL_SRC_ALPHA,MOAIProp2D.GL_ONE_MINUS_SRC_ALPHA)

  65.   prop:setShader(shader)

  66.   prop:setVisible(true)

  67.   -- we need to make a copy of the prop, that will be drawn without shader

  68.   local prop2 = MOAIProp.new()

  69.   prop2:setDeck ( gfxQuad )

  70.   prop2:setLoc(0,0)

  71.   layer:insertProp(prop2)

  72.   prop2:setVisible(true)

  73.  

  74.  

  75. end

  76.  

  77. MOAISim.openWindow ( "test", 640, 480 )

  78.  

  79. viewport = MOAIViewport.new ()

  80. viewport:setSize ( 640, 480 )

  81. viewport:setScale ( 640, 480 )

  82.  

  83. layer = MOAILayer2D.new ()

  84. layer:setViewport ( viewport )

  85.   local gfxQuad = MOAIGfxQuad2D.new ()

  86.   gfxQuad:setTexture ( "bg.png" )

  87.   gfxQuad:setRect ( -320, -240, 320, 240 )

  88.  

  89.   local prop = MOAIProp.new()

  90.   prop:setDeck ( gfxQuad )

  91.   prop:setLoc(0,0)

  92.   layer:insertProp(prop)

  93.  

  94.  

  95. MOAISim.pushRenderPass (layer )

  96. init(128,128)

  97.  

  98.  

Last edited by Galdred on Thu Mar 08, 2018 4:31 pm, edited 3 times in total.
User avatar
Galdred
 
Posts: 53
Joined: Mon May 27, 2013 12:41 am

Re: Sprite outline shader

Postby ibisum » Fri Mar 02, 2018 11:05 am

Nice! Which version of MOAI is the demo code for?
;
--
Email: ibisum@gmail.com
IRC: torpor on FreeNode, see you in the #moai channel
Got a MOAI snippet? Please consider adding it to http://moaisnippets.info/
User avatar
ibisum
 
Posts: 1504
Joined: Mon Oct 17, 2011 1:11 am
Location: Vienna, Austria

Re: Sprite outline shader

Postby Galdred » Fri Mar 02, 2018 1:49 pm

The code was written for moai 1.75
User avatar
Galdred
 
Posts: 53
Joined: Mon May 27, 2013 12:41 am

Re: Sprite outline shader

Postby naturally » Fri Mar 02, 2018 7:13 pm

Great contribution! Thanks G

I had another idea, to do it by calculating the texture outline with MOAIImage (and cache the results) then use MOAIScriptDeck + MOAIDraw to draw the outline. Possibly faster than a shader, but more work. Only mentioning this for the heck of it :lol:
Image - Don't Be Patchman - a sneak-and-grow adventure!
User avatar
naturally
 
Posts: 716
Joined: Thu Aug 29, 2013 8:05 pm
Location: Canada

Re: Sprite outline shader

Postby Galdred » Sat Mar 03, 2018 3:53 am

Actually, I had written a tool to generate an outline for each sprite on a sprite sheet before going the shader route.
I just felt too lazy generating the outlines for all the spritesheet, then generating the texturepacker files.
It should be even faster as it is done before runtime.

Code: Select all
  1.  

  2. -- In order to create the sprite outlines, the ways to do that would be:

  3. -- - Either to do it dynamically, with a shader on the selected sprite elements

  4. -- (add selected frameLayers duplicating each frameLayer with a lower priority)

  5. -- - Either create a spritesheet with the outline

  6. --

  7. -- This program follows the second road :

  8.  

  9.  

  10. local M = {}

  11.  

  12. local gd = require "gd"

  13. local lfs = require "lfs"

  14.  

  15.  

  16.  

  17. function M.outline(im_name)

  18.   if not string.find(im_name, ".png") then

  19.     print("incorrect file extension, .png required")

  20.     return false

  21.   end

  22.   local im = gd.createFromPng(im_name)

  23.   local xSz, ySz = im:sizeXY()

  24.   print(xSz, ySz)

  25.   local new_filename = string.gsub(im_name, ".png", "_outline.png")

  26.   local im2 = gd.createPalette(xSz, ySz)

  27.   local col1 = im2:colorAllocateAlpha(255,255,255,0)

  28.   local col2 = im2:colorAllocateAlpha(0,0,0,110)

  29.   for y=0, ySz-1 do

  30.     for x=0, xSz-1 do

  31.       local c = im:getPixel(x,y)

  32.       if im:alpha(c) ~= 0 then

  33.         local border = false

  34.         for i = -1,1,2 do

  35.           local x1 = x+i

  36.           local y1 = y

  37.           if x1>=0 and x1<xSz and y1>=0 and y1<ySz then

  38.             local c2 =  im:getPixel(x1,y1)

  39.             if im:alpha(c2) ==0 then

  40.               border = true

  41.             end

  42.           end

  43.         end

  44.         for j = -1,1,2 do

  45.           local x1 = x

  46.           local y1 = y+j

  47.           if x1>=0 and x1<xSz and y1>=0 and y1<ySz then

  48.             local c2 =  im:getPixel(x1,y1)

  49.             if im:alpha(c2) ==0 then

  50.               border = true

  51.             end

  52.           end

  53.         end

  54.         if border then

  55.           im2:setPixel(x,y, col1)

  56.         else

  57.           im2:setPixel(x,y,col2)

  58.         end

  59.       else

  60.         im2:setPixel(x,y,col1)

  61.       end

  62.     end

  63.   end

  64.   im2:png("outline/"..new_filename)

  65. end

  66.  

  67. function M.outline_all(dir)

  68.   if dir then

  69.     lfs.chdir(dir)

  70.   end

  71.   lfs.mkdir("outline")

  72.   for fileName, ob in lfs.dir(lfs.currentdir()) do

  73.     M.outline(fileName)

  74.   end

  75. end

  76.  

  77. --M.outline_all("sprites")

  78. return M

  79.  

  80.  



To use, either create a sprite folder spriteFolder with everything you want to outline and call outline_all("spriteFolder"), or call outline for a specific file.

Disclaimer : This program is distributed as is. Use at your own risk. I am not accountable for any permanet damage caused to your files or hardware :D
User avatar
Galdred
 
Posts: 53
Joined: Mon May 27, 2013 12:41 am

Re: Sprite outline shader

Postby Galdred » Thu Mar 08, 2018 4:24 pm

I forgot to post the result, after having made the color variant:
Image
User avatar
Galdred
 
Posts: 53
Joined: Mon May 27, 2013 12:41 am


Return to Game Development

Who is online

Users browsing this forum: No registered users and 0 guests

cron

x