如何在不模糊的情况下拉伸 WebGL 画布? “图像渲染”样式不起作用

Posted

技术标签:

【中文标题】如何在不模糊的情况下拉伸 WebGL 画布? “图像渲染”样式不起作用【英文标题】:How to stretch a WebGL canvas without blurring? The style "image-rendering" doesn't work 【发布时间】:2019-06-20 23:23:48 【问题描述】:

我用width=16height=16 创建了一个画布。然后我使用 WebGL 向它渲染图像。这是它的样子:

之后,我使用width: 256pxheight: 256px 缩放了画布。我还将image-rendering 设置为pixelated

      canvas 
        image-rendering: optimizeSpeed;             /* STOP SMOOTHING, GIVE ME SPEED  */
        image-rendering: -moz-crisp-edges;          /* Firefox                        */
        image-rendering: -o-crisp-edges;            /* Opera                          */
        image-rendering: -webkit-optimize-contrast; /* Chrome (and eventually Safari) */
        image-rendering: pixelated; /* Chrome */
        image-rendering: optimize-contrast;         /* CSS3 Proposed                  */
        -ms-interpolation-mode: nearest-neighbor;   /* IE8+                           */
        width: 256px;
        height: 256px;
      

这是结果:

图像模糊。为什么?我在 OSX Mojave 上使用 Safari 12.0.2。

【问题讨论】:

html5 Canvas 元素生成基于光栅的图像。它们会按其性质进行像素化(模糊)。 你是如何绘制图像的?收到代码了吗? @MattWay 我没有发布代码,因为它太大而不能作为一个最小的例子,而且在 WebGL 上做一个简单的演示太费力了。由于我们已经有了令人满意的答案,我认为没有必要这样做。但如果你愿意,我可以在这里发布。 【参考方案1】:

Safari 尚不支持 WebGL 上的 image-rendering: pixelated;。 Filed a bug

crisp-edges 也没有!= pixelatedcrisp-edges 可以是任意数量的算法。这并不意味着pixelated。这意味着应用一些算法来保持清晰的边缘,其中有很多算法。

The spec itself 显示示例:

鉴于此图像:

这是pixelated

允许浏览器对crisp-edges 使用多种算法,例如结果可能是

换句话说,您的 CSS 可能不会产生您期望的结果。如果浏览器不支持像素化但支持清晰边缘,并且如果它们使用上述算法,那么您将不会看到像素化外观。

在没有image-rendering: pixelated 的情况下绘制像素化图形的最高效方法是绘制到一个小纹理,然后使用NEAREST 过滤将该纹理绘制到画布上。

const vs = `
attribute vec4 position;
void main() 
  gl_Position = position;

`;
const fs = `
precision mediump float;
void main() 
  gl_FragColor = vec4(1, 0, 0, 1);

`;

const screenVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() 
  gl_Position = position;
  // because we know position goes from -1 to 1
  v_texcoord = position.xy * 0.5 + 0.5;

`;
const screenFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_tex;
void main() 
  gl_FragColor = texture2D(u_tex, v_texcoord);

`;

const gl = document.querySelector('canvas').getContext('webgl', antialias: false);

// compile shaders, link programs, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const screenProgramInfo = twgl.createProgramInfo(gl, [screenVS, screenFS]);


const width = 16;
const height = 16;
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);

const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);

// create buffers and put data in
const quadBufferInfo = twgl.createBufferInfoFromArrays(gl, 
  position:  
    numComponents: 2,
    data: [
      -1, -1, 
       1, -1,
      -1,  1,
      -1,  1, 
       1, -1,
       1,  1,
    ],
  
);


render();

