javascript - 如何在webgl中获得像素化效果?

标签 javascript html5-canvas glsl webgl

我想在 webgl 中模拟低分辨率旧电脑(如 atari 或 commodore)的效果,有没有办法绘制图像,然后如何使像素变大?

我是 webgl 的新手,我应该如何开始制作这种效果?

我找到了 this有马赛克效果,但它使用 three.js,我想在没有框架的情况下完成。

最佳答案

有很多方法可以做到这一点。最简单的方法是通过附加一个帧缓冲区来渲染低分辨率纹理,然后将该纹理渲染到 Canvas ,并将纹理过滤设置为 NEAREST

这是一个示例。它正在使用 TWGL这不是一个框架,只是一个让 WebGL 不那么冗长的助手。如果您想将其翻译成冗长的原始 webgl,请参阅评论(和 docs)。

如果您是 webgl 的新手 I'd suggest starting here

const vs = `
attribute vec4 position;
uniform mat4 u_matrix;

void main() {
   gl_Position = u_matrix * position;
}
`;
const fs = `
void main() {
  gl_FragColor = vec4(0, 0, 0, 1); // black
}
`;

const vs2 = `
attribute vec4 position;
attribute vec2 texcoord;

uniform mat4 u_matrix;

varying vec2 v_texcoord;

void main() {
   gl_Position = u_matrix * position;
   v_texcoord = texcoord;
}
`;
const fs2 = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
void main() {
  gl_FragColor = texture2D(u_texture, v_texcoord);
}
`;

"use strict";
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
// compiles shaders, links program, looks up locations
const cubeProgramInfo = twgl.createProgramInfo(gl, [vs, fs]);
const texProgramInfo = twgl.createProgramInfo(gl, [vs2, fs2]);

const cubeArrays = {
  position: [
    1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
  indices:  [
    0, 1, 1, 2, 2, 3, 3, 0, 
    4, 5, 5, 6, 6, 7, 7, 4, 
    8, 9, 9, 10, 10, 11, 11, 8,
    12, 13, 13, 14, 14, 15, 15, 12,
  ],
};
const quadArrays = {
  position: {
    numComponents: 2,
    data: [
      0, 0,
      1, 0,
      0, 1,
      0, 1,
      1, 0,
      1, 1,
    ],
  },
  texcoord: [
    0, 0,
    1, 0,
    0, 1,
    0, 1,
    1, 0,
    1, 1,
  ],
};
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const cubeBufferInfo = twgl.createBufferInfoFromArrays(gl, cubeArrays);
const quadBufferInfo = twgl.createBufferInfoFromArrays(gl, quadArrays);

const fbWidth = 32;
const fbHeight = 32;
// make a 32x32 pixel texture
const cubeTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, cubeTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, fbWidth, fbHeight, 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);

// create a depth renderbuffer
const depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, fbWidth, fbHeight);

// create a framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);

// attach the texture and depth buffer to the framebuffer
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, cubeTexture, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);

function render(time) {
  time *= 0.001;
  twgl.resizeCanvasToDisplaySize(gl.canvas);

  // draw cube

  // this makes WebGL render to the texture and depthBuffer
  // all draw calls will render there instead of the canvas
  // until we bind something else.
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.viewport(0, 0, fbWidth, fbHeight);
  {
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
    const fov = 30 * Math.PI / 180;
    const aspect = fbWidth / fbHeight;
    const zNear = 0.5;
    const zFar = 40;
    const projection = m4.perspective(fov, aspect, zNear, zFar);
    const eye = [1, 4, -7];
    const target = [0, 0, 0];
    const up = [0, 1, 0];

    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);
    const viewProjection = m4.multiply(projection, view);
    const world = m4.rotationY(time);

    gl.useProgram(cubeProgramInfo.program);
    
    // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    twgl.setBuffersAndAttributes(gl, cubeProgramInfo, cubeBufferInfo);
    // calls gl.uniformXXX
    twgl.setUniforms(cubeProgramInfo, {
      u_matrix: m4.multiply(viewProjection, world),
    });
    // calls gl.drawArrays or gl.drawElements
    twgl.drawBufferInfo(gl, cubeBufferInfo, gl.LINES);
  }
  
  // this make WebGL render to the canvas
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  {
     const displayWidth = gl.canvas.clientWidth;
     const displayHeight = gl.canvas.clientHeight;
     const drawHeight = displayHeight;
     const drawWidth = fbWidth * drawHeight / fbHeight;
     const m = m4.ortho(0, gl.canvas.clientWidth, 0, gl.canvas.clientHeight, -1, 1);
     m4.translate(m, [
       (displayWidth - drawWidth) / 2, 
       (displayHeight - drawHeight) / 2,
       0], m);
     m4.scale(m, [drawWidth, drawHeight, 1], m);
     
    gl.useProgram(texProgramInfo.program);
    // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    twgl.setBuffersAndAttributes(gl, texProgramInfo, quadBufferInfo);
    // calls gl.uniformXXX, gl.activeTexture, gl.bindTexture
    twgl.setUniforms(texProgramInfo, {
      u_matrix: m,
      u_texture: cubeTexture,
    });
    // calls gl.drawArrays or gl.drawElements
    twgl.drawBufferInfo(gl, quadBufferInfo);
  }

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>

渲染到纹理(如上)但分辨率更高的纹理,然后使用着色器、mips 和/或线性过滤对其进行过滤也很常见。好处是你会得到更多的抗锯齿

const vs = `
attribute vec4 position;
uniform mat4 u_matrix;

