WebGL Highmap путем смещения карт в соответствии с яркостью текстуры - PullRequest
0 голосов
/ 05 апреля 2020

Я новичок в webgl и opengl es, ниже вершинный шейдер показывает ошибку, которая генерирует только план. Фрагментный шейдер является типичным, он не предоставляется.

uniform mat4 modelview;
uniform mat4 transform;
uniform mat3 normalMatrix;
uniform mat4 texMatrix;
uniform sampler2D texture;
attribute vec4 vertex;
attribute vec4 color;
attribute vec3 normal;
attribute vec2 texCoord;
varying vec4 vertColor;
varying vec4 vertTexCoord;
const float zero_float = 0.0;
const float one_float = 1.0;
const vec3 zero_vec3 = vec3(0);
varying highp float height;


uniform float brightness;

void main() {
  //height =texture2D(texture,vec2(vertex.xz));
  //height =texture2D(texture,vec2(vertex.xz)).r;
  //gl_Position = transform * vertex;
    gl_Position = transform *vec4(vertex.x,vertex.y,brightness,1.0);
  vec3 ecVertex = vec3(modelview * vertex);
  vec3 ecNormal = normalize(normalMatrix * normal);
  vertTexCoord = texMatrix * vec4(texCoord, 1.0, 1.0);
}  

Вышеприведенный вершинный шейдер не работает Отображение highmap с помощью отображения смещения яркости текстурного изображения и смещения только плоскости с текстурой. Помогите, как вершины могут сдвигаться с поверхности сферы (исходной формы) в более высокое положение в соответствии с яркостью пикселей текстур (показать холмы как на поверхности сферы, высота холмов пропорциональна яркости пикселей текстуры)

1 Ответ

1 голос
/ 05 апреля 2020

Вы не можете просто переместить позицию

, представляя, что у вас есть четырехугольная плоскость 2x2

A--B--C
| /| /|
|/ |/ |
D--E--F
| /| /|
|/ |/ |
G--H--I

Точка E имеет одну нормальную перпендикулярную плоскость, но если вы переместите саму точку E перпендикулярно плоскости, внезапно ему нужна отдельная нормаля для каждого треугольника, который ее использует, 6 треугольников на диаграмме выше. И, конечно же, необходимо изменить и нормали других вершин.

Вам нужно будет вычислять новые нормали в шейдере фрагментов, используя стандартные производные.

