Как исправить проблему кроссбраузера с alpha blendmode в контексте webgl? - PullRequest
0 голосов
/ 17 мая 2019

Я пытаюсь сделать фон моего фрагментного шейдера прозрачным с помощью gl.blendFuncSeparate.Это прекрасно работает в Windows (Chrome / FF / Edge), но в MacOS работает только в Firefox.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).Холст прозрачный.Я уже пробовал разные функции смешивания, но ни одна комбинация не работает в разных браузерах.

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

Ответы [ 2 ]

1 голос
/ 17 мая 2019

Я не знаю, что вы ожидали, и вы, очевидно, уже отредактировали свой кодовый блок, сделав ваш вопрос совершенно неуместным, поскольку мы больше не можем проверять проблему.В следующий раз используйте фрагмент

С preserveDrawingBuffer: false холст очищается каждый кадр.в clearCanvas вы очищаете альфа до единицы, а затем выключаете рендеринг в альфа, но поскольку preserveDrawingBuffer имеет значение false (по умолчанию), буфер рисования очищается, что означает, что альфа теперь вернулась к нулю.После этого вы рендерите в него 0,0,0 или 1,1,0.1,1,0,0 - недопустимый цвет, если premultipliedAlpha - это значение по умолчанию.Зачем?Потому что premultiplied означает, что цвета, которые вы положили на холст, были умножены на альфа.альфа равен 0. 0 раз все равно нулю, поэтому когда альфа равна нулю, красный, зеленый и синий также должны быть равны нулю.

Вот почему вы видите разные цвета в разных браузерах.Когда ваши цвета недопустимы, результаты не определены.

Установка preserveDrawingBuffer в значение true не решает вашу проблему.Это просто означает, что вы устанавливаете альфа на 1, а затем оставляете его равным 1, так как вы отключили рендеринг на альфа, чтобы весь холст стал непрозрачным.не совсем ясно (пусть preserverDrawingBuffer: false, сделайте очистку за вас) и не выключайте рендеринг в альфа с gl.colorMask, затем в вашем шейдере напишите 0 в альфа, где вы хотите видеть фон, и 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);

Позвольте мне добавить, что есть также некоторые незначительные проблемы с кодом.Многие из них являются мнениями, поэтому возьмите их или оставьте их.

  1. CSS

    Самый простой способ получить холст для заполнения экрана - это

    body { margin: 0; }
    canvas { width: 100vw; height: 100vh; display: block; }
    

    Это все, что вам нужно

  2. Изменение размера при изменении размера события

    Я бы сказал, Есть лучшие способы

  3. Использование Date.now

    requestAnimationFrame проходит за время с момента загрузки страницы до обратного вызова и имеет более высокое разрешение, чем Date.now()

  4. Структура кода

    Конечно, я не знаю ваших планов, но ожидать, что каждая пара шейдеров будет использовать одни и те же входные данные, кажется необычным.Конечно, это ваш код, так что, возможно, это то, что вы хотели.

  5. Код настроен для нескольких программ, но вызывает gl.useProgram один раз.

    Похоже, updateUniforms следуетВы вызываете gl.useProgram, так что это влияет на правильную программу?

  6. Использование функций стрелок в методах класса?

    См. https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1

    Также AFAIK thisформат пока не поддерживается только в Firefox или Safari Chrome (хотя вы можете использовать Babel для перевода)

  7. Не настраивать область просмотра для каждого кадра

    Это очень личное мнение, носкорее всего, в какой-то момент вы добавите фреймбуферы разных размеров, и в этот момент вам нужно будет постоянно устанавливать область просмотра.Микрооптимизация того, что происходит, когда фрейм едва ли стоит того.

  8. Передача position в качестве атрибута vec3

    по умолчанию 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>
0 голосов
/ 17 мая 2019

Я заметил, что на Firefox он работает нормально, но не на Chrome. После изменения одной вещи все работает, как я вижу.

Видео: https://jmp.sh/AkzOl7b

Codepen: https://codepen.io/anon/pen/ZNKZqo

Я изменил с:

preserveDrawingBuffer: false

до

preserveDrawingBuffer: true
...