8
votes

I could not found example of THREE.BufferGeometry with texture coordinates. Is it supposed to be used for textured mesh? I can't get it to work. Here is my test code:

var quad_vertices =
[
    -30.0,  30.0, 0.0,
     30.0,  30.0, 0.0,
     30.0, -30.0, 0.0,
    -30.0, -30.0, 0.0
];

var quad_uvs =
[
    0.0, 0.0,
    1.0, 0.0,
    1.0, 1.0,
    0.0, 1.0
];

var quad_indices =
[
    0, 2, 1, 0, 3, 2
];

var geometry = new THREE.BufferGeometry();

geometry.attributes =
{
    position:
    {
        itemSize: 3,
        array: new Float32Array(3 * 4)
    },

    uv:
    {
        itemSize: 2,
        array: new Float32Array(2 * 4)
    },

    index:
    {
        itemSize: 1,
        array: new Uint16Array(6)
    }
};

var positions = geometry.attributes.position.array;
var uvs       = geometry.attributes.uv.array;
var indices   = geometry.attributes.index.array;

var i;
for(i = 0; i < positions.length; i += 3)
{
    positions[i]     = quad_vertices[i];
    positions[i + 1] = quad_vertices[i + 1];
    positions[i + 2] = quad_vertices[i + 2];
}

for(i = 0; i < uvs.length; i += 2)
{
    uvs[i]     = quad_uvs[i];
    uvs[i + 1] = quad_uvs[i + 1];
}

for(i = 0; i < indices.length; i++)
    indices[i] = quad_indices[i];

var texture = THREE.ImageUtils.loadTexture('./assets/texture.png');
texture.anisotropy = renderer.getMaxAnisotropy();

var material = new THREE.MeshBasicMaterial( { map: texture } );

var mesh = new THREE.Mesh(geometry, material);

mesh.position.z = -100;

scene.add(mesh);

Just creating mesh with THREE.Geometry is OK so I have no idea what can be wrong with this code. Any thoughts?

2
Look at THREE.BufferGeometryUtils.fromGeometry( geometry, settings ). Also, quads are no longer supported by three.js. Update to r.61.WestLangley
I don't have to create BufferGeometry from Geometry, right? So that would be nothing more than a workaround. Also I am not using three.js quads.user2904567
You do not, but if you used that function once, you would at least see how the BufferGeometry was created.WestLangley
Can't see what is wrong with my code even after reading BufferGeometryUtils.js Seems like there are no indexes created but does that matter? They are created in threejs.org/examples/#webgl_buffergeometry and everything is OK. But not with my code. Nothing is rendered at all, not even some corrupted geometry.user2904567
I think you may need to create an offset for the geometry. See my question & answer here: stackoverflow.com/questions/19613281/…Joel

2 Answers

10
votes

Here is a working example of indexed BufferGeometry with uvs. I updated your example to work with three.js r83. I saw two problems with the old code. First, you can't just set geometry.attributes equal to a JSON object definition. THREE.BufferAttribute is a class, but your JSON is missing the function definitions on its prototype that are required by the THREE.Renderer. Second THREE.ImageUtils has been replaced by THREE.TextureLoader, so I updated that in the example as well.

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );

var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

var quad_vertices =
[
-30.0,  30.0, 0.0,
30.0,  30.0, 0.0,
30.0, -30.0, 0.0,
-30.0, -30.0, 0.0
];

var quad_uvs =
[
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0
];

var quad_indices =
[
0, 2, 1, 0, 3, 2
];

var geometry = new THREE.BufferGeometry();

var vertices = new Float32Array( quad_vertices );
// Each vertex has one uv coordinate for texture mapping
var uvs = new Float32Array( quad_uvs);
// Use the four vertices to draw the two triangles that make up the square.
var indices = new Uint32Array( quad_indices )

// itemSize = 3 because there are 3 values (components) per vertex
geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) );

// Load the texture asynchronously
var textureLoader = new THREE.TextureLoader();
textureLoader.load('./assets/texture.jpg', function (texture){
console.log('texture loaded');

var material = new THREE.MeshBasicMaterial( {map: texture });
var mesh = new THREE.Mesh( geometry, material );
mesh.position.z = -100;

scene.add(mesh);

renderer.render(scene, camera);
}, undefined, function (err) {
console.error('texture not loaded', err)
});

For further reference:

Creating a scene

BufferAttribute

3
votes

For those looking to combine an indexed buffer geometry with a texture and a custom shader material (I believe this approaches the upper bound of performance), I used the following approach. All of the real work happens in loadImage() and in the vertex and fragment shaders, the rest is just boilerplate to set up Three.js (version 92):

/**
* Generate a scene object with a background color
**/

function getScene() {
  var scene = new THREE.Scene();
  scene.background = new THREE.Color(0xffffff);
  return scene;
}

/**
* Generate the camera to be used in the scene. Camera args:
*   [0] field of view: identifies the portion of the scene
*     visible at any time (in degrees)
*   [1] aspect ratio: identifies the aspect ratio of the
*     scene in width/height
*   [2] near clipping plane: objects closer than the near
*     clipping plane are culled from the scene
*   [3] far clipping plane: objects farther than the far
*     clipping plane are culled from the scene
**/

