JPCT special effects(Film Grain and Motion Blur)

Started by .jayderyu, January 20, 2010, 06:56:22 PM

Previous topic - Next topic

.jayderyu

ok I'm a bit of a glutton on punishment. I was playing Mass Effect last night and I while I noticed the special effects used. I didn't really think of using them until my current projects.

What I was wondering was are there ways to do Film Grain and Motion Blur. Well Motion Blur more specificly.

I figure Film Grain from what I read. Artificial film grain is a series(2-4) of highly transparent static'y images that are drawn on top of the current buffer. What I was wondering if there was a fast way to blit a transparent buffer onto buffer before drawn to screen and ways to keep in fast. I figure pre sizing it to the current buffer would be best to reduce resize each frame. Or create a specific size grain images.

The other Motion Blur, not a darn idea how I would do it. Well not entire true. My thought was to keep the last image as a background image. Render a new image, back it up as the next frame background. Then transparent it a lot. Then render it on the last frame background. The idea is that farther objects will "blur" more due to greater effect of distance of movement, while closer objects will stay more in "focus".

EgonOlsen

You can blit transparent, scaled textures. The scaling isn't a problem, it's done by the hardware anyway. It will cost some fillrate though. If that's the way to implement film grain, it should be doable without a problem. However, i hate the effect myself. It was the first setting i turned off in Mass Effect (Mass Effect 2 is coming at the end of January btw, i've preordered it... ;D ).

About the motion blur....i think that your idea will work, i'm just not sure if it's the best way to do it. I assume that modern engines are using some shaders for this, but i've no idea what they are doing exactly. It should be possible with jPCT though once you know how to do it. jPCT's helper class for GLSL shaders is meant for Object3Ds in the first place, but as mentioned in the docs, it should be possible to use it in an IPostProcessor too. I have to admit, that i've never tried this myself but i can't see why it shouldn't work.

.jayderyu

Thanks. I looked it up some more just a bit ago. I have seen some posts around websites saying that MotionBlur being done by GLSL and mentions of IPostProcessor.

I love film grain effect, though I tend to turn it off. I find it slows down the game and I don't have anything near a cutting edge system. I tend to buy the budget $350 computer every 2/3 years  :\ with a graphics card and memory upgrade every other year. Again budget wise :P    As for implementation of filmgrain. I'm not sure if it's the best way, but thats the way I was reading it for video production. I can see doing it that way with rendering being easy. Though maybe as mentioned it can be done with IPostProcessor too.

I was more curios about the idea right now. I don't think I will be implementing these(especially Motion Blur) until I have something more playable. Currently I'm working in software more for the applet to reduce load times for testing.  Thanks for the replies. I will certainly look into at hopefully some point soon :)

.jayderyu

So I said to myself, what the heck let's try some of that film grain. I am starting by trying to create a generic post process that's non engine based. So it's a direct to graphics draw with no other apis involved. Later I figure i'll do a JPCTFilmGrain version.

Though I could use some help on this.

Heres my FilmGrain code. It can generate a few images of grey scale static. Quality depending on choice. lower the better.

/** FilmGrain
This api is designed to give an old time cinematic feel to images or animated
scenes.

@author Jason T. Jarvis
*/

package sre.gfx;

import java.util.Random;
import java.awt.image.BufferedImage;
import java.awt.Graphics;

import java.awt.*;
import java.awt.image.*;


public class FilmGrain
{
  private int _width = 1;
  private int _height = 1;
  private int _alpha = 50;
  private int _quality = 32;
  private int _frames = 1;
  private int _currentFrame = 0;
  private int _duration = 1;
  private int _durationCount = 0;
  private BufferedImage[] _texture;
 
  public FilmGrain(int width, int height)
  {
    _width = width;
    _height = height;
    init();
  }
 
  public FilmGrain(int frames, int width, int height)
  {
    _frames = frames;
    _width = width;
    _height = height;
    init();
  }
 
  public void setAlpha(int alpha)
  {
    if(alpha < 0 || alpha > 255)
      alpha = _alpha;

    _alpha = alpha;
  }
 
  public void setQuality(int quality)
  {
    if(quality < 1 || quality > 64)
      quality = _quality;
   
    _quality = quality;
  }
 
  public void setGrainFrames(int frames)
  {
    if(frames < 1 || frames > 10)
      frames = _frames;
   
    _frames = frames;
  }
 
