1.简介
在我们的场景中,使用鼠标光标点击或“挑选”一个3d对象是很有用的。一种方法是从鼠标投射3d光线,通过相机,进入场景,然后检查光线是否与任何物体相交。这通常被称为光线投射。
我们不是从局部空间中的网格开始,而是从视口空间中的2d鼠标光标位置开始。我们用逆矩阵来逆向进行变换,得到世界空间中的一条射线。
- 首先在局部空间中有一个局部坐标(0.5,1,0)
- 然后乘以model矩阵变换到世界空间坐标(10.5,1,-20,1),这里增加了齐次坐标w=1
- 然后世界空间坐标乘以view矩阵转换到我们摄像机看到的坐标(5,-1,-9.5,1)
- 然后观察坐标乘以projection矩阵转换到裁剪空间(9.05,-2.41,7.67,9.5),这里齐次坐标变为-z
- 然后标准化设备坐标到(-1,1)的空间中,使用x/w,y/w,z/w得到(0.95,-0.25,0.81)
- 最后转换到像素坐标,转换到(1024,768)这个坐标下,得到(998,288),以下为公式。
但是实际上,我们要点击到模型,首先是从视口空间坐标开始,也就是从(998,288)开始,进行反推。
2.计算世界空间坐标
float _near=0.1f,_far=100.0f;
QVector4D AXBOpemglWidget::worldPosFromViewPort(int posX, int posY)
{
float winZ;
glReadPixels(
posX,
this->height()-posY
,1,1
,GL_DEPTH_COMPONENT,GL_FLOAT
,&winZ);
qDebug()<<"z = "<<winZ; //获取深度值
float x=(2.0f*posX)/this->width()-1.0f; //转化为标准设备坐标(-1,1)
float y=1.0f-(2.0f*posY)/this->height();//转化为标准设备坐标(-1,1)
float z=winZ*2.0-1.0f;//转化为标准设备坐标(-1,1)
float w = (2.0 * _near * _far) / (_far + _near - z * (_far - _near));//计算齐次坐标
//float w= _near*_far/(_near*winZ-_far*winZ+_far);
QVector4D wolrdPostion(x,y,z,1);
wolrdPostion=w*wolrdPostion;//裁剪空间的坐标
return view.inverted()*projection.inverted()*wolrdPostion; //获得世界空间的坐标
}
- 首先传入像素坐标x,y
- glReadPixels获取到深度值
- 然后标准化坐标(x,y,z)
- 计算齐次坐标
- 使用w*x,w*y,w*z得到裁剪空间坐标
- 最后乘以projection逆矩阵 view的逆矩阵得到世界空间坐标
3.如何判断点中了模型
这里给出了一个简单的算法,把模型比作一个球体,计算点击位置P点到模型中心O点的距离,如果小于半径r(使用世界坐标来计算距离),则选中物体。当然这个算法并不完美。
for(QMap<QString, ModelInfo>::iterator iter=m_Models.begin();iter!=m_Models.end();iter++){
ModelInfo *modelInfo=&iter.value();
float r=(modelInfo->model->m_maxY-modelInfo->model->m_minY)/2;
if(modelInfo->worldPos.distanceToPoint(QVector3D(wolrdPostion))<r
&&!hasSelected){
modelInfo->isSelected=true;
hasSelected=true;
}
}
4.源码
#include "axbopemglwidget.h"
#include "vertices.h"
const unsigned int timeOutmSec=50;
QVector3D viewInitPos(0.0f,5.0f,20.0f);
float _near=0.1f,_far=100.0f;
QMatrix4x4 model;
QMatrix4x4 view;
QMatrix4x4 projection;
AXBOpemglWidget::AXBOpemglWidget(QWidget *parent) : QOpenGLWidget(parent)
{
connect(&m_timer,SIGNAL(timeout()),this,SLOT(on_timeout()));
m_timer.start(timeOutmSec);
m_time.start();
m_camera.Position=viewInitPos;
setFocusPolicy(Qt::StrongFocus);
//setMouseTracking(true);
}
AXBOpemglWidget::~AXBOpemglWidget()
{
for(auto iter=m_Models.begin();iter!=m_Models.end();iter++){
ModelInfo *modelInfo=&iter.value();
delete modelInfo->model;
}
}
void AXBOpemglWidget::loadModel(string path)
{
static int i=0;
makeCurrent();
Model * _model=new Model(QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>()
,path.c_str());
qDebug()<<"miny = "<<_model->m_minY;
//m_camera.Position=cameraPosInit(_model->m_maxY,_model->m_minY);
m_Models["张三"+QString::number(i++)]=
ModelInfo{_model,QVector3D(0,0-_model->m_minY,0),0.0,0.0,0.0,false,"张三"};
doneCurrent();
}
void AXBOpemglWidget::initializeGL()
{
initializeOpenGLFunctions();
//创建VBO和VAO对象,并赋予ID
bool success;
m_ShaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shaders/shapes.vert");
m_ShaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shaders/shapes.frag");
success=m_ShaderProgram.link();
if(!success) qDebug()<<"ERR:"<<m_ShaderProgram.log();
m_BoxDiffuseTex=new
QOpenGLTexture(QImage(":/images/images/container2.png").mirrored());
m_PlaneDiffuseTex=new
QOpenGLTexture(QImage(":/images/images/wall.jpg").mirrored());
m_PlaneMesh=processMesh(planeVertices,6,m_PlaneDiffuseTex->textureId());
}
void AXBOpemglWidget::resizeGL(int w, int h)
{
Q_UNUSED(w);
Q_UNUSED(h);
}
void AXBOpemglWidget::paintGL()
{
model.setToIdentity();
view.setToIdentity();
projection.setToIdentity();
// float time=m_time.elapsed()/50.0;
projection.perspective(m_camera.Zoom,(float)width()/height(),_near,_far);
view=m_camera.GetViewMatrix();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_ShaderProgram.bind();
m_ShaderProgram.setUniformValue("projection", projection);
m_ShaderProgram.setUniformValue("view", view);
//model.rotate(time, 1.0f, 1.0f, 0.5f);
m_ShaderProgram.setUniformValue("viewPos",m_camera.Position);
// light properties, note that all light colors are set at full intensity
m_ShaderProgram.setUniformValue("light.ambient", 0.7f, 0.7f, 0.7f);
m_ShaderProgram.setUniformValue("light.diffuse", 0.9f, 0.9f, 0.9f);
m_ShaderProgram.setUniformValue("light.specular", 1.0f, 1.0f, 1.0f);
// material properties
m_ShaderProgram.setUniformValue("material.shininess", 32.0f);
m_ShaderProgram.setUniformValue("light.direction", -0.2f, -1.0f, -0.3f);
m_ShaderProgram.setUniformValue("model", model);
m_PlaneMesh->Draw(m_ShaderProgram);
foreach(auto modelInfo,m_Models){
model.setToIdentity();
model.translate(modelInfo.worldPos);
model.rotate(modelInfo.pitch,QVector3D(1.0,0.0,0.0));
model.rotate(modelInfo.yaw,QVector3D(0.0,1.0,0.0));
model.rotate(modelInfo.roll,QVector3D(0.0,0.0,1.0));
m_ShaderProgram.setUniformValue("model", model);
modelInfo.model->Draw(m_ShaderProgram);
}
}
void AXBOpemglWidget::wheelEvent(QWheelEvent *event)
{
m_camera.ProcessMouseScroll(event->angleDelta().y()/120);
}
void AXBOpemglWidget::keyPressEvent(QKeyEvent *event)
{
float deltaTime=timeOutmSec/1000.0f;
switch (event->key()) {
case Qt::Key_W: m_camera.ProcessKeyboard(FORWARD,deltaTime);break;
case Qt::Key_S: m_camera.ProcessKeyboard(BACKWARD,deltaTime);break;
case Qt::Key_D: m_camera.ProcessKeyboard(RIGHT,deltaTime);break;
case Qt::Key_A: m_camera.ProcessKeyboard(LEFT,deltaTime);break;
case Qt::Key_Q: m_camera.ProcessKeyboard(DOWN,deltaTime);break;
case Qt::Key_E: m_camera.ProcessKeyboard(UP,deltaTime);break;
case Qt::Key_Space: m_camera.Position=viewInitPos;break;
default:break;
}
}
void AXBOpemglWidget::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::RightButton){
static QPoint lastPos(width()/2,height()/2);
auto currentPos=event->pos();
QPoint deltaPos=currentPos-lastPos;
lastPos=currentPos;
m_camera.ProcessMouseMovement(deltaPos.x(),-deltaPos.y());
}
}
void AXBOpemglWidget::mousePressEvent(QMouseEvent *event)
{
makeCurrent();
if(event->buttons()&Qt::LeftButton){
QVector4D wolrdPostion=worldPosFromViewPort(event->pos().x(),
event->pos().y());
mousePickingPos(QVector3D(wolrdPostion));
for(QMap<QString, ModelInfo>::iterator iter=m_Models.begin();iter!=m_Models.end();iter++){
ModelInfo *modelInfo=&iter.value();
float r=(modelInfo->model->m_maxY-modelInfo->model->m_minY)/2;
if(modelInfo->worldPos.distanceToPoint(QVector3D(wolrdPostion))<r){
qDebug()<<"selected";
}
}
}
doneCurrent();
}
void AXBOpemglWidget::on_timeout()
{
update();
}
QVector3D AXBOpemglWidget::cameraPosInit(float maxY, float minY)
{
QVector3D temp={0,0,0};
float height=maxY-minY;
temp.setZ(1.5*height);
if(minY>=0)
temp.setY(height/2.0);
viewInitPos=temp;
return temp;
}
Mesh* AXBOpemglWidget::processMesh(float *vertices, int size, unsigned int textureId)
{
vector<Vertex> _vertices;
vector<unsigned int> _indices;
vector<Texture> _textures;
//memcpy(&_vertices[0],vertices,8*size*sizeof(float));
for(int i=0;i<size;i++){
Vertex vert;
vert.Position[0]=vertices[i*5+0];
vert.Position[1]=vertices[i*5+1];
vert.Position[2]=vertices[i*5+2];
vert.TexCoords[0]=vertices[i*5+3];
vert.TexCoords[1]=vertices[i*5+4];
_vertices.push_back(vert);
_indices.push_back(i);
}
Texture tex; tex.id=textureId;
tex.type="texture_diffuse";
_textures.push_back(tex);
return new Mesh(
QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>()
,_vertices,_indices,_textures);
}
QVector4D AXBOpemglWidget::worldPosFromViewPort(int posX, int posY)
{
float winZ;
glReadPixels(
posX,
this->height()-posY
,1,1
,GL_DEPTH_COMPONENT,GL_FLOAT
,&winZ);
float x=(2.0f*posX)/this->width()-1.0f;
float y=1.0f-(2.0f*posY)/this->height();
float z=winZ*2.0-1.0f;
float w = (2.0 * _near * _far) / (_far + _near - z * (_far - _near));
//float w= _near*_far/(_near*winZ-_far*winZ+_far);
QVector4D wolrdPostion(x,y,z,1);
wolrdPostion=w*wolrdPostion;
return view.inverted()*projection.inverted()*wolrdPostion;
}
5.完整工程
https://download.csdn.net/download/wzz953200463/87941643https://download.csdn.net/download/wzz953200463/87941643
6.相关参考
OpenGL 齐次坐标_Mr.codeee的博客-CSDN博客
OpenGL 坐标系统_Mr.codeee的博客-CSDN博客
OpenGL模型加载_Mr.codeee的博客-CSDN博客