function getCamera() {
  var aspectRatio = window.innerWidth / window.innerHeight;
  var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 1000);
  camera.position.set(0, 1, 10);
  return camera;
}

/**
* Generate the renderer to be used in the scene
**/

function getRenderer() {
  // Create the canvas with a renderer
  var renderer = new THREE.WebGLRenderer({antialias: true});
  // Add support for retina displays
  renderer.setPixelRatio(window.devicePixelRatio);
  // Specify the size of the canvas
  renderer.setSize(window.innerWidth, window.innerHeight);
  // Add the canvas to the DOM
  document.body.appendChild(renderer.domElement);
  return renderer;
}

/**
* Generate the controls to be used in the scene
* @param {obj} camera: the three.js camera for the scene
* @param {obj} renderer: the three.js renderer for the scene
**/

function getControls(camera, renderer) {
  var controls = new THREE.TrackballControls(camera, renderer.domElement);
  controls.zoomSpeed = 0.4;
  controls.panSpeed = 0.4;
  return controls;
}

/**
* Load image
**/

function loadImage() {

  var geometry = new THREE.BufferGeometry();

  /*
  Now we need to push some vertices into that geometry to identify the coordinates the geometry should cover
  */

  // Identify the image size
  var imageSize = {width: 10, height: 7.5};

  // Identify the x, y, z coords where the image should be placed
  var coords = {x: -5, y: -3.75, z: 0};

  // Add one vertex for each corner of the image, using the 
  // following order: lower left, lower right, upper right, upper left
  var vertices = new Float32Array([
    coords.x, coords.y, coords.z, // bottom left
    coords.x+imageSize.width, coords.y, coords.z, // bottom right
    coords.x+imageSize.width, coords.y+imageSize.height, coords.z, // upper right
    coords.x, coords.y+imageSize.height, coords.z, // upper left
  ])

  // set the uvs for this box; these identify the following corners:
  // lower-left, lower-right, upper-right, upper-left
  var uvs = new Float32Array([
    0.0, 0.0,
    1.0, 0.0,
    1.0, 1.0,
    0.0, 1.0,
  ])

  // indices = sequence of index positions in `vertices` to use as vertices
  // we make two triangles but only use 4 distinct vertices in the object
  // the second argument to THREE.BufferAttribute is the number of elements
  // in the first argument per vertex
  geometry.setIndex([0,1,2, 2,3,0])
  geometry.addAttribute('position', new THREE.BufferAttribute( vertices, 3 ));
  geometry.addAttribute('uv', new THREE.BufferAttribute( uvs, 2) )

  // Create a texture loader so we can load our image file
  var loader = new THREE.TextureLoader();

  // specify the url to the texture
  var url = 'https://s3.amazonaws.com/duhaime/blog/tsne-webgl/assets/cat.jpg';

  // specify custom uniforms and attributes for shaders
  // Uniform types: https://github.com/mrdoob/three.js/wiki/Uniforms-types
  var material = new THREE.ShaderMaterial({  
    uniforms: {
      texture: {
        type: 't',
        value: loader.load(url)
      },
    },
    vertexShader: document.getElementById('vertex-shader').textContent,
    fragmentShader: document.getElementById('fragment-shader').textContent
  });

  // Combine our image geometry and material into a mesh
  var mesh = new THREE.Mesh(geometry, material);

  // Set the position of the image mesh in the x,y,z dimensions
  mesh.position.set(0,0,0)

  // Add the image to the scene
  scene.add(mesh);
}

/**
* Render!
**/

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  controls.update();
};

var scene = getScene();
var camera = getCamera();
var renderer = getRenderer();
var controls = getControls(camera, renderer);
loadImage();

render();
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script type='x-shader/x-vertex' id='vertex-shader'>
  /**
  * The vertex shader's main() function must define `gl_Position`,
  * which describes the position of each vertex in the space.
  *
  * To do so, we can use the following variables defined by Three.js:        
  *   
  *   uniform mat4 modelViewMatrix - combines:
  *     model matrix: maps a point's local coordinate space into world space
  *     view matrix: maps world space into camera space
  *
  *   uniform mat4 projectionMatrix - maps camera space into screen space
  *
  *   attribute vec3 position - sets the position of each vertex
  *
  *   attribute vec2 uv - determines the relationship between vertices and textures
  *
  * `uniforms` are constant across all vertices
  *
  * `attributes` can vary from vertex to vertex and are defined as arrays
  * with length equal to the number of vertices. Each index in the array
  * is an attribute for the corresponding vertex
  *
  * `varyings` are values passed from the vertex to the fragment shader
  **/

  varying vec2 vUv; // pass the uv coordinates of each pixel to the frag shader

  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
</script>

<script type='x-shader/x-fragment' id='fragment-shader'>
  /**
  * The fragment shader's main() function must define `gl_FragColor`,
  * which describes the pixel color of each pixel on the screen.
  *
  * To do so, we can use uniforms passed into the shader and varyings
  * passed from the vertex shader
  **/

  precision highp float; // set float precision (optional)

  uniform sampler2D texture; // identify the texture as a uniform argument
  varying vec2 vUv; // identify the uv values as a varying attribute

  void main() {
    gl_FragColor = texture2D(texture, vUv);
  }
</script>