  public void setDuration(int duration)
  {
    if(duration < 1)
      duration = _duration;

    _duration = duration;
  }
 
  public void init()
  {
    _texture = new BufferedImage[_frames];
   
    for(int i = 0; i < _frames; i++)
    {
      _texture[i] = new BufferedImage(_width, _height, BufferedImage.TYPE_INT_ARGB);
      createGrain(_texture[i]);
    }
  }
 
 
 
  public void apply(Component c)
  {
    Graphics g = c.getGraphics();
   
    if(_durationCount >= _duration)
    {
      _currentFrame++;
      if(_currentFrame >= _frames)
      {
        _currentFrame = 0;
      }
    }
   
   
    g.drawImage(_texture[_currentFrame], 0, 0, _width, _height, c);
   
    _durationCount++;
  }
 
  public void apply(Graphics g, Component c)
  {
    if(_durationCount >= _duration)
    {
      _currentFrame++;
      if(_currentFrame >= _frames)
      {
        _currentFrame = 0;
      }
    }
   
    g.drawImage(_texture[_currentFrame], 0, 0, _width, _height, c);
   
    _durationCount++;   
  }


  private void createGrain(BufferedImage image)
  {
    int argb = 0x000000;// 0x 00 00 00 00
    int r, g, b;
    int width = (int)_width / _quality;
    int height = (int)_height / _quality;
    int size =  width * height;
    int pixels[] = new int[size];
    Random random = new Random();
   
    for(int i = 0, y = 0; y < height; y++)
    {
      for(int x = 0; x < width; x++)
      {
        r = random.nextInt(256);
        //g = random.nextInt(256);
        //b = random.nextInt(256);
        argb = ( (_alpha<<24) | (r<<16) | (r<<8) | r );
        pixels[i++] = argb;
        image.setRGB(x, y, argb);
        //System.out.println(argb);
      }
    }
    //image.setRGB(0, 0, _width, _height, pixels, offset, scanline);
  }
 
}


Here is the implementation code

--- initilization
filmgrain = new FilmGrain(800, 600);
filmgrain.setGrainFrames(3);
filmgrain.setDuration(3);
filmgrain.setAlpha(200);
filmgrain.setQuality(32);
filmgrain.init();


--- render loop
  dynamicWorld.debugDrawWorld();
          filmgrain.apply(buffer.getGraphics(), frame);
buffer.display(frame.getGraphics());


the problem is that I don't see anything. I have increased the alpha by a lot so I know there is some visible blitting happening and it's just not light. but there pretty much nothing. though the fps drops a lot when filmgrain.apply() is used. so the drawing does seem to happen.

am I drawing wrong or something?



EgonOlsen

Try to add a call to buffer.update() before calling display to see if that helps.

.jayderyu

sorry this was my entire render loop

buffer.clear(java.awt.Color.BLUE);
world.renderScene(buffer);
world.draw(buffer);

  buffer.update();
filmgrain.apply(buffer.getGraphics(), frame);
  dynamicWorld.debugDrawWorld();
buffer.display(frame.getGraphics());



