问:为什么当alpha为零时,sampler2d总是返回vec4(0.0)?

时间:2018-05-28 05:58:08

标签: webgl

我正在尝试将一些非纹理值传递给纹理中的像素着色器,并且我遇到一个奇怪的问题,当纹理的alpha值为零时sampler2d返回vec4(0.0),无论其他3个字节的值如何。

这不是预乘的alpha事物,或者是混合物,当alpha的字节值为1到255时,它不会发生,只有零。

如果你运行下面的代码,你会在小的2d画布中看到2x2纹理被渲染到大型3D画布中。所有4个像素都具有r,g,b = 255.并且所有4个像素具有不同的值。纹理的左上角像素的alpha值为零。

像素着色器始终设置gl_FragColor.a = 1.0

我不相信这是一个预乘alpha的原因是,如果它是,那么肯定所有3个像素将是不同的灰色阴影?

谁能告诉我为什么会这样?

const cvs = document.getElementById("cvs"),
  {
    width: W,
    height: H
  } = cvs.getBoundingClientRect();
cvs.width = W;
cvs.height = H;


const gl = cvs.getContext("experimental-webgl", {
    premultipliedAlpha: false
  }),
  VERTEX_SHADER = `attribute vec4 a_Position;
    attribute vec2 a_TexCoord;
    varying vec2 v_TexCoord;
    void main() {
      gl_Position = a_Position;
      v_TexCoord = a_TexCoord;
    }`,
  FRAGMENT_SHADER = `precision mediump float;
    uniform sampler2D u_Sampler;
    varying vec2 v_TexCoord;
    void main() {
      gl_FragColor.rgb = texture2D(u_Sampler, v_TexCoord).rgb;
      gl_FragColor.a = 1.0;
    }`,
  vshader = gl.createShader(gl.VERTEX_SHADER),
  fshader = gl.createShader(gl.FRAGMENT_SHADER),
  program = gl.createProgram();

gl.shaderSource(vshader, VERTEX_SHADER);
gl.shaderSource(fshader, FRAGMENT_SHADER);
gl.compileShader(vshader);
gl.compileShader(fshader);
gl.attachShader(program, vshader);
gl.attachShader(program, fshader);
gl.linkProgram(program);
gl.useProgram(program);

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
const farr = new Float32Array([-1, 1, 0, 1, -1, -1, 0, 0,
  1, 1, 1, 1,
  1, -1, 1, 0
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, farr, gl.STATIC_DRAW);
const a_Position = gl.getAttribLocation(program, "a_Position"),
  a_TexCoord = gl.getAttribLocation(program, "a_TexCoord"),
  fsize = farr.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 4 * fsize, 0);
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 4 * fsize, 2 * fsize);
gl.enableVertexAttribArray(a_Position);
gl.enableVertexAttribArray(a_TexCoord);

var image = document.getElementById("img"),
  context = image.getContext("2d"),
  imageData = context.getImageData(0, 0, 2, 2),
  pixels = imageData.data;

for (var i = 0; i < pixels.length; i++) {
  pixels[i] = 255;
}
pixels[0 * 4 + 3] = 0;
pixels[1 * 4 + 3] = 1;
pixels[2 * 4 + 3] = 128;

context.putImageData(imageData, 0, 0, 0, 0, 2, 2);

const texture = gl.createTexture(),
  u_Sampler = gl.getUniformLocation(program, "u_Sampler");

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
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, gl.RGBA, gl.UNSIGNED_BYTE, image);

gl.uniform1i(u_Sampler, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
html,
body,
canvas {
  padding: 0;
  margin: 0;
}

canvas {
  border: 1px solid red;
  display: block;
  margin: 5px;
}

body {
  background: black;
  display: block;
  position: absolute;
  width: 100%;
  height: 100%;
}

#cvs {
  width: 100px;
  height: 100px;
}
<canvas id="cvs"></canvas>

<canvas id="img" width="2" height="2"></canvas>

1 个答案:

答案 0 :(得分:2)

@pleup说的是什么。 Canvas 2d值始终写入画布预乘。这意味着您调用putImageData时数据乘以alpha并且数据丢失。

&#13;
&#13;
const ctx = document.createElement("canvas").getContext("2d");
const imgData = ctx.createImageData(2, 2);
const data = imgData.data;
data[ 0] = 255; data[ 3] = 255;
data[ 4] = 255; data[ 7] = 192;
data[ 8] = 255; data[11] = 64;
data[12] = 255; data[15] = 0;

ctx.putImageData(imgData, 0, 0);

const newImgData = ctx.getImageData(0, 0, 2, 2);
const newData = newImgData.data;
console.log(newData[ 0], newData[ 3]);
console.log(newData[ 4], newData[ 7]);
console.log(newData[ 8], newData[11]);
console.log(newData[12], newData[15]);
&#13;
&#13;
&#13;

