5

In a surface shader, given the world's up axis (and the others too), a world space position and a normal in world space, how can we rotate the worldspace position into the space of the normal?

That is, given a up vector and a non-orthogonal target-up vector, how can we transform the position by rotating its up vector?

I need this so I can get the vertex position only affected by the object's rotation matrix, which I don't have access to.

Here's a graphical visualization of what I want to do:

  • Up is the world up vector
  • Target is the world space normal
  • Pos is arbitrary

The diagram is bidimensional, but I need to solve this for a 3D space.

vector diagram

  • Is there a reason you're not passing the rotation matrix to the shader? That seems like the most obvious solution, so I'm assuming there's some information missing. – rutter Aug 27 '15 at 22:32
  • @rutter Unity doesn't expose the rotation matrix to the shader, according to 2 hours of Googling. If it were so easy! – This company is turning evil. Aug 27 '15 at 22:39
  • I haven't hacked much on Unity shaders, but [this manual page](http://docs.unity3d.com/Manual/SL-UnityShaderVariables.html) suggests that you should have several matrices available. It sounds like you'd want `UNITY_MATRIX_IT_MV`? – rutter Aug 27 '15 at 23:04
  • @rutter Isn't the inverese transpose of MV for converting from camera space to world space? What I need is something like `_World2Object`, but only the rotation part. – This company is turning evil. Aug 28 '15 at 04:50
  • I don't fully understand the math, but it's the same transformation that's [typically used](http://stackoverflow.com/questions/13654401/what-is-the-logic-behind-transforming-normals-with-the-transpose-of-the-inverse) to adjust normals from model space to world space. That seems like the same (or similar?) transformation you're seeking here. – rutter Aug 28 '15 at 05:22
  • @rutter Hmm, I did experiment doing `mul(UNITY_MATRIX_IT_MV, IN.worldPos)`, but this zeroes the length of the position? – This company is turning evil. Aug 28 '15 at 14:07
  • Nevermind, I screwed up. But now the value changes with camera movement! – This company is turning evil. Aug 28 '15 at 14:14
  • You said you want to get vertex position but you can't get it in the surface shader. I think you meant the pixel's world position. Anyways, since I am not experienced with shaders, I usually try random stuff until I get what I want. I hope this code gives you some idea, even if it is not what you want exactly. `float4 Pos=float4( IN.worldPos.x, IN.worldPos.y,IN.worldPos.z,1.0 ) - mul( _Object2World, float4( 1,1,1,1) )` – Gokhan Kurt Aug 30 '15 at 20:47
  • 1
    If you use the upper left 3x3 part of the `_World2Object` you get a matrix with only rotation and scaling. For which effect do you need the transformed position? The use case seems to be a bit unusual, maybe there is another approach to solve your problem. – Gnietschow Aug 31 '15 at 09:42
  • @Gnietschow: I've created a shader that generates a grid according to some parameters. This shader is placed on a plane, which can be rotated and scaled. What I need to do is that the grid projects correctly considering the rotation of the object, but not its scale. The position is irrelevant, a solution that does or doesn't account for it will be acceptable. This is because the grid scaling should only be driven by the material parameters, not the object scale. – This company is turning evil. Aug 31 '15 at 16:19

2 Answers2

0

Looks like you're trying to rotate pos by the same rotation that would transform up to new_up.

Using the rotation matrix found here, we can rotate pos using the following code. This will work either in the surface function or a supplementary vertex function, depending on your application:

// Our 3 vectors
float3 pos;
float3 new_up;
float3 up = float3(0,1,0);

// Build the rotation matrix using notation from the link above
float3 v = cross(up, new_up);
float s = length(v);  // Sine of the angle
float c = dot(up, new_up); // Cosine of the angle
float3x3 VX = float3x3(
    0, -1 * v.z, v.y,
    v.z, 0, -1 * v.x,
    -1 * v.y, v.x, 0
); // This is the skew-symmetric cross-product matrix of v
float3x3 I = float3x3(
    1, 0, 0,
    0, 1, 0,
    0, 0, 1
); // The identity matrix
float3x3 R = I + VX + mul(VX, VX) * (1 - c)/pow(s,2) // The rotation matrix! YAY!

// Finally we rotate
float3 new_pos = mul(R, pos);

This is assuming that new_up is normalized.

If the "target up normal" is a constant, the calculation of R could (and should) only happen once per frame. I'd recommend doing it on the CPU side and passing it into the shader as a variable. Calculating it for every vertex/fragment is costly, consider what it is you actually need.

If your pos is a vector-4, just do the above with the first three elements, the fourth element can remain unchanged (it doesn't really mean anything in this context anyway).

I'm away from a machine where I can run shader code, so if I made any syntactical mistakes in the above, please forgive me.

Aaron Krajeski
  • 757
  • 5
  • 18
-3

Not tested, but should be able to input a starting point and an axis. Then all you do is change procession which is a normalized (0-1) float along the circumference and your point will update accordingly.

using UnityEngine;
using System.Collections;

public class Follower : MonoBehaviour {

    Vector3 point;
    Vector3 origin = Vector3.zero;
    Vector3 axis = Vector3.forward;
    float distance;
    Vector3 direction;
    float procession = 0f;  // < normalized

    void Update() {
        Vector3 offset = point - origin;

        distance = offset.magnitude;
        direction = offset.normalized;

        float circumference = 2 * Mathf.PI * distance;

        angle = (procession % 1f) * circumference;

        direction *= Quaternion.AngleAxis(Mathf.Rad2Deg * angle, axis);
        Ray ray = new Ray(origin, direction);

        point = ray.GetPoint(distance);
    }
}
maraaaaaaaa
  • 7,749
  • 2
  • 22
  • 37