Join us at the Oolite Anniversary Party -- London, 7th July 2024, 1pm
More details in this thread.

Render to Framebuffer

An area for discussing new ideas and additions to Oolite.

Moderators: another_commander, winston

User avatar
maik
Wiki Wizard
Wiki Wizard
Posts: 2020
Joined: Wed Mar 10, 2010 12:30 pm
Location: Ljubljana, Slovenia (mainly industrial, feudal, TL12)

Re: Render to Framebuffer

Post by maik »

Cholmondely wrote: Fri Aug 19, 2022 3:51 pm
another_commander wrote: Fri Aug 19, 2022 3:49 pm
We are almost ready to make this official. Pull request is up on github: https://github.com/OoliteProject/oolite/pull/407

Before the final merge, please test and ensure that the code in the above PR works and runs OK for you. This is basically for Lunux (and maybe Mac? anyone?) users, as testing on Windows seems to have produced no blockers. We'll let quite a few days for testing and if there are issues we'll hold the merge until resolved.

I should also note that, as far as Windows is concerned, the feature will be implemented only for 64-bit versions of the OS. I think it is time we retire the (obsolete, really) 32-bit version, which is not scheduled to receive any new features from now on, unless someone interested in maintaining it steps in.

I once again want to express my sincere thanks to both Mauiby de Fug, who made the first attempt at this and kept the flame burning, and tsoj, who bit the bullet and actually made the darn thing work. Evrything I added after that was just fluff, really :-). So, thank you guys, this is something I wanted to see for a long, long time!
I'd love to try it out. But how? I can't compile for toffee - I need an Apple version which I can just download...
Same here... Even the code without the frame buffer pull request needs changes (see the latest conversation in the Mac nightly build thread). I wouldn't hold my breath for someone with a Mac to be able to test this, unfortunately ;(
Commander_X
---- E L I T E ----
---- E L I T E ----
Posts: 666
Joined: Sat Aug 09, 2014 4:16 pm

Re: Render to Framebuffer

Post by Commander_X »

Some observations:
- Compiles nicely under Linux, at least against an existing trunk over which I fetched the pull request
- Washed out colours. Might be a personal "feeling", but it seems that compared with trunk, besides the sky, the (default 3D) planet colours are less saturated, and the sun glare seems stronger. Could be an artifact of the HDR calculations in the new shaders?
- No notable difference between the F9/F9/F9
Again, Linux doesn't have HDR at the moment, and very likely for some more years to come.

On the other hand, tried under Wine the monolithic windows 64 exe, and, maybe due to some of the OXPs included, the overall colour layout didn't strike as bad. Even there, though, no change when F9-ing. It might be the difference is too subtle, though.

EDIT:
HUD antialiasing?
The (big) IFF scanner circle, definitely doesn't get antialiased. The one in the trunk does. (And yes, I have "anti-alias" set to "YES" in my GNUstep defaults plist file).
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6559
Joined: Wed Feb 28, 2007 7:54 am

Re: Render to Framebuffer

Post by another_commander »

Some color differences with trunk are expected. Before, HDR was applied in parts - planets were tonemapped and rendered, then atmospheres were tonemapped and rendered, then ships were tonemapped and rendered etc, with the final scene being the composite result of all those separate tonemapping passes. Now, it is done in the right way: one tonemapping pass for the entire scene in the framebuffer. This will result in color differences where transparent parts are involved, like planet atmospheres. Also, any screen backgrounds will now appear a bit brighter, since now they are part of the tonemapping operation too (they were not in the past). Also, note that if you have been using trunk and have set the sky color gamma correction option in .GnustepDefaylts you must set it back to zero or remove it, because the sky is now also tonemapped and gamma corrected as part of the scene. If you don't, then the nevulae will look wrong, because they will be tonemapped and gamma corrected twice, like in the last screen I posted in the screenshots thread.

Regarding the bloom, it is deliberately made subtle. You will normally get it only where light intensity goes above a certain threshold. If you move around a bit in external view, you will soon hit a sweet spot where it will bloom and it will be super obvious. That,s when you will see the difference using F9.