function main() {
  const gl = document.querySelector('canvas').getContext('webgl');
  const ext = gl.getExtension('OES_standard_derivatives');
  if (!ext) {
    return alert('need OES_standard_derivatives');
  }

  const m4 = twgl.m4;
  const vs = `
  attribute vec4 position;
  attribute vec2 texcoord;

  uniform sampler2D displacementMap;
  uniform mat4 projection;
  uniform mat4 view;
  uniform mat4 model;

  varying vec3 v_worldPosition;

  void main() {
    float displacementScale = 10.0;
    float displacement = texture2D(displacementMap, texcoord).r * displacementScale;
    vec4 displacedPosition = position + vec4(0, displacement, 0, 0);
    gl_Position = projection * view * model * displacedPosition;
    v_worldPosition = (model * displacedPosition).xyz;
  }  
  `;

  const fs = `
  #extension GL_OES_standard_derivatives : enable

  precision highp float;

  varying vec3 v_worldPosition;

  void main() {
    vec3 dx = dFdx(v_worldPosition);
    vec3 dy = dFdy(v_worldPosition);
    vec3 normal = normalize(cross(dy, dx));
    
    // just hard code lightDir and color
    // to make it easy
    vec3 lightDir = normalize(vec3(1, -2, 3));
    float light = dot(lightDir, normal);
    vec3 color = vec3(0.3, 1, 0.1);
    
    gl_FragColor = vec4(color * (light * 0.5 + 0.5), 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.primitives.createPlaneBufferInfo(
      gl,
      96,  // width
      64,  // height
      96,  // quads across
      64,  // quads down
  );

  const tex = twgl.createTexture(gl, {
    src: 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png',
    minMag: gl.NEAREST,
    wrap: gl.CLAMP_TO_EDGE,
  });

  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 = 60 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const near = 0.1;
    const far = 200;
    const projection = m4.perspective(fov, aspect, near, far);

    const eye = [Math.cos(time) * 30, 10, Math.sin(time) * 30];
    const target = [0, 0, 0];
    const up = [0, 1, 0];
    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);
    const model = m4.identity();

    gl.useProgram(programInfo.program);

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

    // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
    twgl.setUniformsAndBindTextures(programInfo, {
      projection,
      view,
      model,
      displacementMap: tex,
    });

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

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas id="canvas"></canvas>

или просмотрев несколько точек на карте смещения или

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

  const m4 = twgl.m4;
  const vs = `
  attribute vec4 position;
  attribute vec2 texcoord;

  uniform sampler2D displacementMap;
  uniform mat4 projection;
  uniform mat4 view;
  uniform mat4 model;

  varying vec2 v_texcoord;

  void main() {
    float displacementScale = 10.0;
    float displacement = texture2D(displacementMap, texcoord).r * displacementScale;
    vec4 displacedPosition = position + vec4(0, displacement, 0, 0);
    gl_Position = projection * view * model * displacedPosition;
    v_texcoord = texcoord;
  }  
  `;

  const fs = `
  precision highp float;

  varying vec2 v_texcoord;

  uniform sampler2D displacementMap;

  void main() {
    // should make this a uniform so it's shared
    float displacementScale = 10.0;
    
    // I'm sure there is a better way to compute
    // what this offset should be
    float offset = 0.01;
    
    vec2 uv0 = v_texcoord;
    vec2 uv1 = v_texcoord + vec2(offset, 0);
    vec2 uv2 = v_texcoord + vec2(0, offset);
    
    float h0 = texture2D(displacementMap, uv0).r;
    float h1 = texture2D(displacementMap, uv1).r;
    float h2 = texture2D(displacementMap, uv2).r;
    
    vec3 p0 = vec3(uv0, h0 * displacementScale);
    vec3 p1 = vec3(uv1, h1 * displacementScale);
    vec3 p2 = vec3(uv2, h2 * displacementScale);
    
    vec3 v0 = p1 - p0;
    vec3 v1 = p2 - p0;
    
    vec3 normal = normalize(cross(v1, v0));
    
    // just hard code lightDir and color
    // to make it easy
    vec3 lightDir = normalize(vec3(1, -3, 2));
    float light = dot(lightDir, normal);
    vec3 color = vec3(0.3, 1, 0.1);
    
    gl_FragColor = vec4(color * (light * 0.5 + 0.5), 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.primitives.createPlaneBufferInfo(
      gl,
      96,  // width
      64,  // height
      96,  // quads across
      64,  // quads down
  );

  const tex = twgl.createTexture(gl, {
    src: 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png',
    minMag: gl.LINEAR,
    wrap: gl.CLAMP_TO_EDGE,
  });

  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 = 60 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const near = 0.1;
    const far = 200;
    const projection = m4.perspective(fov, aspect, near, far);

    const eye = [Math.cos(time) * 30, 10, Math.sin(time) * 30];
    const target = [0, 0, 0];
    const up = [0, 1, 0];
    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);
    const model = m4.identity();

    gl.useProgram(programInfo.program);

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

    // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
    twgl.setUniformsAndBindTextures(programInfo, {
      projection,
      view,
      model,
      displacementMap: tex,
    });

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

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas id="canvas"></canvas>

Обратите внимание, что вместо вычисления нормалей из 3 выборок текстуры вы, вероятно, могли бы предварительно вычислить их во время инициализации, просматривая карту высот и генерируя карту нормалей. , Вы можете поставить это как 3 канала с одинаковой текстурой. Как, скажем, RGB = нормальный и A = высота

async function main() {
  const gl = document.querySelector('canvas').getContext('webgl');
  
  const m4 = twgl.m4;
  const v3 = twgl.v3;
  const vs = `
  attribute vec4 position;
  attribute vec2 texcoord;

  uniform sampler2D displacementMap;
  uniform mat4 projection;
  uniform mat4 view;
  uniform mat4 model;

  varying vec2 v_texcoord;

  void main() {
    float displacementScale = 10.0;
    float displacement = texture2D(displacementMap, texcoord).a * displacementScale;
    vec4 displacedPosition = position + vec4(0, displacement, 0, 0);
    gl_Position = projection * view * model * displacedPosition;
    v_texcoord = texcoord;
  }  
  `;

  const fs = `
  precision highp float;

  varying vec2 v_texcoord;

  uniform sampler2D displacementMap;

  void main() {
    // should make this a uniform so it's shared
    float displacementScale = 10.0;
    
    vec3 data = texture2D(displacementMap, v_texcoord).rgb;
    vec3 normal = data * 2. - 1.;
    
    // just hard code lightDir and color
    // to make it easy
    vec3 lightDir = normalize(vec3(1, -3, 2));
    float light = dot(lightDir, normal);
    vec3 color = vec3(0.3, 1, 0.1);
    
    gl_FragColor = vec4(color * (light * 0.5 + 0.5), 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.primitives.createPlaneBufferInfo(
      gl,
      96,  // width
      64,  // height
      96,  // quads across
      64,  // quads down
  );

  const img = await loadImage('https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png');
  
  // get image data
  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, img.width, img.height);
  
  // generate normals from height data
  const displacementScale = 10;
  const data = new Uint8Array(imgData.data.length);
  for (let z = 0; z < imgData.height; ++z) {
    for (let x = 0; x < imgData.width; ++x) {
      const off = (z * img.width + x) * 4;
      const h0 = imgData.data[off];
      const h1 = imgData.data[off + 4] || 0;  // being lazy at edge
      const h2 = imgData.data[off + imgData.width * 4] || 0; // being lazy at edge
      const p0 = [x    , h0 * displacementScale / 255, z    ];
      const p1 = [x + 1, h1 * displacementScale / 255, z    ];
      const p2 = [x    , h2 * displacementScale / 255, z + 1];
      const v0 = v3.normalize(v3.subtract(p1, p0));
      const v1 = v3.normalize(v3.subtract(p2, p0));
      const normal = v3.normalize(v3.cross(v0, v1));
      data[off + 0] = (normal[0] * 0.5 + 0.5) * 255;
      data[off + 1] = (normal[1] * 0.5 + 0.5) * 255;
      data[off + 2] = (normal[2] * 0.5 + 0.5) * 255;
      data[off + 3] = h0;
    }
  }  

  const tex = twgl.createTexture(gl, {
    src: data,
    width: imgData.width,
    minMag: gl.LINEAR,
    wrap: gl.CLAMP_TO_EDGE,
  });

  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 = 60 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const near = 0.1;
    const far = 200;
    const projection = m4.perspective(fov, aspect, near, far);

    const eye = [Math.cos(time) * 30, 10, Math.sin(time) * 30];
    const target = [0, 0, 0];
    const up = [0, 1, 0];
    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);
    const model = m4.identity();

    gl.useProgram(programInfo.program);

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

    // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
    twgl.setUniformsAndBindTextures(programInfo, {
      projection,
      view,
      model,
      displacementMap: tex,
    });

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

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = _ => resolve(img);
    img.onerror = reject;
    img.crossOrigin = 'anonymous';
    img.src = url;
  });
}

main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas id="canvas"></canvas>
...