How to make projector only affect some specific part of object?

1.4k views Asked by At

I'm working on Unity3D for a fish shooting game. I'm implementing the underwater caustic effect for fishs. I'm using "BlobLightProjector" in StandardAsset of Unity with an attached script to change the texture every frame by :

public Texture2D[] frames;
private int frameIndex;
private Projector projector;
...
// repeat every frame
projector.material.SetTexture("_ShadowTex", frames[frameIndex]);
frameIndex = (frameIndex + 1) % frames.Length;

My list caustic textures look like this :

List caustic texture

Here is what I achieved : My achieve

Now the problem is I want the projector only affect the upper parts of the fish (even though I rotate my fish to any direction), that is something look like this : My wish

Could you give me some advices how to do it, or other solution don't use projector is good so.

2

There are 2 answers

0
Jonathan Sprague On

it seems to me that you're going to need to use a custom Shader. I'll be honest I'm no expert on them but there was a Unite 2016 on shaders which had an example very similar to this.

I'm sorry I don't have any code for you but I hope this will be at least a small step in the right direction

0
Brice V. On

You need to modify Assets\Standard Assets\Effects\Projectors\Shaders\ProjectorLight.shader the following way:

  1. you need to create a new variable that is going to be passed from the vertex shader to the pixel shader in the v2f struct: half fade : TEXCOORD3;
  2. then you need to be able to use the normal in the vertex shader. An easy choice is to use appdata_base instead of only passing the vertex position: v2f vert (appdata_base v) and modify the call to the position by appending v. to vertex everywhere.
  3. you then compute the fade factor based on the normal: I chose to just clamp the world normal's vertical axis (since it ranges from -1 to 1) o.fade = saturate(mul(unity_ObjectToWorld, v.normal).y);. saturate() clamps to 0-1, the matrix mul() converts to world space.
  4. finally apply the fade to the result in the pixel shader texS.rgb *= _Color.rgb * 8.0 * i.fade; (I increase the range by 8 because otherwise values can never be brighter than 1, but that's up to your liking)

            CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_fog
        #include "UnityCG.cginc"
    
        struct v2f {
            float4 pos : SV_POSITION;
            float4 uvShadow : TEXCOORD0;
            float4 uvFalloff : TEXCOORD1;
            UNITY_FOG_COORDS(2)
            half fade : TEXCOORD3;
        };
    
        float4x4 unity_Projector;
        float4x4 unity_ProjectorClip;
    
        v2f vert (appdata_base v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uvShadow = mul (unity_Projector, v.vertex);
            o.uvFalloff = mul (unity_ProjectorClip, v.vertex);
            o.fade = saturate(mul(unity_ObjectToWorld, v.normal).y);
            UNITY_TRANSFER_FOG(o, o.pos);
            return o;
        }
    
        fixed4 _Color;
        sampler2D _ShadowTex;
        sampler2D _FalloffTex;
    
        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 texS = tex2Dproj (_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
            texS.rgb *= _Color.rgb * 8.0 * i.fade;
            texS.a = 1.0-texS.a;
    
            fixed4 texF = tex2Dproj (_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff));
            fixed4 res = texS * texF.a;
    
            UNITY_APPLY_FOG_COLOR(i.fogCoord, res, fixed4(0,0,0,0));
            return res;
        }
        ENDCG