As for antialiasing I am not sure what to tell you. There is nothing changed that would affect this as far as I can see.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6559
Joined: Wed Feb 28, 2007 7:54 am

Re: Render to Framebuffer

Post by another_commander »

Cholmondely wrote: Fri Aug 19, 2022 5:51 pm
Is there any way in which I could take over Getafix's transmogrification duties? What would I need? And how much would I need to know?
I somehow missed this one, sorry. I'm afraid no, if you have to ask what you need to know, then I don't think you can take on this task.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6559
Joined: Wed Feb 28, 2007 7:54 am

Re: Render to Framebuffer

Post by another_commander »

another_commander wrote: Sun Aug 21, 2022 7:01 am
As for antialiasing I am not sure what to tell you. There is nothing changed that would affect this as far as I can see.
Ok I got educated a bit on this one. It looks like when rendering to framebuffer, we need to take care of MSAA ourselves. There is a way to do it ( see here: https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing ), but I will need help with testing, as I do not observe the issue on my system - probably because antialiasing is forced at the driver level.
Commander_X
---- E L I T E ----
---- E L I T E ----
Posts: 666
Joined: Sat Aug 09, 2014 4:16 pm

Re: Render to Framebuffer

Post by Commander_X »

another_commander wrote: Sun Aug 21, 2022 4:42 pm
[...] I do not observe the issue on my system - probably because antialiasing is forced at the driver level.
That's my bet too -- I usually keep the NVidia driver on "Use application settings". The nice thing is trunk _does_ antialiasing.
To reference a bit what I said earlier, here are the equivalents of 4000 words (Ensoreus-planet, Ensoreus-sun):

Trunk:
Image
Image

Render to framebuffer:
Image
Image

Looking at these captures, I realize the HUD is also using faded colours.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6559
Joined: Wed Feb 28, 2007 7:54 am

Re: Render to Framebuffer

Post by another_commander »

I think I managed to re-implement MSAA, this time on the framebuffers. @Commander_X, can you please test this patch against revision 15fa8d8 (the last one in PR #407)?

Code: Select all

diff --git a/src/Core/OOOpenGLExtensionManager.h b/src/Core/OOOpenGLExtensionManager.h
index 2fb596a1..de4d102e 100644
--- a/src/Core/OOOpenGLExtensionManager.h
+++ b/src/Core/OOOpenGLExtensionManager.h
@@ -274,6 +274,8 @@ PFNGLDELETEVERTEXARRAYSPROC				glDeleteVertexArrays;
 PFNGLDELETEBUFFERSPROC					glDeleteBuffers;
 PFNGLDRAWBUFFERSPROC					glDrawBuffers;
 PFNGLCHECKFRAMEBUFFERSTATUSPROC			glCheckFramebufferStatus;
+PFNGLTEXIMAGE2DMULTISAMPLEPROC				glTexImage2DMultisample;
+PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC		glRenderbufferStorageMultisample;
 PFNGLBLITFRAMEBUFFERPROC					glBlitFramebuffer;
 #endif
 
diff --git a/src/Core/OOOpenGLExtensionManager.m b/src/Core/OOOpenGLExtensionManager.m
index 75671d6f..08710e25 100644
--- a/src/Core/OOOpenGLExtensionManager.m
+++ b/src/Core/OOOpenGLExtensionManager.m
@@ -136,6 +136,8 @@ PFNGLDELETEVERTEXARRAYSPROC				glDeleteVertexArrays			= (PFNGLDELETEVERTEXARRAYS
 PFNGLDELETEBUFFERSPROC					glDeleteBuffers					= (PFNGLDELETEBUFFERSPROC)&OOBadOpenGLExtensionUsed;
 PFNGLDRAWBUFFERSPROC						glDrawBuffers					= (PFNGLDRAWBUFFERSPROC)&OOBadOpenGLExtensionUsed;
 PFNGLCHECKFRAMEBUFFERSTATUSPROC			glCheckFramebufferStatus			= (PFNGLCHECKFRAMEBUFFERSTATUSPROC)&OOBadOpenGLExtensionUsed;
+PFNGLTEXIMAGE2DMULTISAMPLEPROC				glTexImage2DMultisample			= (PFNGLTEXIMAGE2DMULTISAMPLEPROC)&OOBadOpenGLExtensionUsed;
+PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC		glRenderbufferStorageMultisample	= (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)&OOBadOpenGLExtensionUsed;
 PFNGLBLITFRAMEBUFFERPROC					glBlitFramebuffer					= (PFNGLBLITFRAMEBUFFERPROC)&OOBadOpenGLExtensionUsed;
 #endif                                                                    
 #endif
@@ -682,6 +684,8 @@ static unsigned IntegerFromString(const GLubyte **ioString)
 		glDeleteBuffers				= (PFNGLDELETEBUFFERSPROC)wglGetProcAddress				("glDeleteBuffers"				);
 		glDrawBuffers				= (PFNGLDRAWBUFFERSPROC)wglGetProcAddress				("glDrawBuffers"				);
 		glCheckFramebufferStatus		= (PFNGLCHECKFRAMEBUFFERSTATUSPROC)wglGetProcAddress		("glCheckFramebufferStatus"				);
+		glTexImage2DMultisample		= (PFNGLTEXIMAGE2DMULTISAMPLEPROC)wglGetProcAddress		("glTexImage2DMultisample"					);
+		glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)wglGetProcAddress ("glRenderbufferStorageMultisample"	);
 		glBlitFramebuffer				= (PFNGLBLITFRAMEBUFFERPROC)wglGetProcAddress			("glBlitFramebuffer"			);
 	}
 #endif