function render() 
  // draw at 16x16 to texture
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.viewport(0, 0, width, height);
  gl.useProgram(programInfo.program);
  // bind buffers and set attributes
  twgl.setBuffersAndAttributes(gl, programInfo, quadBufferInfo);
  
  gl.drawArrays(gl.TRIANGLES, 0, 3);  // only draw the first triangle
  
  // draw texture to canvas
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.useProgram(screenProgramInfo.program);
  // bind buffers and set attributes
  twgl.setBuffersAndAttributes(gl, screenProgramInfo, quadBufferInfo);
  // uniforms default to 0 so in this simple case
  // no need to bind texture or set uniforms since
  // we only have 1 texture, it's on texture unit 0
  // and the uniform defaults to 0
  
  gl.drawArrays(gl.TRIANGLES, 0, 6);
<canvas  ></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

注意:如果您正在渲染 3D 或出于其他原因需要深度缓冲区,则需要将深度渲染缓冲区附件添加到帧缓冲区。

请注意,optimizeSpeed 也不是真正的选择。它已经被弃用很久了,就像crisp-edges 一样,由浏览器来解释。

【讨论】:

Safari 确实支持 上的 image-rendering: pixelated 和 2d 画布。 尽管crisp-edges 确实可以使用任意数量的算法(顺便说一句,没人说过),但这并不意味着它可以是任何算法。规则是“保留图像中的对比度和边缘,并且不会在处理过程中平滑颜色或给图像引入模糊”。 OP 截图中的结果不可能是任何允许的算法之一,但根据我之前的评论,这并不重要,因为 Safari 支持像素化。 crisp-edges 是故意模棱两可的。它允许算法尝试猜测来自 waifu、HQX、XBR、Super-Eagle 等图像的原始意图。这些算法不会平滑颜色或模糊。他们试图保持边缘清晰。他们的意图可以概括为“您拍摄了高分辨率矢量图像并将其缩小,现在将其放大以看起来像原始矢量图像” 无需 dv ;-) 再一次,这一切都无关紧要,应用的规则是最新支持的规则,pixelated。只保留你的解决方法,你会得到我的支持,因为它在我的惰性项目符号列表中得到了更好的解释。 你的答案是错误的。 (1) 必须编写一个像素化着色器,不知道为什么会这样复杂。 (2) 建议使用 imageSmoothingEnabled 绘制到画布上,但在 WebGL 所在的任何地方都不支持 (3) 你引用了一些不相关的东西,称为 CSS-Houdini,它可能在未来几年内仍然无法在旧浏览器上运行,并且可能永远不会发布( 4)如果它不存在(firefox)并且清晰的边缘可能不会给你想要的结果,那么把它放在最后没有帮助。浏览器可以随时更改它以获得规范中的结果。【参考方案2】:

这是一个非常古老的Webkit bug,在 Blink 分叉发生之前。 此后,Blink fixed it,Webkit 仍然没有。 您可能希望通过对尚未解决的问题发表评论来让他们知道这仍然是一个问题。

至于解决方法,有几种,但没有一个完美的。

第一个是直接以正确的大小绘制场景并自己进行像素化。 另一种方法是在 2d 画布上渲染您的 webgl 画布(您可以使用 CSS 技巧调整大小,或使用 2d 上下文 imageSmoothingEnabled 属性直接以正确的大小渲染。 CSS-Houdini 可能会让我们自己解决这个问题。

但这里真正的问题是找出您是否需要这种解决方法。我认为对这种情况进行功能测试没有任何意义(至少没有 Houdini),因此这意味着您要么必须进行丑陋的用户代理检测,要么将解决方法应用于所有人。

【讨论】:

以上是关于如何在不模糊的情况下拉伸 WebGL 画布? “图像渲染”样式不起作用的主要内容,如果未能解决你的问题,请参考以下文章

FFMPEG:如何在不拉伸的情况下移动视频?

如何在不通知用户的情况下将画布保存为 png 文件,

如何将照片变宽,两边填充原照片模糊效果,如图?

如何在不损失图像质量的情况下调整图像大小以适合小型 HTML5 画布? [复制]

如何修复 HTML5 画布中的模糊文本?

p5.j​​s:如何在不改变背景或使用外部画布的情况下在画布形状上打孔?