目录
Exercise 6:放大镜功能
前言
第六次作业感觉是目前为止最难的了,可能也是九次作业中最难的(如果后面有更难的当我没说)
一、在练习5的基础上,实现放大镜功能,放大场景中的任意部分
对于如何实现放大镜效果,我从开始到解决一共使用了三种方法,显然前两种并不正确,或并不是最好的方法,但我也在这里一并记录下来
1.简单修改FOV,放大场景
修改FOV,将FOV减小,理论上就和将广角镜头改为长焦镜头一样,可以让视角更窄,看得更远,这种方法用来作为望远镜效果或许不错,但并不能做到放大场景中的任意部分一点
2.读取鼠标周围的像素数据,并作为贴图贴在一个圆上
读取像素数据的代码如下
void ScreenShot()
{
magnifier.pView[0] = magnifier.mouseX -magnifier.scale;
magnifier.pView[1] = Height-magnifier.mouseY - magnifier.scale;
magnifier.pView[2] = magnifier.scale*2;
magnifier.pView[3] = magnifier.scale*2;
GLsizei numComponet = 3;
GLsizei bufferSize = magnifier.pView[2] * magnifier.pView[3] * sizeof(GLubyte)*numComponet;
magnifier.scaleData = new GLubyte[bufferSize];
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);//设置4字节对齐
glReadPixels(magnifier.pView[0], magnifier.pView[1], magnifier.pView[2], magnifier.pView[3], GL_RGB, GL_UNSIGNED_BYTE, magnifier.scaleData);
}
void GetWorldPos()
{
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY, winZ;
GLdouble object_x, object_y, object_z;
glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
glGetDoublev(GL_PROJECTION_MATRIX, projection);
glGetIntegerv(GL_VIEWPORT, viewport);
winX = (float)magnifier.mouseX;
winY = (float)viewport[3] - (float)magnifier.mouseY;
glReadPixels(magnifier.mouseX, winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
winZ = 0.96; //这里我并没有搞明白,但不将winZ的值修改,得不到正确的鼠标对应位置
gluUnProject((GLdouble)magnifier.mouseX, (GLdouble)winY, (GLdouble)winZ, modelview, projection, viewport, &object_x, &object_y, &object_z);
magnifier.worldX = object_x;
magnifier.worldY = object_y;
}
最终实现了可以在任意位置放大场景,但弊端是在图片放大一定大小后就会糊掉,并不是实现场景放大的最优解
3.使用Render To Texture
这里是采用了Render To Texture的方法,在另一个FrameBuffer上再次渲染场景,并修改对应的FOV和方向,并存储为纹理,以得到更好的放大镜效果
#include<gl/glew.h>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<GL/freeglut.h>
int mainWindow;
int subWindow;
int imageheight;
int imagewidth;
int pixellength;
GLubyte* pixeldata;
GLint Height = 600;
GLint Width = 800;
GLfloat LightPosition0[4] = { 0,0,0,-1 };
int oldX,oldY;
float rotateX, rotateY;
float fovy = 45;
bool mouseScale = false;
bool rotate = false;
struct Magnifier
{
int mouseX, mouseY;
float worldX, worldY;
float radius = 2;
float scale = 35;
};
struct camer {
float eyePos[3] = { 0,0,20 };
float lookAt[3] = { 0,0,0 };
float upDir[3] = { 0,1,0 };
};
Magnifier magnifier;
camer ca;
GLfloat speed = 0;
float aspect;
void Lighting();
GLuint framebuffer;
GLuint textureColorbuffer;
GLuint rbo;
float offsetz;
void GetWorldPos();
void init()
{
glClearColor(0.5, 0.5, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST); //开启深度测试
Lighting();
}
void initFrameBuffer()
{
glGenRenderbuffers(1, &rbo);
glGenFramebuffers(1, &framebuffer);
glGenTextures(1, &textureColorbuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
init();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void DrawScene(bool scale)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(ca.eyePos[0], ca.eyePos[1], ca.eyePos[2], ca.lookAt[0], ca.lookAt[1], ca.lookAt[2], ca.upDir[0], ca.upDir[1], ca.upDir[2]);
glViewport(0, 0, Width, Height);
//进行投影变换前调用下面两个函数,接下来的变换函数将影响投影矩阵
//在窗口改变函数reshape中先用glMatrixMode(GL_PROJECTION)定义视锥,再用glMatrixMode(GL_MODELVIEW)改为模型变换
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(fovy, aspect, 1.0f, 100.0f);//
glViewport(0, 0, Width, Height);
glMatrixMode(GL_MODELVIEW);
if(scale)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//修改fov
gluPerspective(fovy-magnifier.scale, aspect, 1.0f, 100.0f);
glMatrixMode(GL_MODELVIEW);
//修改观测图像的位置,以得到正确的放大结果
glTranslatef(-magnifier.worldX, -magnifier.worldY, 0);
}
else if(!scale&&mouseScale)
{
GetWorldPos();
}
glEnable(GL_LIGHTING);
glLightfv(GL_LIGHT0, GL_POSITION, LightPosition0);
glPushMatrix();
glRotatef(rotateX, 0, 1, 0);
glRotatef(rotateY, 1, 0, 0);
//glTranslated(0, 0, 3);
glPushMatrix();
{
GLfloat ambient[] = { 0,0.52,0,1 };
glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
GLfloat diffuse[] = { 0.86,0.54,0,1 };
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
glTranslated(0, 0, -13);
glutSolidTeapot(4.5);
}
glPopMatrix();
glPushMatrix();
{
GLfloat ambient[] = { 0.12,0,0,1 };
glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
GLfloat diffuse[] = { 0.81,0,0,1 };
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
glTranslated(0, 0, -6);
glutSolidTeapot(3);
}
glPopMatrix();
glPushMatrix();
{
GLfloat ambient[] = { 0.56,0.23,0,1 };
glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
GLfloat diffuse[] = { 0.54,0.13,0.32,0.5 };
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
glEnable(GL_BLEND);
glDepthMask(GL_FALSE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTranslated(0, 0, -1);
glutSolidTeapot(2);
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
}
glPopMatrix();
glPushMatrix();
{
GLfloat ambient[] = { 0.2,0.63,0.5,1 };
glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
GLfloat diffuse[] = { 0.57,0.83,0.62,0.2 };
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
glEnable(GL_BLEND);
glDepthMask(GL_FALSE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTranslated(0, 0, 2);
glutSolidTeapot(1);
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
}
glPopMatrix();
glPushMatrix();
{
GLfloat ambient[] = { 0.2,0.3,0.2,1 };
glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
GLfloat diffuse[] = { 1,1,1,0.35 };
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
glEnable(GL_BLEND);
glDepthMask(GL_FALSE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTranslated(0, 0, 0.5);
glutSolidTeapot(1.2);
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
}
glPopMatrix();
glPopMatrix();
}
void display()
{
DrawScene(false);
if (mouseScale)
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// 创建一个颜色附加纹理
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Width, Height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);
// 为深度和模板附件创建一个renderbuffer对象
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, Width, Height); // 深度和模板缓冲区使用一个renderbuffer对象。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
DrawScene(true);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
DrawScene(false);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glEnable(GL_TEXTURE_2D);
glBegin(GL_TRIANGLE_FAN);
glTexCoord2f(0.5, 0.5);
glVertex2f(magnifier.worldX, magnifier.worldY);
for (double i = 0; i <= 50; i++)
{
glTexCoord2f(0.5 + 0.5*cos(i*2 / 50.0 * 3.1415926), 0.5 + 0.5*sin(i*2 / 50.0 * 3.1415926));
glVertex2f(magnifier.worldX + magnifier.radius*cos(i*2 / 50.0 * 3.1415926), magnifier.worldY+ magnifier.radius*sin(i*2 / 50.0 * 3.1415926));
}
glEnd();
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
glEnable(GL_LIGHTING);
}
glutSwapBuffers();
}
void GetWorldPos()
{
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY, winZ;
GLdouble object_x, object_y, object_z;
glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
glGetDoublev(GL_PROJECTION_MATRIX, projection);
glGetIntegerv(GL_VIEWPORT, viewport);
winX = (float)magnifier.mouseX;
winY = (float)viewport[3] - (float)magnifier.mouseY;
glReadPixels(magnifier.mouseX, winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
winZ = 0.96;
gluUnProject((GLdouble)magnifier.mouseX, (GLdouble)winY, (GLdouble)winZ, modelview, projection, viewport, &object_x, &object_y, &object_z);
magnifier.worldX = object_x;
magnifier.worldY = object_y;
}
void Lighting()
{
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
GLfloat AmbientLight0[4] = { 1.0, 1.0, 1.0, 1.0 };
glLightfv(GL_LIGHT0, GL_AMBIENT, AmbientLight0);
GLfloat DiffuseLight0[4] = { 1.0, 1.0, 1.0, 1.0 };
glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight0);
GLfloat SpecularLight0[4] = { 1.0,1.0,1.0,1.0 };
glLightfv(GL_LIGHT0, GL_SPECULAR, SpecularLight0);
//glEnable(GL_LIGHT1);
GLfloat AmbientLight1[4] = { 0.0, 1.0, 0.0, 1.0 };
glLightfv(GL_LIGHT1, GL_AMBIENT, AmbientLight1);
GLfloat DiffuseLight1[4] = { 0.0, 1.0, 0.0, 1.0 };
glLightfv(GL_LIGHT1, GL_DIFFUSE, DiffuseLight1);
GLfloat SpecularLight1[4] = { 0.0,1.0,0.0,1.0 };
glLightfv(GL_LIGHT1, GL_SPECULAR, SpecularLight1);
//glEnable(GL_LIGHT2);
GLfloat AmbientLight2[4] = { 1.0, 0.0, 0.0, 1.0 };
glLightfv(GL_LIGHT2, GL_AMBIENT, AmbientLight2);
GLfloat DiffuseLight2[4] = { 1.0, 0.0, 0.0, 1.0 };
glLightfv(GL_LIGHT2, GL_DIFFUSE, DiffuseLight2);
GLfloat SpecularLight2[4] = { 1.0,0.0,0.0,1.0 };
glLightfv(GL_LIGHT2, GL_SPECULAR, SpecularLight2);
}
void Idle()
{
glutPostRedisplay();
}
void reshape(int w, int h)
{
aspect = (float)w / ((h) ? h : 1);//平截头体的纵横比,也就是宽度除以高度,(h)?h:1意思是若h=0,则h=1
Height = h;
Width = w;
glViewport(0, 0, w, h);
//进行投影变换前调用下面两个函数,接下来的变换函数将影响投影矩阵
//在窗口改变函数reshape中先用glMatrixMode(GL_PROJECTION)定义视锥,再用glMatrixMode(GL_MODELVIEW)改为模型变换
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(fovy, aspect, 1.0f, 100.0f);//
glViewport(0, 0, w, h);
glMatrixMode(GL_MODELVIEW);
}
void MouseFunc(int button,int state,int x,int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
rotate = true;
oldX = x;
oldY = y;
}
else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
{
rotate = false;
}
else if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
{
magnifier.mouseX = x;
magnifier.mouseY = y;
mouseScale = !mouseScale;
}
}
void MouseWheel(int button, int dir, int x, int y)
{
if (dir > 0&&magnifier.scale>10)
{
magnifier.scale -=2;
}
else if (dir < 0&&magnifier.scale<40)
{
magnifier.scale += 2;
}
}
void MouseMove(int x, int y)
{
if (rotate)
{
rotateX += x - oldX;
rotateY += y - oldY;
oldX = x;
oldY = y;
}
if (mouseScale)
{
magnifier.mouseX = x;
magnifier.mouseY = y;
}
}
void MousePassiveMove(int x, int y)
{
if (mouseScale)
{
magnifier.mouseX = x;
magnifier.mouseY = y;
}
}
void KeyBoard(unsigned char button, int x,int y)
{
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);
glutInitWindowPosition(100, 100);
glutInitWindowSize(Width, Height);
mainWindow = glutCreateWindow("Test");
GLenum err = glewInit(); //如果使用FBO的话,此句不能缺少
init();
initFrameBuffer();
glutDisplayFunc(display); //窗口绘制
glutIdleFunc(Idle);
glutReshapeFunc(reshape);
glutMouseFunc(MouseFunc);
glutMouseWheelFunc(MouseWheel);
glutMotionFunc(MouseMove);
glutKeyboardFunc(KeyBoard);
glutPassiveMotionFunc(MousePassiveMove);
glutMainLoop();
return 0;
}
在我总结了以下几点遇到的问题和我自己的理解:
1.OpenGL的默认渲染Framebuffer编号为0,且只有0号Framebuffer可以渲染在屏幕上,如果调用glBindFramebuffer()修改渲染的Framebuffer时渲染效果是不会显示在屏幕上的,即离屏渲染
2.OpenGL支持生成的纹理总数是有限的,约为2400个,若生成的纹理总数超出,则后续贴图将失效(我不小心放在display函数里踩到的坑)
3.在另一个Framebuffer里渲染场景时,仅靠FOV和物体位移是不够的,必须还要有旋转去调整观察的方向,才能得到指哪看哪的效果,如果不进行旋转的修正,则得到的效果非常奇怪,如下![](https://img-blog.csdnimg.cn/20210430185712754.gif)
需补充加入如下的旋转修正
glRotated(atan(-magnifier.worldY / ca.eyePos[2]) * 180 / 3.14159, 1, 0, 0);
glRotated(atan(magnifier.worldX/ ca.eyePos[2])*180/3.14159, 0, 1, 0);
总结
这次作业得到的效果如下图,有非常好的放大镜效果