Потерянные фрагменты в моем шейдере

Я пытаюсь создать систему плиток в Threejs: зеленый для земли / синий для воды.

Я использую шейдер на PlaneBufferGeometry.

Вот что у меня так далеко:

Соответствующий код:

  • JS: переменная chunk и функция DoPlaneStuff() (оба в начале)
  • HTML: вершинный и фрагментный шейдеры

var chunk = {
  // number of width and height segments for PlaneBuffer
  segments: 32,
  // Heightmap: 0 = water, 1 = ground
  heightmap: [
    [1, 0, 0],
    [1, 1, 0],
    [1, 0, 1],
  ],
  // size of the plane
  size: 40
};

function DoPlaneStuff() {
  var uniforms = {
    heightmap: {
      type: "iv1",
      // transform the 2d Array to a simple array
      value: chunk.heightmap.reduce((p, c) => p.concat(c), [])
    },
    hmsize: {
      type: "f",
      value: chunk.heightmap[0].length
    },
    coord: {
      type: "v2",
      value: new THREE.Vector2(-chunk.size / 2, -chunk.size / 2)
    },
    size: {
      type: "f",
      value: chunk.size
    }
  };
  console.info("UNIFORMS GIVEN :", uniforms);
  var shaderMaterial = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: document.getElementById("v_shader").textContent,
    fragmentShader: document.getElementById("f_shader").textContent
  });
  var plane = new THREE.Mesh(
    new THREE.PlaneBufferGeometry(chunk.size, chunk.size, chunk.segments, chunk.segments),
    shaderMaterial
  );
  plane.rotation.x = -Math.PI / 2;
  scene.add(plane);
}


// --------------------- END OF RELEVANT CODE


window.addEventListener("load", Init);

function Init() {
  Init3dSpace();
  DoPlaneStuff();
  Render();
}

var camera_config = {
  dist: 50,
  angle: (5 / 8) * (Math.PI / 2)
}
var scene, renderer, camera;
function Init3dSpace() {
  scene = new THREE.Scene();
  renderer = new THREE.WebGLRenderer({
    antialias: true,
    logarithmicDepthBuffer: true
  });
  camera = new THREE.PerspectiveCamera(
    50,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  this.camera.position.y = camera_config.dist * Math.sin(camera_config.angle);
  this.camera.position.x = 0;
  this.camera.position.z = 0 + camera_config.dist * Math.cos(camera_config.angle);
  this.camera.rotation.x = -camera_config.angle;
  var light = new THREE.HemisphereLight(0xffffff, 10);
  light.position.set(0, 50, 0);
  scene.add(light);
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);
}

function Render() {
  renderer.render(scene, camera);
}
body {
  overflow: hidden;
  margin: 0;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
<!-- VERTEX SHADER -->
<script id="v_shader" type="x-shader/x-vertex">
  // size of the plane 
  uniform float size; 
  // coordinates of the geometry 
  uniform vec2 coord; 
  // heightmap size (=width and height of the heightmap)
  uniform float hmsize; 
  uniform int heightmap[9]; 
  varying float colorValue; 
  
  void main() { 
    int xIndex = int(floor( 
      (position.x - coord.x) / (size / hmsize)
    ));
    int yIndex = int(floor(
      (-1.0 * position.y - coord.y) / (size / hmsize)
    ));
    // Get the index of the corresponding tile in the array
    int index = xIndex + int(hmsize) * yIndex;
    // get the value of the tile
    colorValue = float(heightmap[index]);
  
    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  }
</script>
<!-- FRAGMENT SHADER -->
<script id="f_shader" type="x-shader/x-fragment">
  varying float colorValue; 
  
  void main() {
    // default color is something is not expected: RED
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    // IF WATER
    if (colorValue == 0.0) { 
      // BLUE
      gl_FragColor = vec4( 0.0, 0.0, 1.0, 1.0 );
    }
    // IF GROUND
    if (colorValue == 1.0) {
      // GREEN 
      gl_FragColor = vec4( 0.1, 0.6, 0.0, 1.0 );
    }
  }
</script>

Как вы можете видеть, это почти работает, но у меня есть эти красные линии, разделяющие зеленые и синие области, и я не могу понять, почему. Я называю эти красные фрагменты "потерянным", потому что они не отображаются ни на одну плитку, и я не могу понять, почему.

Я мог только заметить, что с большим значением chunk.segments (это число сегментов высоты и ширины для геометрии) У меня могут быть более тонкие красные линии.

Я хотел бы знать, как сделать градиентную заливку между зеленой и синей зонами вместо красной.

1 ответ

Решение

Красные линии сформированы треугольниками, у которых есть некоторые вершины, лежащие в основании плитки, и другие вершины в плитке воды. Затем графический процессор интерполирует colorValue вдоль треугольника, создавая плавный градиент со значениями от 0 до 1 вместо резкого шага, который вы, вероятно, ожидаете.

Есть несколько решений для этого. Вы можете изменить условие в своем шейдере, чтобы выбрать цвет на основе средней точки: если colorValue < 0,5, выходной синий, в остальном зеленый. Это не будет хорошо работать, если вы решите, что хотите больше типов плиток позже. Лучшим решением было бы создать вашу геометрию таким образом, чтобы все вершины всех треугольников лежали в одной плитке. Это будет включать удвоение вершин, которые лежат на границах плитки. Вы также можете добавить flat квалификатор интерполяции для colorValue, но сложнее контролировать, какой атрибут вершины будет использовать треугольник.

... Я только что заметил, что вы хотите градиент вместо резкого шага. Это даже проще. Вам нужно переместить код выбора цвета из фрагментного шейдера в вершинный шейдер и просто вернуть полученный интерполированный цвет в фрагментный шейдер.

Другие вопросы по тегам