Looking for a dithering shader

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
alberto_lara
Party member
Posts: 372
Joined: Wed Oct 30, 2013 8:59 pm

Looking for a dithering shader

Post by alberto_lara »

Hi, so I need a dithering shader and I understand I need to use a mask/pattern along with it. I've been playing around with this one here but no luck so far. Any ideas on this? I also tried to port this one but I'm not really good at shaders.

Thanks a lot.
User avatar
pgimeno
Party member
Posts: 3673
Joined: Sun Oct 18, 2015 2:58 pm

Re: Looking for a dithering shader

Post by pgimeno »

That shader quantizes to an 8 colour palette (all combinations of R=0 or 1, G=0 or 1, B=0 or 1). Is that what you want?
User avatar
alberto_lara
Party member
Posts: 372
Joined: Wed Oct 30, 2013 8:59 pm

Re: Looking for a dithering shader

Post by alberto_lara »

Yes, but I also want to apply the dithering effect, not just reduce the colors, this is what I have so far (based on pseudo code here)

Code: Select all

vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
  vec4 pixel = Texel(texture, vec2(texture_coords.x, texture_coords.y));

  vec4 oldPixel = Texel(texture, vec2(texture_coords.x, texture_coords.y));
  vec4 newPixel = floor(oldPixel.rgba * 8 + vec4(0.5)) / 8;
  vec4 error = oldPixel - newPixel;

  newPixel = Texel(texture, vec2(texture_coords.x + 1, texture_coords.y    )) + error * 7 / 2;
  newPixel = Texel(texture, vec2(texture_coords.x - 1, texture_coords.y + 1)) + error * 3 / 2;
  newPixel = Texel(texture, vec2(texture_coords.x,     texture_coords.y + 1)) + error * 5 / 2;
  newPixel = Texel(texture, vec2(texture_coords.x + 1, texture_coords.y + 2)) + error * 1 / 2;

  return newPixel;
}

but I'm sure doing something wrong because I just get a dark and weird image:

Image
User avatar
pgimeno
Party member
Posts: 3673
Joined: Sun Oct 18, 2015 2:58 pm

Re: Looking for a dithering shader

Post by pgimeno »

Ok then.

Image

Image

There are two ways of applying the dithering pattern: relative to the image and relative to the screen.

Relative to the image is more complicated, in that you need to send the dimensions of the image being drawn to the shader. Relative to the screen does not need that. But if you have moving images, you may need to choose one of them.

Code: Select all

local shader = love.graphics.newShader[[

  uniform Image pattern;
  //uniform vec2 dim; // dimensions of the texture to draw (only if rel. to img)

  vec4 effect(vec4 colour, Image tex, vec2 texpos, vec2 scrpos)
  {
    return step(Texel(pattern,
      //texpos*dim/8  // uncomment if relative to the image
      scrpos/8        // comment this line out if relative to the image
    ), Texel(tex, texpos));
  }

]]

local pat = love.graphics.newImage('OrderedDitheringPattern.png')
pat:setWrap('repeat')
pat:setFilter('nearest', 'nearest')
shader:send('pattern', pat)

local img = love.graphics.newImage('image.jpg')

--local vec2 = {0, 0}  -- uncomment if rel. to image

-- this is NOT for the shader, just to move the image around
local imgSizeX, imgSizeY = img:getDimensions()
local xRange = love.graphics.getWidth() - imgSizeX
local yRange = love.graphics.getHeight() - imgSizeY
local t = 0
function love.update(dt)
  t = t + dt
end


function love.draw()
  love.graphics.setShader(shader);

--[[ --uncomment if relative to the image
  vec2[1] = img:getWidth()
  vec2[2] = img:getHeight()
  shader:send('dim', vec2)
--]]

  -- if relative to the image, apply math.floor to the coordinates
--  love.graphics.draw(img, math.floor((math.cos(t*9/7)/2+0.5)*xRange), math.floor((math.sin(t)/2+0.5)*yRange))
  love.graphics.draw(img, (math.cos(t*9/7)/2+0.5)*xRange, (math.sin(t)/2+0.5)*yRange)
