Как добавить текстуру к граням BufferGeometry.?

#javascript #three.js #texture-mapping

#javascript #three.js #текстурное отображение

Вопрос:

Я создал BufferGeometry, которая состоит из 5 плоскостей (100x25) с двумя треугольниками в каждой.

введите описание изображения здесь

 
function createGeometry() {
  var geometry = new THREE.PlaneGeometry(100, 25, 1);
  return geometry;
}

function createScene() {

    var bufferGeometry = new THREE.BufferGeometry();
    var radius = 125;
    var count = 5;

    var positions = [];
    var normals = [];
    var colors = [];

    var vector = new THREE.Vector3();
    var color = new THREE.Color( 0xffffff );
    var heartGeometry = createGeometry();
    var geometry = new THREE.Geometry();

    var step = 0;
    for ( var i = 1, l = count; i <= l; i    ) {

        geometry.copy( heartGeometry );

        const y = i * 30
        geometry.translate(-100, y, 0);

        // color.setHSL( ( i / l ), 1.0, 0.7 );

        geometry.faces.forEach( function ( face ) {
            positions.push( geometry.vertices[ face.a ].x );
            positions.push( geometry.vertices[ face.a ].y );
            positions.push( geometry.vertices[ face.a ].z );
            positions.push( geometry.vertices[ face.b ].x );
            positions.push( geometry.vertices[ face.b ].y );
            positions.push( geometry.vertices[ face.b ].z );
            positions.push( geometry.vertices[ face.c ].x );
            positions.push( geometry.vertices[ face.c ].y );
            positions.push( geometry.vertices[ face.c ].z );

            normals.push( face.normal.x );
            normals.push( face.normal.y );
            normals.push( face.normal.z );
            normals.push( face.normal.x );
            normals.push( face.normal.y );
            normals.push( face.normal.z );
            normals.push( face.normal.x );
            normals.push( face.normal.y );
            normals.push( face.normal.z );

            colors.push( color.r );
            colors.push( color.g );
            colors.push( color.b );
            colors.push( color.r );
            colors.push( color.g );
            colors.push( color.b );
            colors.push( color.r );
            colors.push( color.g );
            colors.push( color.b );

        });
    }
    bufferGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
    bufferGeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
    bufferGeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );


    var material = new THREE.MeshBasicMaterial({
        vertexColors: THREE.VertexColors,
        side: THREE.FrontSide
    });

    var mesh = new THREE.Mesh( bufferGeometry, material );
    scene.add( mesh );

}

  

Теперь вместо того, чтобы раскрашивать каждую плоскость как я могу добавить текст к каждой плоскости. Скажем, я просто хочу отобразить 1,2,3,4,5 в центре каждой плоскости.

Я знаю, что для добавления текстуры необходимо выполнить следующее.

  • Сгенерировать текстуру из холста
  • Измените материал на материал шейдера с помощью map:texture
  • Добавьте uv s в BufferGeometry.

Но какова связь между uv s и текстурой.

У меня есть функция создания текстуры

 //not sure for each character or one texture as a single texture
function createTexture(ch){

  var fontSize = 20;
  var c = document.createElement('canvas');
  c.width = 100;
  c.height = 25;
  var ctx = c.getContext('2d');
  ctx.font = fontSize 'px Monospace';
  ctx.fillText(ch, c.width/2, c.height/2);

  var texture = new THREE.Texture(c);
  texture.flipY = false;
  texture.needsUpdate = true;

  return texture;
}
  

Полный демонстрационный код

Редактировать

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

Самое близкое, что я нашел, — это это, но я не понял, какую именно технику они используют.

Комментарии:

1.Вам не нужно менять свой MeshBasicMaterial на ShaderMaterial , поскольку он уже обрабатывает map свойство. Все, что вам нужно сделать сейчас, это добавить атрибут uv к вашей геометрии в диапазоне [0, 1] , чтобы она следовала этой схеме [0, 0] в одном углу и [1, 1] в противоположном.

Ответ №1:

Вы должны скопировать первый uv-канал .faceVertexUvs свойства из THREE.Geometry формы, аналогично тому, как вы делаете это с координатами вершин и векторами нормалей:

 for ( var i = 1, l = count; i <= l; i    ) {

    geometry.copy( heartGeometry );

    const y = i * 30
    geometry.translate(-100, y, 0);

    geometry.faces.forEach( function ( face ) {
        let f = [face.a, face.b, face.c];
        for (let i=0; i < 3;   i) {
            positions.push( ...geometry.vertices[f[i]].toArray() );
            normals.push( ...face.normal.toArray() );
            colors.push( ...color.toArray() );
        }
    } );

    geometry.faceVertexUvs[0].forEach( function ( faceUvs ) {
        for (let i=0; i < 3;   i) {
            uv.push( ...faceUvs[i].toArray() );
        }
    } );
}
  

Если вы хотите определить несколько текстур для одной геометрии, то вам придется использовать несколько THREE.Material s для одной THREE.Mesh .

