在 webgl 中绘制树的问题

Trouble with drawing tree in webgl

这次又出现了更多错误,只是这次是在 webgl 端而不是数学算法端。

我之前的post只是画了一个简单的二维递归树。我现在要做的是在鼠标点击的位置画一棵树,如果左键单击则用红线,如果右击则用蓝线。我解决了我之前的问题并且能够让树出现在我之前的程序构建中。然而,现在当我点击 canvas 时,树甚至都没有出现。但是,当我控制台记录存储点的数组时,所有点似乎都在那里。我想我遗漏了什么,但我对 webgl 的了解还不足以知道那可能是什么。

我已经制作了一个可以根据鼠标点击在鼠标位置绘制不同颜色点的工作程序,但我仍然没有足够的经验来弄清楚我在该程序中有什么可以让它工作以及为什么当前程序无法显示任何内容。

我当前的节目:

// Vertex shader program
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'void main() {\n' +
    '  gl_Position = a_Position;\n' +
    '}\n';

// Fragment shader program
var FSHADER_SOURCE =
    'precision mediump float;\n' +
    'uniform vec4 u_FragColor;\n' +
    'void main() {\n' +
    '  gl_FragColor = u_FragColor;\n' +
    '}\n';

var m = 0;

function main() {
    var canvas = document.getElementById('webgl');
    // Get the rendering context for WebGL
    var uniform1i = 0;

    var gl = getWebGLContext(canvas);
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }

    // Initialize shaders
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to intialize shaders.');
        return;
    }

    // // Get the storage location of a_Position
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if (a_Position < 0) {
        console.log('Failed to get the storage location of a_Position');
        return;
    }

    // Get the storage location of u_FragColor
    var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
    if (!u_FragColor) {
        console.log('Failed to get the storage location of u_FragColor');
        return;
    }

    // Register function (event handler) to be called on a mouse press
    canvas.onmousedown = function (ev) { click(ev, gl, canvas, a_Position, u_FragColor) };

    // Specify the color for clearing <canvas>
    gl.clearColor(1.0, 1.0, 1.0, 1.0);

    // Clear <canvas>
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.LINES, 0, 1, m);
}


function click(ev, gl, canvas, a_Position, u_FragColor) {
    var x = ev.clientX; // x coordinate of a mouse pointer
    var y = ev.clientY; // y coordinate of a mouse pointer
    var rect = ev.target.getBoundingClientRect();

    x = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
    y = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2);


    if (ev.button == 0) {
        var depth = 4;
        gl.uniform4f(u_FragColor, 1.0, 0, 0, 1.0);// Red
        //red tree, 4 steps, length 50, halved each step

        // Write the positions of vertices to a vertex shader
        var n = initVertexBuffers(gl, x, y);
        if (n < 0) {
            console.log('Failed to set the positions of the vertices');
            return;
        }
        m = n;

    }
    if (ev.button == 2) {
        var depth = 6;
        //blue tree, 6 steps, length 40, halved each step
        gl.uniform4f(u_FragColor, 0, 0, 1.0, 1.0);// Blue
        // Write the positions of vertices to a vertex shader
        var n = initVertexBuffers(gl, x, y);
        if (n < 0) {
            console.log('Failed to set the positions of the vertices');
            return;
        }
        m = n;
    }
}


function initVertexBuffers(gl, x, y) {
    let start = [];
    let points = createPoints(x, y, 0.4, 4, Math.PI / 2, start);
    console.log(points);
    var vertices = new Float32Array(points);
    let n = points.length / 2; // The number of vertices
    // Create a buffer object
    var vertexBuffer = gl.createBuffer();
    if (!vertexBuffer) {
        console.log('Failed to create the buffer object');
        return -1;
    }

    // Bind the buffer object to target
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    // Write date into the buffer object
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if (a_Position < 0) {
        console.log('Failed to get the storage location of a_Position');
        return -1;
    }
    // Assign the buffer object to a_Position variable
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

    // Enable the assignment to a_Position variable
    gl.enableVertexAttribArray(a_Position);

    return n;
}

