One, foreword

A simple case study in the beginner’s webGL process. Learning, writing, mistakes are unavoidable, welcome to exchange learning. Keep learning and accumulating. I saw this case on Github last year and thought it was cool.

  • webgl-wind

  • How I built a wind map with WebGL

  • How to use WebGL musicians playing a wind simulation | shanzhu

Start learning a wave of WebGL and integrate WebGL-Wind into MapBoxGL. Modified part of webGL-Wind source code, I have uploaded my code to Github, interested can communicate with each other.

I usually use OpenLayers and MapBoxGL a lot, and there are few opportunities to write WebGL directly. Learning WebGL and Threejs for a period of time can only be considered as a simple introduction. A year ago, the epidemic wrote a case study based on meteorological data and Mapbox. Share a review and learn.

2. Obtaining meteorological data (wind direction)

Webgl-wind mentions the way data is captured and processed. I tried a wave of software downloads and environment configurations. Failed !!!! It doesn’t seem to work on Windows. The configuration of my computer made me give up.

I found weather test data that someone else had processed online. This is mainly for Marine meteorological visualization, a case of Marine meteorological visualization written in Leaflet. My test data hehereTo obtain.

[Latitude],[Longitude],[Value],[Direction]

[longitude] [latitude] [wind] [direction]

Mapboxgl + WebGL foundation

Here is an example from mapBoxGL’s official website, with a minor modification. If you haven’t studied webGL recommendations before, check out the webGL tutorials and recommend oneLearning website. Modify a small case based on the MapBoxGL pipe network case.Mapboxgl provides custom layers, writes its own shaders, and renders geometric data.

{
            id: 'layerid',
            type: 'custom',
            onAdd: function (map, gl) {
            },
            render: function (gl, matrix) {
            }
};
Copy the code

OnAdd: called after the layer is added to the map

Render: frame loop function called when each frame is rendered. Gl is the map context object and matrix is the camera matrix, which projects the Mercator coordinates of the sphere onto GL coordinates.

WebGL runs on a COMPUTER’s GPU. So you need code that can run on a GPU. Such code needs to provide a pair of methods, one vertex shader and the other slice shader, which together form the shader program. The vertex shader is used to calculate the vertex position, and the slice shader is responsible for the light processing of the geometric lines and the coloring of each pixel.

1. Vertex shaders

uniform mat4 u_matrix; // Uniform float anim, viewport matrix; // Global variable (in this case, used to modify vertex height) attribute vec2 a_pos; Void main() {// gl_Position is a webGL built-in variable // a four-dimensional vector composed of (x,y,z,1), Gl_Position = U_matrix * VEC4 (a_pos, Anim, 1.0); }Copy the code

2, chip shader

Void main() {gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5); }Copy the code

3. Create a shader

Var vertexShader = gl.createshader (gl.vertex_shader); // Provide vertex data source -- point shader code string gl.shaderSource(vertexShader, vertexSource); // compile to generate vertexShader gl.pileshader (vertexShader); Var fragmentShader = gl.createshader (gl.fragment_shader); // Provide the slice shader data source -- the slice shader code string gl.shaderSource(fragmentShader, fragmentSource); // compile the fragmentShader gl.pileshader (fragmentShader); This.program = gl.createProgram(); // Create a shader - and link the vertex shader and the slice shader this.program = gl.createProgram(); gl.attachShader(this.program, vertexShader); gl.attachShader(this.program, fragmentShader); gl.linkProgram(this.program);Copy the code

4. Pass data to the shader

This.apos = gl.getattribLocation (this.program, 'a_pos'); UnifromAnim = gl.getUniformLocation(this.program, "anim"); // Get the location of anim from the vertex shader. {x: 0, y: 0.0016379147860542769, z: {x: 0, y: 0.0016379147860542769, z: {x: 0, y: 0.0016379147860542769, z: {x: 0, y: 0.0016379147860542769, z: 0} var p1 = mapboxgl. MercatorCoordinate. FromLngLat ({LNG: 180.00, lat: 85}); / / {x: 0.5, y: 0.5, z: 0} var. P2 = mapboxgl MercatorCoordinate. FromLngLat ({lat LNG: 0:0}). / / {x: 0, y: 0.9983620852139461, z: 0} var. P3 = mapboxgl MercatorCoordinate. FromLngLat ({} LNG: 180.00, lat: - 85); This.buffer = gl.createBuffer(); this.buffer = gl.createBuffer(); // bindBuffer gl.bindbuffer (gl.array_buffer, this.buffer); Gl. bufferData(gl.ARRAY_BUFFER, New Float32Array([p1.x, p1.y, p2.x, p2.y, p3.x, p3.y]),gl.STATIC_DRAW);Copy the code