diff --git a/src/Core/Universe.h b/src/Core/Universe.h
index bda93dd8..d8ef43a0 100644
--- a/src/Core/Universe.h
+++ b/src/Core/Universe.h
@@ -351,9 +351,12 @@ enum
 	BOOL					_dockingClearanceProtocolActive;
 	BOOL					_doingStartUp;
 
+	GLuint					msaaTextureID;
 	GLuint					targetTextureID;
 	GLuint					passthroughTextureID[2];
 	NSSize					targetFramebufferSize;
+	GLuint					msaaFramebufferID;
+	GLuint					msaaDepthBufferID;
 	GLuint					targetDepthBufferID;
 	GLuint					targetFramebufferID;
 	GLuint					passthroughFramebufferID;
diff --git a/src/Core/Universe.m b/src/Core/Universe.m
index 3659045d..81975237 100644
--- a/src/Core/Universe.m
+++ b/src/Core/Universe.m
@@ -313,6 +313,29 @@ static GLfloat	docked_light_specular[4]	= { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEV
 	GLint previousElementBuffer;
 	OOGL(glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &previousElementBuffer));
 
+	// create MSAA framebuffer and attach MSAA texture and depth buffer to framebuffer
+	OOGL(glGenFramebuffers(1, &msaaFramebufferID));
+	OOGL(glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebufferID));
+	
+	// creating MSAA texture that should be rendered into
+	OOGL(glGenTextures(1, &msaaTextureID));
+	OOGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msaaTextureID));
+	OOGL(glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA16F, viewSize.width, viewSize.height, GL_TRUE));
+	OOGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0));
+	OOGL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, msaaTextureID, 0));
+	
+	// create necessary MSAA depth render buffer
+	OOGL(glGenRenderbuffers(1, &msaaDepthBufferID));
+	OOGL(glBindRenderbuffer(GL_RENDERBUFFER, msaaDepthBufferID));
+	OOGL(glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT32F, viewSize.width, viewSize.height));
+	OOGL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+	OOGL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, msaaDepthBufferID));
+	
+	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+	{
+		OOLogERR(@"initTargetFramebufferWithViewSize.result", @"***** Error: Multisample framebuffer not complete");
+	}
+	
 	// create framebuffer and attach texture and depth buffer to framebuffer
 	OOGL(glGenFramebuffers(1, &targetFramebufferID));
 	OOGL(glBindFramebuffer(GL_FRAMEBUFFER, targetFramebufferID));
