shadow on plane for iphone

hi folks!

could anybody explain to me how I could make a simple plane generate some shadow on an object?

thnx!

You have 3 options depending on what you want since you cannot use realtime shadows on Unity iPhone. I don't know if iPhone 4 even supports it, but at no level will the engine let you.

  1. You can use a projector or blob-shadow. You can use the built-in prefab if you want, but you will need to create a texture that looks like the shadow from a plane. (I'm pretty sure a square plane will never cast that generic round shadow :).) That will give you the best looking result for the iPhone. Since it is a projector, it will fit to your geometry, and always be drawn on top. But, the biggest down side is that there are many "wasted" draw calls when using projectors. Meaning, that projectors require that every object is drawn at least once more so draw calls can get too high quickly.

  2. Another faster method would be to create another plane at ground level with your shadow texture put on it. Then parent you first plane to it. Then your "shadow" will follow your plane like it is a real shadow, and you are will only be charged 1 draw call. The disadvantage, this won't work with uneven terrain.

  3. The third and by far the best if the object doesn't move is to light map it. You can do that in your modeler or in Unity's brand new Beast light mapping tool. That would look the best if your object doesn't move, and be most efficient. The only downside might be that PVTRC compression takes away some quality, but you would more than likely see that in all your textures in exchange for faster performance and load times.

You can use the following script and shader to project a Mesh onto a plane. Just attach the script to the GameObject with the Renderer Component:

SCRIPT (C#):

using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(Renderer))]    
public class PlaneShadows : MonoBehaviour {
    public Transform plane;
    public Renderer objToProjectRenderer;
    private GameObject _objShadow;
    public Material shadowMaterial;
    private Material _shadowMaterial;
    public Light theLight;
    public Camera cam;
    private Matrix4x4 mat = new Matrix4x4();
    private Vector3 L, n, E;
    private float c, d;

    public void SetUp (){
        // Create a copy of the obj to project
        _objShadow = new GameObject(objToProjectRenderer.gameObject.name + " shadow");
        _objShadow.hideFlags = HideFlags.HideInHierarchy;

        if(objToProjectRenderer.GetType() == typeof(MeshRenderer)){
            _objShadow.AddComponent<MeshFilter>();
            _objShadow.GetComponent<MeshFilter>().sharedMesh = objToProjectRenderer.gameObject.GetComponent<MeshFilter>().sharedMesh;

            MeshRenderer shadowMR = _objShadow.AddComponent<MeshRenderer>();
            shadowMR.sharedMaterial = shadowMaterial;
            shadowMR.castShadows = false;
            shadowMR.receiveShadows = false;

            _shadowMaterial = shadowMR.sharedMaterial;
        }
        else{
            if(objToProjectRenderer.GetType() == typeof(SkinnedMeshRenderer)){
                SkinnedMeshRenderer objectSMR = (SkinnedMeshRenderer)objToProjectRenderer;
                SkinnedMeshRenderer shadowSMR = _objShadow.AddComponent<SkinnedMeshRenderer>();

                shadowSMR.sharedMesh = objectSMR.sharedMesh;
                shadowSMR.bones = objectSMR.bones;
                shadowSMR.sharedMaterial = shadowMaterial;
                shadowSMR.castShadows = false;
                shadowSMR.receiveShadows = false;

                _shadowMaterial = shadowSMR.sharedMaterial;
            }
        }
    }

    void Start(){
        SetUp ();
    }