5. Render data

Gl.useprogram (this.program); // Specify which colorer to use before rendering data. Gl. uniformMatrix4fv(gl.getUniformLocation(this.program, 'u_matrix'), false, matrix); AnimTime + = 0.002; AnimTime = animTime % 0.2; Gl. uniform1f(this.unifromAnim, animTime); Gl.bindbuffer (gl.array_buffer, this.buffer); / / enabled a_pos attribute gl. EnableVertexAttribArray (enclosing aPos); // Specify the policy to read data from the buffer gl.vertexattribPointer (this.apos, 2, gl.float, false, 0, 0); Gl.enable (gl.blend); gl.enable(gl.blend); gl.enable(gl.blend) Gl.blendfunc (gl.src_alpha, gl.one_minus_src_alpha); // Draw drawArrays(gl.TRIANGLE_STRIP, 0, 3); // Draw drawArrays(gl.TRIANGLE_STRIP, 0, 3);Copy the code

Iv. Wind data drawing

1. Discrete points

The spatial resolution of the practical test data is 1 degree. The global wind data at a certain time is converted into a type array, and the color gradient is defined. The color of sampling points is specified according to the wind force.

Before conversion:

Precision dimension Wind 75.00,-180.00,17.13 75.00,-179.00,17.77 75.00,-178.00,17.52Copy the code

After the transformation:

// x 0.17729912095967093 // y 0.5725490196078431 // r 0.42745098039215684 // g 0 // b 1 // w ---- separated 0.00277777777778 0.17729912095967093 0.592156862745098 0.40784313725490196 01 ---- separated 0.0055555555555556 0.17729912095967093 0.5843137254901961 0.41568627450980394 01Copy the code

Data processing conversion code

