问:为什么当 alpha 为零时,sampler2d 总是返回 vec4(0.0)? [英] Q: why does sampler2d always return vec4(0.0) when alpha is zero?

查看:65
本文介绍了问:为什么当 alpha 为零时,sampler2d 总是返回 vec4(0.0)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to pass some non-texture values to a pixel shader in a texture and i'm running into a weird problem where sampler2d returns vec4(0.0) when the texture's alpha value is zero, regardless of the value of the other 3 bytes.

这不是预乘 alpha 的东西,也不是混合的东西,当 alpha 的字节值为 1 到 255 时不会发生这种情况,只是零.

this isn't a premultiplied alpha thing, or a blending thing, it doesn't happen when the alpha's byte value is 1 through 255, just zero.

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

if you run the code below you'll see a 2x2 texture in the small 2d canvas being rendered into the large 3d canvas. all 4 pixels have r,g,b = 255. and all 4 pixels have different a values. the top-left pixel of the texture has an alpha value of zero.

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

the pixel shader sets gl_FragColor.a = 1.0 always.

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

the reason i don't believe this is a premultiplied alpha thing is that if it were, then surely all 3 pixels would be different shades of grey?

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

can anyone tell me why this happens?

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>

推荐答案

@pleup 所说的.画布 2d 值总是预先乘以写入画布.这意味着在您调用 putImageData 的那一刻,您的数据乘以 alpha 并且数据丢失了.

What @pleup said. Canvas 2d values are always written into the canvas premultiplied. That means the moment you called putImageData your data was multiplied by alpha and the data was lost.

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]);

使用 255 作为颜色部分隐藏了这个问题,因为在您调用 putImageData 后二维画布中的内容是

Using 255 for color partly hides the issue since what's in the 2d canvas after you call putImageData is

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

未预乘(上传到 WebGL 时)对于除 0 以外的所有值,您都会得到 255 秒

Unpremultiplying (when uploading to WebGL) you get 255s back for all values except 0

                           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 和不同的 alpha

If the color value was say 20 and different 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

只是指出它是多么有损.

Just pointing out how lossy it is.

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]);

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

If you actually want to manually put data in a texture in WebGL you should just use a 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

If you want to read that data instead of going through a 2d canvas just call gl.readPixels

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

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);

canvas { background: red; }

<canvas></canvas>

这篇关于问:为什么当 alpha 为零时,sampler2d 总是返回 vec4(0.0)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