    void Update(){  
        if(_objShadow){
            _objShadow.transform.position = objToProjectRenderer.transform.position;
            _objShadow.transform.rotation = objToProjectRenderer.transform.rotation;
            _objShadow.transform.localScale = objToProjectRenderer.transform.localScale;        

            switch(theLight.type){
                case LightType.Point:
                    // Calculate the projection matrix
                    // Let L be the position of the light
                    // P the position of a vertex of the object we want to shadow
                    // E a point of the plane (not seen in the figure)
                    // n the normal vector of the plane

                    L = theLight.transform.position;
                    n = -plane.up;
                    E = plane.position;

                    d = Vector3.Dot(L, n);
                    c = Vector3.Dot(E, n) - d;

                    mat[0, 0] = L.x*n.x + c;
                    mat[1, 0] = L.y*n.x;
                    mat[2, 0] = L.z*n.x;
                    mat[3, 0] = n.x;

                    mat[0, 1] = L.x*n.y;
                    mat[1, 1] = L.y*n.y + c;
                    mat[2, 1] = L.z*n.y;
                    mat[3, 1] = n.y;

                    mat[0, 2] = L.x*n.z;
                    mat[1, 2] = L.y*n.z;
                    mat[2, 2] = L.z*n.z + c;
                    mat[3, 2] = n.z;

                    mat[0, 3] = -L.x * (c+d);
                    mat[1, 3] = -L.y * (c+d);
                    mat[2, 3] = -L.z * (c+d);
                    mat[3, 3] = -d;
                break;

                case LightType.Spot:
                    goto case LightType.Directional;

                case LightType.Directional:
                    // Calculate the projection matrix
                    // Let L be the direction of the light
                    // P the position of a vertex of the object we want to shadow
                    // E a point of the plane (not seen in the figure)
                    // n the normal vector of the plane

                    L = theLight.transform.forward;
                    n = -plane.up;
                    E = plane.position;

                    d = Vector3.Dot(L, n);
                    c = Vector3.Dot(E, n);

                    mat[0, 0] = d-n.x*L.x;
                    mat[1, 0] = -n.x*L.y;
                    mat[2, 0] = -n.x*L.z;
                    mat[3, 0] = 0;

                    mat[0, 1] = -n.y*L.x;
                    mat[1, 1] = d-n.y*L.y;
                    mat[2, 1] = -n.y*L.z;
                    mat[3, 1] = 0;

                    mat[0, 2] = -n.z*L.x;
                    mat[1, 2] = -n.z*L.y;
                    mat[2, 2] = d-n.z*L.z;
                    mat[3, 2] = 0;

                    mat[0, 3] = c*L.x;
                    mat[1, 3] = c*L.y;
                    mat[2, 3] = c*L.z;
                    mat[3, 3] = d;
                break;
            }

            Shader.SetGlobalMatrix("_projectionMatrix", mat);
            Shader.SetGlobalMatrix("_viewInv", cam.cameraToWorldMatrix);
            Shader.SetGlobalMatrix("_view", cam.worldToCameraMatrix);
            _shadowMaterial.SetVector("_planeNormal", plane.up);
        }
    }
}

SHADER:

Shader "PlaneShadows" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _ShadowColor ("Shadow Color", Color) = (1,1,1,1)
        _planeNormal ("Plane Normal", Vector) = (0,0,0,0)
        _shadowBias ("Bias", Float) = 0.01
    }
    SubShader {
        Pass {
            Tags{ "RenderType" = "Opaque" "Queue" = "Opaque" }          
            CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma exclude_renderers xbox360
                #pragma exclude_renderers ps3
                #pragma target 3.0

                float4 _planeNormal;
                float4 _ShadowColor;
                float _shadowBias;

                float4x4 _projectionMatrix;
                float4x4 _viewInv;
                float4x4 _view;

                // vertex input: position, UV
                struct appdata {
                    float4 vertex : POSITION;
                    float4 texcoord : TEXCOORD0;
                };
                struct v2f {
                    float4 pos : SV_POSITION;
                    float4 uv : TEXCOORD0;
                };              
                v2f vert (appdata v) {
                    v2f o;

                    // Get model (world) matrix
                    float4x4 modelMatrix = mul(_viewInv, UNITY_MATRIX_MV);

                    // Transform to world
                    float4 vertex = mul (modelMatrix, v.vertex);
                    // Project to plane
                    float4 pos = mul (_projectionMatrix, vertex);
                    // Add shadow bias
                    pos += _planeNormal * _shadowBias;
                    // Transform to view
                    pos = mul (_view, pos);
                    // Project to screen
                    o.pos = mul (UNITY_MATRIX_P, pos);

                    return o;
                }               
                half4 frag( v2f i ) : COLOR {
                    return _ShadowColor;
                }               
            ENDCG
        }
    }
}

The code is based on this : http://www.devmaster.net/articles/shadowprojection/

I recommend you skimming through the 3D Platform Game (a.k.a. "Lerpz" Tutorial) found here: Link

Skip everything in the manual and find the section on creating the protagonist's simple shadow. That's a comprehensive, albeit simple, tutorial on how to create a shadow.

Some simple advise though:

  • I'm not sure you can make the Plane object (or any flat mesh for that matter) generate shadows for you. To my knowledge, it's always through dynamic lighting, baking the shadow, or some other neat trick involving totally separate objects.
  • If you're developing for the iPhone (and if you're still considering a 3G release), you're better off "baking" the shadows - that is, creating 'fake' blobs of black or gray that is essentially a "part" of your imported object. Real-time shadows is a big no no in this area.