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?
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
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With