Im trying to retain the ratio and sizes of the rendered content when resizing my window/framebuffer texture on which im rendering exclusively on the xy-plane (z=0) and would like to have an orthographic projection.
Some general questions, do i need to resize both glm::ortho and viewport, or just one of them? Do both of them have to have the same amount of pixels or just the same aspect ratio? What about their x and y offsets?
I understand that i need to update the texture size.
What i have tried (among many other things):
One texture attachment on my framebuffer, a gl_RGB on GL_COLOR_ATTACHMENT0.
I render my framebuffers attached texture to a ImGui window with imgui::image(), When this ImGui window is resized I resize the texture assignent to my FBO using this: (i do not reattach the texture with glFramebufferTexture2D!)
void Texture2D::SetSize(glm::ivec2 size)`
{
Bind();
this->size = size;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size.x, size.y, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
Unbind();
}
I also update the member variable windowSize.
In my renderingloop (for the Framebuffer) i use
view = glm::lookAt(glm::vec3(0, 0, 1), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
glm::vec2 projWidth = windowSize.x;
glm::vec2 projHeight = windowSize.y;
proj = glm::ortho(-projWidth/2, projWidth/2, -projHeight/2, projHeight/2, -1.f, 1.f);
shaderProgram.SetMat4("view", view, 1);
shaderProgram.SetMat4("proj", proj, 1);
shaderProgram.SetMat4("world", world, 1); // world is identity matrix for now
glViewport(-windowSize.x/2, -windowSize.y/2, windowSize.x, windowSize.y);
Now i now do have some variables here that i use to try to implement paning with but i first want to get resizing to work correctly.
EDIT:
ok i have now tried different parameters for the viewport and orthographic projection matrix:
auto aspect = (float)(windowSize.x)/(float)windowSize.y;
glViewport(0, 0, windowSize.x, windowSize.y);
proj = glm::ortho<float>(-aspect, aspect, -1/aspect, 1/aspect, 1, -1);
Resizing the window still stretches my renderTexture, but now it does it uniformely, x is scaled as much as y. My square sprites remain sqares, but becomes smaller as i decrease the window size.
GIF
I've found a way to resize the 2D interface to scale it based by aspect, give this a try and see if this solves your issue:
auto aspectx = windowSize.x/windowSize.y
auto aspecty = (aspectx < 16f / 9f ? aspectx : 16f / 9f) / (16f / 9f);
float srcaspect = 4f / 3f;
float scale = 0.5f*((float)windowSize.y);
proj = glm::ortho(scale - (scale * aspectx / aspecty) - (scale - (scale * srcaspect)), scale + (scale * aspectx / aspecty) - (scale - (scale * srcaspect)), scale - (scale / aspecty), scale + (scale / aspecty), -1.f, 1.f);
and as a future reference, do this if you want the orthographic scaled within the aspect ratio. The source aspect is what scales the object to be within the original screen aspect:
float srcaspect = 4f / 3f;
float dstaspect = (float)windowSize.x/(float)windowSize.y;
float scale = 0.5f*(bottom - top);
float offset = scale + top;
proj = glm::ortho<float>(offset - (scale * dstaspect / yscale) - (offset - (scale * srcaspect)), offset + (scale * dstaspect / yscale) - (offset - (scale * srcaspect)), offset - (scale / yscale), offset + (scale / yscale), 1, -1);
I believe there were problems with my handling of FBOs as i have now found a satisfactory solution, one that also seems to be the obvious one. Simply using the width and height of the viewport (and FBO attached texture) like this:
proj = glm::ortho<float>(-zoom *viewportSize.x/(2) , zoom*viewportSize.x/(2) , -zoom*viewportSize.y/(2), zoom*viewportSize.y/(2), 1, -1);
You might also want to divide the four parameters by a constant (possibly much larger than 2) to make the zooming range more natural around 1.
While i tried this exact code before, I actually did not recreate the framebuffer, but simply used glTexImage2D(); on the attached texture.
It seems as though one need to delete and recreate the whole Framebuffer, and this was also stated to be safer on some posts here on SO, but I never found any sources saying that it was actually required.
So, lesson learnt, delete and create a new framebuffer. Window resizing is not something that happens very often.
Related
I am currently trying to setup orthographic projection for a game in OpenGL, but I struggle a bit with setting it up correctly.
Currently I am calculating my projection using this simple function:
glm::mat4 projection = glm::ortho(0.0f, width, height, 0.0f, -10000.0f, 10000.0f);
where width and height are given by the screen size.
This leads however to a few problems, namely the objects on screen get stretched or compressed when I change the size of the screen. And when the screen size gets bigger more of the current Scene is shown.
What I want is that I always see the same thing on screen independent from the size of the window, but it should also not distort if I resize the window to some weird aspect ratio.
I tried a few things like using the aspect ration, but I either didn't see anything on screen or things were even worse distorted.
Here is also my model and view matrix code in case I have done something wrong with that:
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(TransformComponent.position.x+width/2 /*width and height are needed to place the object in the center of the screen*/, TransformComponent.position.y+height/2, TransformComponent.position.z));
model = glm::scale(model, glm::vec3(renderable.GetWidth(), renderable.GetHeight(), 1.0f));
sh.setMat4("model", model);
ViewMatrix
glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(position,0)) *
glm::rotate(glm::mat4(1.0f), glm::radians(0.0f), glm::vec3(0, 0, 1));
glm::mat4 m_ViewMatrix = glm::inverse(transform);
Small screen
Big screen
Distorted screen
Another distorted example
The math scales the projection centered on resize. If you're using resolution to scale the 2D objects centered, the original offset positions it to be centered on 1:1 ratio.
srcaspect = 4f / 3f;
dstaspect = width / height;
orthosize = (dstaspect < 16f / 9f ? dstaspect : 16f / 9f) / (16f / 9f);
centerscalex = 0.5f*(height - 0);
centerscaley = 0.5f*(height - 0);
centeroffsetx = (scalex + 0) * srcaspect;
centeroffsety = scaley + 0;
then throw all that in the matrix using this formula, and vola, you get a centered aspect display:
glm::mat4 projection = glm::ortho(centeroffsetx - (centerscalex * dstaspect * orthosize),centeroffsetx + (centerscalex * dstaspect * orthosize),centeroffsety - (centerscaley * orthosize),centeroffsety + (centerscaley * orthosize));
I'd recommend you'd use a default height, if you don't want the object to shrink on bigger resolutions, like this:
centerscalex = 0.5f*(480f - 0);
centerscaley = 0.5f*(480f - 0);
centeroffsetx = scalex + 0;
centeroffsety = scaley + 0;
A while ago I asked a question similar to this one, but in that case I was trying to correct the perspective texture mapping of a trapezoid that had the horizontal lines constantly parallel with glTexCoord4f() and this is relatively simple. However, now I'm trying to fix the texture mapping of the floor and ceiling in my engine, the problem is that since both depend on the shape of the map, I need to use triangles to fill in the polygonal shapes that the map may contain.
I tried a few variations of the same method I used for correct texture mapping on trapezoids, the attempt with more "acceptable" results were when I calculated the size of the triangle's edges (with screen coordinates) and used each result in the different 'q' in each glTexCoord4f(), that is how code currently stands.
With that in mind, how can I fix this while using glTexCoord4f()?
Here is the code I used to correct the texture mapping of the walls (functional):
float u, v;
glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
float sza = wyaa - wyab; //Size of the first vertical edge on the wall
float szb = wyba - wybb; //Size of the second vertical edge on the wall
//Does the wall have streeched textures?
if(!(*wall).streechTexture){
u = -texLength;
v = -texHeight;
}else{
u = -1;
v = -1;
}
glBindTexture (GL_TEXTURE_2D, texture.at((*wall).texture));
glBegin(GL_TRIANGLE_STRIP);
glTexCoord4f(0, 0, 0, sza);
glVertex3f(wxa, wyaa + shearing, -tza * 0.001953);
glTexCoord4f(u * szb, 0, 0, szb);
glVertex3f(wxb, wyba + shearing, -tzb * 0.001953);
glTexCoord4f(0, v * sza, 0, sza);
glVertex3f(wxa, wyab + shearing, -tza * 0.001953);
glTexCoord4f(u * szb, v * szb, 0, szb);
glVertex3f(wxb, wybb + shearing, -tzb * 0.001953);
glEnd();
glDisable(GL_TEXTURE_2D);
And here the current code that renders both the floor and the ceiling (which needs to be fixed):
glEnable(GL_TEXTURE_2D);
glBindTexture (GL_TEXTURE_2D, texture.at((*floor).texture));
float difA, difB, difC;
difA = vectorMag(Vertex(fxa, fyaa), Vertex(fxb, fyba)); //Size of the first edge on the triangle
difB = vectorMag(Vertex(fxb, fyba), Vertex(fxc, fyca)); //Size of the second edge on the triangle
difC = vectorMag(Vertex(fxc, fyca), Vertex(fxa, fyaa)); //Size of the third edge on the triangle
glBegin(GL_TRIANGLE_STRIP); //Rendering the floor
glTexCoord4f(ua * difA, va * difA, 0, difA);
glVertex3f(fxa, fyaa + shearing, -tza * 0.001953);
glTexCoord4f(ub * difB, vb * difB, 0, difB);
glVertex3f(fxb, fyba + shearing, -tzb * 0.001953);
glTexCoord4f(uc * difC, vc * difC, 0, difC);
glVertex3f(fxc, fyca + shearing, -tzc * 0.001953);
glEnd();
glBegin(GL_TRIANGLE_STRIP); //Rendering the ceiling
glTexCoord4f(uc, vc, 0, 1);
glVertex3f(fxc, fycb + shearing, -tzc * 0.001953);
glTexCoord4f(ub, vb, 0, 1);
glVertex3f(fxb, fybb + shearing, -tzb * 0.001953);
glTexCoord4f(ua, va, 0, 1);
glVertex3f(fxa, fyab + shearing, -tza * 0.001953);
glEnd();
glDisable(GL_TEXTURE_2D);
Here a picture of how it looks visually (for comparison purposes, the floor has the failed attempt at correct texture mapping, while the ceiling has affine texture mapping):
I understand that it would be easier if I just set a normal perspective view, but that would simply defeat the whole purpose of the engine.
This is an issue only for floor and ceiling (unless your camera can tilt). So you can render your wals as you doing. But for floors and ceiling you have these basic options (As I mentioned in your old duplicate post):
Rasterize scan line on your own
So instead of rendering triangles (which old ray casters did not do) you render vertical lines pixel by pixel using points instead of triangles. That will be much slower of coarse as GL is more suited for polygonal primitives. See draw_scanline functions in here:
Efficient floor/ceiling rendering in Raycaster
Use perspective view and pass z coordinate
Looks like you added the z coordinate already. So now you just need to set perspective view that matches your wall rendering. OpenGL will do the rest on its own. So you should add something like gluPerspective for your GL_PROJECTION matrix. but just for your floors/ceilings ...
Pass z coordinate and overide fragment shader
So you just write fragment shader that computes the perspective correct texture mapping correction in it and just output wanted texel color +/- some lighting. Here example of shaders usage:
complete GL+GLSL+VAO/VBO C++ example
For more info see:
Ray Casting with different height size
This question is related to Repeating OpenGL-es texture bound to hills in cocos2d 2.0
After reading the answers posted in the above post, I've used the following code for computing the vertices and texture coordinates:
CGPoint pt0,pt1;
float ymid = (p0.y + p1.y) / 2;
float ampl = (p0.y - p1.y) / 2;
pt0 = p0;
float U_Off = floor(pt0.x / 512);
for (int j=1; j<_segments+1; j++)
{
pt1.x = p0.x + j*_dx;
pt1.y = ymid + ampl * cosf(_da*j);
float xTex0 = pt0.x/512 - U_Off;
_vertices[vertices++]=CGPointMake(pt0.x, 0);
_vertices[vertices++]=CGPointMake(pt0.x, pt0.y);
_texCoords[texCoords++]=CGPointMake(xTex0, 1.0f);
_texCoords[texCoords++]=CGPointMake(xTex0, 0);
pt0 = pt1;
}
p0 = p1;
But unfortunately, I still get a tear / misalignment in my texture (circled in yellow):
I've attached dumps of the arrays of vertices and texcoords
I'm new to OpenGl, and can't figure out where the miscalculation is. How do I prevent the line (circled in yellow in image) from appearing ?
EDIT: My texture is either 1024x512 or 512x512 depending on the device. I use the following texture parameters:
ccTexParams tp2 = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_CLAMP_TO_EDGE};
Most likely the reason is in non-continuous texture coordinates.
In texcoords dump you have the following coordinates:
(CGPoint) 0x34b0b28 = (x=1.00390625, y=0)
(CGPoint) 0x34b0b30 = (x=0.005859375, y=1)
It means that between these two points texture is mapped from 1 to 0 (in reverse direction). You should continue texcoords after 1.00390625 => 1.005859375 => ... Also, your texture must have power-of-two size and must be set up with REPEAT mode.
If your texture is in atlas and you cannot set REPEAT mode, you may try to clamp texcoords to [0; 1] range and place two edge points with x=1 and x=0 in the same position.
And, at last, if your texture doesn't change in x-axis you may set x = 0.5 for all points.
I have some questions about the screen set up. Originally when I would draw a triangle the x vector 1 would be all the way to the right and -1 would be all the way to the left. Now I have adjusted it to account for the different aspect ratio of the window. My new question how do I make the numbers which are used to render a 2d tri go along with the pixel values. If my window is 480 pixels wide and 320 tall I want to have to enter this to span the screen with a tri
glBegin(GL_TRIANGLES);
glVertex2f(240, 320);
glVertex2f(480, 0);
glVertex2f(0, 0);
glEnd();
but instead it currently looks like this
glBegin(GL_TRIANGLES);
glVertex2f(0, 1);
glVertex2f(1, -1);
glVertex2f(-1, -1);
glEnd();
Any ideas?
You need to use functions glViewport and glOrtho with correct values. Basically glViewport sets the part of your window capable of rendering 3D-Graphics using OpenGL. glOrtho establishes coordinate system within that part of a window using OpenGL's coordinates.
So for your task you need to know exact width and height of your window. If you are saying they are 480 and 320 respectively then you need to call
glViewport(0, 0, 480, 320)
// or: glViewport ( 0,0,w,h)
somewhere, maybe in your SizeChanging-handler(if you are using WINAPI it is WM_SIZE message)
Next, when establishing OpenGL's scene you need to specify OpenGL's coordinates. For orthographic projection they will be the same as dimensions of a window so
glOrtho(-240, 240, -160, 160, -100, 100)
// or: glOrtho ( -w/2, w/2, -h/2, h/2, -100, 100 );
is siutable for your purppose. Not that here I'm using depth of 200 (z goes from -100 to 100).
Next on your rendering routine you may draw your triangle
Since the second piece of code is working for you, I assume your transformation matrices are all identity or you have a shader that bypasses them. Also your viewport is spanning the whole window.
In general if your viewport starts at (x0,y0) and has WxH size, the normalized coordinates (x,y) you feed to glVertex2f will be transformed to (vx,vy) as follows:
vx = x0 + (x * .5f + .5f) * W
vy = y0 + (y * .5f + .5f) * H
If you want to use pixel coordinates you can use the function
void vertex2(int x, int y)
{
float vx = (float(x) + .5f) / 480.f;
float vy = (float(y) + .5f) / 320.f;
glVertex3f(vx, vy, -1.f);
}
The -1 z value is the closest depth to the viewer. It's negative because the z is assumed to be reflected after the transformation (which is identity in your case).
The addition of .5f is because the rasterizer considers a pixel as a 1x1 quad and evaluates the coverage of your triangle in the middle of this quad.
My application is a vector drawing application. It works with OpenGL. I will be modifying it to instead use the Cairo 2D graphics library. The issue is with zooming. With openGL camera and scale factor sort of work like this:
float scalediv = Current_Scene().camera.ScaleFactor / 2.0f;
float cameraX = GetCameraX();
float cameraY = GetCameraY();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
float left = cameraX - ((float)controls.MainGlFrame.Dimensions.x) * scalediv;
float right = cameraX + ((float)controls.MainGlFrame.Dimensions.x) * scalediv;
float bottom = cameraY - ((float)controls.MainGlFrame.Dimensions.y) * scalediv;
float top = cameraY + ((float)controls.MainGlFrame.Dimensions.y) * scalediv;
glOrtho(left,
right,
bottom,
top,
-0.01f,0.01f);
// Set the model matrix as the current matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
hdc = BeginPaint(controls.MainGlContext.mhWnd,&ps);
Mouse position is obtained like this:
POINT _mouse = controls.MainGlFrame.GetMousePos();
vector2f mouse = functions.ScreenToWorld(_mouse.x,_mouse.y,GetCameraX(),GetCameraY(),
Current_Scene().camera.ScaleFactor,
controls.MainGlFrame.Dimensions.x,
controls.MainGlFrame.Dimensions.y );
vector2f CGlEngineFunctions::ScreenToWorld(int x, int y, float camx, float camy, float scale, int width, int height)
{
// Move the given point to the origin, multiply by the zoom factor and
// add the model coordinates of the center point (camera position)
vector2f p;
p.x = (float)(x - width / 2.0f) * scale +
camx;
p.y = -(float)(y - height / 2.0f) * scale +
camy;
return p;
}
From there I draw the VBO's of triangles. This allows me to pan and zoom in. Given that Cairo only can draw based on coordinates, how can I make it so that a vertex is properly scaled and panned without using transformations. Basically GlOrtho sets the viewport usually but I dont think I could do this with Cairo.
Well GlOrtho is able to change the viewport matrix instead of modifying the verticies but how could I instead modify the verticies to get the same result?
Thanks
*Given vertex P, which was obtained from ScreenToWorld, how could I modify it so that it is scaled and panned accordng to the camera and scale factor? Because usually OpenGL would essentially do this
I think Cairo can do what you want ... see http://cairographics.org/matrix_transform/ . Does that solve your problem, and if not, why ?