markusfisch / ShaderEditor

Android app to create GLSL shaders and use them as live wallpaper
https://play.google.com/store/apps/details?id=de.markusfisch.android.shadereditor
MIT License
914 stars 136 forks source link

Saving Data #110

Open Shiv2k3 opened 2 years ago

Shiv2k3 commented 2 years ago

I am new to glsl and I'm trying to create a fluid simulation in this shader editor but I can't seem to find anyway to save the texture with the fluid particle's data, I have tried using arrays but they are very laggy.

markusfisch commented 2 years ago

Hi, have you tried using the backbuffer texture?

Shiv2k3 commented 2 years ago

That one gets reset ever frame right?

markusfisch commented 2 years ago

Not reset, but overwritten with the next frame. So the backbuffer always contains the last image. To some extend, this can be used to transfer information between frames (have a look at the Game of Life sample shader that comes with the app). But that's probably not enough for what you're trying to do, sorry 😬

At the moment, there's no persistent buffer a fragment shader can write to. As far as I can see, there would be two options for that: SSBOs or imageStore in GLES 3.10.

SSBOs would be the way to go I think, but they aren't supported in Shader Editor yet.

Mehran7kl commented 2 years ago

@markusfisch The buffers which shadertoy has provided are great. Will they be implemented in Shader Editor?

rorySomething commented 2 years ago

Related to saving to the backbuffer. Hopefully it can start a discussion of tips and examples of how to save data to the backbuffer for the FAQ.

The slightly short of it.

Any idea why casting resolution uniform as a vec2 is needed in this code to match a pixel / save data?

uniform vec2 resolution

  ivec2 px = ivec2(0, 1);  // pixel coord
  vec2 offset = vec2(0.5);  // texture coords center of texel

  vec2 coord = ( vec2(px) + offset )
            / resolution; //****
  return (coord == uv) ? 1.0 : 0.0;

Fails to match coordinates

vs

  vec2 coord = ( vec2(px) + offset )
            / vec2(resolution); //****
  return (coord == uv) ? 1.0 : 0.0;

Works

I assume it's related to

#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif

If I comment that out (causing low precision?) I lose pixels (not matching) and probably have to switch to a square range of pixels to save one vec3.

So I guess resolution comes in as a lowp and is doing a lowp division. if not cast to a vec3.

The long part

Maybe this is done poorly.

I have this function.

float saveAtPixel(ivec2 px, vec2 resolution, vec2 uv){
    /*
        Return 1.0 if uv == target pixel
        Otherwise return 0.0
        Use like
        vec4 color = calculated color
    color = mix(color, data, saveAtPixel)
        gl_FragColor = color;
        Will swap calculated color for data if at the right coordinate
    */
    vec2 coord = (vec2(px) + vec2(0.5)) / resolution;
    return (coord == uv) ? 1.0 : 0.0;
}

I was passing in the uniform vec3 resolution to the function and it worked just fine.

To stop abusing the gpu calculating everything at every frame for every pixel, I tried moving the test into an updateAndSave function.

In that function I called vec2 coord = (vec2(px) + vec2(0.5)) / resolution; if (coord == uv) dowork = true; Using the resolution uniform directly, and it wouldn't match any pixels unless I recast it as vec2(resolution). I assume changing the precision of the vec2/floats.

Here's some of my first implementation of saving to the backbuffer.

To be more efficient, instead of calculating everything and deciding whether to save the results with mix(outColor, data, doISaveAtThisCoordinate). I'll subject every pixel to many if else checks or adding doISavePosHere + doISaveVelocityHere and calculate the new data depending on if it's a pixel for storage.

  // Keeping an old and new pair of stuff
  // Render old, calculate and save new
  void betterFunctionMaybe(){
    float save = 0.0;
    for(Nobjects){
      save += doISavePos(uv, object);
      save += doISavePropertyX(uv, object);
      // etc
    }
    if (save > 0.5) {
      updateObjects
      physics
      magic
      saveData(uv, updatedObjects)
    }
  }

The end... Apologies if there's a uvw vs stq holy war I'm not aware of.

#define Nballs 5

vec4 readAtPixel(ivec2 px, vec2 res, sampler2D buffer){
    vec2 coord = (vec2(px) + vec2(0.5)) / res;
    return texture2D(buffer, coord);
}

float saveAtPixel(ivec2 px, vec2 resolution, vec2 uv){
    /*
        Return 1.0 if uv == target pixel
        Otherwise return 0.0
        Use like
        vec4 color = mix(color, data, saveAtPixel)
        gl_FragColor = color;
        Will swap calculated color for data if at the right coordinate
    */
    vec2 coord = (vec2(px) + vec2(0.5)) / resolution;
    return (coord == uv) ? 1.0 : 0.0;
}

void readIn(inout vec2[Nballs] balls,
    inout vec2[Nballs] dir,
    inout float[Nballs] speed,
    inout float rand,
    in int row) // should be column
{
  // Should switch to save and read vec4?  Wasn't sure about alpha channel open.
    vec3 data = readAtPixel(
        ivec2(row, 1), resolution, backbuffer).rgb;
    balls[0] = data.rg;
    rand = data.b;
 // ds dir & speed
    vec3 ds = readAtPixel(
        ivec2(row, 3), resolution, backbuffer).rgb;
    dir[0] = ds.rg;
    dir[0] = (dir[0] * 2.) - vec2(1.);
    speed[0] = ds.b;

    for(int i = 1; i < Nballs; i++){
        balls[i] = readAtPixel(
            ivec2(row, 1+i*4), resolution, backbuffer).rg;
        ds = readAtPixel(
            ivec2(row, 3+i*4), resolution, backbuffer).rgb;
        dir[i] = (ds.rg * 2.) - vec2(1.);
        speed[i] = ds.b;
    }
}

main... {
  // Doing all of this at every pixel
  // Nballs = 5 is the best I could do to stay at 60 fps
  // readIn
  // if 0.0, initialize
  // Do something

  vec3 oldColor = texture2D(backbuffer, uv).xyz * colorfade;
     // Fade to black quicker
     // Should change to log or exp with colorfade param

  vec3 ocolor = oldColor;

  for(int i = 0; i < Nballs; i++){
    float b = inCircleSmooth(uv, balls[i], aspectRatio, sqRadius, circleFade);
       // too bright ocolor += color[i] * b;
       ocolor = mix(ocolor, color[i], b);
     }
  // Save objects for next frame
  // Pos is 0-1
  // Dir pis -1 - +1
  // Dir stored + 1 * 0.5

  // Doing all this every pixel to decide if we should save
  // Save data func
  vec3 data = vec3(balls[0].x, balls[0].y, rand);
  // Change color to position data if saveAtPixel returns 1
  //   else pass the 
     ocolor = mix(ocolor,
          data,
          saveAtPixel(ivec2(row, 2), resolution, uv));
     data.xy = (dir[0].xy + vec2(1.)) * 0.5;
     data.z = speed[0];
     ocolor = mix(ocolor,
          data,
          saveAtPixel(ivec2(row, 4), resolution, uv));

  for(int i = 1; i < Nballs; i++){
       data.xy = balls[i].xy;
       ocolor = mix(ocolor, data,
           saveAtPixel(ivec2(row, 2+i*5), resolution,  uv));
       data.xy = (dir[i].xy + vec2(1.)) * 0.5;
       data.z = speed[i];
       ocolor = mix(ocolor, data,
            saveAtPixel(ivec2(row, 4+i*5), resolution,  uv));
     }

    gl_FragColor = vec4(ocolor, 1.0);