- Home /
How to have a sprite shader apply a texture that is fixed to the screen, and does not rotate with the object?
Hello all,
This is a problem that has been confounding me for days, and I can't seem to find anywhere someone else has asked this same question, though some came close. It will take a lot of words to explain clearly, but in short, I'm trying to render a texture on a 4.3 sprite object that does not rotate, scale, or translate with respect to the view when the object itself does.
To better articulate my goal:
I have written a sprite shader that is capable of taking in a number of textures that are configurable by material properties. When a material using this shader is applied to a 4.3 sprite object, the shader will check the colors of this sprite and replace them individually with these textures. In the shader code below (mostly proof-of-concept, not finalized), it will replace white with the texture passed in as _TextureSlot1, and will replace blue with the texture passed in as _TextureSlot2.
The result looks something like this, replacing the solid white and blue in the sprite with separate textures:
That's all well and good, and took me quite a while to figure out how to do, considering this is the first time I've ever written a shader. However, that's not the whole effect I'm going for, and the next step is where I'm failing.
I want the sprite itself and its _MainTex to remain free to rotate, scale, and translate in space, while the replacer textures being applied to it over its solid colors are not. For instance, if the sprite above rotates, I want the result to look like this (notice how the wooden boards are still vertical):
Now, I've worked out how to apply a rotation to these replacer textures relative to the rotation of the sprite, and you'll see where I did this in my shader code below. I attempted to extract the sprite's rotation about its own Z axis from UNITY_MATRIX_MV and some others, and rotate the replacer texture by that same amount in the opposite direction to negate the sprite object's rotation's effect on the orientation of these replacer textures, but had no real success.
Considering all that, my question amounts to this:
a) Is there a more direct way to lock the orientation, scale, and translation of these replacer textures to the screen so that they do not appear to move (so that the color being replaced has the appearance of acting as a sort of window through which a portion of a static background is revealed)?
b) If so, what is it?
c) If not, how can I extract the sprite object's rotation about its Z axis from the transformation matrices available to the shader? I can't rely on reading in a parameter from script that specifies that rotation, because multiple objects will be using the same material while having different transforms, and setting that value for one would set it for all of them.
Given this approach of manually reversing all the transformations of the object in the shader on the replacer textures, I will still need to figure out:
1. How to rotate the replacer textures about the sprite's variable pivot point rather than their own corners (something I fear is impossible given my no-object-specific-parameters-can-be-passed-in-from-script constraint, and I rely on being able to move pivot points for animation purposes)
2. How to offset the replacer textures relative to the sprite's translation with respect to the view
3. How to scale the replacer textures relative to the sprite's scale (though I could live without this)
4. How to tile these replacer textures (though scaling them to fill the entire screen is a workaround for being unable to do this)
However, I'll limit my question here to just the rotation, and will try to struggle through those other challenges on my own after overcoming this one, trying not to be too selfish in asking for help on the rest (unless you're feeling especially charitable!)
I really hope there's a more straightforward approach to this than to manually neutralize the rotation, translation, and scaling of the sprite object within the shader, but I, admittedly an amateur at this, haven't been able to come up with it.
Thank you graciously for your consideration.
Here's my shader as it stands:
Shader "Custom/LockedTexReplaceSprite" {
Properties {
[PerRendererData] _MainTex ("Tex For Alpha (RGB)", 2D) = "white" {}
_TextureSlot1 ("TextureSlot1 (RGB)", 2D) = "white" {}
_TextureSlot2 ("TextureSlot2 (RGB)", 2D) = "blue" {}
_TextureSlot3 ("TextureSlot3 (RGB)", 2D) = "red" {}
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
}
SubShader {
Tags {
"RenderType"="Transparent"
"Queue"="Transparent"
"IgnoreProjector"="True"
"CanUseSpriteAtlas"="True"
"PreviewType"="Plane"
}
Cull Off
Lighting Off
Zwrite Off
Fog {Mode Off}
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
//Surface shader
#pragma surface surf Lambert alpha
//Needed 3.0 because I was exceeding 64 computations
#pragma target 3.0
//Needed UnityCG.cginc to access transformation matrices
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _TextureSlot1;
sampler2D _TextureSlot2;
sampler2D _TextureSlot3;
//Declare components of rotation matrix testing
float4 row0;
float4 row1;
float4 row2;
float4 row3;
float4x4 rotator=float4x4(row0,row1,row2,row3);
struct Input {
float2 uv_MainTex;
float2 uv_TextureSlot1;
float2 uv_TextureSlot2;
float2 uv_TextureSlot3;
};
void surf (Input IN, inout SurfaceOutput o) {
//Begin rotation block for test purposes
float Pi = acos(-1.0);
float theta=0*Pi/180; //rotation of texture, apparently rotates clockwise
row0=float4(cos(theta),-sin(theta),0,0);
row1=float4(sin(theta),cos(theta),0,0);
row2=float4(0,0,1,0);
row3=float4(0,0,0,1);
rotator=float4x4(row0,row1,row2,row3);
//End rotation block for test purposes
//Set up textures for rendering, while rotating _TextureSlot1 (though all slots
// are ultimately meant to rotate)
half4 primary = tex2D (_MainTex, IN.uv_MainTex);
half4 slot1 = tex2D(_TextureSlot1, mul(rotator, float4(IN.uv_TextureSlot1, 0, 1.0)).xy);
half4 slot2 = tex2D (_TextureSlot2, IN.uv_TextureSlot2);
half4 slot3 = tex2D(_TextureSlot3, IN.uv_TextureSlot3);
if(primary.r>.5)
o.Albedo=slot1.rgb;
else if(primary.b>.5)
o.Albedo=slot2.rgb;
else
o.Albedo=primary.rgb;
o.Alpha=primary.a;
o.Albedo=o.Albedo*5;
}
ENDCG
}
//This fallback shader completely discards the effect, and isn't really acceptable, but oh well
FallBack "Diffuse"
}
Answer by AwesomeJohn · Jan 19, 2014 at 07:37 PM
I figured it out! And then after that, I searched on the key bit of code I needed, and found another question that basically explains exactly what I figured out:
Is it possible to texture objects like this in unity?
As hoped, it's far, far, far easier than I was making it out to be. Now, if I can just figure out how to tile the image and maintain its aspect ratio independently of the screen's aspect ratio, I'll be cooking with gas.
< Edit >
I figured out how to do the tiling. Just had to import the texture as a texture instead of as a sprite, and set "Wrap Mode" to "Repeat" instead of "Clamp" (it's obvious, I know; I'm new at this). After that, just scale the UV for that texture with code like this (a (2,2) factor will fit 4 of the texture to the screen in a 2x2 grid):
screenUV *= half2(2/2);
I still haven't figured out how to lock in the aspect ratio, but I'll update this answer if I do.
< /Edit >
Updated shader, in case anyone else finds it helpful:
Shader "Custom/LockedTexReplaceSprite" {
Properties {
[PerRendererData] _MainTex ("Tex For Alpha (RGB)", 2D) = "white" {}
_TextureSlot1 ("TextureSlot1 (RGB)", 2D) = "white" {}
_TextureSlot2 ("TextureSlot2 (RGB)", 2D) = "blue" {}
_TextureSlot3 ("TextureSlot3 (RGB)", 2D) = "red" {}
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
}
SubShader {
Tags {
"RenderType"="Transparent"
"Queue"="Transparent"
"IgnoreProjector"="True"
"CanUseSpriteAtlas"="True"
"PreviewType"="Plane"
}
Cull Off
Lighting Off
Zwrite Off
Fog {Mode Off}
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
//Surface shader
#pragma surface surf Lambert alpha
sampler2D _MainTex;
sampler2D _TextureSlot1;
sampler2D _TextureSlot2;
sampler2D _TextureSlot3;
struct Input {
float2 uv_MainTex;
float2 uv_TextureSlot1;
float2 uv_TextureSlot2;
float2 uv_TextureSlot3;
float4 screenPos;
};
void surf (Input IN, inout SurfaceOutput o) {
half2 screenUV = IN.screenPos.xy/IN.screenPos.w;
half4 primary = tex2D (_MainTex, IN.uv_MainTex);
half4 slot1 = tex2D (_TextureSlot1, screenUV);
half4 slot2 = tex2D (_TextureSlot2, screenUV);
half4 slot3 = tex2D(_TextureSlot3, screenUV);
//Replace colors with textures
if(primary.r>.5) //White
o.Albedo=slot1.rgb;
else if(primary.b>.5) //Blue
o.Albedo=slot2.rgb;
else
o.Albedo=primary.rgb; //Keep sprite textures otherwise
o.Alpha=primary.a; //Keep sprite transparency
o.Albedo=o.Albedo*5; //Restore normal brightness, don't know why
}
ENDCG
}
//This fallback shader completely discards the effect, and isn't really acceptable, but oh well
FallBack "Diffuse"
}
how does this work exactly.... as you say to change the image from a sprite to a texture...when when i do this it kick it out of the sprite renderer.....as i have this material assigned to a sprite renderer.