在 WebGL v.s. 中模拟基于调色板的图形2D画布

Posted

技术标签:

【中文标题】在 WebGL v.s. 中模拟基于调色板的图形2D画布【英文标题】:Emulating palette based graphics in WebGL v.s. Canvas 2D 【发布时间】:2013-11-10 19:37:44 【问题描述】:

目前,我正在使用 2D 画布上下文以大约 25fps 的速率从 javascript 绘制生成的图像(从像素到像素,但在生成的帧之后作为整个缓冲区刷新一次)。生成的图像始终为每个像素一个字节(整数/类型化数组),并且使用固定调色板生成 RGB 最终结果。还需要缩放以适应画布的大小(即:进入全屏)和/或根据用户请求(放大/缩小按钮)。

canvas 的 2D 上下文可以用于此目的,但是我很好奇 WebGL 是否可以提供更好的结果和/或更好的性能。请注意:我不想通过 webGL 放置像素,我想将像素放入我的缓冲区(基本上是 Uint8Array),并使用该缓冲区(一次)刷新上下文。我对 WebGL 不太了解,但是例如使用所需的生成图像作为某种纹理会以某种方式起作用?然后我想我需要以大约 25fps 的速率刷新纹理。

如果 WebGL 以某种方式支持色彩空间转换,那就太好了。对于 2D 上下文,我需要将 1 字节/像素缓冲区转换为 RGBA,用于 JavaScript 中每个像素的图像数据...缩放(用于 2D 上下文)现在通过更改画布的高度/宽度样式来完成,因此浏览器缩放图像然后。但是我想它可能比 WebGL 对硬件支持的速度慢,而且(我希望)WebGL 可以提供更大的灵活性来控制缩放,例如使用 2D 上下文,即使我不想浏览器也会做抗锯齿做(例如:整数缩放因子),也许这就是它有时会很慢的原因。

我已经尝试学习几个 WebGL 教程,但它们都从对象、形状、3D 立方体等开始,我不需要任何 - 经典 - 对象来渲染 2D 上下文也可以做的事情 - 在希望 WebGL 可以成为相同任务的更快解决方案!当然,如果 WebGL 没有成功,我会继续使用 2D 上下文。

需要明确的是:这是用 JavaScript 完成的某种计算机硬件模拟器,其输出(将在与其连接的 PAL 电视上看到的内容)通过画布上下文呈现。机器有256个元素的固定调色板,在内部它只需要一个字节来定义一个像素的颜色。

【问题讨论】:

您可以尝试一些简单的基于palette的格式(例如BMP),收集为二进制字符串,然后应用base64编码(btoa)并将图像src更新为data/url。 你可以看看 pixi.js。查看实验分支,他们在其中测试过滤器,您可能会找到接近您需要的东西。 Canvas 具有足够的性能来流式传输视频,您不需要额外的 hack。 这些教程不是从物体、形状、3d立方体等开始的。games.greggman.com/game/webgl-fundamentals @c69 确实,我有调色数据的问题,使用画布你必须覆盖来自 JS 的每个像素,这是我的性能问题,webGL 理论上可以做到这一点,而画布不能.此外:可以通过 js/html 中的技巧来放大图像(按整数缩放,不是那么花哨的东西):为画布的 css 和实际的画布大小使用不同的尺寸,但是不可能停止浏览器做我不想要的抗锯齿!所以再次出现性能问题:我必须在 JS 中扩大规模以避免问题:非常慢。我猜 WebGL 也可以做到这一点。 【参考方案1】:

您可以使用纹理作为调色板,使用不同的纹理作为图像。然后,您从图像纹理中获取一个值,并使用它从调色板纹理中查找颜色。

调色板纹理为 256x1 RGBA 像素。您的图像纹理是您想要的任何大小,但只是一个单通道 ALPHA 纹理。然后您可以从图像中查找值

    float index = texture2D(u_image, v_texcoord).a * 255.0;

并使用该值在调色板中查找颜色

    gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));

你的着色器可能是这样的

顶点着色器

attribute vec4 a_position;
varying vec2 v_texcoord;
void main() 
  gl_Position = a_position;

  // assuming a unit quad for position we
  // can just use that for texcoords. Flip Y though so we get the top at 0
  v_texcoord = a_position.xy * vec2(0.5, -0.5) + 0.5;
    