void main() {
   gl_Position = u_matrix * position;
}
`;
const fs = `
void main() {
  gl_FragColor = vec4(0, 0, 0, 1); // black
}
`;

const vs2 = `
attribute vec4 position;
attribute vec2 texcoord;

uniform mat4 u_matrix;

varying vec2 v_texcoord;

void main() {
   gl_Position = u_matrix * position;
   v_texcoord = texcoord;
}
`;
const fs2 = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
void main() {
  gl_FragColor = texture2D(u_texture, v_texcoord);
}
`;

"use strict";
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
// compiles shaders, links program, looks up locations
const cubeProgramInfo = twgl.createProgramInfo(gl, [vs, fs]);
const texProgramInfo = twgl.createProgramInfo(gl, [vs2, fs2]);

const cubeArrays = {
  position: [
    1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
  indices:  [
    0, 1, 1, 2, 2, 3, 3, 0, 
    4, 5, 5, 6, 6, 7, 7, 4, 
    8, 9, 9, 10, 10, 11, 11, 8,
    12, 13, 13, 14, 14, 15, 15, 12,
  ],
};
const quadArrays = {
  position: {
    numComponents: 2,
    data: [
      0, 0,
      1, 0,
      0, 1,
      0, 1,
      1, 0,
      1, 1,
    ],
  },
  texcoord: [
    0, 0,
    1, 0,
    0, 1,
    0, 1,
    1, 0,
    1, 1,
  ],
};
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const cubeBufferInfo = twgl.createBufferInfoFromArrays(gl, cubeArrays);
const quadBufferInfo = twgl.createBufferInfoFromArrays(gl, quadArrays);

// using mips only works if we make texture power of 2 (in WebGL1)
// WebGL2 doesn't have that limit
const fbWidth = 128;
const fbHeight = 128;
// calls gl.createTexture, gl.bindTexture, gl.texImage2D, gl.texParameteri
// calls gl.createRenderbuffer, gl.bindRenderbuffer, gl.renderbufferStorage
// calls gl.createFramebuffer, gl.bindFramebuffer, gl.framebufferTexture2D, gl.framebufferRenderbuffer
const fbInfo = twgl.createFramebufferInfo(gl, [
  { format: gl.RGBA, min: gl.LINEAR_MIPMAP_LINEAR, wrap: gl.CLAMP_TO_EDGE, },
  { format: gl.DEPTH_STENCIL, },
], fbWidth, fbHeight);

// extract the created texture
const cubeTexture = fbInfo.attachments[0];


const lowResFBWidth = 32;
const lowResFBHeight = 32;
const lowResFBInfo = twgl.createFramebufferInfo(gl, [
  { format: gl.RGBA, mag: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },
], lowResFBWidth, lowResFBHeight);

// get the texture what was just created.
const lowResTexture = lowResFBInfo.attachments[0];

function render(time) {
  time *= 0.001;
  twgl.resizeCanvasToDisplaySize(gl.canvas);

  // draw cube to the texture
  
  // calls gl.bindFramebuffer, gl.viewport
  twgl.bindFramebufferInfo(gl, fbInfo);
  {
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
    const fov = 30 * Math.PI / 180;
    const aspect = fbWidth / fbHeight;
    const zNear = 0.5;
    const zFar = 40;
    const projection = m4.perspective(fov, aspect, zNear, zFar);
    const eye = [1, 4, -7];
    const target = [0, 0, 0];
    const up = [0, 1, 0];

    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);
    const viewProjection = m4.multiply(projection, view);
    const world = m4.rotationY(time);

    gl.useProgram(cubeProgramInfo.program);
    
    // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    twgl.setBuffersAndAttributes(gl, cubeProgramInfo, cubeBufferInfo);
    // calls gl.uniformXXX
    twgl.setUniforms(cubeProgramInfo, {
      u_matrix: m4.multiply(viewProjection, world),
    });
    // calls gl.drawArrays or gl.drawElements
    twgl.drawBufferInfo(gl, cubeBufferInfo, gl.LINES);
  }
  
  // first generate mips
  gl.bindTexture(gl.TEXTURE_2D, cubeTexture);
  gl.generateMipmap(gl.TEXTURE_2D);
  
  // draw the texture to the lowResTexture.

  // calls gl.bindFramebuffer, gl.viewport 
  twgl.bindFramebufferInfo(gl, lowResFBInfo);
  drawTexture(gl, cubeTexture, fbWidth, fbHeight, lowResFBWidth, lowResFBHeight);
  
  // draw the low-res texture to the canvas
  
  // calls gl.bindFramebuffer, gl.viewport 
  twgl.bindFramebufferInfo(gl, null);
  drawTexture(gl, lowResTexture, lowResFBWidth, lowResFBHeight, gl.canvas.clientWidth, gl.canvas.clientHeight);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

function drawTexture(gl, texture, srcWidth, srcHeight, dstWidth, dstHeight) {
   const drawHeight = dstHeight;
   const drawWidth = srcWidth * drawHeight / srcHeight;
   const m = m4.ortho(0, dstWidth, 0, dstHeight, -1, 1);
   m4.translate(m, [
     (dstWidth - drawWidth) / 2, 
     (dstHeight - drawHeight) / 2,
     0], m);
   m4.scale(m, [drawWidth, drawHeight, 1], m);

  gl.useProgram(texProgramInfo.program);
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, texProgramInfo, quadBufferInfo);
  // calls gl.uniformXXX, gl.activeTexture, gl.bindTexture
  twgl.setUniforms(texProgramInfo, {
    u_matrix: m,
    u_texture: texture,
  });
  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, quadBufferInfo);
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>