Определите группы (см. .addGroup ) для THREE.BufferGeometry . Каждая группа связывает диапазон вершин с материалом.

 var bufferGeometry = new THREE.BufferGeometry();
bufferGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
bufferGeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
bufferGeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
bufferGeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uv, 2 ) );

let materials = []
let v_per_group= positions.length / 3 / count;
for ( var i = 0; i < count; i    ) {
    bufferGeometry.addGroup(i * v_per_group, v_per_group, i);

    let material = new THREE.MeshBasicMaterial({ 
        vertexColors: THREE.VertexColors,
        side: THREE.FrontSide,
        map : createTexture("button"   (i 1))
    });
    materials.push(material);
}

var mesh = new THREE.Mesh( bufferGeometry, materials );
scene.add( mesh );  
  

 var camera, scene, renderer, controls, stats;
init();
animate();


function init() {
  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera( 45.0, window.innerWidth / window.innerHeight, 100, 1500.0 );
  camera.position.z = 480.0;
  scene.add( camera );
  controls = new THREE.TrackballControls( camera, renderer.domElement );
  controls.minDistance = 100.0;
  controls.maxDistance = 800.0;
  controls.dynamicDampingFactor = 0.1;
  
  scene.add( new THREE.AmbientLight( 0xffffff, 1 ) );
  
  createScene();
  //stats = new Stats();
  //document.body.appendChild( stats.dom );
  window.addEventListener( 'resize', onWindowResize, false );
}

function createGeometry() {
  var geometry = new THREE.PlaneGeometry(100, 25, 1);
  return geometry;
}

function createTexture(ch){

  var fontSize = 20;
  var c = document.createElement('canvas');
  c.width = 128;
  c.height = 32;
  var ctx = c.getContext('2d');
  
  ctx.beginPath();
  ctx.rect(0, 0, 128, 32);
  ctx.fillStyle = "white";
  ctx.fill();

  ctx.fillStyle = "black";
  ctx.font = fontSize 'px Monospace';
  ctx.fillText(ch, 20, 24);

  var texture = new THREE.Texture(c);
  texture.flipY = true;
  texture.needsUpdate = true;

  return texture;
}

function createScene() {
  
  var radius = 125;
  var count = 5;
  
  var vector = new THREE.Vector3();
  var color = new THREE.Color( 0xffffff );
  var heartGeometry = createGeometry();
  var geometry = new THREE.Geometry();
  
  var positions = [];
  var normals = [];
  var colors = [];
  var uv = [];

  for ( var i = 1, l = count; i <= l; i    ) {

      geometry.copy( heartGeometry );

      const y = i * 30
      geometry.translate(-100, y, 0);

      geometry.faces.forEach( function ( face ) {
          let f = [face.a, face.b, face.c];
          for (let i=0; i < 3;   i) {
              positions.push( ...geometry.vertices[f[i]].toArray() );
              normals.push( ...face.normal.toArray() );
              colors.push( ...color.toArray() );
          }
      } );

      geometry.faceVertexUvs[0].forEach( function ( faceUvs ) {
          for (let i=0; i < 3;   i) {
              uv.push( ...faceUvs[i].toArray() );
          }
      } );
  }

  var bufferGeometry = new THREE.BufferGeometry();
  bufferGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
  bufferGeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
  bufferGeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
  bufferGeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uv, 2 ) );

  let materials = []
  let v_per_group = positions.length / 3 / count;
  for ( var i = 0; i < count; i    ) {
      bufferGeometry.addGroup(i * v_per_group, v_per_group, i);
      
      let material = new THREE.MeshBasicMaterial({ 
          vertexColors: THREE.VertexColors,
          side: THREE.FrontSide,
          map : createTexture("button"   (i 1))
      });
      materials.push(material);
  }
      
  var mesh = new THREE.Mesh( bufferGeometry, materials );
  scene.add( mesh );   
}
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
  requestAnimationFrame( animate );
  controls.update();
  //stats.update();
  renderer.render( scene, camera );
}  
 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/102/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>  

Комментарии:

1. да, отдельная сетка для каждого текста будет работать, но это не будет масштабируемым решением. Это увеличит количество ячеек в сцене и, следовательно, fps снизится, если мы выйдем за пределы, скажем, 10K.

2. Итак, вы хотите 1 сетку с 5 текстурами (материалами)?

3. Да .. точно .. как и в моем коде, я использовал только одну сетку.

4. @Sarath Вы должны определить группы для геометрии буфера. Я изменил ответ.

5. @Sarath «атлас текстур» означает, что у вас есть одна текстура (например, все символы шрифта подряд). Координаты текстуры «выделяют» прямоугольную область из текстуры. Координаты каждого глифа хранятся в «атласе». Для каждого глифа должен быть отрисован отдельный квадрат, но все квадраты могут быть сохранены в одном буфере и могут быть нарисованы с помощью одной инструкции рисования одновременно.