🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

OpenGL 4 large screenshot

Started by
8 comments, last by taby 3 years, 7 months ago

I'm using glm::frustum to do large screenshots, and it works quite well, except the SSAO shader leaves seams where the frustums meet up.

What is the recommended way of doing large screenshots in OpenGL 4?

The relevant code is:

	void uv_camera::Set_Large_Screenshot(size_t num_cams, size_t cam_index_x, size_t cam_index_y, const int width_px, const int height_px)
	{

		win_x = width_px;
		win_y = height_px;

		// No guarantees about the behaviour of this functionality. It wasn't tested a lot.

		const float deg_to_rad = (1.0f / 360.0f) * 2.0f * glm::pi<float>();
		float aspect = float(win_x) / float(win_y);
		float tangent = tanf((fov / 2.0f) * deg_to_rad);
		float height = near_plane * tangent; // Half height of near_plane plane.
		float width = height * aspect; // Half width of near_plane plane.

		float cam_width = 2 * width / num_cams;
		float cam_height = 2 * height / num_cams;

		float left = -width + cam_index_x * cam_width;
		float right = -width + (cam_index_x + 1) * cam_width;
		float bottom = -height + cam_index_y * cam_height;
		float top = -height + (cam_index_y + 1) * cam_height;


		model_mat = glm::mat4(1.0f);

		projection_mat = frustum(
			left, 
			right, 
			bottom, 
			top, 
			near_plane, 
			far_plane);

		view_mat = lookAt(
			eye,
			look_at,
			up
		);
	}

void take_screenshot(size_t num_cams_wide, const char* filename, const bool reverse_rows = false)
{
	screenshot_mode = true;

	// Set up Targa TGA image data.
	unsigned char  idlength = 0;
	unsigned char  colourmaptype = 0;
	unsigned char  datatypecode = 2;
	unsigned short int colourmaporigin = 0;
	unsigned short int colourmaplength = 0;
	unsigned char  colourmapdepth = 0;
	unsigned short int x_origin = 0;
	unsigned short int y_origin = 0;

	cout << "Image size: " << static_cast<size_t>(win_x) * num_cams_wide << "x" << static_cast<size_t>(win_y) * num_cams_wide << " pixels" << endl;

	if (static_cast<size_t>(win_x) * num_cams_wide > static_cast<unsigned short>(-1) ||
		static_cast<size_t>(win_y) * num_cams_wide > static_cast<unsigned short>(-1))
	{
		cout << "Image too large. Maximum width and height is " << static_cast<unsigned short>(-1) << endl;
		return;
	}

	unsigned short int px = win_x * static_cast<unsigned short>(num_cams_wide);
	unsigned short int py = win_y * static_cast<unsigned short>(num_cams_wide);
	unsigned char  bitsperpixel = 24;
	unsigned char  imagedescriptor = 0;
	vector<char> idstring;

	size_t num_bytes = 3 * px * py;
	vector<unsigned char> pixel_data(num_bytes);

	vector<unsigned char> fbpixels(3 * win_x * win_y);

	const size_t total_cams = num_cams_wide * num_cams_wide;
	size_t cam_count = 0;
	// Loop through subcameras.
	for (size_t cam_num_x = 0; cam_num_x < num_cams_wide; cam_num_x++)
	{
		for (size_t cam_num_y = 0; cam_num_y < num_cams_wide; cam_num_y++)
		{
			cout << "Camera: " << cam_count + 1 << " of " << total_cams << endl;

			// Set up camera, draw, then copy the frame buffer.
			main_camera.Set_Large_Screenshot(num_cams_wide, cam_num_x, cam_num_y, win_x, win_y);



			display_func();
			glReadPixels(0, 0, win_x, win_y, GL_RGB, GL_UNSIGNED_BYTE, &fbpixels[0]);

			// Copy pixels to large image.
			for (GLint i = 0; i < win_x; i++)
			{
				for (GLint j = 0; j < win_y; j++)
				{
					size_t fb_index = 3 * (j * win_x + i);

					size_t screenshot_x = cam_num_x * win_x + i;
					size_t screenshot_y = cam_num_y * win_y + j;
					size_t screenshot_index = 3 * (screenshot_y * (win_x * num_cams_wide) + screenshot_x);

					pixel_data[screenshot_index] = fbpixels[fb_index + 2];
					pixel_data[screenshot_index + 1] = fbpixels[fb_index + 1];
					pixel_data[screenshot_index + 2] = fbpixels[fb_index];
				}
			}

			cam_count++;
		}

	}

	screenshot_mode = false;

	main_camera.calculate_camera_matrices(win_x, win_y);

	// Write Targa TGA file to disk.
	ofstream out(filename, ios::binary);

	if (!out.is_open())
	{
		cout << "Failed to open TGA file for writing: " << filename << endl;
		return;
	}

	out.write(reinterpret_cast<char*>(&idlength), 1);
	out.write(reinterpret_cast<char*>(&colourmaptype), 1);
	out.write(reinterpret_cast<char*>(&datatypecode), 1);
	out.write(reinterpret_cast<char*>(&colourmaporigin), 2);
	out.write(reinterpret_cast<char*>(&colourmaplength), 2);
	out.write(reinterpret_cast<char*>(&colourmapdepth), 1);
	out.write(reinterpret_cast<char*>(&x_origin), 2);
	out.write(reinterpret_cast<char*>(&y_origin), 2);
	out.write(reinterpret_cast<char*>(&px), 2);
	out.write(reinterpret_cast<char*>(&py), 2);
	out.write(reinterpret_cast<char*>(&bitsperpixel), 1);
	out.write(reinterpret_cast<char*>(&imagedescriptor), 1);

	out.write(reinterpret_cast<char*>(&pixel_data[0]), num_bytes);
}

