javascript - WebGL 基础知识 : Using Render Loops to Apply 2D Convolution Filters to Buffer Canvases

标签 javascript image-processing canvas glsl webgl

对 WebGL 非常陌生,并尝试移植一些 2D 图像处理着色器以掌握一些东西。我最初被 MDN 教程误导,认为 WebGL 就像 OpenGL 桌面,但后来发现 these tutorials ,我发现它更符合形式和我的目的。但是,我在格式化渲染循环方面仍然遇到一些麻烦,因此我可以传递不断更新的纹理进行处理。在它确实渲染的情况下,我只会得到一团困惑,而在顶点着色器不是简单传递的情况下,我什么也得不到。我了解 GLSL 和缓冲区如何工作的基础知识,但显然我在这里做了一些非常错误的事情......任何帮助将不胜感激。谢谢!

class GL {
    constructor(canvas){
        this.gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
        if (!this.gl) {
            alert("Unable to initialize WebGL. Your browser may not support it.");
            this.gl = null;
        }
 
        //init shaders
        var fragmentShader = getShader(this.gl, "fshader");
        var vertexShader = getShader(this.gl, "vshader");
        var shaderProgram = this.gl.createProgram();
        this.gl.attachShader(shaderProgram, vertexShader);
        this.gl.attachShader(shaderProgram, fragmentShader);
        this.gl.linkProgram(shaderProgram);
        this.gl.useProgram(shaderProgram);
        
        this.positionLocation = this.gl.getAttribLocation(shaderProgram, "position");
        this.texCoordLocation = this.gl.getAttribLocation(shaderProgram, "texcoord");
        var resolutionLocation = this.gl.getUniformLocation(shaderProgram, "resolution");
        this.width = this.gl.getUniformLocation(shaderProgram, "width");
    
        //set resolution
        this.gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
        
        function getShader(gl, id) {
            var shaderScript, theSource, currentChild, shader;
            shaderScript = document.getElementById(id);

            if (!shaderScript) {
                return null;
            }

            theSource = "";
            currentChild = shaderScript.firstChild;
            
            while(currentChild) {
                if (currentChild.nodeType == currentChild.TEXT_NODE) {
                    theSource += currentChild.textContent;
                }
                currentChild = currentChild.nextSibling;
            }

            if (shaderScript.type == "x-shader/x-fragment") {
                shader = gl.createShader(gl.FRAGMENT_SHADER);
            } else if (shaderScript.type == "x-shader/x-vertex") {
                shader = gl.createShader(gl.VERTEX_SHADER);
            } else {
                // Unknown shader type
                return null;
            }

            gl.shaderSource(shader, theSource);
            gl.compileShader(shader);

            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
                return null;
            }

                return shader;
        };
    };
    
    render(bufferCanvas, x, y) { 
        this.gl.clear(this.gl.COLOR_BUFFER_BIT);
        //texture coordinates
        var texCoordBuffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
        this.gl.enableVertexAttribArray(this.texCoordLocation);
        this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 8, 0);
        
        this.gl.bufferData(
            this.gl.ARRAY_BUFFER, 
            new Float32Array([
                0.0,  0.0,
                1.0,  0.0,
                0.0,  1.0,
                0.0,  1.0,
                1.0,  0.0,
                1.0,  1.0]), 
            this.gl.STATIC_DRAW);
        
        //create texture
        var texture = this.gl.createTexture();
        this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
        
        //normalize image to powers of two
        this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
        this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
        this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
        this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
        
        //load texture from 2d canvas
        this.gl.texImage2D(this.gl.TEXTURE_2D, 
                           0, 
                           this.gl.RGBA, 
                           this.gl.RGBA, 
                           this.gl.UNSIGNED_BYTE, 
                           bufferCanvas);

        //load buffer
        var buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
        this.gl.enableVertexAttribArray(this.positionLocation);
        this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 12, 0);
 
        //draw size and position
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([   
            x, y,
            x + bufferCanvas.width, y,
            x, y + bufferCanvas.height,
            x, y + bufferCanvas.height,
            x+ bufferCanvas.width, y,
            x+ bufferCanvas.width, y + bufferCanvas.height]), this.gl.STATIC_DRAW);
        
        //blur width
        this.gl.enableVertexAttribArray(this.width);
        this.gl.vertexAttribPointer(this.width, 1, this.gl.FLOAT, false, 12, 8);
        
        //draw
        this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
    };
};