I did finally find it. I was scaling wrong :( or more to the point I wasn't scaling at all :P

I still can't imagine anyone wanting to reduce there image quality, but if for some reason some one wants to try and get that old time cinematic feel here is the code. It's direct post effect. I'll do a JPCT version some point in the future using buffer.blit(Texture....) for better speed results.

/** FilmGrain
This api is designed to give an old time cinematic feel to images or animated
scenes.

@author Jason T. Jarvis
*/

import java.util.Random;
import java.awt.image.BufferedImage;
import java.awt.Graphics;
import java.awt.geom.*;

import java.awt.*;
import java.awt.image.*;


public class FilmGrain
{
  public static final int QUALITY_HIGH = 1;
  public static final int QUALITY_GOOD = 2;
  public static final int QUALITY_MEDIUM = 4;
  public static final int QUALITY_LOW = 8;
 
  private int _width = 1;
  private int _height = 1;
  private int _sWidth;
  private int _sHeight;
  private int _alpha = 50;
  private int _quality = 1;
  private int _frames = 1;
  private int _currentFrame = 0;
  private int _duration = 1;
  private int _durationCount = 0;
  private BufferedImage[] _texture;
 
  public FilmGrain(int width, int height)
  {
    _width = width;
    _height = height;
    init();
  }
 
  public FilmGrain(int frames, int width, int height)
  {
    _frames = frames;
    _width = width;
    _height = height;
    init();
  }
 
  public void setAlpha(int alpha)
  {
    if(alpha < 0 || alpha > 255)
      alpha = _alpha;

    _alpha = alpha;
  }
 
  public void setQuality(int quality)
  {
    if(quality < 1 || quality > 64)
      quality = _quality;
   
    _quality = quality;
  }
 
  public void setGrainFrames(int frames)
  {
    if(frames < 1 || frames > 10)
      frames = _frames;
   
    _frames = frames;
  }
 
  public void setDuration(int duration)
  {
    if(duration < 1)
      duration = _duration;

    _duration = duration;
  }
 
  public void init()
  {
    _texture = new BufferedImage[_frames];
    _sWidth = _width / _quality;
    _sHeight = _height / _quality;
   
    for(int i = 0; i < _frames; i++)
    {
      _texture[i] = new BufferedImage(_sWidth, _sHeight, BufferedImage.TYPE_INT_ARGB);
      createGrain(_texture[i]);
    }
  }
 
 
 
  public void apply(Component c)
  {
    Graphics g = c.getGraphics();
   
    if(_durationCount >= _duration)
    {
      _currentFrame++;
      if(_currentFrame >= _frames)
      {
        _currentFrame = 0;
      }
    }
   
    g.drawImage(_texture[_currentFrame],
      0, 0, _width, _height,
      0, 0, _sWidth, _sHeight,
      c);
   
    _durationCount++;
  }
 
  public void apply(Graphics g, Component c)
  {
    if(_durationCount >= _duration)
    {
      _currentFrame++;
      if(_currentFrame >= _frames)
      {
        _currentFrame = 0;
      }
    }
   
    g.drawImage(_texture[_currentFrame],
      0, 0, _width, _height,
      0, 0, _sWidth, _sHeight,
      c);
   
    _durationCount++;   
  }

  public static BufferedImage scale(BufferedImage bsrc, int width, int height){
    BufferedImage bdest =
      new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
   Graphics2D g = bdest.createGraphics();
   AffineTransform at =
      AffineTransform.getScaleInstance((double)width/bsrc.getWidth(),
          (double)height/bsrc.getHeight());
   g.drawRenderedImage(bsrc,at);
   return bdest;
  } 
 

  private void createGrain(BufferedImage image)
  {
    int argb;
    int r, g, b;
    int size =  width * height;
    int pixels[] = new int[size];
    Random random = new Random();
   
    for(int i = 0, y = 0; y < _sHeight; y++)
    {
      for(int x = 0; x < _sWidth; x++)
      {
        r = random.nextInt(256);
        g = random.nextInt(256);
        b = random.nextInt(256);
        argb = ( (_alpha<<24) | (r<<16) | (r<<8) | r );
        //argb = ( (_alpha<<24) | (r<<16) | (g<<8) | b );
        //pixels[i++] = argb;
        image.setRGB(x, y, argb);
      }
    }
  }
 
}

* If you want to have coloured grain use the implemented version of rgb bit shift not the rrr
** You can also use the pixels[] for creating a JPCT Texture. which is of course commented out.




How it works

filmgrain = new FilmGrain(800, 600);

filmgrain.apply([frame/applet/Component]);

This will create a single frame grain on a 1:1 size. Then apply will affect the graphics of the object.

This is 3d engine site. So a single frame is somewhat pointless. So here is how to apply it to the JPCT for animation

filmgrain = new FilmGrain(800, 600);
filmgrain.setGrainFrames(5);
filmgrain.setDuration(3);
filmgrain.setAlpha(50);
filmgrain.setQuality(FilmGrain.QUALITY_MEDIUM);
filmgrain.init();

Create a new grain.
Set how main different grain frames you want.
Set how many frames(apply() ) do you want each frame to stay. This is in frame draws not time. Default is 1.
Setting the alpha. It seems the lower the quality the higher the alpha should be set. 50 seems ideal. 50 is also default so it's not needed to set.
Set the quality(QUALITY_HIGH/GOOD/MEDIUM/LOW) . MEDIUM seems to work well for 3d rendering. Higher the quality slower it will run.
init() recreates the buffers.

And here is the line to integrate it. It's meant to be easy as possible.

       world.renderScene(buffer);
       world.draw(buffer);

       filmgrain.apply(buffer.getGraphics(), [Component|Frame|Applet]);

        buffer.update();
        buffer.display(frame.getGraphics());

.jayderyu

 ugg, me sucky at JPCT buffer images.



src = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
createGrain(src);
_texture[i] = new Texture(src, true);
_texture[i].setAlpha(_alpha);
TextureManager.getInstance().addTexture("FilmGrain"+i, _texture[i]);


here is the blit code

    buffer.blit(_texture[_currentFrame],
      0, 0, 0, 0,
      _quality, _quality, true);
   
    buffer.blit(_texture[_currentFrame],
      0, 0, 0, 0,
      _quality, _quality,
      _width, _height,
      _alpha, true, new Color(0,0,0)); // color is created else where, but I thought I would just show that's it's set to 0 0 0


the top one works, but only blits width/height  as it's supposed to.

The bottom one doesn't work. but it's the bottom one that scales the image.

any idea on what I'm doing wrong?

.jayderyu

yep, posting again. Posting seems to help either by getting some one smarter to to let me try something; or posting helps me put things into perspective to try something else to figure it out.

Anyways. I've managed some progress. I can now blit the static onto the screen. except now you can't see anything but static(reminds me of growing up :P).



src = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB);
createGrain(src);
_texture[i] = new Texture(src, true);
TextureManager.getInstance().addTexture("FilmGrain"+i, _texture[i]);