void display_func(void)
{
	glEnable(GL_DEPTH_TEST);

	if (false == screenshot_mode)
		main_camera.calculate_camera_matrices(win_x, win_y);

	glUseProgram(render.get_program());

	const GLfloat background_colour[] = { 1.0f, 0.5f, 0.0f, 0.0f };
	static const GLfloat one = 1.0f;
	static const GLenum draw_buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };

	glBindFramebuffer(GL_FRAMEBUFFER, render_fbo);
	glEnable(GL_DEPTH_TEST);

	glClearBufferfv(GL_COLOR, 0, background_colour);
	glClearBufferfv(GL_COLOR, 1, background_colour);
	glClearBufferfv(GL_DEPTH, 0, &one);

	glBindBufferBase(GL_UNIFORM_BUFFER, 0, points_buffer);

	draw_axis();

	draw_mesh();

	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	glUseProgram(ssao.get_program());

	glUniform1f(uniforms.ssao.ssao_radius, ssao_radius * float(win_x) / 1000.0f);
	glUniform1f(uniforms.ssao.ssao_level, show_ao ? (show_shading ? 0.3f : 1.0f) : 0.0f);
	glUniform1i(uniforms.ssao.weight_by_angle, weight_by_angle ? 1 : 0);
	glUniform1i(uniforms.ssao.randomize_points, randomize_points ? 1 : 0);
	glUniform1ui(uniforms.ssao.point_count, point_count);


	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fbo_textures[0]);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, fbo_textures[1]);

	glGenVertexArrays(1, &quad_vao);

	glDisable(GL_DEPTH_TEST);
	glBindVertexArray(quad_vao);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	glDeleteVertexArrays(1, &quad_vao);

	glFlush();

	if (false == screenshot_mode)
	{
		glutSwapBuffers();
	}
}

Advertisement

I would think the size of your screenshot will be more a factor of rendertarget size vs frustum configuration. If you are doing work to compute the correct frustum projection parameters to render to a N x N render target. Then why not just use a single frustum and render to an M x M render target where M > N, if the device supports it and not worry about computing different projection matrices ? Short of that, I would validate that your are not having ‘off by 1’ issues in the projection math or pixel center issues.

Thanks for your advice. I added in a second screenshot function; this one uses only one large viewport.