更新

在 2020 年,您可以做的最简单的事情可能就是制作您想要的分辨率的 Canvas ,例如 32x32 并将其 CSS 大小设置得更大,然后使用 image-rendering: pixelated CSS设置告诉浏览器在缩放图像时不要平滑它

<canvas 
    width="32"
    height="32"
    style="
        width: 128px;
        height: 128px;
        image-rendering: crisp-edges; /* for firefox */
        image-rendering: pixelated;   /* for everything else */
    "></canvas>

const vs = `
attribute vec4 position;
uniform mat4 u_matrix;

void main() {
   gl_Position = u_matrix * position;
}
`;
const fs = `
void main() {
  gl_FragColor = vec4(0, 0, 0, 1); // black
}
`;

const vs2 = `
attribute vec4 position;
attribute vec2 texcoord;

uniform mat4 u_matrix;

varying vec2 v_texcoord;

void main() {
   gl_Position = u_matrix * position;
   v_texcoord = texcoord;
}
`;
const fs2 = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
void main() {
  gl_FragColor = texture2D(u_texture, v_texcoord);
}
`;

"use strict";
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {
  antialias: false,
});
// compiles shaders, links program, looks up locations
const cubeProgramInfo = twgl.createProgramInfo(gl, [vs, fs]);
const texProgramInfo = twgl.createProgramInfo(gl, [vs2, fs2]);

const cubeArrays = {
  position: [
    1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
  indices:  [
    0, 1, 1, 2, 2, 3, 3, 0, 
    4, 5, 5, 6, 6, 7, 7, 4, 
    8, 9, 9, 10, 10, 11, 11, 8,
    12, 13, 13, 14, 14, 15, 15, 12,
  ],
};
const quadArrays = {
  position: {
    numComponents: 2,
    data: [
      0, 0,
      1, 0,
      0, 1,
      0, 1,
      1, 0,
      1, 1,
    ],
  },
};
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const cubeBufferInfo = twgl.createBufferInfoFromArrays(gl, cubeArrays);

function render(time) {
  time *= 0.001;

  // draw cube
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  {    
    const fov = 30 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const zNear = 0.5;
    const zFar = 40;
    const projection = m4.perspective(fov, aspect, zNear, zFar);
    const eye = [1, 4, -7];
    const target = [0, 0, 0];
    const up = [0, 1, 0];

    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);
    const viewProjection = m4.multiply(projection, view);
    const world = m4.rotationY(time);

    gl.useProgram(cubeProgramInfo.program);
    
    // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    twgl.setBuffersAndAttributes(gl, cubeProgramInfo, cubeBufferInfo);
    // calls gl.uniformXXX
    twgl.setUniforms(cubeProgramInfo, {
      u_matrix: m4.multiply(viewProjection, world),
    });
    // calls gl.drawArrays or gl.drawElements
    twgl.drawBufferInfo(gl, cubeBufferInfo, gl.LINES);
  }
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas 
    width="32"
    height="32"
    style="
        width: 128px;
        height: 128px;
        image-rendering: crisp-edges; /* for firefox */
        image-rendering: pixelated;   /* for everything else */
    "></canvas>

关于javascript - 如何在webgl中获得像素化效果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43878959/

相关文章:

javascript - 为什么 jCanvas 绘制错误的像素?

javascript - 如何查看给定 JS 对象的所有属性?

javascript - 将 Canvas 与 Div 一起保存

javascript - 使用 AJAX 删除项目而不刷新页面

javascript - 带有两个 anchor 的线 kineticjs

c++ - FreeType - Texture Atlas - 为什么我的文本呈现为四边形?

opengl - OpenCL 中等效的 GLSL 统一变量

javascript - 为什么这个算法着色器有不好的锯齿现象

javascript - 有人可以提供一个使用 jsTestDriver.js 的断言异常的非常简单的完整示例吗

javascript - Bootstrap Modal 关闭时专注于输入