WebGL iOS渲染到浮点纹理

phe*_*cks 7 javascript opengl-es webgl ios

我正在尝试渲染到iOS Safari上的WebGL中的浮点纹理(不是在本机应用程序中).我已经设法让iOS手动(例如从JavaScript)创建浮点纹理,但是当我创建一个浮点类型的帧缓冲区并使用GPU渲染它时,它不起作用.

我已经将问题隔离到渲染到浮点纹理的代码,然后将其传递给另一个要显示的着色器.以下是应用于多维数据集的结果:

纹理渲染绘制绿色正方形,纹理的一半大小,然后应用于立方体的每一侧.

只要绿色方块呈现的纹理类型是标准的无符号字节类型,这在桌面和iOS WebGL上都可以正常工作.但是,将类型更改为浮点会导致iOS设备上的渲染纹理失败(同时继续在桌面浏览器上工作).纹理是空的,就好像没有渲染任何东西一样.

我在这里创建了一个示例项目来演示这个问题:https://github.com/felixpalmer/render-2-texture

使用该THREE.Renderer.precision设置更改着色器的精度没有任何区别

gma*_*man 15

据我所知,iOS设备不支持渲染浮点纹理(此时此时大多数移动设备也不支持)

我对WebGL规范的理解是

OES_texture_float:允许您创建和读取32位浮点纹理,但渲染到浮点是依赖于设备的.

OES_texture_float_linear:允许线性滤镜浮点纹理.如果这不存在OES_texture_float那么你只能gl.NEAREST用于浮点纹理.

OES_texture_half_floatOES_texture_half_float_linear除了半浮动纹理外,它们与上面相同.

在WebGL中可以渲染到浮点纹理的传统方法(假设OES_texture_float存在)是创建一个帧缓冲区,为其附加一个浮点纹理,然后调用gl.checkFramebufferStatus.如果它返回gl.FRAMEBUFFER_COMPLETE那么你可以,如果没有那么你就不能.注意:无论下一段如何,此方法都应该有效.

规范已更新,因此您还可以检查WebGL扩展以查明是否可以渲染到浮点纹理.扩展WEBGL_color_buffer_float应该告诉您可以渲染到浮点纹理.EXT_color_buffer_half_float半浮动纹理的扩展名是相同的.我知道没有浏览器实际显示这些扩展,但如果硬件支持它们,它们支持浮点渲染.

例如我在Chrome 41上的2012 Retina MBP报告

gl = document.createElement("canvas").getContext("webgl").getSupportedExtensions()
["ANGLE_instanced_arrays", 
 "EXT_blend_minmax", 
 "EXT_frag_depth",  
 "EXT_shader_texture_lod",  
 "EXT_sRGB",  
 "EXT_texture_filter_anisotropic",  
 "WEBKIT_EXT_texture_filter_anisotropic",  
 "OES_element_index_uint",  
 "OES_standard_derivatives",  
 "OES_texture_float",  
 "OES_texture_float_linear",  
 "OES_texture_half_float",  
 "OES_texture_half_float_linear",  
 "OES_vertex_array_object",  
 "WEBGL_compressed_texture_s3tc",  
 "WEBKIT_WEBGL_compressed_texture_s3tc",  
 "WEBGL_debug_renderer_info",  
 "WEBGL_debug_shaders",  
 "WEBGL_depth_texture",  
 "WEBKIT_WEBGL_depth_texture",  
 "WEBGL_lose_context",  
 "WEBKIT_WEBGL_lose_context"]
Run Code Online (Sandbox Code Playgroud)

Firefox 36报道

gl = document.createElement("canvas").getContext("webgl").getSupportedExtensions().join("\n")
"ANGLE_instanced_arrays
EXT_blend_minmax
EXT_frag_depth
EXT_sRGB
EXT_texture_filter_anisotropic
OES_element_index_uint
OES_standard_derivatives
OES_texture_float
OES_texture_float_linear
OES_texture_half_float
OES_texture_half_float_linear
OES_vertex_array_object
WEBGL_compressed_texture_s3tc
WEBGL_depth_texture
WEBGL_draw_buffers
WEBGL_lose_context
MOZ_WEBGL_lose_context
MOZ_WEBGL_compressed_texture_s3tc
MOZ_WEBGL_depth_texture"
Run Code Online (Sandbox Code Playgroud)