end

function love.keypressed(k)
  return k == "escape" and love.event.quit()
end

The dithering pattern image is attached below.

Edit: No, pure Floyd-Steinberg won't work with a shader: shaders work locally, that is, they only care about the current pixel, but they can't hold state or do things in a pre-determined order; however, that pseudocode requires propagating the error values from the top left all the way to the bottom right, line by line (notice how they add to the pixel that will be evaluated next). That is, in a way the bottom right pixel depends on the top left pixel. A shader can't do that, or at least not efficiently.

The ShaderToy algorithm, on the other hand, which is the one I've implemented, has the advantage of being local, that is, it only needs to examine the current pixel and apply the threshold in the matrix to it.
Attachments
OrderedDitheringPattern.png
OrderedDitheringPattern.png (255 Bytes) Viewed 8102 times
User avatar
alberto_lara
Party member
Posts: 372
Joined: Wed Oct 30, 2013 8:59 pm

Re: Looking for a dithering shader

Post by alberto_lara »

That works great! what if I want to have more colors? does that mean more gray tones in the pattern?
User avatar
Ulydev
Party member
Posts: 445
Joined: Mon Nov 10, 2014 10:46 pm
Location: Paris
Contact:

Re: Looking for a dithering shader

Post by Ulydev »

Hi alberto_lara,

I tried to play arround with the shaders you mentioned. What kind of effect are you looking for exactly? I think it might be easier to work with pre-defined patterns rather than trying to implement it in the shader:

Image

EDIT: I got ninja'd haha
Attachments
dither.zip
(479.24 KiB) Downloaded 343 times
User avatar
alberto_lara
Party member
Posts: 372
Joined: Wed Oct 30, 2013 8:59 pm

Re: Looking for a dithering shader

Post by alberto_lara »

To be precise, what I need it's a shader to take an image and apply an 8 bit color (so I need 256 colors) dithering (either Floyd–Steinberg or positioned). Edit: actually, I'd like to implement the Floyd–Steinberg method in the shader logic.

Edit 2: I'm currently looking at this to see if I can pull it https://medium.com/100-days-of-algorith ... 5b25ee0a65
User avatar
pgimeno
Party member
Posts: 3673
Joined: Sun Oct 18, 2015 2:58 pm

Re: Looking for a dithering shader

Post by pgimeno »

alberto_lara wrote: Thu May 07, 2020 10:52 pm To be precise, what I need it's a shader to take an image and apply an 8 bit color (so I need 256 colors) dithering (either Floyd–Steinberg or positioned). Edit: actually, I'd like to implement the Floyd–Steinberg method in the shader logic.
As I said, that's not possible because shaders do not work like that. Since a shader can't hold state, the only way to do it would be to process the whole image up to the current pixel for each pixel, which would be extremely slow to the point of being impractical.

The method both Ulydev and I used basically consists in applying a 0-1 dither to every component in the image: first take the R image and generate a dithered one with just two quantization levels, 0 and 1, then do the same with the G one and then with the B one, then put all of them together. I think it would not be too difficult to quantize each component to more levels, e.g. 0, 0.33, 0.67, 1 for 4 levels, though the shader wouldn't be so fast.

If you want 256 colours, it could be done with 8 levels for green, 8 for red and 4 for blue (giving 8*8*4=256 colours). But making it adapt to a predetermined palette is more difficult, and I can't think right away of a strategy that would work in a shader.

Edit: It can be done in Lua, though. I can't vouch for the speed.
User avatar
alberto_lara
Party member
Posts: 372
Joined: Wed Oct 30, 2013 8:59 pm

Re: Looking for a dithering shader

Post by alberto_lara »

Ah, right, I missed that! I guess I'll have to apply the dithering in the file image then and just draw it without shaders. Thanks a lot.
Post Reply

Who is online

Users browsing this forum: No registered users and 6 guests