如何使用webgl顶点和片段着色器向图像添加插值

时间:2017-10-25 11:33:56

标签: javascript webgl

大家好,                 我正在尝试使用webgl着色器渲染图像,并且我已经成功地使用webgl样本完成了该操作,但问题是当我增加图像的大小时图像的质量不好。我想使用顶点和片段着色器对图像进行放大和插值。Here is my sample



"use strict";

function main() {
  var image = new Image();
  requestCORSIfNotSameOrigin(image, "https://upload.wikimedia.org/wikipedia/commons/5/57/Pneumothorax_CT.jpg")
  image.src = "https://upload.wikimedia.org/wikipedia/commons/5/57/Pneumothorax_CT.jpg";
  image.width = 1000;
  image.height = 1000;
  image.onload = function() {
    render(image);
  }
}

function render(image) {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  var canvas = document.getElementById("canvas");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }

  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);

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

  // Create a buffer to put three 2d clip space points in
  var positionBuffer = gl.createBuffer();

  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  // Set a rectangle the same size as the image.
  setRectangle(gl, 0, 0, image.width, image.height);

  // provide texture coordinates for the rectangle.
  var texcoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    0.0, 0.0,
    1.0, 0.0,
    0.0, 1.0,
    0.0, 1.0,
    1.0, 0.0,
    1.0, 1.0,
  ]), gl.STATIC_DRAW);

  // Create a texture.
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set the parameters so we can render any size image.
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  // Upload the image into the texture.
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

  // lookup uniforms
  var resolutionLocation = gl.getUniformLocation(program, "u_resolution");

  webglUtils.resizeCanvasToDisplaySize(gl.canvas);

  // Tell WebGL how to convert from clip space to pixels
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  // Clear the canvas
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // Tell it to use our program (pair of shaders)
  gl.useProgram(program);

  // Turn on the position attribute
  gl.enableVertexAttribArray(positionLocation);

  // Bind the position buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  var size = 2; // 2 components per iteration
  var type = gl.FLOAT; // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    positionLocation, size, type, normalize, stride, offset)

  // Turn on the teccord attribute
  gl.enableVertexAttribArray(texcoordLocation);

  // Bind the position buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);

  // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  var size = 2; // 2 components per iteration
  var type = gl.FLOAT; // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    texcoordLocation, size, type, normalize, stride, offset)

  // set the resolution
  gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);

  // Draw the rectangle.
  var primitiveType = gl.TRIANGLES;
  var offset = 0;
  var count = 6;
  gl.drawArrays(primitiveType, offset, count);
}

function setRectangle(gl, x, y, width, height) {
  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    x1, y1,
    x2, y1,
    x1, y2,
    x1, y2,
    x2, y1,
    x2, y2,
  ]), gl.STATIC_DRAW);
}

main();


// This is needed if the images are not on the same domain
// NOTE: The server providing the images must give CORS permissions
// in order to be able to use the image with WebGL. Most sites
// do NOT give permission.
// See: http://webglfundamentals.org/webgl/lessons/webgl-cors-permission.html
function requestCORSIfNotSameOrigin(img, url) {
  if ((new URL(url)).origin !== window.location.origin) {
    img.crossOrigin = "";
  }
}

@import url("https://webglfundamentals.org/webgl/resources/webgl-tutorials.css");
body {
  margin: 0;
}

canvas {
  width: 100vw;
  height: 100vh;
  display: block;
}

<div style="height:700px; width:700px; overflow:scroll;">
  <canvas id="canvas"></canvas>
</div>


<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
  attribute vec2 a_position; 
  attribute vec2 a_texCoord; 
  uniform vec2 u_resolution; 
  varying vec2 v_texCoord; void main() { 
    // convert the rectangle from pixels to 0.0 to 1.0 
    vec2 zeroToOne = a_position / u_resolution; 
    // convert from 0->1 to 0->2 
    vec2 zeroToTwo = zeroToOne * 2.0; 
    // convert from 0->2 to -1->+1 (clipspace) 
    vec2 clipSpace = zeroToTwo - 1.0; 
    gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); 
    // pass the texCoord to the fragment shader 
    // The GPU will interpolate this value between points.
    v_texCoord = a_texCoord; 
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
 
// our texture
uniform sampler2D u_image;
 
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
 