@@ -395,6 +418,8 @@ static GLfloat	docked_light_specular[4]	= { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEV
             OOLogERR(@"initTargetFramebufferWithViewSize.result", @"***** Error: Pingpong framebuffers not complete");
 		}
     }
+	OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
+	
 	_bloom = YES;
 	_currentPostFX = OO_POSTFX_NONE;
 
@@ -452,10 +477,13 @@ static GLfloat	docked_light_specular[4]	= { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEV
 
 - (void) deleteOpenGLObjects
 {
+	OOGL(glDeleteTextures(1, &msaaTextureID));
 	OOGL(glDeleteTextures(1, &targetTextureID));
 	OOGL(glDeleteTextures(2, passthroughTextureID));
 	OOGL(glDeleteTextures(2, pingpongColorbuffers));
+	OOGL(glDeleteRenderbuffers(1, &msaaDepthBufferID));
 	OOGL(glDeleteRenderbuffers(1, &targetDepthBufferID));
+	OOGL(glDeleteFramebuffers(1, &msaaFramebufferID));
 	OOGL(glDeleteFramebuffers(1, &targetFramebufferID));
 	OOGL(glDeleteFramebuffers(2, pingpongFBO));
 	OOGL(glDeleteFramebuffers(1, &passthroughFramebufferID));
@@ -471,10 +499,20 @@ static GLfloat	docked_light_specular[4]	= { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEV
 - (void) resizeTargetFramebufferWithViewSize:(NSSize)viewSize
 {
 	int i;
+	// resize MSAA color attachment
+	OOGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msaaTextureID));
+	OOGL(glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA16F, viewSize.width, viewSize.height, GL_TRUE));
+	OOGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0));
+	
+	// resize MSAA depth attachment
+	OOGL(glBindRenderbuffer(GL_RENDERBUFFER, msaaDepthBufferID));
+	OOGL(glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT32F, viewSize.width, viewSize.height));
+	OOGL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
+	
 	// resize color attachments
-		OOGL(glBindTexture(GL_TEXTURE_2D, targetTextureID));
-		OOGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, viewSize.width, viewSize.height, 0, GL_RGBA, GL_FLOAT, NULL));
-		OOGL(glBindTexture(GL_TEXTURE_2D, 0));
+	OOGL(glBindTexture(GL_TEXTURE_2D, targetTextureID));
+	OOGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, viewSize.width, viewSize.height, 0, GL_RGBA, GL_FLOAT, NULL));
+	OOGL(glBindTexture(GL_TEXTURE_2D, 0));
 	
 	for (i = 0; i < 2; i++)
 	{
@@ -650,6 +688,8 @@ static GLfloat	docked_light_specular[4]	= { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEV
 	wireframeGraphics = [prefs oo_boolForKey:@"wireframe-graphics" defaultValue:NO];
 	doProcedurallyTexturedPlanets = [prefs oo_boolForKey:@"procedurally-textured-planets" defaultValue:YES];
 	[inGameView setGammaValue:[prefs oo_floatForKey:@"gamma-value" defaultValue:1.0f]];
+	[inGameView setMsaa:[prefs oo_boolForKey:@"anti-aliasing" defaultValue:NO]];
+	OOLog(@"MSAA.setup", @"Multisample anti-aliasing %@requested.", [inGameView msaa] ? @"" : @"not ");
 	[inGameView setFov:OOClamp_0_max_f([prefs oo_floatForKey:@"fov-value" defaultValue:57.2f], MAX_FOV_DEG) fromFraction:NO];
 	if ([inGameView fov:NO] < MIN_FOV_DEG)  [inGameView setFov:MIN_FOV_DEG fromFraction:NO];
 	
@@ -4700,7 +4740,14 @@ static const OOMatrix	starboard_matrix =
 	
 	if([self useShaders])
 	{
-		OOGL(glBindFramebuffer(GL_FRAMEBUFFER, targetFramebufferID));
+		if ([gameView msaa])
+		{
+			OOGL(glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebufferID));
+		}
+		else
+		{
+			OOGL(glBindFramebuffer(GL_FRAMEBUFFER, targetFramebufferID));
+		}
 	}
 	
 	if (!no_update)
@@ -5109,11 +5156,20 @@ static const OOMatrix	starboard_matrix =
 		}
 	}
 	
