OpenGL 深度テスト

1 はじめに

深度バッファーは、カラー バッファーと同様に各フラグメントに情報を格納し (すべてのフラグメントの色を格納します: 視覚的な出力)、(通常は) カラー バッファーと同じ幅と高さを持ちます。深度バッファはウィンドウ システムによって自動的に作成され、その深度値を 16、24、または 32 ビット浮動小数点として格納します。ほとんどのシステムでは、深度バッファーの精度は 24 ビットです。

深度テストが有効になっている場合、OpenGL はフラグメントの深度値を深度バッファーの内容と比較します。OpenGL は深度テストを実行し、テストに合格すると、深度バッファーが新しい深度値で更新されます。深度テストが失敗した場合、フラグメントは破棄されます。

GLSL 組み込み変数gl_FragCoordには、フラグメント シェーダから直接アクセスできます。gl_FragCoord の x コンポーネントと y コンポーネントは、フラグメントの画面空間座標を表します ((0, 0) は左下隅です)。gl_FragCoord には、フラグメントの実際の深さの値を含む z コンポーネントも含まれています。Z 値は、深度バッファーの内容と比較する必要がある値です。

最新の GPU のほとんどは、高度な深度テストと呼ばれるハードウェア機能を提供します。事前に深度テストを行うと、フラグメント シェーダーの前に深度テストを実行できます。フラグメントが決して表示されない (他のオブジェクトの背後にある) ことがわかっている限り、フラグメントを早期に破棄できます。

深度テストを有効にする場合は、GL_DEPTH_TEST オプションを使用して有効にする必要があります。

glEnable(GL_DEPTH_TEST);

これが有効な場合、OpenGL は、深度テストに合格した場合はフラグメントの Z 値を深度バッファーに保存し、深度バッファーに合格しなかった場合はフラグメントを破棄します。深度バッファリングを有効にする場合は、各レンダリング反復の前に GL_DEPTH_BUFFER_BIT を使用して深度バッファーをクリアする必要もあります。そうしないと、以前のレンダリング反復で書き込まれた深度値が引き続き使用されます。

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

場合によっては、すべてのフラグメントに対して深度テストを実行し、対応するフラグメントを破棄する必要があるが、深度バッファーは更新したくない場合があります。基本的に、読み取り専用 (読み取り専用) の深度バッファーを使用します。OpenGL を使用すると、深度バッファの書き込みを無効にすることができます。深度マスク (Depth Mask) を次のように設定するだけですGL_FALSE

glDepthMask(GL_FALSE);

深度テスト機能: OpenGL がフラグメントを渡すか破棄するタイミング、および深度バッファを更新するタイミングを制御できます。

glDepthFunc(GL_LESS);
関数 説明
GL_ALWAYS 常に深度テストに合格する
GL_NEVER 深度テストに決して合格しない
GL_LESS フラグメントの深さの値がバッファの深さの値より小さい場合、テストに合格します。
GL_EQUAL フラグメントの深さの値がバッファの深さの値と等しい場合、テストに合格します。
GL_LEQUAL フラグメントの深さの値がバッファの深さの値以下であればテストに合格します。
GL_GREATER フラグメントの深さの値がバッファの深さの値より大きい場合、テストに合格します。
GL_NOTEQUAL フラグメントの深さの値がバッファの深さの値と等しくない場合、テストに合格します。
GL_GEQUAL フラグメントの深さの値がバッファの深さの値以上である場合、テストに合格します。

2. 例

深度関数を GL_ALWAYS に変更します。

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);

深度テストは常に合格するため、前に描画されたフラグメントが上にレンダリングされるべきだった場合でも、最後に描画されたフラグメントは常に前に描画されたフラグメントの上にレンダリングされます。床を最後にレンダリングするので、床がボックスの破片を覆うことになります。

GL_LESS に戻します。

#include "axbopemglwidget.h"
#include "vertices.h"
const unsigned int timeOutmSec=50;
unsigned int VAO,VBO,lightVAO;
QVector3D lightPos(1.2f, 1.0f, 2.0f);
QVector3D lightColor(1.0f, 1.0f, 1.0f);
QVector3D objectColor(1.0f, 0.5f, 0.31f);
QVector3D viewInitPos(0.0,0.0,5.0);
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()
{
    makeCurrent();
    glDeleteBuffers(1,&VBO);
    glDeleteVertexArrays(1,&VAO);
    glDeleteVertexArrays(1,&lightVAO);
    doneCurrent();
}