var canvas2d = document.getElementById('buffer-canvas');
var context2d = canvas2d.getContext("2d");
var canvasGL = new GL(document.getElementById('main-canvas'));
canvasGL.width = 5.0;

for(var i=0; i<10; i++) {
    var r = Math.floor(Math.random() * 255);
    var g = Math.floor(Math.random() * 255);
    var b = Math.floor(Math.random() * 255);
    var a = Math.floor(Math.random() * 255);
    context2d.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a + ")";
    
    var x = Math.random() * canvas2d.width;
    var y = Math.random() * canvas2d.height;
    var width = canvas2d.width - (Math.random() * canvas2d.width);
    var height = canvas2d.height - (Math.random() * canvas2d.height);
    context2d.fillRect(x, y, width , height);
    
    canvasGL.render(canvas2d, canvas2d.getBoundingClientRect("left"), canvas2d.getBoundingClientRect("top"));
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sylvester/0.1.3/sylvester.min.js"></script>
<script src="https://github.com/mdn/webgl-examples/blob/gh-pages/tutorial/glUtils.js"></script>
  
<script id="vshader" type="x-shader/x-vertex">
precision mediump float;

attribute vec2 position;
attribute vec2 texcoord;

uniform vec2 resolution;
uniform float width;

varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;

void main()
{
    gl_Position = vec4(((position / resolution) * 2.0 - 1.0) * vec2(1, -1), 0, 1);

	// get texcoords
	texcoord11 = texcoord;
	texcoord00 = texcoord + vec2(-width, -width);
	texcoord02 = texcoord + vec2( width, -width);
	texcoord20 = texcoord + vec2( width,  width);
	texcoord22 = texcoord + vec2(-width,  width);
}
</script>
  
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;

uniform sampler2D image;

varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;

void main()
{
	vec4 blur;
	
	blur = texture2D(image, texcoord11);
	blur += texture2D(image, texcoord00);
	blur += texture2D(image, texcoord02);
	blur += texture2D(image, texcoord20);
	blur += texture2D(image, texcoord22);

	gl_FragColor = 0.2 * blur;
}
</script>
</head>

<body>
<canvas id="main-canvas" width="400" height="300" style="border:1px solid black;"></canvas>
<canvas id="buffer-canvas" width="400" height="300" style="visibility:hidden;"></canvas>
</body>

最佳答案

代码存在几个问题

  • 这两个脚本标签似乎都不需要

  • 您将 this.width 分配给统一位置

    this.width = this.gl.getUniformLocation(shaderProgram, "width");
    

    但后来用它来摧毁它

    canvasGL.width = 5.0
    
  • width 是统一的,但您尝试将其设置为属性

    错误

    this.gl.enableVertexAttribArray(this.width);
    this.gl.vertexAttribPointer(this.width, 1, this.gl.FLOAT, false, 12, 8);
    

    this.gl.uniform1f(this.width, whateverYouWantedWidthToBe);
    
  • 您的所有属性都取得了不必要的进步

    错误

    this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 8, 0);
    this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 12, 0);
    

    this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 0, 0);
    this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 0, 0);
    

    好吧,如果你想设置步幅,但位置一是错误的,因为你每个位置使用 2 个浮点而不是 3 个,那么我认为 texcoord 没有错误。为什么不将它们设置为 0 ?

  • 您将 texCoordLocation 分配给 var,然后将其用作 this 的属性

    错误

    var texCoordBuffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
    

    对吗?

    this.texCoordBuffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
    
  • 最重要的是,代码的结构可能不是您想要的。

    在 WebGL 中,您通常不会在渲染函数中调用 gl.createXXX 函数。您可以在初始化期间调用 gl.createXXX 函数。 See here for a more typical structure .

  • 根本不清楚这条线想要做什么

    canvasGL.render(canvas2d, canvas2d.getBoundingClientRect("left"),     
                    canvas2d.getBoundingClientRect("top"))
    

    您认为 canvas2d.getBoundingClientRect() 将返回什么对渲染有用?而且这也不是 getBoundingClientRect 的工作原理。 It returns a rect, it doesn't take any arguments .