-	OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
 	OOLog(@"universe.profile.draw", @"%@", @"End drawing");
 	
 	if([self useShaders])
 	{
+		if ([gameView msaa])
+		{
+			// resolve MSAA framebuffer to target framebuffer
+			glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFramebufferID);
+			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetFramebufferID);
+			glBlitFramebuffer(0, 0, (int)[gameView viewSize].width, (int)[gameView viewSize].height, 0, 0, (int)[gameView viewSize].width, (int)[gameView viewSize].height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+		}
+		
+		OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
+		
 		OOLog(@"universe.profile.secondPassDraw", @"%@", @"Begin second pass draw");
 		[self drawTargetTextureIntoDefaultFramebuffer];
 		OOLog(@"universe.profile.secondPassDraw", @"%@", @"End second pass drawing");
diff --git a/src/SDL/MyOpenGLView.h b/src/SDL/MyOpenGLView.h
index 82426aca..b434f19b 100644
--- a/src/SDL/MyOpenGLView.h
+++ b/src/SDL/MyOpenGLView.h
@@ -173,6 +173,7 @@ extern int debug;
 
 	float				_gamma;
 	float				_fov;
+	float				_msaa;
 
    // Full screen sizes
 	NSMutableArray		*screenSizes;
@@ -313,6 +314,9 @@ extern int debug;
 - (void) setFov:(float)value fromFraction:(BOOL)fromFraction;
 - (float) fov:(BOOL)inFraction;
 
+- (void) setMsaa:(BOOL)newMsaa;
+- (BOOL) msaa;
+
 // Check current state of shift key rather than relying on last event.
 + (BOOL)pollShiftKey;
 
diff --git a/src/SDL/MyOpenGLView.m b/src/SDL/MyOpenGLView.m
index a770587c..d87eee82 100644
--- a/src/SDL/MyOpenGLView.m
+++ b/src/SDL/MyOpenGLView.m
@@ -2514,6 +2514,18 @@ static NSString * kOOLogKeyDown				= @"input.keyMapping.keyPress.keyDown";
 }
 
 
