在WebGL和Canvas 2D中模拟基于调色板的图形

液化天然气

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

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

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

我已经尝试学习一些WebGL教程,但是所有教程均从对象,形状,3D立方体等开始,我不需要任何-经典的-对象来仅渲染2D上下文也可以做-希望对于相同的任务,WebGL可以是更快的解决方案!当然,如果WebGL在这方面没有成功,我将继续使用2D上下文。

需要明确的是:这是用JavaScript完成的某种计算机硬件仿真器,其输出(在与其连接的PAL电视上可以看到的输出)是通过canvas上下文呈现的。该机器具有固定的调色板,其中包含256个元素,内部只需要一个字节就可以定义一个像素的颜色。

man

您可以使用纹理作为调色板,也可以使用其他纹理作为图像。然后,您可以从图像纹理中获取值,并使用它从调色板纹理中查找颜色。

调色板纹理为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" width="256" height="256"></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" width="256" height="256"></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" width="256" height="256"></canvas>

与此瓷砖着色器示例有点相关的http://blog.tojicode.com/2012/07/sprite-tile-maps-on-gpu.html

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类常见问题

具有HEX和RGB格式的SCSS / SASS @each循环调色板

来自分类Dev

GNUPLOT:具有对数和调色板的对数颜色框

来自分类Dev

RecyclerView中的Android调色板

来自分类Dev

<canvas>: Remove the 2d context from memory without removing the webgl context from memory

来自分类Dev

<canvas>:从内存中删除2d上下文,而不从内存中删除webgl上下文

来自分类Dev

使用OpenGL着色器模拟调色板交换(在LibGDX中)

来自分类Dev

如何在单个ggplot2图中指定不同的调色板和线条大小

来自分类Dev

对象,调色板和pm3d

来自分类Dev

如何限制ggplot2中的调色板

来自分类Dev

调色板主题

来自分类Dev

从PHP中的图像生成调色板

来自分类Dev

WebGL VS Canvas 2D硬件加速

来自分类Dev

WebGL中的VBO和EBO状态

来自分类Dev

R Plot_ly:如何更改颜色栏的调色板和色阈值?

来自分类Dev

关于调色板,图形透明度和x轴密度的一些Matplotlib基本问题

来自分类Dev

APNG支持基于框架的调色板还是仅支持全局调色板?

来自分类Dev

为什么webgl canvas上下文比2d canvas使用更多的内存?

来自分类Dev

GoJS如何自上而下地在调色板和图表中排列节点

来自分类Dev

如何实现Honey comb和此图像等更高版本的调色板?

来自分类Dev

在R中设置调色板

来自分类Dev

onClick调色板

来自分类Dev

WebGL中的VBO和EBO状态

来自分类Dev

WebGL中的GL_LINES和FXAA

来自分类Dev

更改 ggplot 中的调色板

来自分类Dev

如何在 opencv (python) 中制作 Lab 和 YCrCb 调色板

来自分类Dev

JAVA:我正在尝试基于 2D 阵列和带有摆动的图形制作 Connect4 游戏

来自分类Dev

创建 vscode 主题时如何自定义命令调色板颜色和键绑定

来自分类Dev

继承 QML 中的调色板

来自分类Dev

在 Gnuplot 中回收调色板

Related 相关文章

热门标签

归档