void main() {
   // Look up a color from the texture.
   gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
&#13;
&#13;
&#13;

我需要在图像缩放时进行插值,或者如Check This sample下面提供的AMI例如最大高度设置

2 个答案:

答案 0 :(得分:1)

这里已经提供了非常丰富的信息和良好的答案和评论。

另外请注意,您使用高质量图像提供的链接具有高分辨率和高质量,至少没有压缩伪像。

以NIFTI数据格式解压~21mb。

使用ami.js来重写它: https://github.com/FNNDSC/ami

使用具有适合您屏幕分辨率的良好图像分辨率的gman示例,应该会给您一个下降结果。

是的,他们是一些算法来修复坏的图像质量和处理图像压缩工件,但(并且我不想重复这里的评论)一般来说,一旦信息丢失,它就消失了。

答案 1 :(得分:0)

目前尚不清楚你想要发生什么。

首先,您将gl.NEAREST设置为过滤。 WebGL有几种过滤covered here。将它们设置为gl.LINEAR会更好,但仅限于此 一点点

问题是WebGL 1.0不支持mips用于不具有2维功能的图像(2,4,8,16,32,128,256,512,1024等等)。 That page描述了mips用于(插值)但mips只能用于2维幂的图像。您尝试显示的图像不是2维的力,它是954×687。

您有几种不同的选择。

  1. 在照片编辑应用程序中下载图像,在两个维度中编辑为2的幂。然后调用gl.generateMipmap生成用于插值的mips,如that page

  2. 中所述
  3. 将图像复制到尺寸为2的画布,然后将画布上传为纹理

  4. 创建一个纹理,它是2的下一个最大幂,然后上传你的图像

    function nearestGreaterOrEqualPowerOf2(v) {
      return Math.pow(2, Math.ceil(Math.log2(v)));
    }
    
    const newWidth = nearestGreaterOrEqualPowerOf2(image.width);
    const newHeight = nearestGreaterOrEqualPowerOf2(image.height);
    
    // first make an empty texture of the new size
    const level = 0;
    const format = gl.RGBA;
    const type = gl.UNSIGNED_BYTE;
    const border = 0;
    gl.texImage2D(gl.TEXTURE_2D, level, format, newWidth, newHeight, border,
                    format, type, null);
    
    // then upload the image into the bottom left corner of the texture
    const xoffset = 0;
    const yoffset = 0;
    gl.texSubImage2D(gl.TEXTURE_2D, level, xoffset, yoffset, format, type, image);
    
    // now because the texture is a power of 2 in both dimensions you can
    // generate mips and turn on maximum filtering
    
    gl.generateMipmap(gl.TEXTURE_2D);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    
  5. 虽然在所有这些情况下你都有一个新问题,即图像现在只是使用纹理的一部分。您必须使用a texture matrix或直接调整纹理坐标来调整纹理坐标。

      // compute needed texture coordinates to show only portion of texture
      var u = newWidth / image.width;
      var v = newHeight / image.height;
    
      // provide texture coordinates for the rectangle.
      var texcoordBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        0, 0,
        u, 0,
        0, v,
        0, v,
        u, 0,
        u, v,
      ]), gl.STATIC_DRAW);
    

    "use strict";
    
    function main() {
      var image = new Image();
      requestCORSIfNotSameOrigin(image, "https://upload.wikimedia.org/wikipedia/commons/5/57/Pneumothorax_CT.jpg")
      image.src = "https://upload.wikimedia.org/wikipedia/commons/5/57/Pneumothorax_CT.jpg";
      image.onload = function() {
        render(image);
      }
    }
    
    function render(image) {
      // Get A WebGL context
      /** @type {HTMLCanvasElement} */
      var canvas = document.getElementById("canvas");
      var gl = canvas.getContext("webgl");
      if (!gl) {
        return;
      }
    
      // setup GLSL program
      var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
    
      // look up where the vertex data needs to go.
      var positionLocation = gl.getAttribLocation(program, "a_position");
      var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");
    
      function nearestGreaterOrEqualPowerOf2(v) {
        return Math.pow(2, Math.ceil(Math.log2(v)));
      }
    
      const newWidth = nearestGreaterOrEqualPowerOf2(image.width);
      const newHeight = nearestGreaterOrEqualPowerOf2(image.height);
    
      // Create a buffer to put three 2d clip space points in
      var positionBuffer = gl.createBuffer();
    
      // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
      // Set a rectangle fit in the canvas at the same aspect as the image.
      const drawWidth = canvas.clientWidth;
      const drawHeight = canvas.clientWidth / drawWidth * image.height;
      setRectangle(gl, 0, 0, drawWidth, drawHeight);
    
      // compute needed texture coordinates to show only portion of texture
      var u = newWidth / image.width;
      var v = newHeight / image.height;
    
      // provide texture coordinates for the rectangle.
      var texcoordBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        0, 0,
        u, 0,
        0, v,
        0, v,
        u, 0,
        u, v,
      ]), gl.STATIC_DRAW);
    
      // Create a texture.
      var texture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, texture);
    
      // first make an empty texture of the new size
      {
        const level = 0;
        const format = gl.RGBA;
        const type = gl.UNSIGNED_BYTE;
        const border = 0;
        gl.texImage2D(gl.TEXTURE_2D, level, format, newWidth, newHeight, border,
                      format, type, null);
    
        // then upload the image into the bottom left corner of the texture
        const xoffset = 0;
        const yoffset = 0;
        gl.texSubImage2D(gl.TEXTURE_2D, level, xoffset, yoffset, format, type, image);
      }
      
      // now because the texture is a power of 2 in both dimensions you can
      // generate mips and turn on maximum filtering
    
      gl.generateMipmap(gl.TEXTURE_2D);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    
      // lookup uniforms
      var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
    
      webglUtils.resizeCanvasToDisplaySize(gl.canvas);
    
      // Tell WebGL how to convert from clip space to pixels
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    
      // Clear the canvas
      gl.clearColor(0, 0, 0, 0);
      gl.clear(gl.COLOR_BUFFER_BIT);
    
      // Tell it to use our program (pair of shaders)
      gl.useProgram(program);
    
      // Turn on the position attribute
      gl.enableVertexAttribArray(positionLocation);
    
      // Bind the position buffer.
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    
      // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
      var size = 2; // 2 components per iteration
      var type = gl.FLOAT; // the data is 32bit floats
      var normalize = false; // don't normalize the data
      var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
      var offset = 0; // start at the beginning of the buffer
      gl.vertexAttribPointer(
        positionLocation, size, type, normalize, stride, offset)
    
      // Turn on the teccord attribute
      gl.enableVertexAttribArray(texcoordLocation);
    
      // Bind the position buffer.
      gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
    
      // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
      var size = 2; // 2 components per iteration
      var type = gl.FLOAT; // the data is 32bit floats
      var normalize = false; // don't normalize the data
      var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
      var offset = 0; // start at the beginning of the buffer
      gl.vertexAttribPointer(
        texcoordLocation, size, type, normalize, stride, offset)
    
      // set the resolution
      gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
    
      // Draw the rectangle.
      var primitiveType = gl.TRIANGLES;
      var offset = 0;
      var count = 6;
      gl.drawArrays(primitiveType, offset, count);
    }
    
    function setRectangle(gl, x, y, width, height) {
      var x1 = x;
      var x2 = x + width;
      var y1 = y;
      var y2 = y + height;
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        x1, y1,
        x2, y1,
        x1, y2,
        x1, y2,
        x2, y1,
        x2, y2,
      ]), gl.STATIC_DRAW);
    }
    
    main();
    
    
    // This is needed if the images are not on the same domain
    // NOTE: The server providing the images must give CORS permissions
    // in order to be able to use the image with WebGL. Most sites
    // do NOT give permission.
    // See: http://webglfundamentals.org/webgl/lessons/webgl-cors-permission.html
    function requestCORSIfNotSameOrigin(img, url) {
      if ((new URL(url)).origin !== window.location.origin) {
        img.crossOrigin = "";
      }
    }
    @import url("https://webglfundamentals.org/webgl/resources/webgl-tutorials.css");
    body {
      margin: 0;
    }
    
    canvas {
      width: 100vw;
      height: 100vh;
      display: block;
    }
    <div style="height:700px; width:700px; overflow:scroll;">
      <canvas id="canvas"></canvas>
    </div>
    
    
    <!-- vertex shader -->
    <script id="2d-vertex-shader" type="x-shader/x-vertex">
      attribute vec2 a_position; 
      attribute vec2 a_texCoord; 
      uniform vec2 u_resolution; 
      varying vec2 v_texCoord; void main() { 
        // convert the rectangle from pixels to 0.0 to 1.0 
        vec2 zeroToOne = a_position / u_resolution; 
        // convert from 0->1 to 0->2 
        vec2 zeroToTwo = zeroToOne * 2.0; 
        // convert from 0->2 to -1->+1 (clipspace) 
        vec2 clipSpace = zeroToTwo - 1.0; 
        gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); 
        // pass the texCoord to the fragment shader 
        // The GPU will interpolate this value between points.
        v_texCoord = a_texCoord; 
    }
    </script>
    <!-- fragment shader -->
    <script id="2d-fragment-shader" type="x-shader/x-fragment">
    precision mediump float;
     
    // our texture
    uniform sampler2D u_image;
     
    // the texCoords passed in from the vertex shader.
    varying vec2 v_texCoord;
     
    void main() {
       // Look up a color from the texture.
       gl_FragColor = texture2D(u_image, v_texCoord);
    }
    </script>
    <script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>