# Spherical UV Projection

Ive got a tile im procedurally generating UVs using a simple spherical projection.

``````var uv = new float2(math.atan2(normal.y, normal.z) / (-2f * math.PI), math.asin(normal.x) / math.PI + 0.5f);
``````

Problem is the further I move away from origin the larger the texture projection is as show with the two tiles in the image. I know the radius from each tile to origin 0.0.0 so I feel there some fancy way to fix this. Please any help.

Are you normalizing the vector before putting it through your formula? Itâ€™s just a shot in the dark.

On second thought, it seems like your trig functions shouldnâ€™t depend on the magnitude of your vector. Could your problem be somewhere else?

Yea they are normalized. I canâ€™t see where this is going wrong elsewhere, very lost

Is that top pentagon the same size in worldspace as the yellow cutout on the one at the bottom? I infer that but I just wanted to ask to be sure, because from that perspective it obviously looks bigger.

I assume these are also using the same instance of the material, ie., same UV multipliers?

Edit: just noticed the bottom ones are hexagonsâ€¦ but are they the same diameter/radius (within reason).

yea same radius â€¦ what is going on here

Yea same material too , so the only real difference between the two tiles is where they are samples . Bottom tile is at y 0 and top at y 25. If I sample bottom tile at y 25 the texture tiling is the same.

It just feels like one of the shader ops is doing something more with local space. It canâ€™t be those trig functions so perhaps however you end up with the normal implies that it be also offset/scaled by local position???

EDIT: the above is NOT correctâ€¦ look below

Oh wait, I think I see it: if you are normalizing the vert positions, then the size of the x and the y will be consequentially smaller as the z (vertical) axis grows larger, which changes your mapping inputs.

Think about 2 points separated by 1 horizontal unit. Raise that 1 vertical and normalize you have 1 and 1, so 0.7071 for horizontal, or sqrt(2). something like (0.7, 0.7, 0.7)

Now lift up by 10 vertical unit and you have norm of (1, 1, 10): this normalizes out to something with much smaller x and y axes (something like (0.1, 0.1, 0.9));

FIX: I think maybe donâ€™t normalize the vertex positions before you put into your atan/asin funcs?

Ahaha, just re-read the top postsâ€¦ I think @eisenpony got it right first. I think normalize has to go.

Man something funky going on here, if I dont use normalized it doesnt even render

Arrâ€¦ youâ€™re right. Asin() wonâ€™t take anything above 1 or below -1â€¦ maybe use the normalized member for that input to asin() ?

Reason: Atan2 is computing its own ratio (as per opposite / adjacent)

Asin is implying a hypotenuse of 1.0 and accepts only the opposite side length.

do you have a different spherical type projection method I could try.

If you can give us a little more of your code, someone might notice a scaling factor. The one line of code from yesterday doesnâ€™t seem like it should depend on radius.

Best we can do is hazard guesses what is wrong with your normal vector.

Sorry, Iâ€™ve never attempted a spherical projection, so I donâ€™t have any examples I prefer.

Nothing fancy, what i did find is if I offset the the normal of y in the fist tile by the radius of the second the textures match

``````private static float3 GetGraphVertexPosition([ReadOnly] ref BlobAssetReference<PlanetGraphData> graph, int index)
{
return new float3(
graph.Value.Vertices[index].PositionX,
graph.Value.Vertices[index].PositionY,
graph.Value.Vertices[index].PositionZ);
}

private static float3 GetGraphVertexNormal([ReadOnly] float3 vertex)
{
return math.normalize(vertex);
}

private static float4 GetGraphVertexTangent([ReadOnly] float3 normal)
{
return new float4(-normal.z, 0, normal.x, -1);
}

private static float2 GetGraphVertexUv00([ReadOnly] float3 normal, bool axesVertical)
{

// TODO : https://gamedev.stackexchange.com/questions/136314/sphere-uv-mapping-texture-streched

if (axesVertical == true)
{
var uv = new float2(math.atan2(normal.x, normal.z) / (-2f * math.PI), math.asin(normal.y) / math.PI + 0.5f);
if (uv.x < 0f) uv.x += 1f;
return uv;
}
else
{
var uv = new float2(math.atan2(normal.y, normal.z)/ (-2f * math.PI), math.asin(normal.x) / math.PI + 0.5f);
if (uv.x < 0f) uv.x += 1f; // TODO maybe should be y
return uv;
}
}

//...

var posB = GetGraphVertexPosition(ref GraphInput, tri[1]);
var norB = GetGraphVertexNormal( posB);
var tanB = GetGraphVertexTangent( norB);
var uv0B = GetGraphVertexUv00(norB, true);
var vertexDataB = new PlanetVertexData
{
Position = posB,
Normal = norB,
Tangent = tanB,
UVs = uv0B,
UvBarycentric = new float2(0f, 1f),
Color = new float4(InputColor[tri[0]].AlbedoIndex, InputColor[tri[1]].AlbedoIndex, InputColor[tri[2]].AlbedoIndex, 0)
};

//...
``````

This is what I was suggesting in my Reply #11 above. Note the addition of the actual vector argument, which is un-normalized.

You would intuit this would not make a difference because the use is in a ratio, but I think it makes a difference on two subsequent calls to this UV-maker with linearly-spaced points being pulled further up the axis.

Try it. You just have to modify your call site to also supply the actual vert in addition to the normal.

``````private static float2 GetGraphVertexUv00_MOAR([ReadOnly] float3 actual, [ReadOnly] float3 normal, bool axesVertical)
{

// TODO : https://gamedev.stackexchange.com/questions/136314/sphere-uv-mapping-texture-streched

if (axesVertical == true)
{
var uv = new float2(math.atan2(actual.x, actual.z) / (-2f * math.PI), math.asin(normal.y) / math.PI + 0.5f);
if (uv.x < 0f) uv.x += 1f;
return uv;
}
else
{
var uv = new float2(math.atan2(actual.y, actual.z)/ (-2f * math.PI), math.asin(normal.x) / math.PI + 0.5f);
if (uv.x < 0f) uv.x += 1f; // TODO maybe should be y
return uv;
}
}
``````

OK that worked thanks so much appreciate the help

1 Like