Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Big Normal Question

Discussion in 'Scripting' started by marty, Jun 28, 2007.

  1. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    If I cast a ray downward, using something like this:

    var hit : RaycastHit;
    Physics.Raycast (transform.position, transform.TransformDirection (-Vector3.up), hit);

    How can I then calculate the average normal of the triangle hit and its immediate neighbors?

    Plus, how can I make it so that closer neighboring trianlges are weighted more heavily in the average, proportionate to how close they are?
     
  2. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    I can't figure out a way to do exactly what you want. Would it suffice to cast multiple rays and average the normals of their hits?
     
  3. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    you can use the face index of the raycast and then interpolate between the normals of the 3 vertices based on the barycentric coordinate provided by the raycast hit.
     
  4. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    that's not even english :wink:
     
  5. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    I tried to strip down the portion of the "Painted Surface" Procedural demo code that does this, and got the attached. It doesn't seem to work though.

    Plus, even if that code worked, it would only give me one average normal for a given face. What I would like is for the average normal to be unique even as I move across the face whose triangle is being hit by the ray, since the neighboring triangles are weighted based on distance to the ray hit point.

    The net result that I'm after is being able to very smoothly track a transfrom across a "grainy" (low-poly) surface, via this averaging of the normal data.


    Code (csharp):
    1.        
    2. var hit : RaycastHit;  
    3. ray = Physics.Raycast (transform.position, transform.TransformDirection (-Vector3.up), hit);
    4.            
    5. var filter : MeshFilter = hit.collider.GetComponent(MeshFilter);
    6. var mesh = filter.mesh;
    7. var position = filter.transform.InverseTransformPoint(hit.point);
    8.  
    9. var inRadius = 5.0;
    10. var vertices = mesh.vertices;
    11. var normals = mesh.normals;
    12. var sqrRadius = inRadius * inRadius;
    13.    
    14. //  calculate averaged normal of all surrounding vertices  
    15. var averageNormal = Vector3.zero;
    16.        
    17. for (var i=0;i<vertices.length;i++) {
    18.     var sqrMagnitude = (vertices[i] - position).sqrMagnitude;
    19.  
    20.     //  early out if too far away
    21.     if (sqrMagnitude > sqrRadius) continue;
    22.  
    23.         var distance = Mathf.Sqrt(sqrMagnitude);
    24.         var falloff = Mathf.Clamp01(1.0 - distance / inRadius);
    25.         averageNormal += normals[i];
    26.         averageNormal += falloff * normals[i];
    27. }
    28.            
    29. averageNormal = averageNormal.normalized;
    30.    
    31. transform.rotation = Quaternion.LookRotation (Vector3.Cross (averageNormal, transform.TransformDirection (Vector3.left)), averageNormal);
    32.  
    33. transform.position = hit.point + transform.TransformDirection (Vector3.up * surfaceOffset);
    34.  
    35.  
     
  6. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Thats exactly what my suggestion would do.
    The vertex normals stored in the mesh are smoothed already. That means, if you have a sphere, the vertex normals are exactly the average between all the bordering triangles. Thus when you interpolate like i mentioned above you get a smoothly changing interpolated normal. Exactly what you want.
     
  7. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    But I would only get one average normal per face, right?

    I want to be able to move across a face, casting the ray down, and get a new average normal with each bit of translation across the "surface" of that face.

    Would your suggestion accomplish that?

    And, if so, any chance you could pen a bit of the 3D Code Of The Gods you are referring to? Please, please, please? :)
     
  8. RockHound

    RockHound

    Joined:
    Apr 14, 2006
    Posts:
    132
    I've been helped a lot in these forums, but I haven't contributed a lot, so I'll take this opportunity.

    The following code will smoothly interpolate the normals. The top part of the Update function was taken directly from the triangleIndex documentation of RaycastHit, where the mouse is tracked to provide the raycast point. You can modify that as you need.

    The next part performs interpolation of the intersected triangle normals. At the bottom, the variable N is assigned the interpolated normal. I had (a form of) this code lying around in C#, so I had to translate it to JavaScript.

    Code (csharp):
    1.  
    2.  
    3. var P  : Vector3;
    4. var P1 : Vector3;
    5. var P2 : Vector3;
    6. var P3 : Vector3;
    7.  
    8. var N  : Vector3;
    9. var N1 : Vector3;
    10. var N2 : Vector3;
    11. var N3 : Vector3;
    12.  
    13.  
    14. function Update ()
    15. {
    16.     // Only if we hit something, do we continue
    17.     var hit : RaycastHit;
    18.     if (!Physics.Raycast (camera.ScreenPointToRay(Input.mousePosition), hit))
    19.         return;
    20.  
    21.     // Just in case, also make sure the collider also has a renderer
    22.     // material and texture
    23.     var meshCollider = hit.collider as MeshCollider;
    24.     if (meshCollider == null || meshCollider.sharedMesh == null)
    25.         return;
    26.  
    27.     var mesh : Mesh = meshCollider.sharedMesh;
    28.     var vertices = mesh.vertices;
    29.     var normals = mesh.normals;
    30.     var triangles = mesh.triangles;
    31.  
    32.     // Extract local space vertices that were hit
    33.     var P1 = vertices[triangles[hit.triangleIndex * 3 + 0]];
    34.     var P2 = vertices[triangles[hit.triangleIndex * 3 + 1]];    
    35.     var P3 = vertices[triangles[hit.triangleIndex * 3 + 2]];  
    36.  
    37.     // Extract normals
    38.     var N1 = normals[triangles[hit.triangleIndex * 3 + 0]];
    39.     var N2 = normals[triangles[hit.triangleIndex * 3 + 1]];    
    40.     var N3 = normals[triangles[hit.triangleIndex * 3 + 2]];  
    41.  
    42.     // Transform local space vertices to world space
    43.     var hitTransform : Transform = hit.collider.transform;
    44.     P1 = hitTransform.TransformPoint(P1);
    45.     P2 = hitTransform.TransformPoint(P2);
    46.     P3 = hitTransform.TransformPoint(P3);
    47.     N1 = hitTransform.TransformDirection(N1);
    48.     N2 = hitTransform.TransformDirection(N2);
    49.     N3 = hitTransform.TransformDirection(N3);
    50.     P = hit.point;
    51.  
    52.     // finally, interpolate vertex normals
    53.     var Nt = Vector3.Cross(P2-P1, P3-P1);
    54.     var Na = Vector3.Cross(P3-P2,  P-P2);
    55.     var Nb = Vector3.Cross(P1-P3,  P-P3);
    56.     var Nc = Vector3.Cross(P2-P1,  P-P1);
    57.  
    58.     // avoid divide by small
    59.     var N_norm_squared = Nt.sqrMagnitude;
    60.     if (N_norm_squared < 0.001)
    61.         Debug.Log("triangle vertices are too close");
    62.  
    63.     // compute barycentric coords
    64.     // taken from Pete Shirley's tiger book (Fundamentals of Computer Graphics, 1st Ed.), p. 46
    65.     var one_over = 1.0 / N_norm_squared;
    66.     var alpha = Vector3.Dot(Nt, Na) * one_over;
    67.     var beta = Vector3.Dot(Nt, Nb) * one_over;
    68.     var gamma = Vector3.Dot(Nt, Nc) * one_over;
    69.  
    70.     // Check if barycentric coords are within triangle
    71.     // We shouldn't have to perform this check because we assume
    72.     //  the triangle index returned by RayCast hit is correct
    73.     // --------
    74.     //if (alpha < 0 || alpha > 1 ||
    75.     //  beta < 0 ||  beta > 1 ||
    76.     //  gamma < 0 || gamma > 1)
    77.     //  Debug.Log("hit point is outside of triangle");
    78.  
    79.     // interpolate normal
    80.     N = N1*alpha + N2*beta + N3*gamma;
    81.     Debug.DrawLine(P, P + N, Color.green);
    82. }
    83.  
     
  9. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    Wow!

    That's amazing. And it works like a charm.

    You appear to be from the same planet of Supergeniusrobot Coders that Joe hails from, RockHound - great to have you here with us on Earth!

    ;-)
     
  10. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    BTW, RH, what did you develop this code for orignally?
     
  11. RockHound

    RockHound

    Joined:
    Apr 14, 2006
    Posts:
    132
    I used it to do the same type of smoothing for colors that you need for your normals. At the time, we were darkening our character based on the baked vertex lighting of the scene. I yanked the code when 1.6 (I think) introduced the textureCoord field in the RaycastHit struct, and we could simply use a light map.

    Which raises the question, maybe the 'normal' field of the RaycastHit struct is already doing smooth (barycentric) interpolation, in which case my contributed code is moot. If not, it could easily do so, since I assume uv's are interpolated similarly.
     
  12. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    You reading this, Joe? ;-)
     
  13. RockHound

    RockHound

    Joined:
    Apr 14, 2006
    Posts:
    132
    Hi Marty,

    I just remembered that the above code does not necessarily preserve the length of the interpolated normals. If you need the smoothed normal to have a similar length as the original three normals, you can replace this line (at the bottom of the Update function)

    Code (csharp):
    1. N = N1*alpha + N2*beta + N3*gamma;
    with this
    Code (csharp):
    1. N = N1*alpha + N2*beta + N3*gamma;
    2. N.Normalize();
    3. N *= N1.magnitude*alpha + N2.magnitude*beta + N3.magnitude*gamma;
    It adds some processing, but you probably won't see any performance hit.
     
  14. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    Thanks, RH.

    A non-normalized normal is okay for orienting a transform, right?

    Such as in this code snippet:

    Code (csharp):
    1. transform.rotation = Quaternion.LookRotation (Vector3.Cross (averagedNormal, transform.TransformDirection (Vector3.left)), averagedNormal);
     
  15. RockHound

    RockHound

    Joined:
    Apr 14, 2006
    Posts:
    132
    Yea, that works just fine without normalizing.