使用255作为颜色部分隐藏了问题,因为在您致电putImageData后,2d画布中的内容是

red = 255   alpha = 255
red = 192   alpha = 192
red =  64   alpha = 64
red =   0   alpha = 0

Unpremultiplying(上传到WebGL时)除了0之外的所有值都会返回255秒

                           red        alpha  result
red = 255   alpha = 255  = 255 * 255 / 255 = 255 
red = 192   alpha = 192  = 192 * 255 / 192 = 255
red =  64   alpha = 64   =  64 * 255 / 64  = 255
red =   0   alpha = 0    =   0 * 0         =   0

如果颜色值为20且不同的alphas

red = 20 *  10 / 255 = 1   alpha = 10
red = 20 *   7 / 255 = 1   alpha = 7
red = 20 *   4 / 255 = 0   alpha = 4
red = 20 *   0 / 255 = 0   alpha = 0

然后取而代之的是

 1 * 255 / 10  = 26    not even close to what we put in
 1 * 255 /  7  = 36    not even close to what we put in
 0 * 255 /  4  = 0
 0 * 0         = 0

指出它是多么有损。

&#13;
&#13;
const ctx = document.createElement("canvas").getContext("2d");
const imgData = ctx.createImageData(2, 2);
const data = imgData.data;
data[ 0] = 20; data[ 3] = 10;
data[ 4] = 20; data[ 7] = 7;
data[ 8] = 20; data[11] = 4;
data[12] = 20; data[15] = 0;

ctx.putImageData(imgData, 0, 0);

const newImgData = ctx.getImageData(0, 0, 2, 2);
const newData = newImgData.data;
console.log(newData[ 0], newData[ 3]);
console.log(newData[ 4], newData[ 7]);
console.log(newData[ 8], newData[11]);
console.log(newData[12], newData[15]);
&#13;
&#13;
&#13;

如果您真的想在WebGL中手动将数据放入纹理中,您应该只使用typedArray

const width = 2;
const height = 2;
const data = new Uint8Array(width * height * 4);

data[ 0] = 255; data[ 3] = 255;
data[ 4] = 255; data[ 7] = 192;
data[ 8] = 255; data[11] = 64;
data[12] = 255; data[15] = 0;

const level = 0;
const internalFormat = gl.RGBA;
const border = 0;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border,
              format, type, data);

如果您想要阅读该数据而不是通过2d画布,请致电gl.readPixels

const newData = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, format, type);

&#13;
&#13;
const gl = document.querySelector("canvas").getContext("webgl", {
  premultipliedAlpha: false,
});
const VERTEX_SHADER = `
  attribute vec4 a_Position;
  attribute vec2 a_TexCoord;
  varying vec2 v_TexCoord;
  void main() {
    gl_Position = a_Position;
    v_TexCoord = a_TexCoord;
  }
`;
const FRAGMENT_SHADER = `
  precision mediump float;
  uniform sampler2D u_Sampler;
  varying vec2 v_TexCoord;
  void main() {
    gl_FragColor = texture2D(u_Sampler, v_TexCoord);
  }
`;
const vshader = gl.createShader(gl.VERTEX_SHADER);
const fshader = gl.createShader(gl.FRAGMENT_SHADER);
const program = gl.createProgram();

gl.shaderSource(vshader, VERTEX_SHADER);
gl.shaderSource(fshader, FRAGMENT_SHADER);
gl.compileShader(vshader);
gl.compileShader(fshader);
gl.attachShader(program, vshader);
gl.attachShader(program, fshader);
gl.linkProgram(program);
gl.useProgram(program);

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

const farr = new Float32Array([
  -1, 1, 0, 1, 
  -1, -1, 0, 0,
  1, 1, 1, 1,
  1, -1, 1, 0
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, farr, gl.STATIC_DRAW);
const a_Position = gl.getAttribLocation(program, "a_Position");
const a_TexCoord = gl.getAttribLocation(program, "a_TexCoord");
const fsize = farr.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 4 * fsize, 0);
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 4 * fsize, 2 * fsize);
gl.enableVertexAttribArray(a_Position);
gl.enableVertexAttribArray(a_TexCoord);

const pixels = new Uint8Array(2 * 2 * 4);
pixels.fill(255);
pixels[0 * 4 + 3] = 0;
pixels[1 * 4 + 3] = 1;
pixels[2 * 4 + 3] = 128;
const texture = gl.createTexture();
const u_Sampler = gl.getUniformLocation(program, "u_Sampler");

gl.bindTexture(gl.TEXTURE_2D, texture);
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, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
&#13;
canvas { background: red; }
&#13;
<canvas></canvas>
&#13;
&#13;
&#13;