片段着色器

precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_image;
uniform sampler2D u_palette;

void main() 
    float index = texture2D(u_image, v_texcoord).a * 255.0;
    gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));

那么你只需要一个调色板纹理。

 // Setup a palette.
 var palette = new Uint8Array(256 * 4);

 // I'm lazy so just setting 4 colors in palette
 function setPalette(index, r, g, b, a) 
     palette[index * 4 + 0] = r;
     palette[index * 4 + 1] = g;
     palette[index * 4 + 2] = b;
     palette[index * 4 + 3] = a;
 
 setPalette(1, 255, 0, 0, 255); // red
 setPalette(2, 0, 255, 0, 255); // green
 setPalette(3, 0, 0, 255, 255); // blue

 // upload palette
 ...
 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, 
               gl.UNSIGNED_BYTE, palette);

还有你的形象。这是一张只有 alpha 的图像,所以只有 1 个通道。

 // Make image. Just going to make something 8x8
 var image = new Uint8Array([
     0,0,1,1,1,1,0,0,
     0,1,0,0,0,0,1,0,
     1,0,0,0,0,0,0,1,
     1,0,2,0,0,2,0,1,
     1,0,0,0,0,0,0,1,
     1,0,3,3,3,3,0,1,
     0,1,0,0,0,0,1,0,
     0,0,1,1,1,1,0,0,
 ]);

 // upload image
 ....
 gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, 8, 8, 0, gl.ALPHA, 
               gl.UNSIGNED_BYTE, image);

您还需要确保两个纹理都使用gl.NEAREST 进行过滤,因为一个代表索引,另一个代表调色板,在这些情况下,在值之间进行过滤是没有意义的。

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

这是一个工作示例:

var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");

// Note: createProgramFromScripts will call bindAttribLocation
// based on the index of the attibute names we pass to it.
var program = twgl.createProgramFromScripts(
    gl, 
    ["vshader", "fshader"], 
    ["a_position", "a_textureIndex"]);
gl.useProgram(program);
var imageLoc = gl.getUniformLocation(program, "u_image");
var paletteLoc = gl.getUniformLocation(program, "u_palette");
// tell it to use texture units 0 and 1 for the image and palette
gl.uniform1i(imageLoc, 0);
gl.uniform1i(paletteLoc, 1);

// Setup a unit quad
var positions = [
      1,  1,  
     -1,  1,  
     -1, -1,  
      1,  1,  
     -1, -1,  
      1, -1,  
];
var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);

// Setup a palette.
var palette = new Uint8Array(256 * 4);

// I'm lazy so just setting 4 colors in palette
function setPalette(index, r, g, b, a) 
    palette[index * 4 + 0] = r;
    palette[index * 4 + 1] = g;
    palette[index * 4 + 2] = b;
    palette[index * 4 + 3] = a;

setPalette(1, 255, 0, 0, 255); // red
setPalette(2, 0, 255, 0, 255); // green
setPalette(3, 0, 0, 255, 255); // blue

// make palette texture and upload palette
gl.activeTexture(gl.TEXTURE1);
var paletteTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, paletteTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, palette);

// Make image. Just going to make something 8x8
var image = new Uint8Array([
    0,0,1,1,1,1,0,0,
    0,1,0,0,0,0,1,0,
    1,0,0,0,0,0,0,1,
    1,0,2,0,0,2,0,1,
    1,0,0,0,0,0,0,1,
    1,0,3,3,3,3,0,1,
    0,1,0,0,0,0,1,0,
    0,0,1,1,1,1,0,0,
]);
    
// make image textures and upload image
gl.activeTexture(gl.TEXTURE0);
var imageTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, imageTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, 8, 8, 0, gl.ALPHA, gl.UNSIGNED_BYTE, image);
    
gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2);
canvas  border: 1px solid black; 
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vshader" type="whatever">
    attribute vec4 a_position;
    varying vec2 v_texcoord;
    void main() 
      gl_Position = a_position;
    
      // assuming a unit quad for position we
      // can just use that for texcoords. Flip Y though so we get the top at 0
      v_texcoord = a_position.xy * vec2(0.5, -0.5) + 0.5;
        
