Morphing

Discussion in 'Shaders' started by slgooding, May 24, 2010.

  1. slgooding

    slgooding

    Member

    Joined:
    Jan 12, 2009
    Messages:
    112
    Would it be possible to create a shader that would allow me to morph an object? The idea is to get vertex transformations to run through hardware instead of software. I'm looking for functionality similar to this:

    http://forum.unity3d.com/viewtopic.php?t=16425&highlight=morphtargets

    I guess the key question is whether or not I can transform vertices using a shader. From there we'd have to figure out how to create targets.

    Thanks!
     
  2. Kuba

    Kuba

    Unity Technologies

    Joined:
    Jan 13, 2009
    Messages:
    225
    Hey,
    you can easily transform vertices in the vertex shader, actually, that's what the vertex shader does :)

    Normally the vertex shader just multiplies the vertex position by the model-view-projection matrix (excerpt from UnityCG.cginc):
    Code (csharp):
    1. pos = mul( glstate.matrix.mvp, v );
    Morphing is just changing the vertex position v before the multiplication by the MVP matrix happens.

    The problem is though, how to create morph targets -- so to know what additional transformation to apply per vertex.

    Unfortunately Unity does not support vertex streams so you would have to pass the information to the vertex shader in any of the per vertex data (tangents, bone weights, normals, uvs) you're not currently using.

    If your transformation can be described procedurally, then you can just code that in the vertex shader.

    A good starting point would be to see how can it be done with vertex streams and not do it this way:
    http://http.developer.nvidia.com/GPUGems/gpugems_ch04.html
     
  3. slgooding

    slgooding

    Member

    Joined:
    Jan 12, 2009
    Messages:
    112
    Thanks for the help with direction. Unfortunately I don't think I can do it procedurally. I could however have the vertex positions beforehand. Would it be possible to code the target positions into the shader? I'm talking about thousands of vertices...

    Read through some of that link and it looks like this method is all done on hardware and (if I get it working) would be significantly faster than the software morph script.

    Thanks again!
     
  4. Kuba

    Kuba

    Unity Technologies

    Joined:
    Jan 13, 2009
    Messages:
    225
    Let's start with the most important question:
    why do you need to do it on the hardware? :)

    Since Unity doesn't aid you in doing so, maybe calculating that on the CPU will be perfectly fine?
    You could just try the code from the post you referred to and profile it with your scenes.

    Anyways, how many morph targets do you need? Just one additional? If so, that can be done on the GPU with Unity. You would need one mesh. Mesh.vertices would store the vertices of the original mesh, Mesh.tangents would store the vertices of the morph target. In the vertex shader you would then lerp between the two:
    Code (csharp):
    1. float4 finalVertex = lerp(v, tangent, _BlendAmount);
    where _BlendAmount is a property set on the material (Material.SetFloat).
     
  5. slgooding

    slgooding

    Member

    Joined:
    Jan 12, 2009
    Messages:
    112
    Kuba,

    I'm working on this solution and I'm not sure exactly how I need to approach it (in terms of giving the shader the vertices and tangents). From what I see I can use the appdata_base and that will give me the information I need for the base shape (based on the object that I have the shader applied to). How do I go about sending the tangents to the shader? I don't have a lot of background with Cg (basically learning as I go). Is there a different way of sending the vertex and tangent information to the shader?

    I may not even be asking the right question...

    Thanks again.
     
  6. slgooding

    slgooding

    Member

    Joined:
    Jan 12, 2009
    Messages:
    112
    Okay, so I think I have the basics of it working. The only problem I'm running into now is applying the tangents of the target mesh to the base mesh. I've tried doing this through a script, but I don't know how to overwrite the tangents that are already on the geometry.

    Sooooo close....here is what I have right now...

    Code (csharp):
    1.  
    2. Shader "Blend 2 Shapes2" {
    3.  
    4.     Properties {
    5.  
    6.         _Blend ("Blend", Range (-1, 1) ) = 0.0
    7.  
    8.         _Color ("Main Color", Color) = (.5, .5, .5, 0)
    9.  
    10.         _MainTex ("Base (RGB) Alpha (A)", 2D) = "white" {}
    11.  
    12.     }
    13.  
    14.  
    15.  
    16.     SubShader {    
    17.  
    18.         Pass {         
    19.  
    20.             Lighting On
    21.  
    22.  
    23.  
    24.             CGPROGRAM
    25.  
    26.                 #pragma vertex vert
    27.  
    28.                 #include "UnityCG.cginc" // Standard Unity properties
    29.  
    30.                 struct v2f {
    31.  
    32.                    float4 pos : POSITION;
    33.  
    34.                    float4 color : COLOR0;
    35.  
    36.                    float4 uv : TEXCOORD0;
    37.  
    38.                    float4 tangent;
    39.  
    40.                 };
    41.  
    42.                
    43.  
    44.                 uniform float _Blend;   // blend value between both shapes
    45.  
    46.                 v2f vert (appdata_tan v)
    47.  
    48.                 {
    49.  
    50.                     v2f o;
    51.  
    52.                     v.vertex = lerp(v.tangent, v.vertex,_Blend);
    53.  
    54.                     o.pos = mul(glstate.matrix.mvp, v.vertex);
    55.  
    56.                     return o;  
    57.  
    58.                 }  
    59.  
    60.             ENDCG
    61.  
    62.         }
    63.  
    64.     }
    65.  
    66. }
    67.  
    68.  
    Here's the C# code that I'm running on the object with the shader.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Threading;
    5.  
    6. public class Morpher : MonoBehaviour {
    7.     Vector3[] vertices;
    8.     Vector4[] tangents;
    9.     Thread thread;
    10.    
    11.     public MeshFilter targetMeshFilter;
    12.     Mesh targetMesh;
    13.     Mesh mesh;
    14.     MeshFilter meshFilter;
    15.     public Shader shader;
    16.     // Use this for initialization
    17.     void Awake () {
    18.         targetMesh = targetMeshFilter.mesh; //target mesh
    19.         meshFilter = GetComponent(typeof(MeshFilter)) as MeshFilter;    //mesh filter of current object
    20.         mesh = meshFilter.mesh;
    21.         vertices = mesh.vertices;
    22.        
    23.         for (int i=0;i<vertices.Length;i++)
    24.         {
    25.             //mesh.tangents[i] = new Vector4(targetMesh.vertices[i].x,targetMesh.vertices[i].y,targetMesh.vertices[i].z,-1.0f);
    26.             mesh.tangents[i] = targetMesh.vertices[i];
    27.         }
    28.     }
    29.     void Start() {
    30.     }
    31.    
    32.     // Update is called once per frame
    33.     void Update () {   
    34.         //parse vertices in a separate thread
    35.         thread = new Thread(ParseVertices);
    36.         thread.Start();    
    37.     }
    38.     void ParseVertices () {
    39.         for (int i=0;i<vertices.Length;i++)
    40.         {
    41.             // Assign tangents of target mesh to base tangents
    42.             //mesh.tangents[i] = new Vector4(targetMesh.vertices[i].x,targetMesh.vertices[i].y,targetMesh.vertices[i].z,-1.0f);
    43.             mesh.tangents[i] = targetMesh.vertices[i];
    44.         }
    45.  
    46.     }
    47. }
    48.  
    Sorry about the double space...not sure why it's doing that.[/code]
     
  7. Kuba

    Kuba

    Unity Technologies

    Joined:
    Jan 13, 2009
    Messages:
    225
    Hey,
    sorry, I totally missed your response, forum marked everything as read for me.

    The problem is on the C# side. mesh.tangents returns the copy of the array, so you should first assign it to a local value, then loop over all elements from that local array and set them to desired values, and then assign the whole array back to mesh.tangents.

    Code (csharp):
    1. for (int i = 0; i < tangents.Length; i++)
    2.     tangents[i] = calculated_value;
    3.  
     
  8. slgooding

    slgooding

    Member

    Joined:
    Jan 12, 2009
    Messages:
    112
    Thanks for the tip. I'm definitely getting something different, however it's not morphing properly. I have a sphere, and then another sphere that just has the vertices pulled out at some parts (like spikes). When I change the blend value the sphere just seems to get larger (scales) :( as a whole instead of just moving the vertices that have different positions (and vertices seem to go to a random place).

    It's probably something simple that I'm overlooking. Thanks again!
     
  9. slgooding

    slgooding

    Member

    Joined:
    Jan 12, 2009
    Messages:
    112
    Okay, I have it working...with several bugs.


    So, issue #1

    The mesh SCALES when I change the blend value. It will morph the mesh as well, but it scales it really big too.

    How do I fix this?
     
  10. Kuba

    Kuba

    Unity Technologies

    Joined:
    Jan 13, 2009
    Messages:
    225
    Hard to say without seeing the code. You can post a unitypackage here...