Passing a table to a 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
Le Fauve
Prole
Posts: 5
Joined: Wed Nov 26, 2014 12:51 pm

Passing a table to a shader

Post by Le Fauve »

Hi,

First of all, I'm new to Löve, to lua and to glsl shaders, so I apologize in advance if I ask something stupid :).

I'm trying to achieve a simple starfield effect, and I think it should be interesting to use pixel shaders for this.
I made some tests and I'm getting there, however I do have an issue: I need to give to my shader a table of vec3.

I found in the documentation a way to do it, but it works by passing explicitly all elements of the array:

Code: Select all

myShader = love.graphics.newShader[[
	extern vec3 field[3]; 
.../...
]]

for i=0,599 do
	field[i] = { i+.5,2,.5 }
end
myShader:send("field",field[0],field[1],field[2]);
This is working, but my problem is that I want to pass a 600 vec3 array...

For the record, I did try to pass the 600 parameters to myShader:send(), and it fails with "Syntax error: main.lua:xx: function or expression too complex near 'field'".

Any idea how I could do that?
My search on this forum found two interesting topic on shaders, but none of them address this issue (viewtopic.php?f=4&t=78252 and viewtopic.php?f=4&t=32605).

I'm considering passing a 1x600 pixels image, but that seems a little overkill (and the only way I found for accessing a given pixel from an image is to use the Texel() function, which will require me to normalize my 0-599 index, forcing me to do an extra multiplication/pixel).
Also I'm concern that OpenGL may try to be smart and to antialias the pixel it will return if I am not straight on the center of the pixel, which would ruin the effect.

Long story short, I could really use a little push in the right direction here :) and I'm open to any suggestion!
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Passing a table to a shader

Post by s-ol »

Le Fauve wrote:Hi,

First of all, I'm new to Löve, to lua and to glsl shaders, so I apologize in advance if I ask something stupid :).

I'm trying to achieve a simple starfield effect, and I think it should be interesting to use pixel shaders for this.
I made some tests and I'm getting there, however I do have an issue: I need to give to my shader a table of vec3.

I found in the documentation a way to do it, but it works by passing explicitly all elements of the array:

Code: Select all

myShader = love.graphics.newShader[[
	extern vec3 field[3]; 
.../...
]]

for i=0,599 do
	field[i] = { i+.5,2,.5 }
end
myShader:send("field",field[0],field[1],field[2]);
This is working, but my problem is that I want to pass a 600 vec3 array...

For the record, I did try to pass the 600 parameters to myShader:send(), and it fails with "Syntax error: main.lua:xx: function or expression too complex near 'field'".

Any idea how I could do that?
My search on this forum found two interesting topic on shaders, but none of them address this issue (viewtopic.php?f=4&t=78252 and viewtopic.php?f=4&t=32605).

I'm considering passing a 1x600 pixels image, but that seems a little overkill (and the only way I found for accessing a given pixel from an image is to use the Texel() function, which will require me to normalize my 0-599 index, forcing me to do an extra multiplication/pixel).
Also I'm concern that OpenGL may try to be smart and to antialias the pixel it will return if I am not straight on the center of the pixel, which would ruin the effect.

Long story short, I could really use a little push in the right direction here :) and I'm open to any suggestion!
If you're passing 600 values to the shader you're probably doing something wrong already, but nevertheless this could work:

Code: Select all

myShader:send("field",unpack(field));

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
Wojak
Party member
Posts: 134
Joined: Tue Jan 24, 2012 7:15 pm

Re: Passing a table to a shader

Post by Wojak »

I think that in this case you don't need to send anything... (I assume that 600 is screen height)

Code: Select all

myShader = love.graphics.newShader[[
	vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords ){
		//note that GLSL counts y coordinates from the bottom of the screen so you may want to add:
		// screen_coords.y = 600- screen_coords.y;
		vec3 field = vec3(screen_coords.y+0.5,2,0.5);
		//you should be able to access the values by  field.x,  field.y and field.z
	}
]]
Le Fauve
Prole
Posts: 5
Joined: Wed Nov 26, 2014 12:51 pm

Re: Passing a table to a shader

Post by Le Fauve »

S0lll0s wrote: If you're passing 600 values to the shader you're probably doing something wrong already, but nevertheless this could work:

Code: Select all

myShader:send("field",unpack(field));
Thanks S0lll0s!

The idea is to pass the 600 values only once. I'm new to shaders, but I've seen peoples passing whole texture images, so I guess that my 600x4 values are kind of small, aren't they?

What I'm trying to achieve is a kind of basic starfield; (with one star/pixels line)
- for each line I store the initial position of the star, it's speed and it's color
- at each frame I pass the time, and for each pixel I compute the x position of the pixel on this row, and if it matches the current pixel I draw it.

How will you do without passing the initial values to the shader?

Perhaps there is a smarter way to do it, but like I said I'm discovering the shaders so I may miss something obvious.

It would be nice if I could do the computation only once per pixel row per frame, but I'm not really sure I can store values inside the shader, and also I cannot assume a lot of pixels won't be processed at the same time (they probably will, if I understand the whole shaders idea).
Le Fauve
Prole
Posts: 5
Joined: Wed Nov 26, 2014 12:51 pm

Re: Passing a table to a shader