</script>
<script id="fshader" type="whatever">
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_image;
uniform sampler2D u_palette;
    
void main() 
    float index = texture2D(u_image, v_texcoord).a * 255.0;
    gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));

</script>
<canvas id="c"  ></canvas>

要制作动画,只需更新图像,然后将其重新上传到纹理中

 gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, 8, 8, 0, gl.ALPHA, 
               gl.UNSIGNED_BYTE, image);

例子:

var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");

// Note: createProgramFromScripts will call bindAttribLocation
// based on the index of the attibute names we pass to it.
var program = twgl.createProgramFromScripts(
    gl, 
    ["vshader", "fshader"], 
    ["a_position", "a_textureIndex"]);
gl.useProgram(program);
var imageLoc = gl.getUniformLocation(program, "u_image");
var paletteLoc = gl.getUniformLocation(program, "u_palette");
// tell it to use texture units 0 and 1 for the image and palette
gl.uniform1i(imageLoc, 0);
gl.uniform1i(paletteLoc, 1);

// Setup a unit quad
var positions = [
      1,  1,  
     -1,  1,  
     -1, -1,  
      1,  1,  
     -1, -1,  
      1, -1,  
];
var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);

// Setup a palette.
var palette = new Uint8Array(256 * 4);

// I'm lazy so just setting 4 colors in palette
function setPalette(index, r, g, b, a) 
    palette[index * 4 + 0] = r;
    palette[index * 4 + 1] = g;
    palette[index * 4 + 2] = b;
    palette[index * 4 + 3] = a;

setPalette(1, 255, 0, 0, 255); // red
setPalette(2, 0, 255, 0, 255); // green
setPalette(3, 0, 0, 255, 255); // blue

// make palette texture and upload palette
gl.activeTexture(gl.TEXTURE1);
var paletteTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, paletteTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, palette);

// Make image. Just going to make something 8x8
var width = 8;
var height = 8;
var image = new Uint8Array([
    0,0,1,1,1,1,0,0,
    0,1,0,0,0,0,1,0,
    1,0,0,0,0,0,0,1,
    1,0,2,0,0,2,0,1,
    1,0,0,0,0,0,0,1,
    1,0,3,3,3,3,0,1,
    0,1,0,0,0,0,1,0,
    0,0,1,1,1,1,0,0,
]);
    
// make image textures and upload image
gl.activeTexture(gl.TEXTURE0);
var imageTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, imageTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, width, height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, image);
    
var frameCounter = 0;
function render()   
  ++frameCounter;
    
  // skip 3 of 4 frames so the animation is not too fast
  if ((frameCounter & 3) == 0) 
    // rotate the image left
    for (var y = 0; y < height; ++y) 
      var temp = image[y * width];
      for (var x = 0; x < width - 1; ++x) 
        image[y * width + x] = image[y * width + x + 1];
      
      image[y * width + width - 1] = temp;
    
    // re-upload image
    gl.activeTexture(gl.TEXTURE0);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, width, height, 0, gl.ALPHA,
                  gl.UNSIGNED_BYTE, image);
    
    gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2);
  
  requestAnimationFrame(render);

render();
canvas  border: 1px solid black; 
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vshader" type="whatever">
    attribute vec4 a_position;
    varying vec2 v_texcoord;
    void main() 
      gl_Position = a_position;
    
      // assuming a unit quad for position we
      // can just use that for texcoords. Flip Y though so we get the top at 0
      v_texcoord = a_position.xy * vec2(0.5, -0.5) + 0.5;
        
</script>
<script id="fshader" type="whatever">
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_image;
uniform sampler2D u_palette;
    
void main() 
    float index = texture2D(u_image, v_texcoord).a * 255.0;
    gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));

</script>
<canvas id="c"  ></canvas>

当然,假设您的目标是通过操作像素在 CPU 上制作动画。否则,您可以使用任何普通的 webgl 技术来操作纹理坐标或其他任何东西。

您也可以为调色板动画更新调色板。只需修改调色板并重新上传即可

 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, 
               gl.UNSIGNED_BYTE, palette);

例子:

var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");