//var points = [x, y];
//var angle = 0;
//var prevPoints = [];
//var prevPointIndex = 0;
function createPoints(x, y, length, depth, angle, points) {
    if (depth > 0) {
        //draws line
        let x2 = x + length * Math.cos(angle);
        let y2 = y + length * Math.sin(angle);
        points.push(x, y, x2, y2);

        //draw left branch;
        createPoints(x2, y2, length / 2, depth - 1, angle + Math.PI / 4, points);

        //goes back
        //points.push(x2, y2);

        //draw right branch
        createPoints(x2, y2, length / 2, depth - 1, angle - Math.PI / 4, points);

        //goes back
        //points.push(x2, y2);
        //console.log(points);
        return points;
    }
    return;
}

我敢打赌,我在主要功能或点击功能中遗漏了一些东西,但我post正在检查所有内容,因为我不是 100% 确定。

您发布的代码仅在 main 中调用 gl.drawXXX 一次,因此它永远不会再绘制任何东西。

你已经设置好了当鼠标被按下时 click 会被调用但是 click 永远不会调用 gl.drawXXX

此外,每次调用 click 时,您都会创建一个包含一组新点的新缓冲区。这不是使用 WebGL 的正常方式。通常的方法是设置一次你的点(每个你想画的东西一次)然后使用矩阵来改变位置、方向、比例。

我建议您阅读一些关于 WebGL 的其他教程。 This one kind of does what you're doing now but it follows up with how to change position, orientation, and scale, followed by how to do it with matrices for more flexibility. It also covers drawing multiple things

在任何情况下修复您的代码,因为您需要在更改顶点后绘制

// Vertex shader program
var VSHADER_SOURCE = 
    'attribute vec4 a_Position;\n' +
    'void main() {\n' +
    '  gl_Position = a_Position;\n' +
    '}\n';

// Fragment shader program
var FSHADER_SOURCE =
    'precision mediump float;\n' +
    'uniform vec4 u_FragColor;\n' +
    'void main() {\n' +
    '  gl_FragColor = u_FragColor;\n' +
    '}\n';

var m = 0;

function main() {
    var canvas = document.getElementById('webgl');
    // Get the rendering context for WebGL
    var uniform1i = 0;

    var gl = canvas.getContext('webgl');
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }

    // Initialize shaders
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to intialize shaders.');
        return;
    }

    // // Get the storage location of a_Position
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if (a_Position < 0) {
        console.log('Failed to get the storage location of a_Position');
        return;
    }

    // Get the storage location of u_FragColor
    var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
    if (!u_FragColor) {
        console.log('Failed to get the storage location of u_FragColor');
        return;
    }

    // Register function (event handler) to be called on a mouse press
    canvas.onmousedown = function (ev) { click(ev, gl, canvas, a_Position, u_FragColor) };

}


function click(ev, gl, canvas, a_Position, u_FragColor) {
    var x = ev.clientX; // x coordinate of a mouse pointer
    var y = ev.clientY; // y coordinate of a mouse pointer
    var rect = ev.target.getBoundingClientRect();

    x = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
    y = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2);

    if (ev.button == 0) {
        var depth = 4;
        gl.uniform4f(u_FragColor, 1.0, 0, 0, 1.0);// Red
        //red tree, 4 steps, length 50, halved each step

        // Write the positions of vertices to a vertex shader
        var n = initVertexBuffers(gl, x, y);
        if (n < 0) {
            console.log('Failed to set the positions of the vertices');
            return;
        }
        m = n;

    }
    if (ev.button == 2) {
        var depth = 6;
        //blue tree, 6 steps, length 40, halved each step
        gl.uniform4f(u_FragColor, 0, 0, 1.0, 1.0);// Blue
        // Write the positions of vertices to a vertex shader
        var n = initVertexBuffers(gl, x, y);
        if (n < 0) {
            console.log('Failed to set the positions of the vertices');
            return;
        }
        m = n;
    }
    
    // Specify the color for clearing <canvas>
    gl.clearColor(1.0, 1.0, 1.0, 1.0);

    // Clear <canvas>
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.LINES, 0, m);
    
}