Post by Le Fauve »

Wojak wrote:I think that in this case you don't need to send anything... (I assume that 600 is screen height)

Code: Select all

myShader = love.graphics.newShader[[
	vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords ){
		//note that GLSL counts y coordinates from the bottom of the screen so you may want to add:
		// screen_coords.y = 600- screen_coords.y;
		vec3 field = vec3(screen_coords.y+0.5,2,0.5);
		//you should be able to access the values by  field.x,  field.y and field.z
	}
]]
Thanks Wojak,

But those values are hardcoded to make it easier to test the shader; once it works, the data won't be so obvious :)

However you've got a point: I could use a hash function to determine a "pseudo-random" initial position, speed and color from the y position.
However I feel a little bad about this because it means I'll have my shader recomputing the same thing for every pixel of every frame...

But perhaps I'm wrong and this is the way to go?

What would you suggest?

(For being complete, another solution I considered was to hardcode the initial values as a constant vector array in the shader. The starfield would always be the same, but I don't think that would cause any issue).
User avatar
Positive07
Party member
Posts: 1014
Joined: Sun Aug 12, 2012 4:34 pm
Location: Argentina

Re: Passing a table to a shader

Post by Positive07 »

If you are doing this just once, store them in an ImageData 30x20 pixels using R,G,B,A to store the values you want, then send a texture and go through the pixels of the image read the color and do something with it
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
bizziboi
Citizen
Posts: 57
Joined: Sat Apr 16, 2011 9:24 am

Re: Passing a table to a shader

Post by bizziboi »

I am not sure why you feel using a texture is overkill.

When you pass a texture you
a) Give the shader something it's made to work with and work with fast
b) Only use up one shader parameter slot

When you pass 600 values
a) I think you can't do that. Are there that many parameter slots?
b) Regardless, the shader has to bind 600 elements, that's more expensive than looking up in a texture (the gfx card is made to do that and do it fast)
c) You use pretty much the same amount of data

There's a reason people use lookup textures.
Le Fauve
Prole
Posts: 5
Joined: Wed Nov 26, 2014 12:51 pm

Re: Passing a table to a shader

Post by Le Fauve »

Well, I managed to make it work :)
bizziboi wrote:I am not sure why you feel using a texture is overkill.

When you pass a texture you
a) Give the shader something it's made to work with and work with fast
b) Only use up one shader parameter slot
As I said, this is a totally new world for me; "texture" sounds like an awfully complicated solution when what I need is just a small array of values (it doesn't really sound natural to me to turn my data into an image, and then in each pixel's shader turn the image back into data).
However, it appear you're right (see below)
bizziboi wrote:When you pass 600 values
a) I think you can't do that. Are there that many parameter slots?
b) Regardless, the shader has to bind 600 elements, that's more expensive than looking up in a texture (the gfx card is made to do that and do it fast)
c) You use pretty much the same amount of data
I had no idea there was a limit to "parameter slots", but indeed, the maximal array I was able to pass was 511 elements, and when I added an extra parameter to implement a "speed effect", it crashed again, and I had to reduce the size to 510.

In the end, I only used 400 elements, for some safety margin, but I think I'll recreate it with hardcoded values into the shader (it doesn't really mater if the starfield always starts at the same position).

Once again, thanks for your help, I wouldn't have got it working without you ;)

bizziboi wrote:There's a reason people use lookup textures.
I totally agree now :)
Le Fauve
Prole
Posts: 5
Joined: Wed Nov 26, 2014 12:51 pm

Re: Passing a table to a shader

Post by Le Fauve »

Hi again!

As I said in my previous reply, I tried to remove the "extern", and use instead a const array (so no need to use any parameter slot at all).
Well, I was very surprised to notice a dramatic loss of framerate.
The result was about 2 FPS; Adding a "const" seems to boost it up to about 3 FPS.

I'm probably doing something wrong here, even if I don't really see what.

Looking again into textures, I realized my error in this page: http://www.arcsynthesis.org/gltut/Textu ... %2014.html which enforces @bizziboi's comment:
Perhaps the most common misconception about textures is that textures are pictures: images of skin, rock, or something else that you can look at in an image editor. While it is true that many textures are pictures of something, it would be wrong to limit your thoughts in terms of textures to just being pictures.
(thanks again to put me back in the right direction ;) )

According to this page, textures exists in any number of dimensions, and in my case a 1D texture should be enough; however I'm not sure at all Love2D can handle such textures. Am I right? This is not a big deal as I can use a 2D texture instead, but I'd like to know I'm not missing (again) something obvious.

Also, textures being so much used, I thought it should be easy to find a small utility that would build an image from, for example a CSV file but I cannot see any; That probably means that it wasn't a good idea in the first place, so my question is: What is the best way to transform a data table into an image?

Thanks for having read my question :)
Wojak
Party member
Posts: 134
Joined: Tue Jan 24, 2012 7:15 pm

Re: Passing a table to a shader

Post by Wojak »

Le Fauve wrote:so my question is: What is the best way to transform a data table into an image?
love.image.newImageData
ImageData:setPixel
love.graphics.newImage

The setPixel part is the place where you can encode the data (4 integers (r,g,b and a) within range 0-255 per pixel)

Exemplar:
viewtopic.php?p=173100#p173100
Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 2 guests