// Note: createProgramFromScripts will call bindAttribLocation
// based on the index of the attibute names we pass to it.
var program = twgl.createProgramFromScripts(
    gl, 
    ["vshader", "fshader"], 
    ["a_position", "a_textureIndex"]);
gl.useProgram(program);
var imageLoc = gl.getUniformLocation(program, "u_image");
var paletteLoc = gl.getUniformLocation(program, "u_palette");
// tell it to use texture units 0 and 1 for the image and palette
gl.uniform1i(imageLoc, 0);
gl.uniform1i(paletteLoc, 1);

// Setup a unit quad
var positions = [
      1,  1,  
     -1,  1,  
     -1, -1,  
      1,  1,  
     -1, -1,  
      1, -1,  
];
var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);

// Setup a palette.
var palette = new Uint8Array(256 * 4);

// I'm lazy so just setting 4 colors in palette
function setPalette(index, r, g, b, a) 
    palette[index * 4 + 0] = r;
    palette[index * 4 + 1] = g;
    palette[index * 4 + 2] = b;
    palette[index * 4 + 3] = a;

setPalette(1, 255, 0, 0, 255); // red
setPalette(2, 0, 255, 0, 255); // green
setPalette(3, 0, 0, 255, 255); // blue

// make palette texture and upload palette
gl.activeTexture(gl.TEXTURE1);
var paletteTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, paletteTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, palette);

// Make image. Just going to make something 8x8
var width = 8;
var height = 8;
var image = new Uint8Array([
    0,0,1,1,1,1,0,0,
    0,1,0,0,0,0,1,0,
    1,0,0,0,0,0,0,1,
    1,0,2,0,0,2,0,1,
    1,0,0,0,0,0,0,1,
    1,0,3,3,3,3,0,1,
    0,1,0,0,0,0,1,0,
    0,0,1,1,1,1,0,0,
]);
    
// make image textures and upload image
gl.activeTexture(gl.TEXTURE0);
var imageTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, imageTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, width, height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, image);
    
var frameCounter = 0;
function render()   
  ++frameCounter;
    
  // skip 3 of 4 frames so the animation is not too fast
  if ((frameCounter & 3) == 0) 
    // rotate the 3 palette colors
    var tempR = palette[4 + 0];
    var tempG = palette[4 + 1];
    var tempB = palette[4 + 2];
    var tempA = palette[4 + 3];
    setPalette(1, palette[2 * 4 + 0], palette[2 * 4 + 1], palette[2 * 4 + 2], palette[2 * 4 + 3]);
    setPalette(2, palette[3 * 4 + 0], palette[3 * 4 + 1], palette[3 * 4 + 2], palette[3 * 4 + 3]);
    setPalette(3, tempR, tempG, tempB, tempA);

    // re-upload palette
    gl.activeTexture(gl.TEXTURE1);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA,
                  gl.UNSIGNED_BYTE, palette);
    
    gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2);
  
  requestAnimationFrame(render);

render();
canvas  border: 1px solid black; 
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vshader" type="whatever">
    attribute vec4 a_position;
    varying vec2 v_texcoord;
    void main() 
      gl_Position = a_position;
    
      // assuming a unit quad for position we
      // can just use that for texcoords. Flip Y though so we get the top at 0
      v_texcoord = a_position.xy * vec2(0.5, -0.5) + 0.5;
        
</script>
<script id="fshader" type="whatever">
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_image;
uniform sampler2D u_palette;
    
void main() 
    float index = texture2D(u_image, v_texcoord).a * 255.0;
    gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));

</script>
<canvas id="c"  ></canvas>

稍微相关的是这个瓷砖着色器示例 http://blog.tojicode.com/2012/07/sprite-tile-maps-on-gpu.html

【讨论】:

