Pure WebGL Dashed Line

三世轮回 提交于 2021-02-07 09:46:20

问题


I'm trying to create a dashed line using pure webgl. I know there is already a question on this, and maybe I'm dumb, but I cannot figure out how to make it work. I understand the concept, but I do not know how to get the distance along the path in the shader. A previous answer had the following line:

varying float LengthSoFar; // <-- passed in from the vertex shader

So how would I get LengthSoFar? How can I calculate it in the vertex shader?

Am I totally missing something? Can someone give me a working example? Or at least some good leads? I've been banging my head against the wall on this for days.


回答1:


I'm assuming it works like this. You have a buffer of positions. You make a corresponding buffer of lengthSoFar so,

function distance(array, ndx1, ndx2) 
{
  ndx1 *= 3;
  ndx2 *= 3;

  var dx = array[ndx1 + 0] - array[ndx2 + 0];
  var dy = array[ndx1 + 1] - array[ndx2 + 1];
  var dz = array[ndx1 + 2] - array[ndx2 + 2];

  return Math.sqrt(dx * dx + dy * dy + dz * dz);
}

var positions = 
[
  0.123, 0.010, 0.233,
  0.423, 0.312, 0.344,
  0.933, 1.332, 0.101,
];

var lengthSoFar = [0];  // the length so far starts at 0
for (var ii = 1; ii < positions.length / 3; ++ii) 
{
  lengthSoFar.push(lengthSoFar[ii - 1] + distance(positions, ii - 1, ii));
}

Now you can make buffers for both positions and lengthSoFar and pass lengthSoFar as an attribute into your vertex shader and from there pass it as a varying to to your fragment shader.

Unfortunately it won't work with indexed geometry (the most common type?). In other words it won't work with gl.drawElements, only with gl.drawArrays. Also the dashed line would be dashed in 3D not 2D so a line going into the screen (away from the viewer) would look different than a line going across the screen. Of course if you're drawing 2D then there's no problem.

If those limitations are good for you does this answer you question?

const gl = document.querySelector("#c").getContext("webgl");

// Note: createProgramFromScripts will call bindAttribLocation
// based on the index of the attibute names we pass to it.
var program = twgl.createProgramFromScripts(
    gl, 
    ["vshader", "fshader"], 
    ["a_position", "a_lengthSoFar"]);
gl.useProgram(program);

function distance(array, ndx1, ndx2) 
{
  ndx1 *= 3;
  ndx2 *= 3;

  var dx = array[ndx1 + 0] - array[ndx2 + 0];
  var dy = array[ndx1 + 1] - array[ndx2 + 1];
  var dz = array[ndx1 + 2] - array[ndx2 + 2];

  return Math.sqrt(dx * dx + dy * dy + dz * dz);
}

// Used this line in the console to generate the positions
// var sub = 10; for (var ii = 0; ii <= sub; ++ii) { r = (ii & 1) ? 0.5 : 0.9; a = ii * Math.PI * 2 / sub; console.log((Math.cos(a) * r).toFixed(3) + ", " + (Math.sin(a) * r).toFixed(3) + ", "); }

var positions = [
  0.900, 0.000, 0,
  0.405, 0.294, 0,
  0.278, 0.856, 0,
  -0.155, 0.476, 0,
  -0.728, 0.529, 0,
  -0.500, 0.000, 0,
  -0.728, -0.529, 0,
  -0.155, -0.476, 0,
  0.278, -0.856, 0,
  0.405, -0.294, 0,
  0.900, -0.000, 0,
];
    
var lengthSoFar = [0];  // the length so far starts at 0
for (var ii = 1; ii < positions.length / 3; ++ii) 
{
  lengthSoFar.push(lengthSoFar[ii - 1] + distance(positions, ii - 1, ii));
}

var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);

var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(lengthSoFar), gl.STATIC_DRAW);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 0, 0);

// Since uniforms default to '0' and since 
// the default active texture is '0' we don't
// have to setup the texture uniform. 
var pixels = [
    0, 0, 255, 255,
    0, 0, 255, 255,
    0, 0, 255, 255,
    0, 0, 255, 255,
    0, 0, 0, 0,
    0, 0, 0, 0,
    255, 0, 0, 255,
    0, 0, 0, 0,
    0, 0, 0, 0,
    255, 0, 0, 255,
    0, 0, 0, 0,
    0, 0, 0, 0,
];

var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
    gl.TEXTURE_2D, 0, gl.RGBA, pixels.length / 4, 1, 0,
    gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(pixels));
gl.texParameteri(
    gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(
    gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  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.drawArrays(gl.LINE_STRIP, 0, positions.length / 3);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script id="vshader" type="whatever">
    attribute vec4 a_position;
    attribute float a_lengthSoFar;
    varying float v_lengthSoFar;
    void main() {
      gl_Position = a_position;
      v_lengthSoFar = a_lengthSoFar;
    }    
</script>
<script id="fshader" type="whatever">
precision mediump float;
varying float v_lengthSoFar;
uniform sampler2D u_pattern;
#define NumDashes 6.0
void main() {
    gl_FragColor = texture2D(
      u_pattern, 
      vec2(fract(v_lengthSoFar * NumDashes)), 0.5);
}
</script>
<canvas id="c" width="300" height="300"></canvas>

Note: Here's an article that might help explain how varyings work

Also note you can't change the thickness of the lines. To do that you need to draw lines from triangles




回答2:


Pass "LengthSoFar" as a vertex attribute to the shader. When you create the shader program in your javascript code (the js part, not the shader part), you can create something like

program.lengthSoFar = gl.getAttribLocation(program, 'aLengthSoFar');

where aLengthSoFar in an attribute in your vertex shader

I recommend this tutorial on webgl, it has all the basics: http://www.youtube.com/watch?v=me3BviH3nZc

You'll quickly realize the problem with graphics programming in webgl. While shader programming in of itself is not hard, setting up all the details in js and webgl to just get started is incredibly painful.

Tip: always remember that graphics programming is a one-way street. You have your model, i.e. javascript code. This model will send information to your vertex shader which will send information to your fragment shader which will display the pixels on the screen. Unfortunately, there are no corners to be cut.

This is why they came up with such a thing as attributes and varyings. You can set attributes in a vertex shader (like parameters in a function) from javascript. But attributes are read-only. So, you have to copy them and pass them as varyings to the fragmentshader.



来源:https://stackoverflow.com/questions/19341590/pure-webgl-dashed-line

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!