使用 GL_ANY_SAMPLES_PASSED 的遮挡查询在片段被遮挡时返回 true

Occlusion query using GL_ANY_SAMPLES_PASSED returning true when fragments are occluded

我正在为我的引擎实现镜头发光效果。

但是,仅当相关片段完全被遮挡时才尝试使用遮挡查询 returns 为真。

也许问题出在我手动写入每个顶点的 z 值,因为我使用的是对数深度缓冲区。但是,我不确定为什么这会影响遮挡测试。

以下是相关的代码片段:

public class Query implements Disposable{
    private final int id;
    private final int type;

    private boolean inUse = false;

    public Query(int type){
        this.type = type;
        int[] arr = new int[1];
        Gdx.gl30.glGenQueries(1,arr,0);
        id = arr[0];
    }

    public void start(){
        Gdx.gl30.glBeginQuery(type, id);
        inUse = true;
    }

    public void end(){
        Gdx.gl30.glEndQuery(type);
    }

    public boolean isResultReady(){
        IntBuffer result = BufferUtils.newIntBuffer(1);
        Gdx.gl30.glGetQueryObjectuiv(id,Gdx.gl30.GL_QUERY_RESULT_AVAILABLE, result);
        return result.get(0) == Gdx.gl.GL_TRUE;
    }

    public int getResult(){
        inUse = false;
        IntBuffer result = BufferUtils.newIntBuffer(1);
        Gdx.gl30.glGetQueryObjectuiv(id, Gdx.gl30.GL_QUERY_RESULT, result);
        return result.get(0);
    }

    public boolean isInUse(){
        return inUse;
    }

    @Override
    public void dispose() {
        Gdx.gl30.glDeleteQueries(1, new int[]{id},0);
    }
}

下面是我实际测试的方法:

private void doOcclusionTest(Camera cam){
        if(query.isResultReady()){
            int visibleSamples = query.getResult();
            System.out.println(visibleSamples);
        }


        temp4.set(cam.getPosition());
        temp4.sub(position);
        temp4.normalize();
        temp4.mul(getSize()*10);
        temp4.add(position);
        occlusionTestPoint.setPosition(temp4.x,temp4.y,temp4.z);


        if(!query.isInUse()) {
            query.start();
            Gdx.gl.glEnable(Gdx.gl.GL_DEPTH_TEST);
            occlusionTestPoint.render(renderer.getPointShader(), cam);
            query.end();
        }
    }

我的点顶点着色器,包括对数深度缓冲区计算:

#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 modelView;
uniform mat4 projection;
uniform float og_farPlaneDistance;
uniform float u_logarithmicDepthConstant;

vec4 modelToClipCoordinates(vec4 position, mat4 modelViewPerspectiveMatrix, float depthConstant, float farPlaneDistance){
    vec4 clip = modelViewPerspectiveMatrix * position;

    clip.z = ((2.0 * log(depthConstant * clip.z + 1.0) / log(depthConstant * farPlaneDistance + 1.0)) - 1.0) * clip.w;
    return clip;
}

void main()
{
    gl_Position = modelToClipCoordinates(vec4(aPos, 1.0), projection * modelView, u_logarithmicDepthConstant, og_farPlaneDistance);
}

点的片段着色器:

#version 330 core

uniform vec4 color;

void main() {
    gl_FragColor = color;
}

因为我只是测试单个点的遮挡,所以我知道替代方法是在渲染所有内容后简单地检查该像素的深度值。但是,我不确定如何计算 CPU.

上某个点的对数 z 值

我找到了解决问题的方法。这是一种解决方法,仅适用于单个点,不适用于整个模型,但它是这样的:

首先,您必须计算您的点的 z-value 及其所在的像素坐标。计算 z-value 应该是 straight-forward,但是在我的例子中,我使用的是对数深度缓冲区。出于这个原因,我不得不为 z-value.

做一些额外的计算

这里是我在Normalized Device Coordinate中获取坐标的方法,包括z-value(temp4f可以是任何Vector4f):

public Vector4f worldSpaceToDeviceCoords(Vector4f pos){
    temp4f.set(pos);
    Matrix4f projection = transformation.getProjectionMatrix(FOV, screenWidth,screenHeight,1f,MAXVIEWDISTANCE);
    Matrix4f view = transformation.getViewMatrix(camera);
    view.transform(temp4f); //Multiply the point vector by the view matrix
    projection.transform(temp4f); //Multiply the point vector by the projection matrix


    temp4f.x = ((temp4f.x / temp4f.w) + 1) / 2f; //Convert x coordinate to range between 0 to 1
    temp4f.y = ((temp4f.y / temp4f.w) + 1) / 2f; //Convert y coordinate to range between 0 to 1

    //Logarithmic depth buffer z-value calculation (Get rid of this if not using a logarithmic depth buffer)
    temp4f.z = ((2.0f * (float)Math.log(LOGDEPTHCONSTANT * temp4f.z + 1.0f) /
            (float)Math.log(LOGDEPTHCONSTANT * MAXVIEWDISTANCE + 1.0f)) - 1.0f) * temp4f.w;

    temp4f.z /= temp4f.w; //Perform perspective division on the z-value
    temp4f.z = (temp4f.z + 1)/2f; //Transform z coordinate into range 0 to 1

    return temp4f;
}

另一种方法是用来获取屏幕上像素的坐标(temp2是任意Vector2f):

    public Vector2f projectPoint(Vector3f position){
    temp4f.set(worldSpaceToDeviceCoords(temp4f.set(position.x,position.y,position.z, 1)));
    temp4f.x*=screenWidth;
    temp4f.y*=screenHeight;

    //If the point is not visible, return null
    if (temp4f.w < 0){
        return null;
    }


    return temp2f.set(temp4f.x,temp4f.y);

}

最后,获取给定像素处存储的深度值的方法(outBuff 是任何直接的 FloatBuffer):

public float getFramebufferDepthComponent(int x, int y){
    Gdx.gl.glReadPixels(x,y,1,1,Gdx.gl.GL_DEPTH_COMPONENT,Gdx.gl.GL_FLOAT,outBuff);
    return outBuff.get(0);
}

所以使用这些方法,你需要做的是找出某个点是否被遮挡:

  1. 检查点位于哪个像素(第二种方法)
  2. 检索该像素当前存储的 z-value(第三种方法)
  3. 得到点的计算z-value(第一种方法)
  4. 如果计算出的z-value低于存储的z-value,则该点可见

请注意,您应该在对深度缓冲区进行采样之前绘制场景中的所有内容,否则提取的深度缓冲区值将不会反映所有渲染的内容。