另外,加起来,imgData[idx++] = window.colorBufView[position+2]; // red imgData[idx++] = window.colorBufView[position+1]; // green imgData[idx++] = window.colorBufView[position]; // blue imgData[idx++] = 255; // alpha的canvas的逻辑可以写在fragment shader中吗(见下文注释) 'void main(void) ','float index = texture2D(u_image, v_texcoord).a;', 'gl_FragColor.r = texture2D(u_palette, vec2(index , 0.5)).r ;', 'gl_FragColor.g = texture2D(u_palette, vec2(index + 0.001, 0.5)).g;', 'gl_FragColor.b = texture2D(u_palette, vec2(index + 0.002, 0.5)).b;', ' gl_FragColor.a = 1.0;', '' 在原始示例中,u_palette 已经指向 RGBA 纹理,因此一个 texture2D 命令会提取 RGBA。无需单独将它们拉出。您的 1.0, 2.0, 3.0 作为 texcoord 也无济于事。在钳位或重复时,它们是相同的坐标,因为纹理坐标仅从 0.0 变为 1.0。 PS:我更新了上面的片段着色器。 See this post as to why。其他想法,您需要确保所有纹理都使用 gl.NEAREST 进行过滤,因为它们是调色板的索引,而过滤会破坏这一点。 好吧,稍微调整一下颜色表后,我得到了预期的结果。谢谢。但是,如果我将第二个参数的值保持为 0.0,它会正确渲染图像,更改它会更改颜色。'gl_FragColor = texture2D(u_palette, vec2(index, 0.0));', 恭喜!是的,如果纹理是 1 行高,那么第二个值可以是任何值。可以说,对纹素的中心进行采样比对边缘进行采样更正确。请参阅链接到第一个值(不是第二个)的情况的帖子【参考方案2】:

大概您正在构建一个大小约为 512 x 512(PAL 大小)的 javascript 数组...

WebGL 片段着色器绝对可以很好地完成您的调色板转换。食谱会是这样的:

    设置 WebGL 的“几何”只有两个跨越视口的三角形。 (GL 都是三角形。)这是最大的麻烦,如果你还不是 GL 流利的。但这并不是那么糟糕。与http://learningwebgl.com/blog/?page_id=1217 共度美好时光。但这将是大约 100 行的东西。入场费。 将内存中的帧缓冲区扩大 4 倍。 (我认为纹理总是必须是 RGBA?)并用您的像素值填充每四个字节的 R 分量。使用 new Float32Array 来分配它。您可以使用值 0-255,或将其划分为 0.0 到 1.0。我们将把它作为纹理传递给 webgl。这个每帧都会改变。 构建第二个 256 x 1 像素的纹理,这是您的调色板查找表。这个永远不会改变(除非调色板可以修改?)。 在您的片段着色器中,使用您的模拟帧缓冲区纹理作为对调色板的查找。调色板中的第一个像素在像素的中间位置 (0.5/256.0, 0.5) 访问。 在每一帧上,重新提交模拟的帧缓冲区纹理并重绘。将像素推送到 GPU 成本很高……但按照现代标准,PAL 大小的图像非常小。 奖励步骤:您可以增强片段着色器以在现代高分辨率显示器上模仿扫描线、隔行扫描视频或其他可爱的仿真伪影(荧光点?),而这一切都不需要您的 javascript!

这只是一个草图。但它会起作用。 WebGL 是一个非常低级的 API,而且非常灵活,但值得付出努力(如果你喜欢那种东西,我会这样做。:-))。

同样,http://learningwebgl.com/blog/?page_id=1217 被推荐用于整体 WebGL 指导。

【讨论】:

"2. 将内存中的帧缓冲区扩大 4 倍。"实际上,片段着色器可以通过 4 个颜色分量 r、g、b 和 a 进行索引,因此您可以更紧密地打包纹理……但我会在第一遍中使用基本的。 :) 您可以制作一个通道纹理。 gl.ALPHAgl.LUMINANCE。至于在 256x1 纹理中查找,如果您将纹理过滤设置为 gl.NEAREST,那么您应该能够直接使用类似的东西进行查找。 float index = texture2D(u_pixels, v_texcoord).r; gl_FragColor = texture2D(u_palette, vec2(index, 0.5));

以上是关于在 WebGL v.s. 中模拟基于调色板的图形2D画布的主要内容,如果未能解决你的问题,请参考以下文章

带你走进WebGL的随机美学

计算机图形学基于WebGL的纹理贴图

Game boy模拟器:图形

Webgl/Threejs 学习及实践总结

交互式计算机图形学(基于webGL)资源使用

计算机图形学之WebGL绘图入门