如何解决 webgl 上下文中 alpha 混合模式的跨浏览器问题?
How to fix crossbrowser issue with alpha blendmode in webgl context?
我尝试使用 gl.blendFuncSeparate 使片段着色器的背景透明。
这在 Windows (Chrome/FF/Edge) 上工作正常,但在 MacOS 上它在 Firefox 中仅 运行s。 Chrome Mac 和 Safari 绘制整个视口透明。
class Render {
constructor() {
this.pos = [];
this.program = [];
this.buffer = [];
this.ut = [];
this.resolution = [];
this.frame = 0;
this.start = Date.now();
this.options = {
alpha: true,
premultipliedAlpha: true,
preserveDrawingBuffer: false
};
this.canvas = document.querySelector('canvas');
this.gl = this.canvas.getContext('webgl', this.options);
this.width = this.canvas.width;
this.height = this.canvas.height;
this.gl.viewport(0, 0, this.width, this.height);
this.gl.enable(this.gl.BLEND);
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
this.clearCanvas();
window.addEventListener('resize', this.resetCanvas, true);
this.init();
}
init = () => {
let vertexSource = document.querySelector('#vertexShader').textContent;
let fragmentSource = document.querySelector('#fragmentShader').textContent;
this.createGraphics(vertexSource, fragmentSource, 0);
this.canvas.addEventListener('mousemove', (e) => {
this.mouseX = e.pageX / this.canvas.width;
this.mouseY = e.pageY / this.canvas.height;
}, false);
this.renderLoop();
};
resetCanvas = () => {
this.width = 300; //this.shaderCanvas.width;
this.height = 300; // this.shaderCanvas.height;
this.gl.viewport(0, 0, this.width, this.height);
this.clearCanvas();
};
createShader = (type, source) => {
let shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
let success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
if (!success) {
console.log(this.gl.getShaderInfoLog(shader));
this.gl.deleteShader(shader);
return false;
}
return shader;
};
createProgram = (vertexSource, fragmentSource) => {
// Setup Vertext/Fragment Shader functions //
this.vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
this.fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
// Setup Program and Attach Shader functions //
let program = this.gl.createProgram();
this.gl.attachShader(program, this.vertexShader);
this.gl.attachShader(program, this.fragmentShader);
this.gl.linkProgram(program);
this.gl.useProgram(program);
return program;
};
createGraphics = (vertexSource, fragmentSource, i) => {
// Create the Program //
this.program[i] = this.createProgram(vertexSource, fragmentSource);
// Create and Bind buffer //
this.buffer[i] = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer[i]);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]),
this.gl.STATIC_DRAW
);
this.pos[i] = this.gl.getAttribLocation(this.program[i], 'pos');
this.gl.vertexAttribPointer(
this.pos[i],
2, // size: 2 components per iteration
this.gl.FLOAT, // type: the data is 32bit floats
false, // normalize: don't normalize the data
0, // stride: 0 = move forward size * sizeof(type) each iteration to get the next position
0 // start at the beginning of the buffer
);
this.gl.enableVertexAttribArray(this.pos[i]);
this.importProgram(i);
};
clearCanvas = () => {
this.gl.clearColor(1,1,1,1);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// Turn off rendering to alpha
this.gl.colorMask(true, true, true, false);
};
updateUniforms = (i) => {
this.importUniforms(i);
this.gl.drawArrays(
this.gl.TRIANGLE_FAN, // primitiveType
0, // Offset
4 // Count
);
};
importProgram = (i) => {
this.ut[i] = this.gl.getUniformLocation(this.program[i], 'time');
this.resolution[i] = new Float32Array([300, 300]);
this.gl.uniform2fv(
this.gl.getUniformLocation(this.program[i],'resolution'),
this.resolution[i]
);
};
importUniforms = (i) => {
this.gl.uniform1f(this.ut[i], (Date.now() - this.start) / 1000);
};
renderLoop = () => {
this.frame++;
this.updateUniforms(0);
this.animation = window.requestAnimationFrame(this.renderLoop);
};
}
let demo = new Render(document.body);
body {
background: #333;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
width: 200px;
height: 200px;
background: transparent;
}
<canvas width="300" height="300"></canvas>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 pos;
void main() {
gl_Position=vec4(pos, .5);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform float time;
uniform vec2 resolution;
mat2 rotate2d(float angle){
return mat2(cos(angle),-sin(angle),
sin(angle),cos(angle));
}
float variation(vec2 v1, vec2 v2, float strength, float speed) {
return sin(
dot(normalize(v1), normalize(v2)) * strength + time * speed
) / 100.0;
}
vec4 paintCircle (vec2 uv, vec2 center, float rad, float width) {
vec2 diff = center-uv;
float len = length(diff);
len += variation(diff, vec2(0.0, 1.0), 3.0, 2.0);
len -= variation(diff, vec2(1.0, 0.0), 3.0, 2.0);
float circle = 1. -smoothstep(rad-width, rad, len);
return vec4(circle);
}
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec4 color;
float radius = 0.15;
vec2 center = vec2(0.5);
color = paintCircle(uv, center, radius, .2);
vec2 v = rotate2d(time) * uv;
color *= vec4(255,255, 0,255);
gl_FragColor = color;
}
</script>
上面的代码片段在 MacOS Chrome 上不起作用,但 运行 在 Windows Chrome 上成功。您应该会看到一个流动的黄色圆圈。目标是只看到 HTML 背景上的动画人物 (#333)。 canvas 是透明的。
我已经尝试过不同的混合功能,但没有任何组合可以跨浏览器工作。
this.options = {
alpha: true,
premultipliedAlpha: true,
preserveDrawingBuffer: false
};
this.gl.enable(this.gl.BLEND);
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
clearCanvas = () => {
this.gl.clearColor(1,1,1,1);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// Turn off rendering to alpha
this.gl.colorMask(true, true, true, false);
};
我注意到它在 Firefox 上运行良好,但在 Chrome 上却不行。改变一件事后,它的工作原理如我所见。
代码笔:
https://codepen.io/anon/pen/ZNKZqo
我改自:
preserveDrawingBuffer: false
到
preserveDrawingBuffer: true
我不知道你期望发生什么,你显然已经编辑了你的代码笔,使你的问题完全无关紧要,因为我们无法再检查问题。下次请使用snippet
对于preserveDrawingBuffer: false
,每帧都会清除canvas。在 clearCanvas
中,您将 alpha 清除为 1,然后关闭对 alpha 的渲染,但是因为 preserveDrawingBuffer
为 false(默认值),绘图缓冲区被清除,这意味着 alpha 现在回到零。之后,您将 0,0,0 或 1,1,0 渲染到其中。当 premultipliedAlpha
为真(默认值)时,1,1,0,0 是无效颜色。为什么?因为 premultiplied
意味着您在 canvas 中输入的颜色已乘以 alpha。 alpha 是 0。0 乘以任何东西都是零,所以当 alpha 为零时,红色、绿色和蓝色也必须为零。
这就是您在不同浏览器上看到不同颜色的原因。当您的颜色无效时,结果是不确定的。
将 preserveDrawingBuffer
设置为 true 并不能解决您的问题。这只是意味着您将 alpha 设置为 1,然后将其保留为 1,因为您关闭了对 alpha 的渲染,因此整个 canvas 是不透明的。
对于您似乎想要的内容,正确的解决方法是根本不清除(让 preserverDrawingBuffer: false,为您清除)并且不要使用 gl.colorMask
关闭对 alpha 的渲染然后在你的着色器中,在你想看到背景的地方写 0 到 alpha,在你不想看到背景的地方写 1
const vertexSource = `
attribute vec3 pos;
void main() {
gl_Position=vec4(pos, .5);
}
`;
const fragmentSource = `
precision mediump float;
uniform float time;
uniform vec2 resolution;
mat2 rotate2d(float angle){
return mat2(cos(angle),-sin(angle),
sin(angle),cos(angle));
}
float variation(vec2 v1, vec2 v2, float strength, float speed) {
return sin(
dot(normalize(v1), normalize(v2)) * strength + time * speed
) / 100.0;
}
// vec3 paintCircle (vec2 uv, vec2 center, float rad, float width) {
vec4 paintCircle (vec2 uv, vec2 center, float rad, float width) {
vec2 diff = center-uv;
float len = length(diff);
len += variation(diff, vec2(0.0, 1.0), 3.0, 2.0);
len -= variation(diff, vec2(1.0, 0.0), 3.0, 2.0);
float circle = 1. -smoothstep(rad-width, rad, len);
// return vec3(circle);
return vec4(circle);
}
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
// vec3 color;
vec4 color;
float radius = 0.15;
vec2 center = vec2(0.5);
color = paintCircle(uv, center, radius, .2);
vec2 v = rotate2d(time) * uv;
//color *= vec3(v.x, v.y, 0.7-v.y*v.x);
// color *= vec3(255,255, 0);
color *= vec4(255,255, 0,255);
//color += paintCircle(uv, center, radius, 0.01);
// gl_FragColor = vec4(color, 1.0);
gl_FragColor = color;
}
`;
class Render {
constructor() {
this.pos = [];
this.program = [];
this.buffer = [];
this.ut = [];
this.resolution = [];
this.frame = 0;
this.start = Date.now();
this.options = {
// these are already the defaults
// alpha: true,
// premultipliedAlpha: true,
// preserveDrawingBuffer: false
};
this.canvas = document.querySelector('canvas');
this.gl = this.canvas.getContext('webgl', this.options);
this.width = this.canvas.width;
this.height = this.canvas.height;
this.gl.viewport(0, 0, this.width, this.height);
// this.gl.enable(this.gl.BLEND);
// this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
//this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
//this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
// this.clearCanvas();
window.addEventListener('resize', this.resetCanvas, true);
this.init();
}
init = () => {
this.createGraphics(vertexSource, fragmentSource, 0);
this.renderLoop();
};
resetCanvas = () => {
this.width = 300; //this.shaderCanvas.width;
this.height = 300; // this.shaderCanvas.height;
this.gl.viewport(0, 0, this.width, this.height);
this.clearCanvas();
};
createShader = (type, source) => {
let shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
let success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
if (!success) {
console.log(this.gl.getShaderInfoLog(shader));
this.gl.deleteShader(shader);
return false;
}
return shader;
};
createProgram = (vertexSource, fragmentSource) => {
// Setup Vertext/Fragment Shader functions //
this.vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
this.fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
// Setup Program and Attach Shader functions //
let program = this.gl.createProgram();
this.gl.attachShader(program, this.vertexShader);
this.gl.attachShader(program, this.fragmentShader);
this.gl.linkProgram(program);
this.gl.useProgram(program);
return program;
};
createGraphics = (vertexSource, fragmentSource, i) => {
// Create the Program //
this.program[i] = this.createProgram(vertexSource, fragmentSource);
// Create and Bind buffer //
this.buffer[i] = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer[i]);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]),
this.gl.STATIC_DRAW
);
this.pos[i] = this.gl.getAttribLocation(this.program[i], 'pos');
this.gl.vertexAttribPointer(
this.pos[i],
2, // size: 2 components per iteration
this.gl.FLOAT, // type: the data is 32bit floats
false, // normalize: don't normalize the data
0, // stride: 0 = move forward size * sizeof(type) each iteration to get the next position
0 // start at the beginning of the buffer
);
this.gl.enableVertexAttribArray(this.pos[i]);
this.importProgram(i);
};
updateUniforms = (i) => {
this.importUniforms(i);
this.gl.drawArrays(
this.gl.TRIANGLE_FAN, // primitiveType
0, // Offset
4 // Count
);
};
importProgram = (i) => {
this.ut[i] = this.gl.getUniformLocation(this.program[i], 'time');
this.resolution[i] = new Float32Array([300, 300]);
this.gl.uniform2fv(
this.gl.getUniformLocation(this.program[i],'resolution'),
this.resolution[i]
);
};
importUniforms = (i) => {
this.gl.uniform1f(this.ut[i], (Date.now() - this.start) / 1000);
};
renderLoop = () => {
this.frame++;
this.updateUniforms(0);
this.animation = window.requestAnimationFrame(this.renderLoop);
};
}
let demo = new Render(document.body);
body {
background-color: red;
background-image: linear-gradient(45deg, blue 25%, transparent 25%, transparent 75%, blue 75%, blue),
linear-gradient(-45deg, blue 25%, transparent 25%, transparent 75%, blue 75%, blue);
background-size: 30px 30px;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
width: 300px;
height: 300px;
background: transparent;
}
<canvas width="300" height="300"></canvas>
请注意,我将背景设置为一个图案,以便我们可以看到它正在运行。
不确定你是不是指这一行
color *= vec4(255,255, 0,255);
使用 255。WebGL 中的颜色从 0 到 1,所以也许你真的是这个意思
color *= vec4(1, 1, 0, 1);
让我补充一下代码也有一些小问题。其中很多都是意见,所以要么接受要么离开。
CSS
让 canvas 填满屏幕的最简单方法是这个
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
这就是你所需要的
根据调整大小事件调整大小
使用Date.now
requestAnimationFrame 传入自页面加载到回调的时间,分辨率高于 Date.now()
代码结构
当然我不知道你的计划,但期望每对着色器使用相同的输入似乎有点不寻常。当然这是你的代码,所以也许这就是你想要的。
该代码是为多个程序设置的,但调用了一次 gl.useProgram
。
似乎 updateUniforms
应该调用 gl.useProgram
所以它影响了正确的程序?
在 class 方法上使用箭头函数?
此外,据我所知,只有 Firefox 或 Safari 尚不支持此格式 Chrome(尽管您可以使用 Babel 进行翻译)
不是每帧都设置视口
这是非常个人的意见,但您可能会在某个时候添加
不同大小的帧缓冲区,此时您需要始终设置视口。 Micro-optimizing一旦一帧发生的事情几乎不值得。
作为 vec3
传入 position
属性默认为 0,0,0,1,因此如果您没有从缓冲区中获取所有 4 个值,您将得到您所需要的。
这是一个包含其中一些更改的版本
const vertexSource = `
attribute vec4 pos;
void main() {
gl_Position = pos;
}
`;
const fragmentSource = `
precision mediump float;
uniform float time;
uniform vec2 resolution;
mat2 rotate2d(float angle){
return mat2(cos(angle),-sin(angle),
sin(angle),cos(angle));
}
float variation(vec2 v1, vec2 v2, float strength, float speed) {
return sin(
dot(normalize(v1), normalize(v2)) * strength + time * speed
) / 100.0;
}
vec4 paintCircle (vec2 uv, vec2 center, float rad, float width) {
vec2 diff = center-uv;
float len = length(diff);
len += variation(diff, vec2(0.0, 1.0), 3.0, 2.0);
len -= variation(diff, vec2(1.0, 0.0), 3.0, 2.0);
float circle = 1. -smoothstep(rad-width, rad, len);
return vec4(circle);
}
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec4 color;
float radius = 0.15;
vec2 center = vec2(0.5);
color = paintCircle(uv, center, radius, .2);
vec2 v = rotate2d(time) * uv;
color *= vec4(1,1, 0,1);
gl_FragColor = color;
}
`;
class Render {
constructor() {
this.pos = [];
this.program = [];
this.buffer = [];
this.ut = [];
this.ures = [];
this.frame = 0;
this.canvas = document.querySelector('canvas');
this.gl = this.canvas.getContext('webgl');
this.renderLoop = this.renderLoop.bind(this);
this.init();
}
init() {
this.createGraphics(vertexSource, fragmentSource, 0);
this.renderLoop(0);
}
createShader(type, source) {
let shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
let success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
if (!success) {
console.log(this.gl.getShaderInfoLog(shader));
this.gl.deleteShader(shader);
return false;
}
return shader;
}
createProgram (vertexSource, fragmentSource) {
// Setup Vertext/Fragment Shader functions //
this.vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
this.fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
// Setup Program and Attach Shader functions //
let program = this.gl.createProgram();
this.gl.attachShader(program, this.vertexShader);
this.gl.attachShader(program, this.fragmentShader);
this.gl.linkProgram(program);
return program;
}
createGraphics (vertexSource, fragmentSource, i) {
// Create the Program //
this.program[i] = this.createProgram(vertexSource, fragmentSource);
// Create and Bind buffer //
this.buffer[i] = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer[i]);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]),
this.gl.STATIC_DRAW
);
this.pos[i] = this.gl.getAttribLocation(this.program[i], 'pos');
this.gl.vertexAttribPointer(
this.pos[i],
2, // size: 2 components per iteration
this.gl.FLOAT, // type: the data is 32bit floats
false, // normalize: don't normalize the data
0, // stride: 0 = move forward size * sizeof(type) each iteration to get the next position
0 // start at the beginning of the buffer
);
this.gl.enableVertexAttribArray(this.pos[i]);
this.importProgram(i);
}
updateUniforms(i, time) {
this.gl.useProgram(this.program[i]);
this.importUniforms(i, time);
this.gl.drawArrays(
this.gl.TRIANGLE_FAN, // primitiveType
0, // Offset
4 // Count
);
};
importProgram(i) {
this.ut[i] = this.gl.getUniformLocation(this.program[i], 'time');
this.ures[i] = this.gl.getUniformLocation(this.program[i],'resolution');
};
importUniforms(i, time) {
this.gl.uniform1f(this.ut[i], time / 1000);
this.gl.uniform2f(this.ures[i], this.gl.canvas.width, this.gl.canvas.height);
}
resizeCanvasToDisplaySize() {
const canvas = this.gl.canvas;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width ||
canvas.height !== height;
if (needResize) {
canvas.width = width;
canvas.height = height;
}
return needResize;
}
renderLoop(time) {
this.resizeCanvasToDisplaySize();
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
this.frame++;
this.updateUniforms(0, time);
this.animation = window.requestAnimationFrame(this.renderLoop);
}
}
let demo = new Render(document.body);
body {
background-color: red;
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<canvas></canvas>
我尝试使用 gl.blendFuncSeparate 使片段着色器的背景透明。 这在 Windows (Chrome/FF/Edge) 上工作正常,但在 MacOS 上它在 Firefox 中仅 运行s。 Chrome Mac 和 Safari 绘制整个视口透明。
class Render {
constructor() {
this.pos = [];
this.program = [];
this.buffer = [];
this.ut = [];
this.resolution = [];
this.frame = 0;
this.start = Date.now();
this.options = {
alpha: true,
premultipliedAlpha: true,
preserveDrawingBuffer: false
};
this.canvas = document.querySelector('canvas');
this.gl = this.canvas.getContext('webgl', this.options);
this.width = this.canvas.width;
this.height = this.canvas.height;
this.gl.viewport(0, 0, this.width, this.height);
this.gl.enable(this.gl.BLEND);
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
this.clearCanvas();
window.addEventListener('resize', this.resetCanvas, true);
this.init();
}
init = () => {
let vertexSource = document.querySelector('#vertexShader').textContent;
let fragmentSource = document.querySelector('#fragmentShader').textContent;
this.createGraphics(vertexSource, fragmentSource, 0);
this.canvas.addEventListener('mousemove', (e) => {
this.mouseX = e.pageX / this.canvas.width;
this.mouseY = e.pageY / this.canvas.height;
}, false);
this.renderLoop();
};
resetCanvas = () => {
this.width = 300; //this.shaderCanvas.width;
this.height = 300; // this.shaderCanvas.height;
this.gl.viewport(0, 0, this.width, this.height);
this.clearCanvas();
};
createShader = (type, source) => {
let shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
let success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
if (!success) {
console.log(this.gl.getShaderInfoLog(shader));
this.gl.deleteShader(shader);
return false;
}
return shader;
};
createProgram = (vertexSource, fragmentSource) => {
// Setup Vertext/Fragment Shader functions //
this.vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
this.fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
// Setup Program and Attach Shader functions //
let program = this.gl.createProgram();
this.gl.attachShader(program, this.vertexShader);
this.gl.attachShader(program, this.fragmentShader);
this.gl.linkProgram(program);
this.gl.useProgram(program);
return program;
};
createGraphics = (vertexSource, fragmentSource, i) => {
// Create the Program //
this.program[i] = this.createProgram(vertexSource, fragmentSource);
// Create and Bind buffer //
this.buffer[i] = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer[i]);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]),
this.gl.STATIC_DRAW
);
this.pos[i] = this.gl.getAttribLocation(this.program[i], 'pos');
this.gl.vertexAttribPointer(
this.pos[i],
2, // size: 2 components per iteration
this.gl.FLOAT, // type: the data is 32bit floats
false, // normalize: don't normalize the data
0, // stride: 0 = move forward size * sizeof(type) each iteration to get the next position
0 // start at the beginning of the buffer
);
this.gl.enableVertexAttribArray(this.pos[i]);
this.importProgram(i);
};
clearCanvas = () => {
this.gl.clearColor(1,1,1,1);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// Turn off rendering to alpha
this.gl.colorMask(true, true, true, false);
};
updateUniforms = (i) => {
this.importUniforms(i);
this.gl.drawArrays(
this.gl.TRIANGLE_FAN, // primitiveType
0, // Offset
4 // Count
);
};
importProgram = (i) => {
this.ut[i] = this.gl.getUniformLocation(this.program[i], 'time');
this.resolution[i] = new Float32Array([300, 300]);
this.gl.uniform2fv(
this.gl.getUniformLocation(this.program[i],'resolution'),
this.resolution[i]
);
};
importUniforms = (i) => {
this.gl.uniform1f(this.ut[i], (Date.now() - this.start) / 1000);
};
renderLoop = () => {
this.frame++;
this.updateUniforms(0);
this.animation = window.requestAnimationFrame(this.renderLoop);
};
}
let demo = new Render(document.body);
body {
background: #333;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
width: 200px;
height: 200px;
background: transparent;
}
<canvas width="300" height="300"></canvas>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 pos;
void main() {
gl_Position=vec4(pos, .5);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform float time;
uniform vec2 resolution;
mat2 rotate2d(float angle){
return mat2(cos(angle),-sin(angle),
sin(angle),cos(angle));
}
float variation(vec2 v1, vec2 v2, float strength, float speed) {
return sin(
dot(normalize(v1), normalize(v2)) * strength + time * speed
) / 100.0;
}
vec4 paintCircle (vec2 uv, vec2 center, float rad, float width) {
vec2 diff = center-uv;
float len = length(diff);
len += variation(diff, vec2(0.0, 1.0), 3.0, 2.0);
len -= variation(diff, vec2(1.0, 0.0), 3.0, 2.0);
float circle = 1. -smoothstep(rad-width, rad, len);
return vec4(circle);
}
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec4 color;
float radius = 0.15;
vec2 center = vec2(0.5);
color = paintCircle(uv, center, radius, .2);
vec2 v = rotate2d(time) * uv;
color *= vec4(255,255, 0,255);
gl_FragColor = color;
}
</script>
上面的代码片段在 MacOS Chrome 上不起作用,但 运行 在 Windows Chrome 上成功。您应该会看到一个流动的黄色圆圈。目标是只看到 HTML 背景上的动画人物 (#333)。 canvas 是透明的。 我已经尝试过不同的混合功能,但没有任何组合可以跨浏览器工作。
this.options = {
alpha: true,
premultipliedAlpha: true,
preserveDrawingBuffer: false
};
this.gl.enable(this.gl.BLEND);
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
clearCanvas = () => {
this.gl.clearColor(1,1,1,1);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// Turn off rendering to alpha
this.gl.colorMask(true, true, true, false);
};
我注意到它在 Firefox 上运行良好,但在 Chrome 上却不行。改变一件事后,它的工作原理如我所见。
代码笔: https://codepen.io/anon/pen/ZNKZqo
我改自:
preserveDrawingBuffer: false
到
preserveDrawingBuffer: true
我不知道你期望发生什么,你显然已经编辑了你的代码笔,使你的问题完全无关紧要,因为我们无法再检查问题。下次请使用snippet
对于preserveDrawingBuffer: false
,每帧都会清除canvas。在 clearCanvas
中,您将 alpha 清除为 1,然后关闭对 alpha 的渲染,但是因为 preserveDrawingBuffer
为 false(默认值),绘图缓冲区被清除,这意味着 alpha 现在回到零。之后,您将 0,0,0 或 1,1,0 渲染到其中。当 premultipliedAlpha
为真(默认值)时,1,1,0,0 是无效颜色。为什么?因为 premultiplied
意味着您在 canvas 中输入的颜色已乘以 alpha。 alpha 是 0。0 乘以任何东西都是零,所以当 alpha 为零时,红色、绿色和蓝色也必须为零。
这就是您在不同浏览器上看到不同颜色的原因。当您的颜色无效时,结果是不确定的。
将 preserveDrawingBuffer
设置为 true 并不能解决您的问题。这只是意味着您将 alpha 设置为 1,然后将其保留为 1,因为您关闭了对 alpha 的渲染,因此整个 canvas 是不透明的。
对于您似乎想要的内容,正确的解决方法是根本不清除(让 preserverDrawingBuffer: false,为您清除)并且不要使用 gl.colorMask
关闭对 alpha 的渲染然后在你的着色器中,在你想看到背景的地方写 0 到 alpha,在你不想看到背景的地方写 1
const vertexSource = `
attribute vec3 pos;
void main() {
gl_Position=vec4(pos, .5);
}
`;
const fragmentSource = `
precision mediump float;
uniform float time;
uniform vec2 resolution;
mat2 rotate2d(float angle){
return mat2(cos(angle),-sin(angle),
sin(angle),cos(angle));
}
float variation(vec2 v1, vec2 v2, float strength, float speed) {
return sin(
dot(normalize(v1), normalize(v2)) * strength + time * speed
) / 100.0;
}
// vec3 paintCircle (vec2 uv, vec2 center, float rad, float width) {
vec4 paintCircle (vec2 uv, vec2 center, float rad, float width) {
vec2 diff = center-uv;
float len = length(diff);
len += variation(diff, vec2(0.0, 1.0), 3.0, 2.0);
len -= variation(diff, vec2(1.0, 0.0), 3.0, 2.0);
float circle = 1. -smoothstep(rad-width, rad, len);
// return vec3(circle);
return vec4(circle);
}
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
// vec3 color;
vec4 color;
float radius = 0.15;
vec2 center = vec2(0.5);
color = paintCircle(uv, center, radius, .2);
vec2 v = rotate2d(time) * uv;
//color *= vec3(v.x, v.y, 0.7-v.y*v.x);
// color *= vec3(255,255, 0);
color *= vec4(255,255, 0,255);
//color += paintCircle(uv, center, radius, 0.01);
// gl_FragColor = vec4(color, 1.0);
gl_FragColor = color;
}
`;
class Render {
constructor() {
this.pos = [];
this.program = [];
this.buffer = [];
this.ut = [];
this.resolution = [];
this.frame = 0;
this.start = Date.now();
this.options = {
// these are already the defaults
// alpha: true,
// premultipliedAlpha: true,
// preserveDrawingBuffer: false
};
this.canvas = document.querySelector('canvas');
this.gl = this.canvas.getContext('webgl', this.options);
this.width = this.canvas.width;
this.height = this.canvas.height;
this.gl.viewport(0, 0, this.width, this.height);
// this.gl.enable(this.gl.BLEND);
// this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
//this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
//this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
// this.clearCanvas();
window.addEventListener('resize', this.resetCanvas, true);
this.init();
}
init = () => {
this.createGraphics(vertexSource, fragmentSource, 0);
this.renderLoop();
};
resetCanvas = () => {
this.width = 300; //this.shaderCanvas.width;
this.height = 300; // this.shaderCanvas.height;
this.gl.viewport(0, 0, this.width, this.height);
this.clearCanvas();
};
createShader = (type, source) => {
let shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
let success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
if (!success) {
console.log(this.gl.getShaderInfoLog(shader));
this.gl.deleteShader(shader);
return false;
}
return shader;
};
createProgram = (vertexSource, fragmentSource) => {
// Setup Vertext/Fragment Shader functions //
this.vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
this.fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
// Setup Program and Attach Shader functions //
let program = this.gl.createProgram();
this.gl.attachShader(program, this.vertexShader);
this.gl.attachShader(program, this.fragmentShader);
this.gl.linkProgram(program);
this.gl.useProgram(program);
return program;
};
createGraphics = (vertexSource, fragmentSource, i) => {
// Create the Program //
this.program[i] = this.createProgram(vertexSource, fragmentSource);
// Create and Bind buffer //
this.buffer[i] = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer[i]);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]),
this.gl.STATIC_DRAW
);
this.pos[i] = this.gl.getAttribLocation(this.program[i], 'pos');
this.gl.vertexAttribPointer(
this.pos[i],
2, // size: 2 components per iteration
this.gl.FLOAT, // type: the data is 32bit floats
false, // normalize: don't normalize the data
0, // stride: 0 = move forward size * sizeof(type) each iteration to get the next position
0 // start at the beginning of the buffer
);
this.gl.enableVertexAttribArray(this.pos[i]);
this.importProgram(i);
};
updateUniforms = (i) => {
this.importUniforms(i);
this.gl.drawArrays(
this.gl.TRIANGLE_FAN, // primitiveType
0, // Offset
4 // Count
);
};
importProgram = (i) => {
this.ut[i] = this.gl.getUniformLocation(this.program[i], 'time');
this.resolution[i] = new Float32Array([300, 300]);
this.gl.uniform2fv(
this.gl.getUniformLocation(this.program[i],'resolution'),
this.resolution[i]
);
};
importUniforms = (i) => {
this.gl.uniform1f(this.ut[i], (Date.now() - this.start) / 1000);
};
renderLoop = () => {
this.frame++;
this.updateUniforms(0);
this.animation = window.requestAnimationFrame(this.renderLoop);
};
}
let demo = new Render(document.body);
body {
background-color: red;
background-image: linear-gradient(45deg, blue 25%, transparent 25%, transparent 75%, blue 75%, blue),
linear-gradient(-45deg, blue 25%, transparent 25%, transparent 75%, blue 75%, blue);
background-size: 30px 30px;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
width: 300px;
height: 300px;
background: transparent;
}
<canvas width="300" height="300"></canvas>
请注意,我将背景设置为一个图案,以便我们可以看到它正在运行。
不确定你是不是指这一行
color *= vec4(255,255, 0,255);
使用 255。WebGL 中的颜色从 0 到 1,所以也许你真的是这个意思
color *= vec4(1, 1, 0, 1);
让我补充一下代码也有一些小问题。其中很多都是意见,所以要么接受要么离开。
CSS
让 canvas 填满屏幕的最简单方法是这个
body { margin: 0; } canvas { width: 100vw; height: 100vh; display: block; }
这就是你所需要的
根据调整大小事件调整大小
使用
Date.now
requestAnimationFrame 传入自页面加载到回调的时间,分辨率高于
Date.now()
代码结构
当然我不知道你的计划,但期望每对着色器使用相同的输入似乎有点不寻常。当然这是你的代码,所以也许这就是你想要的。
该代码是为多个程序设置的,但调用了一次
gl.useProgram
。似乎
updateUniforms
应该调用gl.useProgram
所以它影响了正确的程序?在 class 方法上使用箭头函数?
此外,据我所知,只有 Firefox 或 Safari 尚不支持此格式 Chrome(尽管您可以使用 Babel 进行翻译)
不是每帧都设置视口
这是非常个人的意见,但您可能会在某个时候添加 不同大小的帧缓冲区,此时您需要始终设置视口。 Micro-optimizing一旦一帧发生的事情几乎不值得。
作为 vec3
传入position
属性默认为 0,0,0,1,因此如果您没有从缓冲区中获取所有 4 个值,您将得到您所需要的。
这是一个包含其中一些更改的版本
const vertexSource = `
attribute vec4 pos;
void main() {
gl_Position = pos;
}
`;
const fragmentSource = `
precision mediump float;
uniform float time;
uniform vec2 resolution;
mat2 rotate2d(float angle){
return mat2(cos(angle),-sin(angle),
sin(angle),cos(angle));
}
float variation(vec2 v1, vec2 v2, float strength, float speed) {
return sin(
dot(normalize(v1), normalize(v2)) * strength + time * speed
) / 100.0;
}
vec4 paintCircle (vec2 uv, vec2 center, float rad, float width) {
vec2 diff = center-uv;
float len = length(diff);
len += variation(diff, vec2(0.0, 1.0), 3.0, 2.0);
len -= variation(diff, vec2(1.0, 0.0), 3.0, 2.0);
float circle = 1. -smoothstep(rad-width, rad, len);
return vec4(circle);
}
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec4 color;
float radius = 0.15;
vec2 center = vec2(0.5);
color = paintCircle(uv, center, radius, .2);
vec2 v = rotate2d(time) * uv;
color *= vec4(1,1, 0,1);
gl_FragColor = color;
}
`;
class Render {
constructor() {
this.pos = [];
this.program = [];
this.buffer = [];
this.ut = [];
this.ures = [];
this.frame = 0;
this.canvas = document.querySelector('canvas');
this.gl = this.canvas.getContext('webgl');
this.renderLoop = this.renderLoop.bind(this);
this.init();
}
init() {
this.createGraphics(vertexSource, fragmentSource, 0);
this.renderLoop(0);
}
createShader(type, source) {
let shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
let success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
if (!success) {
console.log(this.gl.getShaderInfoLog(shader));
this.gl.deleteShader(shader);
return false;
}
return shader;
}
createProgram (vertexSource, fragmentSource) {
// Setup Vertext/Fragment Shader functions //
this.vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
this.fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
// Setup Program and Attach Shader functions //
let program = this.gl.createProgram();
this.gl.attachShader(program, this.vertexShader);
this.gl.attachShader(program, this.fragmentShader);
this.gl.linkProgram(program);
return program;
}
createGraphics (vertexSource, fragmentSource, i) {
// Create the Program //
this.program[i] = this.createProgram(vertexSource, fragmentSource);
// Create and Bind buffer //
this.buffer[i] = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer[i]);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]),
this.gl.STATIC_DRAW
);
this.pos[i] = this.gl.getAttribLocation(this.program[i], 'pos');
this.gl.vertexAttribPointer(
this.pos[i],
2, // size: 2 components per iteration
this.gl.FLOAT, // type: the data is 32bit floats
false, // normalize: don't normalize the data
0, // stride: 0 = move forward size * sizeof(type) each iteration to get the next position
0 // start at the beginning of the buffer
);
this.gl.enableVertexAttribArray(this.pos[i]);
this.importProgram(i);
}
updateUniforms(i, time) {
this.gl.useProgram(this.program[i]);
this.importUniforms(i, time);
this.gl.drawArrays(
this.gl.TRIANGLE_FAN, // primitiveType
0, // Offset
4 // Count
);
};
importProgram(i) {
this.ut[i] = this.gl.getUniformLocation(this.program[i], 'time');
this.ures[i] = this.gl.getUniformLocation(this.program[i],'resolution');
};
importUniforms(i, time) {
this.gl.uniform1f(this.ut[i], time / 1000);
this.gl.uniform2f(this.ures[i], this.gl.canvas.width, this.gl.canvas.height);
}
resizeCanvasToDisplaySize() {
const canvas = this.gl.canvas;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width ||
canvas.height !== height;
if (needResize) {
canvas.width = width;
canvas.height = height;
}
return needResize;
}
renderLoop(time) {
this.resizeCanvasToDisplaySize();
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
this.frame++;
this.updateUniforms(0, time);
this.animation = window.requestAnimationFrame(this.renderLoop);
}
}
let demo = new Render(document.body);
body {
background-color: red;
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<canvas></canvas>