OpenGL ES записывает данные глубины в цвет

Я пытаюсь реализовать функциональность, подобную DepthBuffer, используя OpenGL ES на Android.

Другими словами, я пытаюсь получить трехмерную точку на поверхности, которая отображается в точке [x, y] на пользовательском устройстве. Чтобы сделать это, мне нужно иметь возможность прочитать расстояние фрагмента в данной точке.

Ответьте при разных обстоятельствах:

При использовании обычного OpenGL вы можете достичь этого, создав FrameBuffer а затем прикрепите либо RenderBuffer или же Texture с компонентом глубины к нему. Оба эти подхода используют glReadPixels с внутренним форматом GL_DEPTH_COMPONENT извлечь данные из буфера / текстуры. К сожалению, OpenGL ES поддерживает только GL_ALPHA, GL_RGB, а также GL_RGBA Что касается форматов чтения, то на самом деле нет никакого способа напрямую получить данные о глубине фреймбуфера.

Единственный жизнеспособный подход, о котором я могу подумать (и который я нашел предложенным в Интернете), - это создавать разные шейдеры только для буферизации глубины. Шейдер, который используется только для глубокой визуализации, должен записать gl_FragCoord.z значение (= значение расстояния, которое мы хотим прочитать.) на gl_FragColor, Тем не мение:

Актуальный вопрос:

Когда я пишу gl_FragCoord.z значение на gl_FragColor = new Vec4(vec3(gl_FragCoord.z), 1.0); и позже использовать glReadPixels для считывания значений rgb эти считанные значения не совпадают с входными данными.

Что я пробовал:

Я понимаю, что есть только 24 бита (r, g, b * 8 бит каждый), представляющих данные глубины, поэтому я попытался сместить возвращаемое значение на 8 - чтобы получить 32 бита, но, похоже, это не сработало. Я также пытался сместить расстояние, применяя его к красному, зеленому и синему, но это не сработало, как ожидалось. Я пытался выяснить, что не так, наблюдая биты, результаты внизу.

фрагмент Shader.glsl(кандидат № 3):

void main() {

highp float distance = 1.0; //currently just 1.0 to test the results with different values.
lowp float red = distance / exp2(16.0);
lowp float green = distance / exp2(8.0);
lowp float blue = distance / exp2(0.0);
gl_FragColor = vec4(red, green, blue, 1.0);

}

Способ чтения значений (=glReadPixels)

private float getDepth(int x, int y){
    FloatBuffer buffer = GeneralSettings.getFloatBuffer(1); //just creates FloatBuffer with capacity of 1 float value.
    terrainDepthBuffer.bindFrameBuffer(); //bind the framebuffer before read back.
    GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, buffer); //read the values from previously bind framebuffer.
    GeneralSettings.checkGlError("glReadPixels"); //Make sure there is no gl related errors.
    terrainDepthBuffer.unbindCurrentFrameBuffer(); //Remember to unbind the buffer after reading/writing.
    System.out.println(buffer.get(0)); //Print the value.
}

Наблюдения в битах, используя шейдер и метод выше:

Value   |           Shader input            | ReadPixels output
1.0f    |  111111100000000000000000000000   | 111111110000000100000000
0.0f    |               0                   | 0
0.5f    |   111111000000000000000000000000  | 100000000000000100000000

0 ответов

Значение с плавающей точкой в ​​rangle [0.0, 1.0] может быть упаковано в vec3 и распаковал из vec3 (см. Как упаковать один 32-битный int в 4, 8-битный ints в glsl / webgl? и Как мне конвертировать между float и vec4, vec3, vec2?):