+- (BOOL) msaa
+{
+	return _msaa;
+}
+
+
+- (void) setMsaa:(BOOL)newMsaa
+{
+	_msaa = !!newMsaa;
+}
+
+
 - (OOOpenGLMatrixManager *) getOpenGLMatrixManager
 {
 	return matrixManager;

Comparison shots
Without MSAA:
Image

With MSAA:
Image
Commander_X
---- E L I T E ----
---- E L I T E ----
Posts: 666
Joined: Sat Aug 09, 2014 4:16 pm

Re: Render to Framebuffer

Post by Commander_X »

another_commander wrote: Mon Aug 22, 2022 10:42 am
I think I managed to re-implement MSAA, this time on the framebuffers. @Commander_X, can you please test this patch against revision 15fa8d8 (the last one in PR #407)?
[...]
Confirmed, the patch solves the antialiasing.
I am wondering if the washed out colours are a result of using a non-HDR OS+hardware setup.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6559
Joined: Wed Feb 28, 2007 7:54 am

Re: Render to Framebuffer

Post by another_commander »

Commander_X wrote: Mon Aug 22, 2022 1:52 pm
I am wondering if the washed out colours are a result of using a non-HDR OS+hardware setup.
No, the washed out colors (HUD and texts basically) are the result of gamma correction. We are used to seeing the garish colors for so many years now, but the fact is that we were using linear space colors all this time without a gamma correction step at the end. This resulted in colors appearing darker than intended. This is true also for the current trunk, where the HDR/gamma post processing happens only on specific items in the scene, namely ships, planets and atmosphees. The GUI, the sun, the nebulae, the stars the screen backgrounds and the screen texts are not receiving this processing in trunk, but they are receiving it now in the framebuffer test builds. There are three ways to go about this if one is not happy with the result: 1. Render the HUD on a separate framebuffer and apply the result at the final texture after all post-proc steps have been applied. This is somewhat complicated, but it is a pretty common practice in games. 2. Make the GUI colors darker, so when they are tonemapped and gamma corrected they appear closer to what we are used to. This is quite a bit of work on the game's data files. 3. Reduce the exposure in the final scene. This can be tweaked in the oolite-final.fragment shader. Easiest of the three. But, personally speaking, I don't really mind the brighter colors that much. I think it is a matter of habit at the end.

Regarding HDR on Linux, I believe what Linux lacks is support for HDR on the modern HDR-capable display devices like last-gen TVs and monitors reaching thousands of nits of luminance. I see no reason why Linux cannot output a tonemapped HDR image on a typical monitor, when the tonemapping implementation in use targets standard SDR displays. I am pretty sure that what you see on your screen is the same as what I see on mine.
Commander_X
---- E L I T E ----
---- E L I T E ----
Posts: 666
Joined: Sat Aug 09, 2014 4:16 pm

Re: Render to Framebuffer

Post by Commander_X »

Can confirm HDR colours were not the issue (thus not OS+HW, whew!), but the tone mapping+gamma. Without them, things get back to "normal" ;)
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6559
Joined: Wed Feb 28, 2007 7:54 am

Re: Render to Framebuffer

Post by another_commander »

Commander_X wrote: Mon Aug 22, 2022 10:19 pm
Can confirm HDR colours were not the issue (thus not OS+HW, whew!), but the tone mapping+gamma. Without them, things get back to "normal" ;)
The easiest way to check what it looks like without the tonemapping and the gamma correction is to open oolite-final.fragment and take a look at the end. There is currently a commented out line reading //FragColor = vec4(bloomColor, 1.0);. If you uncomment this and comment the last one instead, when you run the game you will see the contents of the bloom buffer, i.e. the blurred scene image holding only the over-the-brightness-threshold values, which is what generates the bloom effect. Now, if you change that line to FragColor = vec4(hdrColor, 1.0);, you can see the contents of the buffer containing the actual scene without any post-processing applied. However, what this looks like is anything but normal. Since everything is clamped to [0.0...1.0], there is a huge amount of detail lost both in the highlights, as well as in the dark areas. There is also no distinction in brightness levels. All highlights above 1.0 look exactly the same regardless of their intensity. It's basically all wrong and the two final passes for tonemapping and gamma are absolutely necessary. See https://learnopengl.com/Advanced-Lighting/HDR and https://learnopengl.com/Advanced-Lighti ... Correction for more details.

I can of course see the problem where the GUI is concerned. There is a case to be made here that GUI elements are (or should not be) subject to lighting, or that they should have a different post-processing workflow of their own. But sometimes you want them to be part of the standard post-processing pipeline, like in the case of the colorblindness filters. But that's something to look into at a later stage maybe.
Commander_X
---- E L I T E ----
---- E L I T E ----
Posts: 666
Joined: Sat Aug 09, 2014 4:16 pm

Re: Render to Framebuffer

Post by Commander_X »

another_commander wrote: Tue Aug 23, 2022 5:30 am
[...] However, what this looks like is anything but normal. Since everything is clamped to [0.0...1.0], there is a huge amount of detail lost both in the highlights, as well as in the dark areas. There is also no distinction in brightness levels. All highlights above 1.0 look exactly the same regardless of their intensity.[...]
I think that only using "hdrColor" as the "result" in the buffer (without tone mapping and gamma correction), is preserving some better contrast of the final image than the other way. I tried to normalize the result for hdrColor (which is a sort of clamping by itself) and the result blobbed most of the highlights, and washed out everything even worse (which led me to think the HDR -- i.e. outside of [0.0 ... 1.0] range colour values -- is still there, in hdrColor). While my OS and HW don't do HDR, the values outside this range shouldn't matter, regardless (unless NVidia driver is doing some extra adjustments behind the scenes).
User avatar
tsoj
Deadly
Deadly
Posts: 199
Joined: Wed May 18, 2016 8:19 pm
Location: Berlin
Contact:

Re: Render to Framebuffer

Post by tsoj »

In this commit, the second render pass (i.e. all these nice effects) is only applied to all the 3D graphics stuff, but not the HUD or GUI. Especially for all the color correcting stuff, it is probably best not doing it on the HUD or GUI, as these are designed to look best as they are rendered previously.

It was actually quite a simple solution (I simply moved all the extra framebuffer render functions before drawing the HUD), except for one thing: Apparently it is necessary to call OOSetOpenGLState(OPENGL_STATE_OVERLAY) before drawing the contents of the custom framebuffer (so all the rendered 3D entities) to the default screen framebuffer. I don't really know why, and it irks me a bit, but it seems to work.

It should also be possible to include the HUD but not the GUI in the second render pass, or even have three different second render passes for the HUD, the GUI, and the 3D entities. But that can probably be done later if there's demand for that.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6559
Joined: Wed Feb 28, 2007 7:54 am

Re: Render to Framebuffer

Post by another_commander »

Hey, nicely done, thanks! I have made a few modifications to be able to fit it with the code of PR #407 and will commit to the PR later today.

One of the modifications I have made is related to when exactly we want the HUD to be rendered separately and when not. Not all post processing effects are the same and some require that the screen is treated as one big whole. So we would probably need to define somewhere which effects use the scene including the HUD and which use the 3d scene only. For now, rendering the HUD in a separate pass is hardcoded to correspond to no post fx (i.e. the standard rendering) and the cloak vision effect. The remaining post-proc effects currently sitting inside the final shader, namely the three colorblindness filters, grayscale, old silent movie and CRT, all render the HUD in the same pass as the rest of the universe.

Please have a look and let me know if this works ok for you too. I think we are very close to committing to master now.

Here is the patch against commit 149aa40 of PR #407.

Code: Select all

diff --git a/src/Core/Universe.m b/src/Core/Universe.m
index 6cee503a..5367d13e 100644
--- a/src/Core/Universe.m
+++ b/src/Core/Universe.m
@@ -202,6 +202,7 @@ static OOComparisonResult comparePrice(id dict1, id dict2, void * context);
 - (void) initTargetFramebufferWithViewSize:(NSSize)viewSize;
 - (void) deleteOpenGLObjects;
 - (void) resizeTargetFramebufferWithViewSize:(NSSize)viewSize;
+- (void) prepareToRenderIntoDefaultFramebuffer;
 - (void) drawTargetTextureIntoDefaultFramebuffer;
 
 - (BOOL) doRemoveEntity:(Entity *)entity;
@@ -4732,28 +4733,29 @@ static const OOMatrix	starboard_matrix =
 
 - (void) drawUniverse
 {
+	int currentPostFX = [self currentPostFX];
+	BOOL hudSeparateRenderPass =  [self useShaders] && (currentPostFX == OO_POSTFX_NONE || currentPostFX == OO_POSTFX_CLOAK);
 	NSSize  viewSize = [gameView viewSize];
 	OOLog(@"universe.profile.draw", @"%@", @"Begin draw");
-
-	if ((int)targetFramebufferSize.width != (int)viewSize.width || (int)targetFramebufferSize.height != (int)viewSize.height)
-	{
-		[self resizeTargetFramebufferWithViewSize:viewSize];
-	}
 	
-	if([self useShaders])
+	if (!no_update)
 	{
-		if ([gameView msaa])
+		if ((int)targetFramebufferSize.width != (int)viewSize.width || (int)targetFramebufferSize.height != (int)viewSize.height)
 		{
-			OOGL(glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebufferID));
+			[self resizeTargetFramebufferWithViewSize:viewSize];
 		}
-		else
+	
+		if([self useShaders])
 		{
-			OOGL(glBindFramebuffer(GL_FRAMEBUFFER, targetFramebufferID));
+			if ([gameView msaa])
+			{
+				OOGL(glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebufferID));
+			}
+			else
+			{
+				OOGL(glBindFramebuffer(GL_FRAMEBUFFER, targetFramebufferID));
+			}
 		}