void AXBOpemglWidget::loadModel(string path)
{
    if(m_model !=NULL)
        delete m_model;

    m_model=NULL;
    makeCurrent();
    m_model=new Model(QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>()
                      ,path.c_str());
    m_camera.Position=cameraPosInit(m_model->m_maxY,m_model->m_minY);
    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/container2_specular.png").mirrored());

    m_CubeMesh=processMesh(cubeVertices,36,m_BoxDiffuseTex->textureId());
    m_PlaneMesh=processMesh(planeVertices,6,m_PlaneDiffuseTex->textureId());
}

void AXBOpemglWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);
    Q_UNUSED(h);
}

void AXBOpemglWidget::paintGL()
{

    QMatrix4x4 model;
    QMatrix4x4 view;
    QMatrix4x4 projection;
    // float time=m_time.elapsed()/50.0;
    projection.perspective(m_camera.Zoom,(float)width()/height(),0.1,100);
    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_CubeMesh->Draw(m_ShaderProgram);
    m_PlaneMesh->Draw(m_ShaderProgram);
    if(m_model==NULL) return;
        m_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.0;

    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::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);
}

3. 深さ値の精度

深度バッファーには 0.0 から 1.0 までの深度値が含まれており、視聴者から見えるシーン内のすべてのオブジェクトの Z 値と比較されます。

深度バッファーの値は画面空間内で線形ではありません。

奥行き値の大部分が小さな Z 値によって決定され、これにより近くのオブジェクトに優れた奥行き精度が与えられることがわかります。 

非常に近いオブジェクトの深度値は 0.0 に近い値に設定され、遠い平面に非常に近いオブジェクトの深度値は 1.0 に非常に近くなります。

4. デプスバッファの可視化

組み込みの gl_FragCoord ベクトルの z 値には、その特定のフラグメントの深さの値が含まれています。この深度値を色として出力すると、シーン内のすべてのフラグメントの深度値を表示できます。

void main() {
    FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
}

すべてが白で、すべての深度値が 1.0 で最大になっているように見えます。画面空間内の深度値は非線形です。つまり、小さな Z 値の精度が高く、Z 値が大きいほど精度が高くなります。精度が低くなります。フラグメントの深さの値は距離とともに急速に増加するため、ほとんどすべての頂点の深さの値は 1.0 に近くなります。オブジェクトに注意深く近づくと、色は徐々に暗くなり、Z 値は徐々に減少します。

画面空間の非線形深度値を線形深度値に変換する完全なフラグメント シェーダは次のとおりです。上記の方程式によると、

#version 330 core
out vec4 FragColor;

float near = 0.1; 
float far  = 100.0; 

float LinearizeDepth(float depth) 
{
    float z = depth * 2.0 - 1.0; // back to NDC 
    return (2.0 * near * far) / (far + near - z * (far - near));    
}

void main()
{             
    float depth = LinearizeDepth(gl_FragCoord.z) / far; // 为了演示除以 far
    FragColor = vec4(vec3(depth), 1.0);
}

深度の値の範囲が近い面の 0.1から、まだ私たちから非常に遠い遠方の面の 100 までであるため、色はほとんど黒です。その結果、比較的近い平面に近づくため、より低い (より暗い) 深度値が得られます。 

5. 深刻な対立を防ぐ

非常に一般的な視覚エラーは、2 つの平面または三角形が互いに平行に非常に接近して配置されているため、深度バッファーの精度が 2 つの形状のどちらが前面にあるかを判断するのに十分ではない場合に発生することがあります。その結果、2 つの形状が常に前後に切り替わり、奇妙なパターンが発生する可能性があります。この現象はディープコンフリクトと呼ばれます。

箱の底部は常に箱の底部と床の間で切り替わり、ジグザグのパターンを形成します。

  • 三角形の一部が重なるほど複数のオブジェクトを近づけて配置しないでください。
  • 近い面をできるだけ遠くに設定します
  • より高精度の深度バッファを使用します。ほとんどの深度バッファーの精度は 24 ビットですが、ほとんどのグラフィックス カードは 32 ビットの深度バッファーをサポートするようになり、精度が大幅に向上します。

おすすめ

転載: blog.csdn.net/wzz953200463/article/details/131293929