function initVertexBuffers(gl, x, y) {
    let start = [];
    let points = createPoints(x, y, 0.4, 4, Math.PI / 2, start);
    console.log(points);
    var vertices = new Float32Array(points);
    let n = points.length / 2; // The number of vertices
    // Create a buffer object
    var vertexBuffer = gl.createBuffer();
    if (!vertexBuffer) {
        console.log('Failed to create the buffer object');
        return -1;
    }

    // Bind the buffer object to target
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    // Write date into the buffer object
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if (a_Position < 0) {
        console.log('Failed to get the storage location of a_Position');
        return -1;
    }
    // Assign the buffer object to a_Position variable
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

    // Enable the assignment to a_Position variable
    gl.enableVertexAttribArray(a_Position);

    return n;
}

//var points = [x, y];
//var angle = 0;
//var prevPoints = [];
//var prevPointIndex = 0;
function createPoints(x, y, length, depth, angle, points) {
    if (depth > 0) {
        //draws line
        let x2 = x + length * Math.cos(angle);
        let y2 = y + length * Math.sin(angle);
        points.push(x, y, x2, y2);

        //draw left branch;
        createPoints(x2, y2, length / 2, depth - 1, angle + Math.PI / 4, points);

        //goes back
        //points.push(x2, y2);

        //draw right branch
        createPoints(x2, y2, length / 2, depth - 1, angle - Math.PI / 4, points);

        //goes back
        //points.push(x2, y2);
        //console.log(points);
        return points;
    }
    return;
}
main();

//----
function initShaders(gl, vSrc, fSrc) {
  const program = twgl.createProgram(gl, [vSrc, fSrc]);
  gl.program = program; // THIS IS EXTREMELY BAD AND WRONG CODE!!!
  gl.useProgram(program); // THIS IS ALSO WRONG!
  return program;
}
canvas { border: 1px solid black; }
<canvas id="webgl"></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

代码有很多问题

  1. gl.program不是东西。无论您从哪本书或教程中学习,这都是一件可怕、糟糕和错误的事情。 WebGL 程序通常有多个着色器程序,所以像 initShaders 这样的函数需要 return 创建的程序,以便您可以使用不同的顶点着色器源和片段着色器源多次调用它来制作多个程序。它不应该将一个创建的程序破解到它不属于的 webgl 上下文中。

    注意:尽管我实现了 initShaders,因为您没有提供它,但从您的代码中可以清楚地看出它正在执行 gl.program = gl.createProgram。那是无意义的代码。有多个程序很常见。

  2. initShaders 中调用 gl.useProgram 也可以说是错误的。 (再次清楚,因为您的 initShaders 版本没有在您的代码中的任何地方调用它。同样,这没有任何意义,因为在普通的 WebGL 页面中您有多个着色器,因此您需要调用 gl.useProgram多次。

  3. 您正在阅读的教程看起来很旧,因为它是为 GLSL 连接字符串。制作 GLSL 最简单的方法是使用多行模板文字

    var VSHADER_SOURCE = `
        attribute vec4 a_Position;
        void main() {
          gl_Position = a_Position;
        }
    `;
    

    轻松多了

  4. 它正在调用某个函数 getWebGLContext。不需要那样的东西。只需使用 canvas.getContext('webgl')

  5. gl.drawArrays 的调用在错误的地方有 mgl.drawArrays的正确调用方式是gl.drawArrays(primitiveType, offset, vertexCount)。偏移量几乎为0。没有第4个参数。

请考虑阅读一些更好的教程,例如上面链接的教程。