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

我想在webgl中模拟像atari或commodore这样的旧PC低分辨率的效果,是否可以绘制图像,然后如何使像素变大?

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

我发现this有马赛克效果,但是它使用three.js,我想在没有框架的情况下进行.

最佳答案
有很多方法可以做到这一点.最简单的方法是通过将其附加到帧缓冲区来渲染为低分辨率的纹理,然后将纹理过滤设置为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>

渲染到纹理(如上)但分辨率更高的纹理,然后使用着色器,mip和/或线性过滤将其滤掉也是很常见的.好处是您将获得更多的抗锯齿

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>

转载注明原文:javascript-如何在webgl中获得像素化效果? - 代码日志