如何基于世界坐标计算立方体贴图球面(CubeSphere)的切线向量?
Hey there! This is a tricky but interesting geometry problem—let's break it down step by step to find a clean, mathematically sound solution.
First, let's recap what we need: the tangent vector points along the positive UV x-axis direction in world space, and it must be perpendicular to the cubesphere's normal (which is just normalize(uvw) for any world space point uvw).
Key Insight: Derive from the UV Generation Logic
Your provided HLSL code for generating cubesphere UVs holds the key to solving this. Let's focus on one face first (say, the positive X face) since you mentioned handling other faces via rotation/flipping is trivial.
For the positive X face (where abs(x) > abs(y) and abs(x) > abs(z), plus x > 0):
The UV x-coordinate u is calculated as:
vec2 uv = v.yz/v.x; // v = uvw, so uv = (y/x, z/x) vec2 distort = 1.45109572583 - 0.451095725826*abs(uv); uv *= distort; float u = 0.5 + 0.5*uv.x;
Simplifying, u depends on y/x (scaled by the distortion factor). To find the direction of increasing u, we compute the gradient of u with respect to world coordinates (x,y,z).
The critical observation here is that the distortion factor is a positive scalar multiplier—it affects the magnitude of the gradient but not its direction. After computing partial derivatives and simplifying, we find the direction of maximum u increase is perpendicular to both the normal and the X-axis:
vec3 tangent = normalize(vec3(-y, x, 0));
Let's verify with examples:
- At the center of the positive X face
(1,0,0), the tangent is(0,1,0)—which makes sense, since moving along positive Y increasesufrom 0 to 1. - At
(1,1,0), the tangent isnormalize(vec3(-1,1,0))—moving in this direction increasesy/x(sinceygoes up andxgoes down slightly), which in turn increasesu.
Extending to All Faces
Using the same logic, we can derive the tangent vector for every face of the cubesphere. Here's the complete set of formulas:
Positive X face (
abs(x) ≥ abs(y), abs(x) ≥ abs(z), x > 0):vec3 tangent = normalize(vec3(-uvw.y, uvw.x, 0));Negative X face (
abs(x) ≥ abs(y), abs(x) ≥ abs(z), x < 0):vec3 tangent = normalize(vec3(-uvw.y, uvw.x, 0));(The negative
xautomatically adjusts the direction to match increasingu.)Positive Y face (
abs(y) ≥ abs(x), abs(y) ≥ abs(z), y > 0):vec3 tangent = normalize(vec3(0, -uvw.z, uvw.y));Negative Y face (
abs(y) ≥ abs(x), abs(y) ≥ abs(z), y < 0):vec3 tangent = normalize(vec3(0, -uvw.z, uvw.y));Positive Z face (
abs(z) ≥ abs(x), abs(z) ≥ abs(y), z > 0):vec3 tangent = normalize(vec3(uvw.z, 0, -uvw.x));Negative Z face (
abs(z) ≥ abs(x), abs(z) ≥ abs(y), z < 0):vec3 tangent = normalize(vec3(uvw.z, 0, -uvw.x));
Why This Works (and Naive Approaches Don't)
- The naive approach of using
cross(Normal, vec3(0,1,0))fails because it doesn't account for how UVs are warped across the cubesphere face—this formula correctly aligns with the actual direction of increasingu. - Unlike approximate solutions, these formulas are mathematically derived directly from the UV generation logic, so they'll match the tangent direction you'd get from a precomputed cubesphere mesh.
Implementing This in Code
In your shader, first determine which face the current world vector is on, then apply the corresponding formula:
vec3 uvw = normalize(worldPosition); // Your world space vector vec3 n = abs(uvw); vec3 tangent; if(n.x > n.y && n.x > n.z) { tangent = normalize(vec3(-uvw.y, uvw.x, 0)); } else if(n.y > n.z) { tangent = normalize(vec3(0, -uvw.z, uvw.y)); } else { tangent = normalize(vec3(uvw.z, 0, -uvw.x)); }
This will give you the correct unit tangent vector aligned with the positive UV x-axis in world space, no precomputed mesh data required!
备注:内容来源于stack exchange,提问作者geht sie gar nix an

