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

How to project a mesh onto another mesh

Discussion in 'Shaders' started by rageingnonsense, Jan 23, 2016.

  1. rageingnonsense

    rageingnonsense

    Joined:
    Dec 3, 2014
    Posts:
    99
    A few months ago I posted to the answers site, but never got a concrete answer. hopefully someone here can help me.

    Consider the following example image:


    Here we have an irregular shaped mesh (although guaranteed to be flat). I want to write a shader that will render the mesh not where it actually is in the scene, but projected onto the surface below (as shown by the green arrows).

    I thought about using a rendertexture to capture the mesh render, then using that in a projector; but I feel that as the mesh grows in size, the quality would reduce since the rendertexture is of a finite size. I could be wrong though.

    A real world use case is to visualize a selection area that is not guaranteed to be a perfect rectangle, and needs to relay to the user the contour of the terrain (and other terrain decorations) in the selection area. Another use case woudl be to add terrain decals that are based off of dynamic 3D objects as opposed to flat images.

    How would you go about this? There are games that I see this effect in (Cities Skylines has this effect when placing roads), but I haven't a clue regarding the best way to accomplish this. Looking for any and all solutions/ideas/clues.
     
  2. MikeUpchat

    MikeUpchat

    Joined:
    Sep 24, 2010
    Posts:
    1,056
    I wouldn't think it was done with a shader, I reckon they deform the mesh to fit the ground like in the system below.
     
  3. rageingnonsense

    rageingnonsense

    Joined:
    Dec 3, 2014
    Posts:
    99
    This is very cool, but I am almost certain it is not a mesh. The reason being that the resolution is far far far too high to be a mesh; it even conforms to the leaves on trees (in the cities skylines example).

    It is definitely some sort of projection.
     
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
  5. rageingnonsense

    rageingnonsense

    Joined:
    Dec 3, 2014
    Posts:
    99
    I had weird results with projectors last time I tried to use them for something like this, but I gave it another go today and finally figured out a way to do what I need (at least the core concept). Here is the result:



    The trick is to do the following:
    1. Create a gameobject with both a projector AND a camera. have it face the direction you want (down)
    2. Create a material for the projector, and give it the correct shader. any projection shader will do, but I made a new one that doesn't treat it as light or shadow
    3. Create a RenderTexture. Give it the settings you want.
    4. Create a layer for the object you want to project. I called mine ProjectionSample. set the object you want to project's layer to this.
    5. set the layer mask for both the projector and the camera to be this layer.
    6. set the texture for the camera to be the rendertexture (so that it renders there instead of screen)
    7. set the texture of the projector material to the same rendertexture.
    8. ensure the camera and the projector are both set to orthographic, and the orthographic size must match in both.
    9. make sure the camera's clear flag is set to solid color, and that the color is black.
    This is basically all you need. As an additional step, I wrote a small script to dynamically change the size of the projector and camera combo, and position to match that of the object being projected (to be placed on the camera/projector combo gameobject):

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class DecalTracker : MonoBehaviour {
    5.     public GameObject targetObject;
    6.  
    7.     private Renderer targetRenderer;
    8.  
    9.     private Projector projector;
    10.     private Camera camera;
    11.  
    12.  
    13.     void Start() {
    14.         targetRenderer = targetObject.GetComponent<Renderer>();
    15.  
    16.         projector = GetComponent<Projector>();
    17.         camera = GetComponent<Camera>();
    18.     }
    19.  
    20.     void Update () {
    21.         Bounds targetBounds = targetRenderer.bounds;
    22.  
    23.         Vector3 newPosition = targetBounds.center;
    24.         newPosition.y += targetBounds.extents.y + 100;
    25.         transform.position = newPosition;
    26.  
    27.         // find largest side
    28.         float size = (targetBounds.extents.x > targetBounds.extents.z) ? targetBounds.extents.x : targetBounds.extents.z;
    29.  
    30.         // add 1 to prevent possibility of render reaching edge of texture and giving stretched results
    31.         projector.orthographicSize = size + 1;
    32.         camera.orthographicSize = size + 1;
    33.  
    34.         camera.farClipPlane = targetBounds.size.y + 100;
    35.     }
    36. }
    37.  
    And the shader:

    Code (CSharp):
    1. Shader "Projector/Texture" {
    2.     Properties {      
    3.         _ShadowTex ("Cookie", 2D) = "" {}      
    4.     }
    5.  
    6.     Subshader {
    7.         Tags {"Queue"="Transparent"}
    8.         Pass {
    9.             ZWrite Off
    10.             ColorMask RGBA
    11.             Blend OneMinusDstColor One
    12.             Offset -1, -1
    13.  
    14.             CGPROGRAM
    15.             #pragma vertex vert
    16.             #pragma fragment frag          
    17.             #include "UnityCG.cginc"
    18.          
    19.             struct v2f {
    20.                 float4 uvShadow : TEXCOORD0;
    21.                 float4 pos : SV_POSITION;
    22.             };
    23.          
    24.             float4x4 _Projector;
    25.             float4x4 _ProjectorClip;
    26.          
    27.             v2f vert (float4 vertex : POSITION)
    28.             {
    29.                 v2f o;
    30.                 o.pos = mul (UNITY_MATRIX_MVP, vertex);
    31.                 o.uvShadow = mul (_Projector, vertex);                          
    32.                 return o;
    33.             }          
    34.          
    35.             sampler2D _ShadowTex;          
    36.          
    37.             fixed4 frag (v2f i) : SV_Target
    38.             {
    39.                 fixed4 texS = tex2Dproj (_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));                                                  
    40.                 return texS;
    41.             }
    42.             ENDCG
    43.         }
    44.     }
    45. }
    46.  

    This is far from perfect, but it is a good start in case anyone else needs something similar.

    EDIT: If you want the texture to be splatted on the surface exactly; just change the Blend mode to SrcAlpha OneMinusSrcAlpha. also ensure the background color of the camera has full transparency in addition to being black.
     
    Last edited: Jan 24, 2016
    abductor and skullthug like this.