stevendesu
stevendesu

Reputation: 16841

Three.JS UV Mapping for partial spheres

In a previous question I was looking to map a stereographic projection onto a sphere for the sake of live-streaming a virtual reality event.

I ultimately learned quite a bit about UV mapping and was able to construct a very accurate mapping that looks incredible. However there's one thing I don't like about this mapping method: there's a hack where the bottom of the sphere has to map to "nothing", so I map it to a corner of the texture.

In an effort to improve upon this solution, I tried creating only a partial sphere by adjusting the theta length per the Three.JS documentation on SphereGeometry.

While I got the shape of the sphere right, I noticed something odd about the UV mapping:

var fov = 270;
var geometry = new THREE.SphereGeometry(
    1, // Radius
    50, // Horizontal segments
    50, // Vertical segments
    0, // Phi start
    2 * Math.PI, // Phi length
    Math.acos((360 - fov) * Math.PI / 180 / 2), // Theta start
    Math.PI - Math.acos((360 - fov) * Math.PI / 180 / 2) // Theta length
);
var minX, minY, minZ, maxX, maxY, maxZ;
var faceVertexUvs = geometry.faceVertexUvs[0];
for ( var i = 0; i < faceVertexUvs.length; i++ ) {
    var face = geometry.faces[i];
    for ( var j = 0; j < 3; j ++ ) {
        var x = face.vertexNormals[j].x;
        var y = face.vertexNormals[j].y;
        var z = face.vertexNormals[j].z;

        // Capture the upper and lower bounds for all points
        if (!minX || x < minX) minX = x;
        if (!maxX || x > maxX) maxX = x;
        if (!minY || y < minY) minY = y;
        if (!maxY || y > maxX) maxY = y;
        if (!minZ || z < minZ) minZ = z;
        if (!maxZ || z > maxZ) maxZ = z;
    }
}
console.log("minX: " + minX + ", maxX: " + maxX);
console.log("minY: " + minY + ", maxY: " + maxY);
console.log("minZ: " + minZ + ", maxZ: " + maxZ);

I learned previously when performing UV mapping that the x, y, and z values range from -1 to 1 - regardless of the actual size of the mesh. If the sphere had a radius of 1 or a radius of 100, an x value of 1 means "the far right of the sphere" and a y value of 1 means "the top of the sphere". However when I have an incomplete sphere like this (using thetaStart and thetaLength to cut a hole in the top of the sphere) suddenly maxY was equal to about 0.7854.

Why is it that this mesh ends at 0.7854 instead of scaling from -1 to 1? I had hoped that by changing the shape of the sphere I could simplify my UV mapping logic (removing the scaledY term from my previous question that I linked) however altering the shape of the sphere seems to have had no affect on the UV map.

Is there a way to tell Three.JS that this partial sphere is the full shape and its coordinates should range from -1 to 1?

Upvotes: 5

Views: 1710

Answers (1)

M -
M -

Reputation: 28497

I believe you're confusing Normals with UVs in your description and code snippet. You're reading the value of Normals above in these lines:

    var x = face.vertexNormals[j].x;
    var y = face.vertexNormals[j].y;
    var z = face.vertexNormals[j].z;

With your SphereGeometry attributes, the min and max y normals match the expected result: enter image description here

What you want to do is look at the min and max UV values. I've updated the code to get UV values:

var fov = 270;
var geometry:THREE.SphereGeometry = new THREE.SphereGeometry(
    2, // Radius
    50, // Horizontal segments
    50, // Vertical segments
    0, // Phi start
    2 * Math.PI, // Phi length
    Math.acos((360 - fov) * Math.PI / 180 / 2), // Theta start
    Math.PI - Math.acos((360 - fov) * Math.PI / 180 / 2) // Theta length
);
var minX, minY, minZ, maxX, maxY, maxZ;
var faceVertexUvs = geometry.faceVertexUvs[0];

for ( var i = 0; i < faceVertexUvs.length; i++ ) {
    var vertUV = faceVertexUvs[i];
    for ( var j = 0; j < 3; j ++ ) {
        var x = vertUV[j].x;
        var y = vertUV[j].y;

        // Capture the upper and lower bounds for all points
        if (!minX || x < minX) minX = x;
        if (!maxX || x > maxX) maxX = x;
        if (!minY || y < minY) minY = y;
        if (!maxY || y > maxX) maxY = y;
    }
}
console.log("minX: " + minX + ", maxX: " + maxX);
console.log("minY: " + minY + ", maxY: " + maxY);

When you run this code, you'll see that the range is always [0, 1], not [-1, 1]. Additionally, keep in mind that UVs don't have a Z value; only X and Y, since they're used to map a 2-dimensional image.

Normals:

  • x: (-1, 1)
  • y: (-1, 1)
  • z: (-1, 1)

UVs:

  • x: [0, 1]
  • y: [0, 1]

You can see how ThreeJS builds sphere UVs in this code, specifically in lines 90, 94, and 111.

Upvotes: 3

Related Questions