0

I know that in theory you have to first find the coordinates on the height map like (x = width HM / width Terrain * x Terrain) and y coordinate (y = height HM / height Terrain * y Terrain) and after getting the location on the height map, we get the actual height by min_height + (colorVal / (max_color - min_color) * *max_height - min_height) thus returning a Z value for a particular segment.

But how can i actually import the height map and take its parameters? I'm writing in javascript with no additional libraries (three,babylon).

edit

Currently i'm hardcoding the z values based on x and y ranges:

    Plane.prototype.modifyGeometry=function(x,y){
    if((x>=0&&x<100)&&(y>=0&&y<100)){
        return 25;
    }
    else if((x>=100&&x<150)&&(y>=100&&y<150)){
        return 20;
    }
    else if((x>=150&&x<200)&&(y>=150&&y<200)){
        return 15;
    }
    else if((x>=200&&x<250)&&(y>=200&&y<250)){
        return 10;
    }
    else if((x>=250&&x<300)&&(y>=250&&y<300)){
        return 5;
    }
    else{
        return 0;
    }

** edit **

i can get a flat grid (or with randomly generated heights), but as soon as i add the image data, i get a blank screen(no errors though). Here is the code (i changed it up a bit):

var gl;
var canvas;

var img = new Image();
// img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';


var gridWidth;
var gridDepth;
var gridPoints = [];
var gridIndices = [];
var rowOff = 0;
var rowStride = gridWidth + 1;
var numVertices = (gridWidth * 2 * (gridDepth + 1)) + (gridDepth * 2 * (gridWidth + 1));


//creating plane
function generateHeightPoints() {

    var ctx = document.createElement("canvas").getContext("2d"); //using 2d canvas to read image
    ctx.canvas.width = img.width;
    ctx.canvas.height = img.height;
    ctx.drawImage(img, 0, 0);
    var imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);

    gridWidth = imgData.width - 1;
    gridDepth = imgData.height - 1;

    for (var z = 0; z <= gridDepth; ++z) {
        for (var x = 0; x <= gridWidth; ++x) {
            var offset = (z * imgData.width + x) * 4;
            var height = imgData.data[offset] * 10 / 255;
            gridPoints.push(x, height, z);
        }
    }
}

function generateIndices() {
for (var z = 0; z<=gridDepth; ++z) {
    rowOff = z*rowStride;
    for(var x = 0; x<gridWidth; ++x) {
        gridIndices.push(rowOff+x,rowOff+x+1);
    }
}

for (var x = 0; x<=gridWidth; ++x) {
    for(var z = 0; z<gridDepth; ++z) {
        rowOff = z * rowStride;
        gridIndices.push(rowOff+x,rowOff+x+rowStride);
    }
}
}
//init

//program init
window.onload = function init()
{ 
canvas = document.getElementById( "gl-canvas" );


gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }

gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );




generateHeightPoints();
generateIndices();


var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints), 
gl.STATIC_DRAW);

    var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices), 
 gl.STATIC_DRAW);

var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );

var matrixLoc = gl.getUniformLocation(program, 'matrix');

var m4 = twgl.m4;
var projection = m4.perspective(60 * Math.PI / 180, gl.canvas.clientWidth / 
gl.canvas.clientHeight, 0.1, 100);
var cameraPosition = [-18, 15, -10];
var target = [gridWidth / 2, -10, gridDepth / 2];
var up = [0, 1, 0];
var camera = m4.lookAt(cameraPosition, target, up);
var view = m4.inverse(camera);
var mat = m4.multiply(projection, view);

gl.uniformMatrix4fv(matrixLoc, false, mat);




 render();




}