一旦你解决了所有问题,就不清楚你想要的宽度是多少。我假设您希望它是像素,但在这种情况下它需要是 1/canvas2D.width 并且您需要在着色器中为 height 提供一个单独的值,因为您需要不同的值来上下移动一定数量的像素(相对于左右移动)。

其他建议

  • gl 拉入局部变量。那么您就不必到处执行 this.gl 。更少的打字、更短、更快

  • 这样就可以获取script标签的内容

    var theSource = shaderScript.text;
    
  • 您正在检查着色器编译错误,而不是链接错误。

  • visibility: hidden; doesn't do what I think you think it does 。您可能需要 display: none;

这是一个可以做某事的版本。

class GL {
    constructor(canvas){
        var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
        if (!gl) {
            alert("Unable to initialize WebGL. Your browser may not support it.");
            return;  
        }
        this.gl = gl;
 
        //init shaders
        var fragmentShader = getShader(gl, "fshader");
        var vertexShader = getShader(gl, "vshader");
        var shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);
        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("An error occurred linking the shaders: " + gl.getProgramInfoLog(shaderProgram));
            return;
        }
        
      
        this.shaderProgram = shaderProgram;
        
        this.positionLocation = gl.getAttribLocation(shaderProgram, "position");
        this.texCoordLocation = gl.getAttribLocation(shaderProgram, "texcoord");
        this.resolutionLocation = gl.getUniformLocation(shaderProgram, "resolution");
        this.blurOffsetLocation = gl.getUniformLocation(shaderProgram, "blurOffset");
    
        // init texture coordinates
        this.texCoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);        
        gl.bufferData(
            gl.ARRAY_BUFFER, 
            new Float32Array([
                0.0,  0.0,
                1.0,  0.0,
                0.0,  1.0,
                0.0,  1.0,
                1.0,  0.0,
                1.0,  1.0]), 
            gl.STATIC_DRAW);
        
      
        // create position buffer
        this.positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
      
        //create texture
        this.texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, this.texture);
        
        //normalize image to powers of two
        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);
        
        function getShader(gl, id) {
            var shaderScript, theSource, currentChild, shader;
            shaderScript = document.getElementById(id);

            if (!shaderScript) {
                return null;
            }

            var theSource = shaderScript.text;
            
            if (shaderScript.type == "x-shader/x-fragment") {
                shader = gl.createShader(gl.FRAGMENT_SHADER);
            } else if (shaderScript.type == "x-shader/x-vertex") {
                shader = gl.createShader(gl.VERTEX_SHADER);
            } else {
                // Unknown shader type
                return null;
            }

            gl.shaderSource(shader, theSource);
            gl.compileShader(shader);

            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
                return null;
            }

                return shader;
        };
    };
    
    render(bufferCanvas, x, y, blurAmount) {
        var gl = this.gl;
        gl.clear(this.gl.COLOR_BUFFER_BIT);
      
        gl.useProgram(this.shaderProgram);

        // setup buffers and attributes
        gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);        
        gl.enableVertexAttribArray(this.texCoordLocation);
        gl.vertexAttribPointer(this.texCoordLocation, 2, gl.FLOAT, false, 0, 0);
      
        //load buffer
        gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
        gl.enableVertexAttribArray(this.positionLocation);
        gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, 0, 0);
 
        //draw size and position
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([   
            x, y,
            x + bufferCanvas.width, y,
            x, y + bufferCanvas.height,
            x, y + bufferCanvas.height,
            x+ bufferCanvas.width, y,
            x+ bufferCanvas.width, y + bufferCanvas.height]), gl.STATIC_DRAW);
              
        //load texture from 2d canvas
        gl.bindTexture(gl.TEXTURE_2D, this.texture);
        gl.texImage2D(gl.TEXTURE_2D, 
                      0, 
                      gl.RGBA, 
                      gl.RGBA, 
                      gl.UNSIGNED_BYTE, 
                      bufferCanvas);

        //blur width
        gl.uniform2f(this.blurOffsetLocation, 
                     blurAmount / bufferCanvas.width,
                     blurAmount / bufferCanvas.height);
      
        //set resolution
        gl.uniform2f(this.resolutionLocation, 
                     gl.canvas.width, 
                     gl.canvas.height);     
      
        //draw
        gl.drawArrays(gl.TRIANGLES, 0, 6);
    };
};


