Hey guys,
I've recently experimented with framebuffers in openGL to achieve a pixelated look.
I'm basically rendering my scene into a 320*192px wide texture and afterwards drawing the texture to the screen (as far as I know the idea behind framebuffers).
To avoid distortion I'm using the methods 'recalculateViewport' and 'fitViewport' (Graphics.java), which adjust the viewport to preserve the aspect ratio of the 320*192px scene.
This is working as intended but inside the viewport the resolutions seems to be "reversed". :/
Example (with lower resolution):
Display dimensions: 200*64px
Render resolution: 8*4px
As expected, the viewport is set to 128*64px and moved 36px to the right.
But now the problem: instead of the resolution of 8*4px and a pixel-size of 16*16, I'm getting a resolution of 4*8 pixels with each rendered pixel being 32*8 actual pixels wide.
What I expected:
(http://s14.directupload.net/images/141022/you4jd95.png)
What I got:
(http://s14.directupload.net/images/141022/sc6n7qcu.png)
Here's a screenshot of the program with those settings:
(http://s14.directupload.net/images/141022/8ogcrdrj.png)
It would be great if someone had an idea how to fix this, I'm searching for a solution for hours now.
- Mario :D
PS: Please excuse mistakes, english is not my first language :)
Game.java
package net.frucost.game;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.EXTFramebufferObject.*;
import net.frucost.berry.core.Berry;
import org.lwjgl.opengl.Display;
public class Game {
private long lasttick;
private final int tps = 60;
private final int tickdelay = 1000 / tps;
public int framebuffer;
public int texture;
public static void main(String[] args) {
new Game().init();
}
public void init() {
Berry.init();
Berry.statemachine.addState("menu.main", new MenuState());
Berry.statemachine.setNextState("menu.main");
// create framebuffer and colortexture
framebuffer = glGenFramebuffersEXT();
texture = glGenTextures();
// init framebuffer and colortexture
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (int) Berry.graphics.resolution.x, (int) Berry.graphics.resolution.y, 0, GL_RGBA, GL_INT, (java.nio.ByteBuffer) null);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texture, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
loop();
}
public void loop() {
while(!Berry.app.closeRequested()) {
if(System.currentTimeMillis() > lasttick + tickdelay) {
update();
lasttick += tickdelay;
}
render();
}
}
public void update() {
Berry.tick();
Berry.statemachine.getCurrentState().update();
}
public void render() {
{ // Render scene into framebuffer
glViewport(0, 0, (int) Berry.graphics.resolution.x, (int) Berry.graphics.resolution.y);
glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture
glBindFramebufferEXT((GL_FRAMEBUFFER_EXT), framebuffer); // Bind backbuffer
glPushMatrix();
{
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
Berry.statemachine.getCurrentState().render();
}
glPopMatrix();
}
{ // Render framebuffer to screen
glEnable(GL_TEXTURE_2D);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Bind frontbuffer
Berry.graphics.fitViewport();
glPushMatrix();
{
glColor3f(1, 1, 1);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, texture);
glBegin(GL_QUADS);
{
glTexCoord2f(0.0f, 0.0f);
glVertex2i(0, 0);
glTexCoord2f(0.0f, 1.0f);
glVertex2i((int) Berry.graphics.resolution.x, 0);
glTexCoord2f(1.0f, 1.0f);
glVertex2i((int) Berry.graphics.resolution.x, (int) Berry.graphics.resolution.y);
glTexCoord2f(1.0f, 0.0f);
glVertex2i(0, (int) Berry.graphics.resolution.y);
}
glEnd();
glDisable(GL_TEXTURE_2D);
}
}
Display.update();
Display.sync(60);
}
}
Graphics.java (a instance of this class is referenced by 'Berry.graphics')
package net.frucost.berry.engine;
import java.util.ArrayList;
import java.util.logging.Logger;
import net.frucost.berry.utils.LogUtils;
import net.frucost.berry.utils.Vec2;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;
public class Graphics {
/**
* An array of valid DisplayModes (fullscreen-capable, with bitdepth of 32 and frequency of 60hz)
*/
private DisplayMode[] displaymodes;
/**
* Currently active Displaymode
*/
private DisplayMode displaymode;
/**
* Resolution of the rendering-area, will be scaled up to fit into window
*/
public final Vec2 resolution = new Vec2(320, 192);
/**
* Size of the actual window
*/
public Vec2 dimension = new Vec2(1280, 720);
/**
* Cached position of the fit viewport
*/
public Vec2 viewportPos;
/**
* Cached dimension of the fit viewport
*/
public Vec2 viewportDim;
/**
* Class logger object
*/
private Logger logger = LogUtils.get(Graphics.class);
/**
* Collect valid DisplayModes, initialize Display and fit Viewport
*/
public void init() {
// Collect and filter DisplayModes
logger.fine("Collecting displaymodes");
try {
ArrayList<DisplayMode> displaylist = new ArrayList<DisplayMode>();
for(DisplayMode dm : Display.getAvailableDisplayModes()) {
if(dm.getBitsPerPixel() == 32 && dm.getFrequency() == 60 && dm.isFullscreenCapable()) {
logger.finest(dm.toString() + " (1:" + ((float) dm.getWidth() / (float) dm.getHeight()) + ")");
displaylist.add(dm);
}
}
this.displaymodes = new DisplayMode[displaylist.size()];
for(int i = 0; i < displaylist.size(); i++) {
this.displaymodes[i] = displaylist.get(i);
if(displaylist.get(i).getWidth() == dimension.x && displaylist.get(i).getHeight() == dimension.y) {
setDisplaymode(displaylist.get(i), false);
}
}
}
catch(LWJGLException e) {
logger.severe("Failed to collect displaymodes: " + e.getLocalizedMessage());
}
// Create LWJGL display
try {
Display.setResizable(true);
Display.create();
initGL();
}
catch(LWJGLException e) {
logger.severe("Failed to create LWJGL display: " + e.getLocalizedMessage());
}
}
/**
* Initialize openGL
*/
public void initGL() {
fitViewport();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
/**
* Recalculate position and dimension of the viewport inside the window
*/
public void recalculateViewport() {
// Fit and center view
double w2 = resolution.x, h2 = resolution.y, w1 = Display.getWidth(), h1 = Display.getHeight();
double outerAspectRatio = w1 / h1;
double innerAspectRatio = w2 / h2;
double factor = (innerAspectRatio >= outerAspectRatio) ? (w1 / w2) : (h1 / h2);
double w = w2 * factor;
double h = h2 * factor;
double l = (w1 - w) / 2;
double t = (h1 - h) / 2;
viewportPos = new Vec2(l, t);
viewportDim = new Vec2(w, h);
}
/**
* Apply position and dimension of the viewport
*/
public void fitViewport() {
if(viewportPos == null || viewportDim == null) {
recalculateViewport();
}
glMatrixMode(GL_PROJECTION);
{
glLoadIdentity();
System.out.println(viewportPos + ", " + viewportDim);
glViewport((int) viewportPos.x, (int) viewportPos.y, (int) viewportDim.x, (int) viewportDim.y);
// (0|0) in the upper-left corner
glOrtho(0, resolution.x, resolution.y, 0, 1, -1);
}
glMatrixMode(GL_MODELVIEW);
}
/**
* @return a list of valid DisplayModes
*/
public DisplayMode[] getDisplaymodes() {
return displaymodes;
}
/**
* @return the current DisplayMode
*/
public DisplayMode getDisplaymode() {
return displaymode;
}
/**
* Try to enter a DisplayMode
*
* @param displaymode
* @param fullscreen
*/
public void setDisplaymode(DisplayMode displaymode, boolean fullscreen) {
for(DisplayMode mode : displaymodes) {
if(mode.getWidth() == displaymode.getWidth() && mode.getHeight() == displaymode.getHeight()) {
this.displaymode = mode;
setFullscreen(fullscreen);
updateDisplayMode();
return;
}
}
logger.warning("Tried to set unregistered displaymode: " + displaymode + (fullscreen ? " fullscreen" : ""));
}
/**
* Try to enter a DisplayMode
*
* @param width
* @param height
* @param fullscreen
*/
public void setDisplaymode(int width, int height, boolean fullscreen) {
setDisplaymode(new DisplayMode(width, height), fullscreen);
}
/**
* Try to enter/leave fullscreen mode
*
* @param fullscreen
*/
public void setFullscreen(boolean fullscreen) {
try {
Display.setFullscreen(fullscreen);
}
catch(LWJGLException e) {
logger.severe("Failed to set fullscreen mode");
}
}
/**
* @return Whether fullscreen mode is active
*/
public boolean isFullscreen() {
return Display.isFullscreen();
}
/**
* Try to apply displaymode
*/
public void updateDisplayMode() {
try {
Display.setDisplayMode(displaymode);
logger.config("Updated displaymode: " + displaymode);
}
catch(LWJGLException e) {
logger.severe("Failed to update displaymode: " + e.getLocalizedMessage());
}
}
}
I have not looked over your code but I can tell you that you could simply change the way you render to get a look like that much simpler and easier.
There is absolutely no reason to use FrameBuffers for what you want to do. Just scale everything up.
Hey, thanks for your answer but how would you do that?
As far as I know the GL_NEAREST scaling-policy can only be applied to textures and not to the glOrtho-view itself.
Am I wrong with that? And if so, how would I?
- Mario
I dont see what GL_NEAREST has to do with it.
There is no build in anti-aliasing in openGL.
Well, if I'm just setting the resolution of the ortho-view, everything becomes blurry.
There has to be a way to change the interpolation mode, right?
Could you show an image of how exactly it looks like?
Do you use GL_NEAREST or GL_LINEAR as the mag filter of your textures?
I'm using GL_NEAREST :)
Game.java, Line 36:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
I'm at my laptop at the moment, i'll post an image of what I mean when I'm at home :D
You're mixing vertices indeed ...
You also may want to check if the Rectangle Texture Extension (https://www.opengl.org/wiki/Rectangle_Texture) gets better results, because of non-power-of-two texture sizes. That may be old school doing with newer G. Cards, but it's worth to check.
Quote from: frucost on October 22, 2014, 01:44:15
(..)This is working as intended but inside the viewport the resolutions seems to be "reversed". :/
glBindTexture(GL_TEXTURE_2D, texture);
glBegin(GL_QUADS);
{
glTexCoord2f(0.0f, 0.0f);
glVertex2i(0, 0);
glTexCoord2f(0.0f, 1.0f); // this is (1f, 0f)
glVertex2i((int) Berry.graphics.resolution.x, 0);
glTexCoord2f(1.0f, 1.0f);
glVertex2i((int) Berry.graphics.resolution.x, (int) Berry.graphics.resolution.y);
glTexCoord2f(1.0f, 0.0f);// this is (0f, 1f)
glVertex2i(0, (int) Berry.graphics.resolution.y);
}
glEnd();
glDisable(GL_TEXTURE_2D);
:D cu
THANKS!!
I feel so stupid right now :/
But it's working :D
Btw: Thanks for the link, I'll definitively have a look at those rectange textures ;)
(http://s14.directupload.net/images/141022/lqvhwonn.png)
- Mario