void take_screenshot2(size_t num_cams_wide, const char* filename)
{
	size_t ss_width = win_x * num_cams_wide;
	size_t ss_height = win_y * num_cams_wide;

	glViewport(0, 0, ss_width, ss_height);

	GLuint      fbo = 0;
	GLuint      fbo_tex[3] = { 0, 0, 0 };

	glGenFramebuffers(1, &fbo);
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);

	glGenTextures(3, fbo_tex);

	glBindTexture(GL_TEXTURE_2D, fbo_tex[0]);
	glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB32F, ss_width, ss_height);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	glBindTexture(GL_TEXTURE_2D, fbo_tex[1]);
	glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, ss_width, ss_height);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	glBindTexture(GL_TEXTURE_2D, fbo_tex[2]);
	glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH_COMPONENT32F, ss_width, ss_height);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, fbo_tex[0], 0);
	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, fbo_tex[1], 0);
	glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, fbo_tex[2], 0);

	static const GLenum draw_buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };

	glDrawBuffers(2, draw_buffers);

	glEnable(GL_DEPTH_TEST);

	glUseProgram(render.get_program());

	const GLfloat background_colour[] = { 1.0f, 0.5f, 0.0f, 0.0f };
	static const GLfloat one = 1.0f;

	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glEnable(GL_DEPTH_TEST);

	glClearBufferfv(GL_COLOR, 0, background_colour);
	glClearBufferfv(GL_COLOR, 1, background_colour);
	glClearBufferfv(GL_DEPTH, 0, &one);

	glBindBufferBase(GL_UNIFORM_BUFFER, 0, points_buffer);

	draw_axis();
	draw_mesh();

	glUseProgram(ssao.get_program());

	glUniform1f(uniforms.ssao.ssao_radius, ssao_radius * float(ss_width) / 1000.0f);
	glUniform1f(uniforms.ssao.ssao_level, show_ao ? (show_shading ? 0.3f : 1.0f) : 0.0f);
	glUniform1i(uniforms.ssao.weight_by_angle, weight_by_angle ? 1 : 0);
	glUniform1i(uniforms.ssao.randomize_points, randomize_points ? 1 : 0);
	glUniform1ui(uniforms.ssao.point_count, point_count);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fbo_tex[0]);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, fbo_tex[1]);

	glGenVertexArrays(1, &quad_vao);

	glDisable(GL_DEPTH_TEST);
	glBindVertexArray(quad_vao);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	glDeleteVertexArrays(1, &quad_vao);



	vector<unsigned char> output_pixels(ss_width * ss_height * 3);

	glReadBuffer(GL_COLOR_ATTACHMENT0);
	glReadPixels(0, 0, ss_width, ss_height, GL_RGB, GL_UNSIGNED_BYTE, &output_pixels[0]);


	glBindFramebuffer(GL_FRAMEBUFFER, 0);


	// Set up Targa TGA image data.
	unsigned char  idlength = 0;
	unsigned char  colourmaptype = 0;
	unsigned char  datatypecode = 2;
	unsigned short int colourmaporigin = 0;
	unsigned short int colourmaplength = 0;
	unsigned char  colourmapdepth = 0;
	unsigned short int x_origin = 0;
	unsigned short int y_origin = 0;

	unsigned short int px = ss_width;
	unsigned short int py = ss_height;
	unsigned char  bitsperpixel = 24;
	unsigned char  imagedescriptor = 0;
	vector<char> idstring;

		for (size_t i = 0; i < ss_width; i++)
		{
			for (size_t j = 0; j < ss_height; j++)
			{
				size_t index = 3 * (j * ss_width + i);

				unsigned char temp_char;
				temp_char = output_pixels[index + 0];
				output_pixels[index + 0] = output_pixels[index + 2];
				output_pixels[index + 2] = temp_char;
			}
		}

	// Write Targa TGA file to disk.
	ofstream out(filename, ios::binary);

	if (!out.is_open())
	{
		cout << "Failed to open TGA file for writing: " << filename << endl;
		return;
	}

	out.write(reinterpret_cast<char*>(&idlength), 1);
	out.write(reinterpret_cast<char*>(&colourmaptype), 1);
	out.write(reinterpret_cast<char*>(&datatypecode), 1);
	out.write(reinterpret_cast<char*>(&colourmaporigin), 2);
	out.write(reinterpret_cast<char*>(&colourmaplength), 2);
	out.write(reinterpret_cast<char*>(&colourmapdepth), 1);
	out.write(reinterpret_cast<char*>(&x_origin), 2);
	out.write(reinterpret_cast<char*>(&y_origin), 2);
	out.write(reinterpret_cast<char*>(&px), 2);
	out.write(reinterpret_cast<char*>(&py), 2);
	out.write(reinterpret_cast<char*>(&bitsperpixel), 1);
	out.write(reinterpret_cast<char*>(&imagedescriptor), 1);

	out.write(reinterpret_cast<char*>(&output_pixels[0]), ss_width * ss_height * 3 * sizeof(unsigned char));

	out.close();


	glViewport(0, 0, win_x, win_y);


	glDeleteFramebuffers(1, &fbo);
	glDeleteTextures(3, fbo_tex);
}

Oh man, now it turns out that the SSAO code draws a seam along the diagonal, interior edge of the fullscreen “quad”. I can't win LOL

Thanks for your help!

@taby then it's time to replace the fullscreen quad (tri pair) to a single triangle!

Sorry, what?

@taby instead of a fullscreen quad with two triangles you can render a single stretched fullscreen triangle without a diagonal edge.

For example:

https://www.gamedev.net/forums/topic/659574-fullscreen-triangle/

Ok, I’ll give it a shot. Strangely enough, the shader program that I’m using makes the quad in the vertex shader, just like how they made a triangle in the vertex shader. Nice coincidence.

BTW… the shadow mapping code that I am using is from Opengl 4 Shading Language Cookbook by Wolff. Great book!

(Oops, I posted this in the wrong thread)

This topic is closed to new replies.

Advertisement