Function tof32Array(CSVSTR) {let tmparr = csvstr.split(/[\n]/); function tof32Array(CSVSTR) {let tmparr = csvstr.split(/[\n]/); let res = []; for (let i = 0; i < tmparr.length; i += 1) { let item = tmparr[i]; let tmpitem = item.split(','); let nor = mapboxgl.MercatorCoordinate.fromLngLat({ lng: tmpitem[1], lat: tmpitem[0], }); let tmp = [ nor.x, nor.y, ...calcColor(tmpitem[2])]; res.push(... tmp); } return res; } function calcColor(value) {var aa = d3.rgb(255, 0, 0); Var bb = d3.rgb(0, 255, 0); Interpolate (aa, interpolate); // Interpolate (aa, interpolate); var linear = d3.scale.linear() .domain([0, 30]) .range([1, 0]); let tmpres = compute(linear(value)); let rgb = tmpres.split(','); let r = rgb[0].split('(')[1]; let g = rgb[1]; let b = rgb[2].split(')')[0]; Return [r / 255.0, g / 255.0, b / 255.0, 1.0]}Copy the code

Shader code:

// 顶点着色器
uniform   mat4  u_matrix;
attribute vec2  a_pos;
attribute vec4  inColor;
varying   vec4  outColor;
void main() {   
      outColor     = inColor;
      gl_Position  = u_matrix * vec4(a_pos,0 , 1.0); 
      gl_PointSize = 2.0;
}
// 片元着色器
precision  lowp  float;
varying    vec4  outColor;
void main() {
    gl_FragColor = outColor;
}
Copy the code

Variable reads data from the buffer:

Coordinates and colors are stored in the same buffer and define the variable’s strategy for reading data from the buffer.

// Gl. vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 4 * 6, 0); InColor, 4, gl.float, false, 4 * 6, 4 * 2); // Point color gl.vertexattribpointer (this.inColor, 4, gl.float, false, 4 * 6, 4 * 2); Gl. drawArrays(gl.POINTS, 0, pointLen);Copy the code

Results:

2. Discrete point + elevation

A height attribute can be added to the point. For example, the higher the wind force is, the higher the height of the sampling point is. The wind force value can be reduced by 350 times as the height of the current sampling point. The buffered data created adds a dimension to the height attribute. As a result, the strategy for reading data from the variable buffer has also changed.

0: 0 // x 1: 0.17729912095967093 // y 2: 0.5725490196078431 // r 3: 0.42745098039215684 // g 4: 0 // b 5: 1 // w 6: 0.04894285714285714 // Wind /350 7: 0.0027777777777778 8: 0.17729912095967093 9: 0.592156862745098 10: 0.40784313725490196 11: 0 12:1 13:0.05077142857142857 14: 0.0055555555555556 15: 0.17729912095967093 16: 0.5843137254901961 17: 0.41568627450980394 18: 0 19:1 20: 0.050057142857142856 21: 0.0083333333333333Copy the code
// Gl. vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 4 * 7, 0); InColor, 4, gl.float, false, 4 * 7, 4 * 2); // Colorgl.vertexattribpointer (this.inColor, 4, gl.float, false, 4 * 7, 4 * 2); // Gl. vertexAttribPointer(this.pointH, 1, gl.FLOAT, false, 4 * 7, 4 * 6);Copy the code

Results:

3, the interpolation

After webGL draws a triangle and specifies the colors of three vertices, webGL will interpolate the colors of each pixel according to the colors of the three vertices during rasterization. Previously, we used TRIANGLES to determine the color of each vertex based on the force of the wind. So if we change the type of TRIANGLES to TRIANGLES, WebGL will interpolate when using raster.

Drawing a triangle requires supplying webGL with three points, and drawing a rectangle requires six points, since a rectangle is made up of two triangles. Of course, gl.triangle_strip can draw a rectangle with four points.

Here, a global wind sampling point of 1 degree is needed to construct a triangulation network, that is, the order of drawing points needs to be determined. Global 1 degree division, a total of 150 lines and 360 columns, traversed by line, from left to right, with one-dimensional array storage data. The array index indicates the sampling point number.

Construct a three-level grid using the array index number to represent the array items, for example: the first triangle consists of (0,1,361) three points,

The second triangle consists of (1,361,362) three points, and the third triangle consists of (1,2,362). If you build the buffer this way, you repeat two points for every two triangles drawn. Therefore, an index buffer is constructed on the basis of the original buffer, and the triangular vertex index is specified by the index number. In the case of a large amount of data, the amount of data passed into the video memory can be reduced.To build a vertex index, refer to the following code

Function getElementIndex() {var arr = []; for (var i = 0; i < 150; i++) { for (var j = 0; j < 360; j++) { var x = j + 361 * i; var y = x + 1; var z = x + 361; var arr1 = [x, y, z, y, z, ++z]; arr.push(... arr1); } } return arr; }Copy the code

Draw the triangle mesh using LINE_STRIP

gl.drawElements(gl.LINE_STRIP, indexEle.length, gl.UNSIGNED_SHORT, 0);
Copy the code

Change the palette to Say, Using TRIANGLES, so the rasterized fill interpolates based on the node color

gl.drawElements(gl.TRIANGLES, indexEle.length, gl.UNSIGNED_SHORT, 0);
Copy the code

Also, modify the point shader to set the height, the redder the color, the higher the height, the more wind.

Gl_Position = u_matrix * vec4(a_pos,pointH, 1.0);Copy the code

4. Multi-day global wind animation

If the global wind thermal map at a certain time can be drawn, the wind at multiple times can be drawn in the same way, and the multi-time meteorological heat map can be realized by constantly switching in the circular animation.

Through the processing of ten days, twenty-one moments (12 hour interval time resolution) of the data, a total of 14M. Here is to load all the data at one time, according to different times to build different buffers, in the animation cycle constantly cut drawing buffer to achieve animation drawing.