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>