vec3 PackDepth( in float depth )
{
    float depthVal = depth * (256.0*256.0*256.0 - 1.0) / (256.0*256.0*256.0);
    vec4 encode = fract( depthVal * vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
    return encode.xyz - encode.yzw / 256.0 + 1.0/512.0;
}

float UnpackDepth( in vec3 pack )
{
    float depth = dot( pack, 1.0 / vec3(1.0, 256.0, 256.0*256.0) );
    return depth * (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);
}

Обратите внимание, в общем, глубина (gl_FragCoord.z / gl_FragDepth) рассчитывается следующим образом (см. GLSL gl_FragCoord.z ​​Расчет и настройка gl_FragDepth):

float ndc_depth = clip_space_pos.z / clip_space_pos.w;
float depth = (((farZ-nearZ) * ndc_depth) + nearZ + farZ) / 2.0;

Смотрите следующий пример WebGL для PackDepth:

var ShaderProgram = {};
ShaderProgram.Create = function( shaderList, uniformNames ) {
    var shaderObjs = [];
    for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
        var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
        if ( shderObj == 0 )
            return 0;
        shaderObjs.push( shderObj );
    }
    var progObj = this.LinkProgram( shaderObjs )
    if ( progObj != 0 ) {
        progObj.unifomLocation = {};
        for ( var i_n = 0; i_n < uniformNames.length; ++ i_n ) {
            var name = uniformNames[i_n];
            progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
        }
    }
    return progObj;
}
ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); } 
ShaderProgram.SetUniformInt = function( progObj, name, val ) { gl.uniform1i( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniformFloat = function( progObj, name, val ) { gl.uniform1f( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniform2f = function( progObj, name, arr ) { gl.uniform2fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniform3f = function( progObj, name, arr ) { gl.uniform3fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformMat44 = function( progObj, name, mat ) { gl.uniformMatrix4fv( progObj.unifomLocation[name], false, mat ); }
ShaderProgram.CompileShader = function( source, shaderStage ) {
    var shaderScript = document.getElementById(source);
    if (shaderScript) {
      source = "";
      var node = shaderScript.firstChild;
      while (node) {
        if (node.nodeType == 3) source += node.textContent;
        node = node.nextSibling;
      }
    }
    var shaderObj = gl.createShader( shaderStage );
    gl.shaderSource( shaderObj, source );
    gl.compileShader( shaderObj );
    var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
    if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
    return status ? shaderObj : 0;
} 
ShaderProgram.LinkProgram = function( shaderObjs ) {
    var prog = gl.createProgram();
    for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
        gl.attachShader( prog, shaderObjs[i_sh] );
    gl.linkProgram( prog );
    status = gl.getProgramParameter( prog, gl.LINK_STATUS );
    if ( !status ) alert("Could not initialise shaders");
    gl.useProgram( null );
    return status ? prog : 0;
}
        
function drawScene(){

    var canvas = document.getElementById( "ogl-canvas" );
    var vp = [canvas.width, canvas.height];
    var depthValue = document.getElementById( "depth" ).value / 1000.0;
    document.getElementById( "depth_val" ).innerHTML = depthValue;

    gl.viewport( 0, 0, canvas.width, canvas.height );
    gl.enable( gl.DEPTH_TEST );
    gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
    gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
    ShaderProgram.Use( progDraw );
    ShaderProgram.SetUniformFloat( progDraw, "u_depth", depthValue )
    gl.enableVertexAttribArray( progDraw.inPos );
    gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
    gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 ); 
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
    gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
    gl.disableVertexAttribArray( progDraw.pos );

    var w = vp[0];
    var h = vp[1];
    var readPixels = new Uint8Array( w * h * 4);
    gl.readPixels( 0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, readPixels );
    
    // flip Y
    var pixels = new Uint8Array( w * h * 4);
    for ( var i_x = 0; i_x < 4 * w; ++ i_x ) {
        for ( var i_y = 0; i_y < h; ++ i_y ) {
        var i_src = i_y * w * 4 + i_x;
        var i_dest = (h - (i_y+1)) * w * 4 + i_x;
        pixels[i_dest] = readPixels[i_src];
        }
    }

    document.getElementById( "red_val" ).innerHTML = pixels[0];
    document.getElementById( "green_val" ).innerHTML = pixels[1];
    document.getElementById( "blue_val" ).innerHTML = pixels[2];
}  

var gl;
var prog;
var bufObj = {};
function sceneStart() {

    var canvas = document.getElementById( "ogl-canvas");
    gl = canvas.getContext( "experimental-webgl" );
    if ( !gl )
      return;

    progDraw = ShaderProgram.Create( 
      [ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
        { source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
      ],
      [ "u_depth" ] );
    progDraw.inPos = gl.getAttribLocation( progDraw, "inPos" );
    if ( prog == 0 )
        return;

    var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
    var inx = [ 0, 1, 2, 0, 2, 3 ];
    bufObj.pos = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
    bufObj.inx = gl.createBuffer();
    bufObj.inx.len = inx.length;
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );

    setInterval(drawScene, 50);
}
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;

attribute vec2 inPos;

varying vec2 vertPos;

void main()
{
    vertPos = inPos;
    gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>

<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;

varying vec2 vertPos;

uniform float u_depth;

vec3 PackDepth( in float depth )
{
    float depthVal = depth * (256.0*256.0*256.0 - 1.0) / (256.0*256.0*256.0);
    vec4 encode = fract( depthVal * vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
    return encode.xyz - encode.yzw / 256.0 + 1.0/512.0;
}

float UnpackDepth( in vec3 pack )
{
  float depth = dot( pack, 1.0 / vec3(1.0, 256.0, 256.0*256.0) );
  return depth * (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);
}

void main()
{
    vec3  color = PackDepth( u_depth );
    float depth = UnpackDepth( color );
    gl_FragColor = vec4( mix( color, vec3(depth), step(vertPos.y, 0.0) ), 1.0 );
}
</script>

<body onload="sceneStart();">
    <div style="margin-left: 260px;">
        <div style="float: right; width: 100%; background-color: #CCF;">
            <form name="inputs">
                <table>
                    <tr> <input type="range" style="width:100%" id="depth" min="0" max="1000" value="500"/> </tr>
                    <tr> <td> depth </td ><td><span id="depth_val">0</span> </td> </tr>
                    <tr> <td> red </td ><td><span id="red_val">0</span> </td> </tr>
                    <tr> <td> green </td ><td><span id="green_val">0</span> </td> </tr>
                    <tr> <td> blue </td ><td><span id="blue_val">0</span> </td> </tr>
                 </table>
            </form>
        </div>
        <div style="float: right; width: 260px; margin-left: -260px;">
            <canvas id="ogl-canvas" style="border: none;" width="256" height="256"></canvas>
        </div>
        <div style="clear: both;"></div>
    </div>
</body>

Другие вопросы по тегам