var canvas2d = document.getElementById('buffer-canvas');
var context2d = canvas2d.getContext("2d");
var canvasGL = new GL(document.getElementById('main-canvas'));

function render(time) {
    time *= 0.001;
    var r = Math.floor(Math.random() * 255);
    var g = Math.floor(Math.random() * 255);
    var b = Math.floor(Math.random() * 255);
    var a = Math.floor(Math.random() * 255);
    context2d.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a + ")";
    
    var x = Math.random() * canvas2d.width;
    var y = Math.random() * canvas2d.height;
    var width = canvas2d.width - (Math.random() * canvas2d.width);
    var height = canvas2d.height - (Math.random() * canvas2d.height);
    context2d.fillRect(x, y, width , height);
    canvasGL.render(canvas2d, 0, 0, Math.sin(time) * 5);
    requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script id="vshader" type="x-shader/x-vertex">
precision mediump float;

attribute vec2 position;
attribute vec2 texcoord;

uniform vec2 resolution;
uniform vec2 blurOffset;

varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;

void main()
{
    gl_Position = vec4(((position / resolution) * 2.0 - 1.0) * vec2(1, -1), 0, 1);

	// get texcoords
	texcoord11 = texcoord;
	texcoord00 = texcoord + blurOffset * vec2(-1, -1);
	texcoord02 = texcoord + blurOffset * vec2( 1, -1);
	texcoord20 = texcoord + blurOffset * vec2( 1,  1);
	texcoord22 = texcoord + blurOffset * vec2(-1,  1);
}
</script>
  
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;

uniform sampler2D image;

varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;

void main()
{
	vec4 blur;
	
	blur = texture2D(image, texcoord11);
	blur += texture2D(image, texcoord00);
	blur += texture2D(image, texcoord02);
	blur += texture2D(image, texcoord20);
	blur += texture2D(image, texcoord22);

    // do you really want to blend the alpha?
	gl_FragColor = 0.2 * blur;
}
</script>

<canvas id="main-canvas" width="400" height="300" style="border:1px solid black;"></canvas>
<canvas id="buffer-canvas" width="400" height="300" style="display: none;"></canvas>

关于javascript - WebGL 基础知识 : Using Render Loops to Apply 2D Convolution Filters to Buffer Canvases,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38195501/

相关文章:

android - 使用 Android OpenCV 在图像中检测到图案时拍照

javascript - 在 javascript 数组中绘制读取值的问题 - 在 HTML5 Canvas 中绘制

javascript - 如何替换两个特定元素之间的一些 HTML 标签?

javascript - 是否所有修改方法都调用数组代理的设置陷阱

python - 如何验证两个图像完全相同?

c# - 如何将 C# 中的图像调整为特定的硬盘大小?

android - 我们如何将 Canvas 上的绘图与 Android 中的预定义图像进行比较?

javascript - 清理 Canvas

javascript - React Redux 不会重置某些组件的存储

javascript - 带 LFO 的 WebAudio PannerNode 自动化