-	}
-	
-	if (!no_update)
-	{
 		@try
 		{
 			no_update = YES;	// block other attempts to draw
@@ -5053,7 +5055,23 @@ static const OOMatrix	starboard_matrix =
 
 			}
 			
-
+			// actions when the HUD should be rendered separately from the 3d universe
+			if (hudSeparateRenderPass)
+			{
+				OOCheckOpenGLErrors(@"Universe after drawing entities");
+				OOSetOpenGLState(OPENGL_STATE_OVERLAY);  // FIXME: should be redundant.
+				
+				[self prepareToRenderIntoDefaultFramebuffer];	
+				OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
+				
+				OOLog(@"universe.profile.secondPassDraw", @"%@", @"Begin second pass draw");
+				[self drawTargetTextureIntoDefaultFramebuffer];
+				OOCheckOpenGLErrors(@"Universe after drawing from custom framebuffer to screen framebuffer");
+				OOLog(@"universe.profile.secondPassDraw", @"%@", @"End second pass drawing");
+	
+				OOLog(@"universe.profile.drawHUD", @"%@", @"Begin HUD drawing");
+			}
+			
 			/* Reset for HUD drawing */
 			OOGLResetProjection();
 			OOGLFrustum(-0.5, 0.5, -aspect*0.5, aspect*0.5, 1.0, MAX_CLEAR_DEPTH);
@@ -5128,6 +5146,7 @@ static const OOMatrix	starboard_matrix =
 			[self drawWatermarkString:@"Development version " @OOLITE_SNAPSHOT_VERSION];
 #endif
 			
+			OOLog(@"universe.profile.drawHUD", @"%@", @"End HUD drawing");
 			OOCheckOpenGLErrors(@"Universe after drawing HUD");
 			
 			OOGL(glFlush());	// don't wait around for drawing to complete
@@ -5159,21 +5178,34 @@ static const OOMatrix	starboard_matrix =
 	
 	OOLog(@"universe.profile.draw", @"%@", @"End drawing");
 	
+	// actions when the HUD should be rendered together with the 3d universe
+	if(!hudSeparateRenderPass)
+	{
+		if([self useShaders])
+		{
+			[self prepareToRenderIntoDefaultFramebuffer];
+			OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
+			
+			OOLog(@"universe.profile.secondPassDraw", @"%@", @"Begin second pass draw");
+			[self drawTargetTextureIntoDefaultFramebuffer];
+			OOLog(@"universe.profile.secondPassDraw", @"%@", @"End second pass drawing");
+		}
+	}
+}
+
+
+- (void) prepareToRenderIntoDefaultFramebuffer
+{
+	NSSize viewSize = [gameView viewSize];
 	if([self useShaders])
 	{
 		if ([gameView msaa])
 		{
 			// resolve MSAA framebuffer to target framebuffer
-			glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFramebufferID);
-			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetFramebufferID);
-			glBlitFramebuffer(0, 0, (GLint)viewSize.width, (GLint)viewSize.height, 0, 0, (GLint)viewSize.width, (GLint)viewSize.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+			OOGL(glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFramebufferID));
+			OOGL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetFramebufferID));
+			OOGL(glBlitFramebuffer(0, 0, (GLint)viewSize.width, (GLint)viewSize.height, 0, 0, (GLint)viewSize.width, (GLint)viewSize.height, GL_COLOR_BUFFER_BIT, GL_NEAREST));
 		}
-		
-		OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
-		
-		OOLog(@"universe.profile.secondPassDraw", @"%@", @"Begin second pass draw");
-		[self drawTargetTextureIntoDefaultFramebuffer];
-		OOLog(@"universe.profile.secondPassDraw", @"%@", @"End second pass drawing");
 	}
 }
 

Edit: Reposted the code due to bug found (and fixed) when changing detail levels.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6559
Joined: Wed Feb 28, 2007 7:54 am

Re: Render to Framebuffer

Post by another_commander »

If there are no objections or concerns, I would like to merge PR #407 with master during the weekend.
Post Reply