function render() {


gl.drawElements(gl.LINES, numVertices, gl.UNSIGNED_SHORT, 0);



gl.drawElements(gl.LINES,numVertices,gl.UNSIGNED_SHORT,0);
requestAnimFrame(render);
}
cosmo
  • 83
  • 1
  • 1
  • 7
  • what do you mean "import a height map". There is no standard format for a height map so there is no answer for "import a height map". Maybe you mean read a CSV file? A PNG file? A JPEG? a JSON file? – gman Dec 10 '19 at 07:07
  • You're not [waiting for the image to load](https://stackoverflow.com/questions/2342132/waiting-for-image-to-load-in-javascript) before trying to use it. – gman Dec 16 '19 at 04:06
  • @gman i figured window.onload waits until everything (including images) is loaded. When i only use the run function at img.onload, i get a TypeError (cannot read property of null) here: `gl = WebGLUtils.setupWebGL( canvas );` , but when i first use run, then window.onload init()... i still only get a blank screen – cosmo Dec 16 '19 at 08:48
  • The code you posted is not waiting for window.onload when using the image. It's calling `init` on onload but the image itself is used immediately. Further you compute `rowStride` out initializing `gridWidth`. I'd suggect you [learn to use a debugger and step through your code](https://developers.google.com/web/tools/chrome-devtools/javascript). Note: I haven't used windon.onload in > 8 years. It's not common and not the recommended way to do things in 2019. – gman Dec 16 '19 at 09:16

1 Answers1

3

You basically just make a grid of points and change the Z values.

First a flat grid

const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
  gl_Position = matrix * position;
}
`;
const fs = `
precision highp float;
void main() {
  gl_FragColor = vec4(0, 1, 0, 1);
}
`;

const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const matrixLoc = gl.getUniformLocation(program, 'matrix');

const gridWidth = 40;
const gridDepth = 40;
const gridPoints = [];
for (let z = 0; z <= gridDepth; ++z) {
  for (let x = 0; x <= gridWidth; ++x) {
    gridPoints.push(x, 0, z);
  }
}

const gridIndices = [];
const rowStride = gridWidth + 1;
// x lines
for (let z = 0; z <= gridDepth; ++z) {
  const rowOff = z * rowStride;
  for (let x = 0; x < gridWidth; ++x) {
    gridIndices.push(rowOff + x, rowOff + x + 1);
  }
}
// z lines
for (let x = 0; x <= gridWidth; ++x) {
  for (let z = 0; z < gridDepth; ++z) {
    const rowOff = z * rowStride;
    gridIndices.push(rowOff + x, rowOff + x + rowStride);
  }
}

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints), gl.STATIC_DRAW);

const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices), gl.STATIC_DRAW);

twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

const m4 = twgl.m4;
const projection = m4.perspective(
  60 * Math.PI / 180,   // field of view
  gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
  0.1,  // near
  100,  // far
);
const cameraPosition = [-gridWidth / 8, 10, -gridDepth / 8];
const target = [gridWidth / 2, -10, gridDepth / 2];
const up = [0, 1, 0];
const camera = m4.lookAt(cameraPosition, target, up);
const view = m4.inverse(camera);
const mat = m4.multiply(projection, view);

gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(
    positionLoc,  // location
    3,            // size
    gl.FLOAT,     // type
    false,        // normalize
    0,            // stride
    0,            // offset
);

gl.useProgram(program);
gl.uniformMatrix4fv(matrixLoc, false, mat);

const numVertices = (gridWidth * 2 * (gridDepth + 1)) +
                    (gridDepth * 2 * (gridWidth + 1));
gl.drawElements(
    gl.LINES,           // primitive type
    numVertices,        //
    gl.UNSIGNED_SHORT,  // type of indices
    0,                  // offset
);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

Grid with height map.

Here's a gray scale image we can use as a height map

Read it by loading a img, drawing to a 2D canvas, calling getImageData. Then just read the red values for height.

const img = new Image();
img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';


function run() {
  const gl = document.querySelector('canvas').getContext('webgl');
  const vs = `
  attribute vec4 position;
  uniform mat4 matrix;
  void main() {
    gl_Position = matrix * position;
  }
  `;
  const fs = `
  precision highp float;
  void main() {
    gl_FragColor = vec4(0, 1, 0, 1);
  }
  `;

  const program = twgl.createProgram(gl, [vs, fs]);
  const positionLoc = gl.getAttribLocation(program, 'position');
  const matrixLoc = gl.getUniformLocation(program, 'matrix');

  // use a canvas 2D to read the image
  const ctx = document.createElement('canvas').getContext('2d');
  ctx.canvas.width = img.width;
  ctx.canvas.height = img.height;
  ctx.drawImage(img, 0, 0);
  const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);

  const gridWidth = imgData.width - 1;
  const gridDepth = imgData.height - 1;
  const gridPoints = [];
  for (let z = 0; z <= gridDepth; ++z) {
    for (let x = 0; x <= gridWidth; ++x) {
      const offset = (z * imgData.width + x) * 4;
      // height 0 to 10
      const height = imgData.data[offset] * 10 / 255;
      gridPoints.push(x, height, z);
    }
  }

  const gridIndices = [];
  const rowStride = gridWidth + 1;
  // x lines
  for (let z = 0; z <= gridDepth; ++z) {
    const rowOff = z * rowStride;
    for (let x = 0; x < gridWidth; ++x) {
      gridIndices.push(rowOff + x, rowOff + x + 1);
    }
  }
  // z lines
  for (let x = 0; x <= gridWidth; ++x) {
    for (let z = 0; z < gridDepth; ++z) {
      const rowOff = z * rowStride;
      gridIndices.push(rowOff + x, rowOff + x + rowStride);
    }
  }

  const positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints), gl.STATIC_DRAW);

  const indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices), gl.STATIC_DRAW);

  twgl.resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  const m4 = twgl.m4;
  const projection = m4.perspective(
    60 * Math.PI / 180,   // field of view
    gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
    0.1,  // near
    100,  // far
  );
  const cameraPosition = [-10, 10, -10];
  const target = [gridWidth / 2, -10, gridDepth / 2];
  const up = [0, 1, 0];
  const camera = m4.lookAt(cameraPosition, target, up);
  const view = m4.inverse(camera);
  const mat = m4.multiply(projection, view);

  gl.enableVertexAttribArray(positionLoc);
  gl.vertexAttribPointer(
      positionLoc,  // location
      3,            // size
      gl.FLOAT,     // type
      false,        // normalize
      0,            // stride
      0,            // offset
  );

  gl.useProgram(program);
  gl.uniformMatrix4fv(matrixLoc, false, mat);

  const numVertices = (gridWidth * 2 * (gridDepth + 1)) +
                      (gridDepth * 2 * (gridWidth + 1));
  gl.drawElements(
      gl.LINES,           // primitive type
      numVertices,        //
      gl.UNSIGNED_SHORT,  // type of indices
      0,                  // offset
  );
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

Then instead of making a grid of lines make a grid of triangles. There's lots of ways to do that. You could put 2 triangle per grid square. This code put's 4. You also need to generate normals. I copied the code to generate normals from this article which is fairly generic normal generating code. Being a grid you could make a grid specific normal generator which would be faster since being a grid you know which vertices are shared.

This code is also using twgl because WebGL is too verbose but it should be clear how to do it in plain WebGL from reading the names of the twgl functions.

'use strict';

/* global twgl, m4, requestAnimationFrame, document */

const img = new Image();
img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';


function run() {
  // use a canvas 2D to read the image
  const ctx = document.createElement('canvas').getContext('2d');
  ctx.canvas.width = img.width;
  ctx.canvas.height = img.height;
  ctx.drawImage(img, 0, 0);
  const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);


  function getHeight(offset) {
    const v =  imgData.data[offset * 4]; // x4 because RGBA
    return v * 10 / 255; // 0 to 10
  }

  // first generate a grid of triangles, at least 2 or 4 for each cell
  // 
  //    2          4
  // +----+     +----+ 
  // |   /|     |\  /|
  // |  / |     | \/ |
  // | /  |     | /\ |
  // |/   |     |/  \|
  // +----+     +----+ 
  //

  const positions = [];
  const texcoords = [];
  const indices = [];

  const cellsAcross = imgData.width - 1;
  const cellsDeep = imgData.height - 1;
  for (let z = 0; z < cellsDeep; ++z) {
    for (let x = 0; x < cellsAcross; ++x) {
      const base0 = z * imgData.width + x;
      const base1 = base0 + imgData.width;

      const h00 = getHeight(base0);       const h01 = getHeight(base0 + 1);
      const h10 = getHeight(base1);
      const h11 = getHeight(base1 + 1);
      const hm = (h00 + h01 + h10 + h11) / 4;

      const x0 = x;
      const x1 = x + 1;
      const z0 = z;
      const z1 = z + 1;

      const ndx = positions.length / 3;
      positions.push(
        x0, h00, z0,
        x1, h01, z0,
        x0, h10, z1,
        x1, h11, z1,
        (x0 + x1) / 2, hm, (z0 + z1) / 2,
      );
      const u0 = x / cellsAcross;
      const v0 = z / cellsDeep;
      const u1 = (x + 1) / cellsAcross;
      const v1 = (z + 1) / cellsDeep;
      texcoords.push(
        u0, v0,
        u1, v0,
        u0, v1,
        u1, v1,
        (u0 + u1) / 2, (v0 + v1) / 2,
      );

  //         
  //      0----1 
  //      |\  /|
  //      | \/4|
  //      | /\ |
  //      |/  \|
  //      2----3 

      indices.push(
        ndx, ndx + 4, ndx + 1,
        ndx, ndx + 2, ndx + 4,
        ndx + 2, ndx + 3, ndx + 4,
        ndx + 1, ndx + 4, ndx + 3,
      );
    }
  }

  const maxAngle = 2 * Math.PI / 180;  // make them facetted
  const arrays = generateNormals({
    position: positions,
    texcoord: texcoords,
    indices,
  }, maxAngle);


  const gl = document.querySelector('canvas').getContext('webgl');

  const vs = `
  attribute vec4 position;
  attribute vec3 normal;
  attribute vec2 texcoord;

  uniform mat4 projection;
  uniform mat4 modelView;

  varying vec3 v_normal;
  varying vec2 v_texcoord;

  void main() {
    gl_Position = projection * modelView * position;
    v_normal = mat3(modelView) * normal;
    v_texcoord = texcoord;
  }
  `;

  const fs = `
  precision highp float;

  varying vec3 v_normal;
  varying vec2 v_texcoord;
  varying float v_modelId;

  void main() {
    vec3 lightDirection = normalize(vec3(1, 2, -3));  // arbitrary light direction

    float l = dot(lightDirection, normalize(v_normal)) * .5 + .5;
    gl_FragColor = vec4(vec3(0,1,0) * l, 1);
  }
  `;

  // compile shader, link, look up locations
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

  // make some vertex data
  // calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
  const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

  function render(time) {
    time *= 0.001;  // seconds

    twgl.resizeCanvasToDisplaySize(gl.canvas);

    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.CULL_FACE);

    const fov = Math.PI * 0.25;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const near = 0.1;
    const far = 100;
    const projection = m4.perspective(fov, aspect, near, far);

    const eye = [0, 10, 25];
    const target = [0, 0, 0];
    const up = [0, 1, 0];
    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);
    let modelView = m4.yRotate(view, time);
    modelView = m4.translate(modelView, imgData.width / -2, 0, imgData.height / -2)

    gl.useProgram(programInfo.program);

    // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

    // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
    twgl.setUniforms(programInfo, {
      projection,
      modelView,
    });  

    // calls gl.drawArrays or gl.drawElements
    twgl.drawBufferInfo(gl, bufferInfo);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);


  function generateNormals(arrays, maxAngle) {
    const positions = arrays.position;
    const texcoords = arrays.texcoord;

    // first compute the normal of each face
    let getNextIndex = makeIndiceIterator(arrays);
    const numFaceVerts = getNextIndex.numElements;
    const numVerts = arrays.position.length;
    const numFaces = numFaceVerts / 3;
    const faceNormals = [];

    // Compute the normal for every face.
    // While doing that, create a new vertex for every face vertex
    for (let i = 0; i < numFaces; ++i) {
      const n1 = getNextIndex() * 3;
      const n2 = getNextIndex() * 3;
      const n3 = getNextIndex() * 3;

      const v1 = positions.slice(n1, n1 + 3);
      const v2 = positions.slice(n2, n2 + 3);
      const v3 = positions.slice(n3, n3 + 3);

      faceNormals.push(m4.normalize(m4.cross(m4.subtractVectors(v1, v2), m4.subtractVectors(v3, v2))));
    }

    let tempVerts = {};
    let tempVertNdx = 0;

    // this assumes vertex positions are an exact match

    function getVertIndex(x, y, z) {

      const vertId = x + "," + y + "," + z;
      const ndx = tempVerts[vertId];
      if (ndx !== undefined) {
        return ndx;
      }
      const newNdx = tempVertNdx++;
      tempVerts[vertId] = newNdx;
      return newNdx;
    }

    // We need to figure out the shared vertices.
    // It's not as simple as looking at the faces (triangles)
    // because for example if we have a standard cylinder
    //
    //
    //      3-4
    //     /   \
    //    2     5   Looking down a cylinder starting at S
    //    |     |   and going around to E, E and S are not
    //    1     6   the same vertex in the data we have
    //     \   /    as they don't share UV coords.
    //      S/E
    //
    // the vertices at the start and end do not share vertices
    // since they have different UVs but if you don't consider
    // them to share vertices they will get the wrong normals

    const vertIndices = [];
    for (let i = 0; i < numVerts; ++i) {
      const offset = i * 3;
      const vert = positions.slice(offset, offset + 3);
      vertIndices.push(getVertIndex(vert));
    }

    // go through every vertex and record which faces it's on
    const vertFaces = [];
    getNextIndex.reset();
    for (let i = 0; i < numFaces; ++i) {
      for (let j = 0; j < 3; ++j) {
        const ndx = getNextIndex();
        const sharedNdx = vertIndices[ndx];
        let faces = vertFaces[sharedNdx];
        if (!faces) {
          faces = [];
          vertFaces[sharedNdx] = faces;
        }
        faces.push(i);
      }
    }

    // now go through every face and compute the normals for each
    // vertex of the face. Only include faces that aren't more than
    // maxAngle different. Add the result to arrays of newPositions,
    // newTexcoords and newNormals, discarding any vertices that
    // are the same.
    tempVerts = {};
    tempVertNdx = 0;
    const newPositions = [];
    const newTexcoords = [];
    const newNormals = [];

    function getNewVertIndex(x, y, z, nx, ny, nz, u, v) {
      const vertId =
          x + "," + y + "," + z + "," +
          nx + "," + ny + "," + nz + "," +
          u + "," + v;

      const ndx = tempVerts[vertId];
      if (ndx !== undefined) {
        return ndx;
      }
      const newNdx = tempVertNdx++;
      tempVerts[vertId] = newNdx;
      newPositions.push(x, y, z);
      newNormals.push(nx, ny, nz);
      newTexcoords.push(u, v);
      return newNdx;
    }

    const newVertIndices = [];
    getNextIndex.reset();
    const maxAngleCos = Math.cos(maxAngle);
    // for each face
    for (let i = 0; i < numFaces; ++i) {
      // get the normal for this face
      const thisFaceNormal = faceNormals[i];
      // for each vertex on the face
      for (let j = 0; j < 3; ++j) {
        const ndx = getNextIndex();
        const sharedNdx = vertIndices[ndx];
        const faces = vertFaces[sharedNdx];
        const norm = [0, 0, 0];
        faces.forEach(faceNdx => {
          // is this face facing the same way
          const otherFaceNormal = faceNormals[faceNdx];
          const dot = m4.dot(thisFaceNormal, otherFaceNormal);
          if (dot > maxAngleCos) {
            m4.addVectors(norm, otherFaceNormal, norm);
          }
        });
        m4.normalize(norm, norm);
        const poffset = ndx * 3;
        const toffset = ndx * 2;
        newVertIndices.push(getNewVertIndex(
            positions[poffset + 0], positions[poffset + 1], positions[poffset + 2],
            norm[0], norm[1], norm[2],
            texcoords[toffset + 0], texcoords[toffset + 1]));
      }
    }

    return {
      position: newPositions,
      texcoord: newTexcoords,
      normal: newNormals,
      indices: newVertIndices,
    };

  }

  function makeIndexedIndicesFn(arrays) {
    const indices = arrays.indices;
    let ndx = 0;
    const fn = function() {
      return indices[ndx++];
    };
    fn.reset = function() {
      ndx = 0;
    };
    fn.numElements = indices.length;
    return fn;
  }

  function makeUnindexedIndicesFn(arrays) {
    let ndx = 0;
    const fn = function() {
      return ndx++;
    };
    fn.reset = function() {
      ndx = 0;
    }
    fn.numElements = arrays.positions.length / 3;
    return fn;
  }

  function makeIndiceIterator(arrays) {
    return arrays.indices
        ? makeIndexedIndicesFn(arrays)
        : makeUnindexedIndicesFn(arrays);
  }
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/3d-math.js"></script>
<canvas></canvas>
gman
  • 100,619
  • 31
  • 269
  • 393
  • thank you very much sir, i'm getting a blank screen running the heighmap image, if you could please take a look at my code in the edit – cosmo Dec 15 '19 at 18:56
  • based on what do you determine the texture coordinates and what if i wanted to get them from an image? – cosmo Dec 23 '19 at 16:32
  • What do you mean "get texture coordinates from an image". You don't generally get texture coordinates from an image. [You use them to reference parts of an image](https://webglfundamentals.org/webgl/lessons/webgl-3d-textures.html). The texture coords in the example are not used. They just spread over the grid an a normal fashion so if you did use them they'd map an image flat on. https://jsfiddle.net/greggman/b6x0su5r/ – gman Dec 23 '19 at 17:29