1.示例效果图
选中模型对象,出现模型轮廓。
2.简介
当片段着色器处理完一个片段之后,模板测试会开始执行,和深度测试一样,它也可能会丢弃片段。接下来,被保留的片段会进入深度测试,它可能会丢弃更多的片段。模板测试是根据又一个缓冲来进行的,它叫做模板缓冲。
一个模板缓冲中,(通常)每个模板值是8位的。所以每个像素/片段一共能有256种不同的模板值。我们可以将这些模板值设置为我们想要的值,然后当某一个片段有某一个模板值的时候,我们就可以选择丢弃或是保留这个片段了。
模板缓冲首先会被清除为0,之后在模板缓冲中使用1填充了一个空心矩形。场景中的片段将会只在片段的模板值为1的时候会被渲染(其它的都被丢弃了)。
使用模板缓冲的时候,大体的步骤如下:
- 启用模板缓冲的写入。
- 渲染物体,更新模板缓冲的内容。
- 禁用模板缓冲的写入。
- 渲染(其它)物体,这次根据模板缓冲的内容丢弃特定的片段。
启用GL_STENCIL_TEST来启用模板测试。
glEnable(GL_STENCIL_TEST);
和颜色和深度缓冲一样,需要在每次迭代之前清除模板缓冲。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
和深度测试的glDepthMask函数一样,模板缓冲也有一个类似的函数。glStencilMask允许我们设置一个位掩码。
glStencilMask(0xFF); // 可写入
glStencilMask(0x00); // 禁用写入,可读
模板函数:
和深度测试一样,我们对模板缓冲应该通过还是失败,以及它应该如何影响模板缓冲,也是有一定控制的。一共有两个函数能够用来配置模板测试:glStencilFunc和glStencilOp。
glStencilFunc(GLenum func, GLint ref, GLuint mask)一共包含三个参数:
func
:设置模板测试函数。这个测试函数将会应用到已储存的模板值上和glStencilFunc函数的ref
值上。可用的选项有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它们的语义和深度缓冲的函数类似。ref
:设置了模板测试的参考值,模板缓冲的内容将会与这个值进行比较。mask
:设置一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。初始情况下所有位都为1。
示例,函数被设置为如下:
glStencilFunc(GL_EQUAL, 1, 0xFF)
这会告诉OpenGL,只要一个片段的模板值等于(GL_EQUAL
)参考值1,片段将会通过测试并被绘制,否则会被丢弃。
glStencilFunc仅仅描述了OpenGL应该对模板缓冲内容做什么,而不是我们应该如何更新缓冲。
如何更新缓冲:使用glStencilOp。
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)一共包含三个选项,我们能够设定每个选项应该采取的行为:
sfail
:模板测试失败时采取的行为。dpfail
:模板测试通过,但深度测试失败时采取的行为。dppass
:模板测试和深度测试都通过时采取的行为。
每个选项都可以选用以下的其中一种行为:
行为 | 描述 |
---|---|
GL_KEEP | 保持当前储存的模板值 |
GL_ZERO | 将模板值设置为0 |
GL_REPLACE | 将模板值设置为glStencilFunc函数设置的ref 值 |
GL_INCR | 如果模板值小于最大值则将模板值加1 |
GL_INCR_WRAP | 与GL_INCR一样,但如果模板值超过了最大值则归零 |
GL_DECR | 如果模板值大于最小值则将模板值减1 |
GL_DECR_WRAP | 与GL_DECR一样,但如果模板值小于0则将其设置为最大值 |
GL_INVERT | 按位翻转当前的模板缓冲值 |
默认情况下glStencilOp是设置为(GL_KEEP, GL_KEEP, GL_KEEP)
的,所以不论任何测试的结果是如何,模板缓冲都会保留它的值。默认的行为不会更新模板缓冲,所以如果你想写入模板缓冲的话,你需要至少对其中一个选项设置不同的值。
所以,通过使用glStencilFunc和glStencilOp,我们可以精确地指定更新模板缓冲的时机与行为了,我们也可以指定什么时候该让模板缓冲通过,即什么时候片段需要被丢弃。
3.示例-物体轮廓
当你想要在策略游戏中选中一个单位进行操作的,想要告诉玩家选中的是哪个单位的时候,这个效果就非常有用了。为物体创建轮廓的步骤如下:
- 在绘制(需要添加轮廓的)物体之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1。
- 渲染物体。
- 禁用模板写入以及深度测试。
- 将物体放大一点点。
- 使用一个不同的片段着色器,输出一个单独的(边框)颜色。
- 再次绘制物体,但只在它们片段的模板值不等于1时才绘制。
- 再次启用模板写入和深度测试。
#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;
QPoint lastPos;
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());
//m_camera.Position=cameraPosInit(_model->m_maxY,_model->m_minY);
m_Models["Julian"+QString::number(i++)]=
ModelInfo{_model,QVector3D(0,0-_model->m_minY,0)
,0.0,0.0,0.0,false,QString::fromLocal8Bit("张三")+QString::number(i++)};
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_SingleColorShaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shaders/shapes.vert");
m_SingleColorShaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shaders/singleColor.frag");
success=m_SingleColorShaderProgram.link();
if(!success) qDebug()<<"ERR:"<<m_SingleColorShaderProgram.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);
glEnable(GL_STENCIL_TEST); //开启模板测试
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);//设置如何更新缓冲
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);//清除模板缓冲
glStencilMask(0x00);//禁止写入
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);
m_SingleColorShaderProgram.bind();
m_SingleColorShaderProgram.setUniformValue("projection", projection);
m_SingleColorShaderProgram.setUniformValue("view", view);
m_SingleColorShaderProgram.release();
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));
//第一次绘制,写入模板缓冲区,当物体的片段被渲染时,将模板缓冲更新为1
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
m_ShaderProgram.bind();
m_ShaderProgram.setUniformValue("model", model);
modelInfo.model->Draw(m_ShaderProgram);//渲染物体
m_ShaderProgram.release();
if(modelInfo.isSelected==false) continue;
//第二次绘制的时候,只须绘制不等于1的部分,等于1的部分就不需要绘制,禁用模板写入
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
float height=modelInfo.model->m_maxY-modelInfo.model->m_minY;
float width=modelInfo.model->m_maxX-modelInfo.model->m_minX;
if(modelInfo.model->m_minY>=0)
model.translate(0.0f,height/2,0.0f);
model.scale(1.1f,1.0+0.1*(width/height));//将物体放大一点点
if(modelInfo.model->m_minY>=0)
model.translate(0.0f,-height/2,0.0f);
m_SingleColorShaderProgram.bind();//使用一个不同的片段着色器,输出一个单独的(边框)颜色。
m_SingleColorShaderProgram.setUniformValue("model", model);
modelInfo.model->Draw(m_SingleColorShaderProgram);//再次绘制物体,但只在它们片段的模板值不等于1时才绘制
m_SingleColorShaderProgram.release();
//再次启用模板写入和深度测试
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
}
}
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)
{
makeCurrent();
if(m_modelMoving){
for(auto iter=m_Models.begin();iter!=m_Models.end();iter++){
ModelInfo *modelInfo=&iter.value();
if(!modelInfo->isSelected) continue;
modelInfo->worldPos=
QVector3D(worldPosFromViewPort(event->pos().x(),event->pos().y()));
}
}else
if(event->buttons() & Qt::RightButton
|| event->buttons() & Qt::LeftButton
|| event->buttons() & Qt::MiddleButton){
auto currentPos=event->pos();
QPoint deltaPos=currentPos-lastPos;
lastPos=currentPos;
if(event->buttons() & Qt::RightButton)
m_camera.ProcessMouseMovement(deltaPos.x(),-deltaPos.y());
else
for(auto iter=m_Models.begin();iter!=m_Models.end();iter++){
ModelInfo *modelInfo=&iter.value();
if(!modelInfo->isSelected) continue;
if(event->buttons() & Qt::MiddleButton){
modelInfo->roll+=deltaPos.x();
}
else if(event->buttons() & Qt::LeftButton){
modelInfo->yaw+=deltaPos.x();
modelInfo->pitch+=deltaPos.y();
}
}
}
doneCurrent();
}
void AXBOpemglWidget::mousePressEvent(QMouseEvent *event)
{
bool hasSelected=false;
makeCurrent();
lastPos=event->pos();
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
&&!hasSelected){
modelInfo->isSelected=true;
hasSelected=true;
}
else
modelInfo->isSelected=false;
// qDebug()<<modelInfo->worldPos.distanceToPoint(QVector3D(wolrdPostion))
// <<"<"<<r<<"="<<modelInfo->isSelected;
}
}
doneCurrent();
}
void AXBOpemglWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
Q_UNUSED(event);
if(m_modelMoving){
//再次双击取消移动
m_modelMoving=false;
}else
foreach(auto modelInfo,m_Models){
//双击启动移动
if(modelInfo.isSelected==true)
m_modelMoving=true;
qDebug()<<modelInfo.name<<modelInfo.isSelected;
}
}
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;
}
4.完整工程
https://download.csdn.net/download/wzz953200463/87948151https://download.csdn.net/download/wzz953200463/87948151
5.相关参考
OpenGL模型加载_Mr.codeee的博客-CSDN博客
OpenGL 鼠标拾取模型_Mr.codeee的博客-CSDN博客
OpenGL模型控制(旋转、平移)_Mr.codeee的博客-CSDN博客