- Home /
Shader programming question for Cg expert : Scaling textures
Hi folks,
This is a follow up to my question here: http://answers.unity3d.com/questions/162769/preventing-quotzombie-fishquot-a-question-about-st.html
I have been learning a little about shader programming since I asked that question, and now I'm wondering if its possible to solve this in Cg with a custom particle shader. It feels like it is, but I am having a hard time finding examples.
What I would like to do, is scale my texture vertically by negative 1 when my particle is rotated between 90º and 270º.
It seems like I could do the scaling by muliplying my texture transformation matrix: (UNITY_MATRIX_TEXTURE0) by this matrix: ((1,0,0,0),(0,-1,0,0),(0,0,1,0),(0,0,0,1)). I would need to do this, only if my particle is rotated which I believe I can somehow ascertain from the projection transform matrix (UNITY_MATRIX_MVP).
Thats about as far as I got before I hit a wall though. I can't find many examples out there, and my grasp of Cg and matrix math is very shaky. Does someone more knowledgable know if A: this is possible. B: can you give me some code samples or pointers.
Thanks much,
-Matt
Answer by Matt Woods · Feb 03, 2012 at 03:19 PM
The following shader is the result of Owen's help with shader code, and Jesse's clever idea of using vertex color information to tell the particles to right themselves. It solves my original question, so I'm going to mark this answered. Unfortunately, I also needed the particles to be vertex lit, and we haven't been able to get that to work with the flipped texture. This one, the particles will take their color from the primary light, but thats as close as I've been able to come to get the lighting working.
Shader "FishParticles4" {
Properties {
_TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
_MainTex ("Particle Texture", 2D) = "white" {}
_EmisTex ("Emmision Texture", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "Lighting.cginc"
uniform sampler2D _MainTex;
uniform sampler2D _EmisTex;
ENDCG
SubShader
{
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "LightMode" = "ForwardBase"}
Blend SrcAlpha OneMinusSrcAlpha
AlphaTest Greater .01
ColorMask RGB
Cull Off Lighting On ZWrite On
Pass
{
Lighting On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#pragma multi_compile_particles
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
};
struct VSOut
{
float4 pos : SV_POSITION;
fixed4 color : COLOR;
float2 uv : TEXCOORD1;
LIGHTING_COORDS(3,4)
};
VSOut vert(appdata_t v)
{
VSOut o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord.xy;
if(v.color.b>0.5) o.uv.y*=-1;
float3 ldir = normalize( ObjSpaceLightDir( v.vertex ) );
float diffuse = dot( v.normal, ldir );
o.color = 1 * unity_LightColor[0];//1;
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}
float4 frag(VSOut i) : COLOR
{
return i.color*tex2D(_MainTex, i.uv)+tex2D(_EmisTex, i.uv);
}
ENDCG
}
}
FallBack "Diffuse"
}
Use this shader with the following javascript to change the particle color:
function LateUpdate () {
var particles = particleEmitter.particles;
for (var i = 0; i < particles.Length; i++) {
var m : Matrix4x4 = Camera.main.worldToCameraMatrix;
var v : Vector3 = m.MultiplyVector(particles[i].velocity);
if (v.x<=0){particles[i].color = Color.red;} else{particles[i].color = Color.blue;}
particleEmitter.particles = particles;
}
Answer by Jessy · Jan 31, 2012 at 05:09 PM
It's not possible, because the MVP is for an entire object (the particle system in this case). The mesh data for the system is inaccessible to us, so you need to write your own particle system if some number of Unity's can't get you what you need.
Hmmm... Thanks for the tip. It felt possible. I thought using the $$anonymous$$VP would give the transform of the particle, not the particle system. Would using a javascript to loop through all the particles and check their Particle.rotation then set the custom shader's fliping matrix with $$anonymous$$aterial.Set$$anonymous$$atrix work? Or would that not work, because the material is attached to the renderer, not to the individual particle, thus using Set$$anonymous$$atrix external to the shader would effect all particles rather than one at a time.
The latter sums it up. A particle system is sent to the GPU as a single mesh (and if it's batched, even more particle systems may be included in the mesh), because that's what will run on today's hardware, not because it's easy to deal with. I believe the only way you may be able to do this is to set the color of the particle based on whatever you want, and then use the vertex colors to alter UV mapping.
Hah! Tricky. I like that idea. I'm not using the particle's color property, just the texture, so if I modify the Particle.color, I can access that within the shader? Let me play around with that. I may be back with more questions.
Particle.color sets the vertex color for every vert of the particle.
Ok, progress. Testing the particle rotation with "Particle.rotation" doesn't work, because apparently Strectched particles don't effect the Particle.rotation value. I was able to test the particle velocity however, and transform it into camera space. Using this script:
function LateUpdate () {
var particles = particleEmitter.particles;
for (var i = 0; i < particles.Length; i++) {
var m : $$anonymous$$atrix4x4 = Camera.main.worldToCamera$$anonymous$$atrix;
var v : Vector3 = m.$$anonymous$$ultiplyVector(particles[i].velocity);
if (v.x<=0){particles[i].color = Color.red;} else{particles[i].color = Color.blue;}
particleEmitter.particles = particles;
}
This does a pretty good job of making the fish blue when heading right, and red when heading left. Unfortunately, I'm still stuck on the CG shader code. $$anonymous$$ost of the examples I can find are based on surface shaders and don't seem to work correctly with particles (they turn pink). Could someone help me with a simple conditional statement that will flip the texture based on color that will work with particles?
The following is my shader for biolu$$anonymous$$escent particle fish which I have been trying to modify. ($$anonymous$$y other fish still use the standard shader) I'm not sure where to put the CGPROGRA$$anonymous$$.
Shader "ParticleLitUnlit" {
Properties { _$$anonymous$$ainTex ("Particle Texture", 2D) = "white" {} _EmisTex ("Emissive Texture", 2D) = "white" {} _Color ("$$anonymous$$ain Color", Color) = (1,1,1,1) }
SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" } Tags { "Light$$anonymous$$ode" = "Vertex" } Cull Off Lighting On
Color$$anonymous$$aterial AmbientAndDiffuse
ZWrite On
Color$$anonymous$$ask RGB
Blend SrcAlpha One$$anonymous$$inusSrcAlpha
AlphaTest Greater .001
Pass {
/*CGPROGRA$$anonymous$$ #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc"
//if(UNITY_$$anonymous$$ATRIX_$$anonymous$$VP._m00>0.05) // && UNITY_$$anonymous$$ATRIX_$$anonymous$$VP._m00<1.01)
o.texcoord.y*=-1;
ENDCG*/
SetTexture [_$$anonymous$$ainTex] { combine primary * texture }
//SetTexture [_EmisTex] { combine previous + texture }
}
} }
Thanks for the help!
Answer by Owen-Reynolds · Feb 01, 2012 at 05:57 PM
The comments are getting way too long, but this is a reply to the OPs giant comment.
The shader you've got uses the old "switches" method, which Unity calls shaderLab. You can set lots of values, such as "blend with the previous texture, based on one minus its alpha", but can't actually write code. For anything odd ("blue flips texture" counts as odd) you have to get the "write your code here" version.
I assume you started with something dragged in from builtin_shaders
(Didn't see your's there. Is it mobile? If you got it some where else builtin_shaders
is downloadable from Unity.) If you drag in something like Particle/AlphaBlend shader, you'll see spots to write code:
v2f vert (appdata_t v) {
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
[.. deleted soft particles ..]
o.color = v.color;
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
This runs for each corner of the billboard. Since you're using the color as a marker, and not for color, don't copy it -- just set to white. This should be self-explainitory:
[.. deleted soft particles ..]
o.color = 1; // sets tintcolor to opaque white ("do nothing")
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
//NOTE: the blue value is either 0 (if we are red) or 1 (if we are blue)
if(v.color.b>0.5) o.texcoord.y*=-1;
return o;
There are some improvements. For example the pixel shader (just below -- "frag") multiples everything by o.color (it calls it i.color.) You could just delete that part and not set o.color. TRANSFORM_TEX applies the tiling and offset. You probably will never have any, for fish, so could delete that.
Not doing mobile. I started with the Particle VertexLit Blended shader, and modified it to have an emissive property. Taking a look at the Particle/AlphaBlend shader now. Its a little intimidating. I'll see if I can decipher it. Thanks for the tips.
Btw, I do need the particles to be effected by lighting. I think thats why I started with vertexlit blended. Particle/AlphaBlend doesn't seem to be effected by lights, and I have no idea how to add them back in.
Answer by Owen-Reynolds · Jan 31, 2012 at 06:49 PM
To just flip upside down, put o.texcoord.y*=-1;
in the vertex shader (just after the line that computes o.texcoord.) To verify this, try using *=-2. That will double the vertical fish, as well as flipping them.
Turns out the texture matrix isn't used that much -- if you rotate the model (in this case, the plane billboard,) the tex-coords go with it anyway. The texMatrix is for when you have a stationary billboard and want to spin the texture (which is rare.)
But, a quick test with the MV and MVP matrices has me baffled. We really want the MV matrix -- model rotation with respect to the camera. The P stands for projection, and is just there to adjust size for depth. The shader can't "see" the motion, but the billboard is obviously rotated, with a "wrong way" rotated 180 degrees.
The thing is, testing shows the rotation numbers in MV and MVP don't change in any useful way (is there a Unity shader debugger?) Rotation about just z, for example, should change slots 00, 02, 20 and 22. The test (in the vert shader):
if(UNITY_MATRIX_MVP._m00>0.05) // && UNITY_MATRIX_MVP._m00<1.01)
o.texcoord.y=0.5; // in-your-face test value
This changes as you scroll in and out (for depth,) but doesn't seem to care about rotated particles. If you leave out the P, 00 is always 1 (the identity.) It appears as if the secret particle code is adjusting the billboard verts itself, and the shader has no information about the spin.
Adding Update code to the emitter can set shader values for everyone, but I don't think for each fish. Aren't there fish that look about the same upside down?
Thanks for the testing and code example Owen. I think Jessy was on the right track of sending the particle orientation to the shader using Particle.color. I'm about to leave for the day, but I'll be doing some more testing tomorrow, and let people know my results.
Answer by Matt Woods · Feb 01, 2012 at 10:26 PM
Ok, the texture flipping is working great. No more upside down fish! I'm now trying to get my vertex lighting, and emissive texture back into my particles. I've tried turning lighting tags on, but that seems to have no effect. I tried getting the vertex color, but that doesn't seem to incorporate the lighting like "primary" did in my old shader. I tried making a separate pass using the previous syntax, but that seems to draw one over the other without combining. I tried enabling #pragma surface surf Lambert but the particle renderer doesn't seem to like that. Here is my current code. Any tips for adding vertex lighting, and incorporating my emissive texture?
Thanks for both of your help.
-Matt
Shader "FishParticles" { Properties { _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5) _MainTex ("Particle Texture", 2D) = "white" {} _EmisTex ("Emmision Texture", 2D) = "white" {} }
Category { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha AlphaTest Greater .01 ColorMask RGB Cull Off Lighting On ZWrite On Fog { Color (0,0,0,0) } BindChannels { Bind "Color", color Bind "Vertex", vertex Bind "TexCoord", texcoord }
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
// #pragma surface surf Lambert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_particles
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _EmisTex;
fixed4 _TintColor;
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float2 texcoord1 : TEXCOORD1;
};
struct v2f {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float2 texcoord1 : TEXCOORD1;
};
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.color = 1;//v.color;
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
o.texcoord1 = TRANSFORM_TEX(v.texcoord,_MainTex);
if(v.color.b>0.5) o.texcoord.y*=-1;
if(v.color.b>0.5) o.texcoord1.y*=-1;
return o;
}
fixed4 frag (v2f i) : COLOR
{
return i.color* tex2D(_MainTex, i.texcoord);
}
ENDCG
}
/*Pass {
SetTexture [_MainTex] { combine primary * texture }
SetTexture [_EmisTex] { combine previous + texture }
}*/
}
} }
A surface shader takes: inout appdata_full v
in the vert shader and you specify it with #pragma surface surf Lambert vertex:vert
, where vert is the name of the vertex shader. I got this surface shader working, but it completely ignores point lights and the light intensity. I couldn't get the vertex part working (could find the appdatafull docs,) so it flips per/pixel, which is slower. At this point, making your own fish-mover is seemer easier:
Shader "Particles/fish" {
Properties {
_TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
_$$anonymous$$ainTex ("Particle Texture", 2D) = "white" {}
_InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
}
Category {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Blend SrcAlpha One$$anonymous$$inusSrcAlpha
AlphaTest Greater .01
Color$$anonymous$$ask RGB
Cull Off Lighting On ZWrite Off Fog { Color (0,0,0,0) }
BindChannels {
Bind "Color", color
Bind "Vertex", vertex
Bind "TexCoord", texcoord
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRA$$anonymous$$
#pragma surface surf Lambert alpha
sampler2D _$$anonymous$$ainTex;
struct Input {
float2 uv_$$anonymous$$ainTex;
float4 color : COLOR;
};
void surf (Input IN, inout SurfaceOutput o) {
if(IN.color.r<0.5) IN.uv_$$anonymous$$ainTex*=-1;
half4 cc = tex2D (_$$anonymous$$ainTex, IN.uv_$$anonymous$$ainTex);
o.Albedo = cc.rgb;
o.Alpha = cc.a * IN.color.a;
}
ENDCG
}
//Fallback "Diffuse" // wrong }
}
Hi Owen, I tried your shader, but couldn't get it working. Does it only work with directional lights? The big reason I need the lighting is I have spotlights on my submarine. I haven't been able to get any surface shaders to work well with particles. I had been trying the vertex/frag shader route based on these two links: http://unity3d.com/support/documentation/Components/SL-Attenuation.html
http://answers.unity3d.com/questions/180298/how-do-i-sample-a-shadowmap-in-a-custom-shader.html
I'll post my resulting shader in a new answer, but I agree I'm about ready to give up on this. I've learned a lot more about shaders though. Thank you for your help. If you're ever come to western $$anonymous$$assachusetts, I'll buy you a beer.