浏览器供应商正在忙于实现WebGL 2.0,并且鉴于该gl.checkFramebufferStatus方法的工作原理,没有时间花费时间使其他扩展字符串出现.

显然有些iOS设备支持,EXT_color_buffer_half_float所以你可以尝试创建一个半浮动纹理,将它附加到帧缓冲区并检查其状态,然后看看是否有效.

这是检查支持的示例.我在我的iPadAir2和我的iPhone5s上运行它

can make floating point textures
can linear filter floating point textures
can make half floating point textures
can linear filter floating point textures
can **NOT** render to FLOAT texture
successfully rendered to HALF_FLOAT_OES texture
Run Code Online (Sandbox Code Playgroud)

这正是我们的预期.

"use strict";

function log(msg) {
  var div = document.createElement("div");
  div.appendChild(document.createTextNode(msg));
  document.body.appendChild(div);
}                  

function glEnum(gl, v) {
  for (var key in gl) {
    if (gl[key] === v) {
      return key;
    }
  }
  return "0x" + v.toString(16);
}

window.onload = function() {
  // Get A WebGL context
  var canvas = document.getElementById("c");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }
  
  
  function getExt(name, msg) {
    var ext = gl.getExtension(name);
    log((ext ? "can " : "can **NOT** ") + msg);
    return ext;
  }
  
  var testFloat = getExt("OES_texture_float", "make floating point textures");
  getExt("OES_texture_float_linear", "linear filter floating point textures");
  var testHalfFloat = getExt("OES_texture_half_float", "make half floating point textures");
  getExt("OES_texture_half_float_linear", "linear filter half floating point textures");
  
  gl.HALF_FLOAT_OES = 0x8D61;
  
  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
  gl.useProgram(program);

  // look up where the vertex data needs to go.
  var positionLocation = gl.getAttribLocation(program, "a_position"); 
  var colorLoc = gl.getUniformLocation(program, "u_color");

  // provide texture coordinates for the rectangle.
  var positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
     -1.0, -1.0,
      1.0, -1.0,
     -1.0,  1.0,
     -1.0,  1.0,
      1.0, -1.0,
      1.0,  1.0]), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
  
  var whiteTex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, whiteTex);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, 
                new Uint8Array([255, 255, 255, 255]));
  
  function test(format) {
    var tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, format, null);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    
    var fb = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
    var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
    if (status !== gl.FRAMEBUFFER_COMPLETE) {
      log("can **NOT** render to " + glEnum(gl, format) + " texture");
      return;
    }
    
    // Draw the rectangle.
    gl.bindTexture(gl.TEXTURE_2D, whiteTex);
    gl.uniform4fv(colorLoc, [0, 10, 20, 1]);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
    
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    
    
    gl.clearColor(1, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    gl.uniform4fv(colorLoc, [0, 1/10, 1/20, 1]);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
    
    var pixel = new Uint8Array(4);
    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
    
    if (pixel[0] !== 0 ||
        pixel[1] < 248 ||
        pixel[2] < 248 ||
        pixel[3] < 254) {
      log("FAIL!!!: Was not able to actually render to " + glEnum(gl, format) + " texture");
    } else {
      log("succesfully rendered to " + glEnum(gl, format) + " texture");
    }    
  }
  if (testFloat) {
    test(gl.FLOAT);
  }
  if (testHalfFloat) {
    test(gl.HALF_FLOAT_OES);
  }
}
Run Code Online (Sandbox Code Playgroud)
canvas {
  border: 1px solid black;
}
Run Code Online (Sandbox Code Playgroud)
<script src="//webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<canvas id="c" width="16" height="16"></canvas>  
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;

void main() {
   gl_Position = a_position;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
uniform sampler2D u_texture;

void main() {
   gl_FragColor = texture2D(u_texture, vec2(0.5, 0.5)) * u_color;
}
</script>
Run Code Online (Sandbox Code Playgroud)