Removing the setAlpha() now let's me see it. setting the alpha to anything would show nothing. I also using RGB or ARGB with no difference in effect.


heres the blit line

    buffer.blit(_texture[_currentFrame],
      0, 0, 0, 0,
      _quality, _quality,
      _width, _height,
      _alpha, false, null);

Alpha can be anything and it will have no effect.

EgonOlsen

Is this the software renderer? If so, i'm not really sure how it behaves concerning scaled alpha blits. The whole scaling thing for a blit into a software rendered buffer is a kind of hack as it uses an Overlay internally to achieve this.

Maybe you can try to write a little test case, which simply loads a texture with proper alpha channel defined (some png should do) and see how that one appears on screen. In my tests, this works fine with the software renderer, but one never knows...

.jayderyu

yep, it's the software renderer. I'll try out the idea of test png when I get the chance.

Thanks

EgonOlsen


.jayderyu

#11
ok thanks. I did some test and here is what I came up with.

Using the alpha value in the blit doesn't do anything with the png or created texture.

Doing a Texture.setAlpha(anything) will causes the texture to not render at all. Same for a PNG or create texture.

A png with a defined transparent spot will be transparent. So all the "white" around the tank and legs are see through. The rest of the fishtank has no transparency in any level of alpha. So the tank, fish and legs block the scene.

edit: correction. If the alpha is set to less than 10 or so. you can barely see past texture. Past that it's pretty much blocked.

EgonOlsen

That's normal behaviour. Actually, the blits with alpha are meant to be used with a transparency level of 0. The initial implementation of alpha for the software renderer didn't even support anything higher than 0. I've just added it for completeness.

So please try with 0 and play around with your own alpha value instead.

.jayderyu

Thanks that solves it all. I thought the blit alpha was along the standard 0-255 scale. setting it 0 has done wonders.

I'll post the JPCTFilmGrain code after some clean up and polish. Though I thought that using JPCT I could get really fast blitting. it seems not so much. I think FilmGrain might have to stick with hardware rendering for best effect. At which point it would be just better to use GLSL.

EgonOlsen

Quote from: .jayderyu on January 24, 2010, 03:19:54 PM
Thanks that solves it all. I thought the blit alpha was along the standard 0-255 scale. setting it 0 has done wonders.
Yes, i suppose that can be a bit misleading. I'll add something about it do the docs.

Quote from: .jayderyu on January 24, 2010, 03:19:54 PM
Though I thought that using JPCT I could get really fast blitting. it seems not so much. I think FilmGrain might have to stick with hardware rendering for best effect. At which point it would be just better to use GLSL.
The software renderer is fillrate limited almost all of the time....and your full screen effect eats fillrate for breakfast. If you have a multi-core cpu, you can try to enable multithreading in Config, if haven't already...