I’m trying to simulate slowly rolling clouds overhead and the shadows they create below using a projector. Problem is, changing the UV offset doesn’t seem to have any effect for projectors. I realize you’re “supposed” to use a clamped texture, but I’m just wondering if there’s any way to do this? Perhaps by modifying the projector shader? I’m a complete n00b with it comes to writing shaders, so I’m not sure where to begin if that’s a workable option.
in the same shader technique as the sunlight, just lookup your cloud texture (i’m assuming it’s a tileable ‘noise/cloud’ texture) with the worldspace XY of the vertex modulated by Time, that’s about it for basic scrolling clouds, up to you how you wish to blend it with the diffuse etc
CloudUV = (Vertex.xy * Scale) + (Time * ScrollSpeed)
…something like that I think ![]()
Thanks, but that’s the basic technique I tried and it doesn’t seem to work with projectors. No UV offset seems to alter how the projector…err… projects its cookie. I’m basically trying to project the cloud shadows onto the ground (and other objects on the ground).
Then again, maybe I misunderstood what you were trying to say?
hmm, sounds like you’re trying to make the light itself project a texture of the clouds? such as a spotlight?
what I meant was, in the ambient/outdoor pass of your shaders - simply index the cloud texture based on the vertices position, this is some code I used in a different engine:
CloudUV = (WorldSpaceVertex.xy * CloudTileFactor) + float2(Time, Time) * CloudScrollSpeed;
With a spotlight projection, it’s not mesh UV’s that are indexing the cookie, it’s a projection matrix constructed from the spotlight so altering the mesh UV’s in the shader has nothing to do with the projection of the cookie texture. My example above avoids all of that… doesn’t require a projection matrix and doesn’t even need UV’s, because the vertices worldspace xy positions are the UV’s (you could visualize that as being a perfect top down orthographic projection) ![]()
You could just move the projector.
/P
Urgrund, ok, now I see where you’re going with this. I may have to post the question over in the shaderlab forum then to see exactly how to do something like this. But that makes perfect sense.
Metervara, yes, I considered doing that, and if push comes to shove, I may have to. But I’d prefer to avoid it since that would mean I’d need at least two projectors when I’d like to be able to get away with one for performance reasons (this is for iPhone).
Replace Matrix [_Projector] with let’s say Matrix [_MyMatrix] in your shader and add a script to the projector that creates _MyMatrix and feeds it to your shader like so:
Matrix4x4 _Projector = m.GetMatrix("_Projector");
Matrix4x4 off = Matrix4x4.TRS(transform.TransformDirection(-Vector3.up)*Time.time, Quaternion.identity, Vector3.one);
Matrix4x4 _MyMatrix = _Projector * off;
Shader.SetGlobalMatrix("_MyMatrix", _MyMatrix);
/Patrik
Positively brilliant! It works perfectly!
Thanks a ton!
urgrund, I’m still interested in pursuing your idea as well… I’m thinking it might save some extra rendering since it seems it could be accomplished in a single pass, whereas using a projector has to render the geometry a second time.
Still, having a scrollable projector is REALLY handy in cases where you don’t want a more or less orthogonal projection.
One extra note:
Over time, as the values in the matrix begin to accumulate, the shadows start looking more like reflections from a pool of water or something. It’s really weird (but cool looking if that’s what you’re after). So I figured the values needed to loop within an acceptable range. This lead to still more problems because it was difficult to find the “loop point” in the value going into the matrix. The reason for this is, if you find one that works for a distant object, it doesn’t work for a nearby object. So I switched to an orthographic projection (which is what I really need anyway for cloud cover). Now I just clamp the values to 0-1. I also decided I could get down and dirty with the matrix itself and directly manipulate the pertinent value to avoid all the extra math each frame. So here’s what I wound up with:
void Start()
{
// Get the projector matrix...
_Projector = p.material.GetMatrix("_Projector");
}
void Update()
{
// Scroll the texture:
_Projector.m13 += scrollSpeed * Time.deltaTime;
if(_Projector.m13 > 1f)
_Projector.m13 = 0;
Shader.SetGlobalMatrix("_AnimatedProjector", _Projector);
}
That, on an orthographic projector, works perfectly and seamlessly, and should be really, really fast.
Thanks again!!! Now onto trying out urgrund’s solution!
urgrund, okay, I know this is probably straying into the Shaderlab forum domain a bit now, but I wanted to follow up on your suggestion. I know next to nothing about shader programming, but I’m trying to teach myself. It struck me that essentially what I’m trying to do here is lightmapping, only with non-conventional UVs. So I looked up the iPhone lightmapping shader:
Shader "iPhone/Lightmap/Lightmap Only"
{
Properties
{
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_LightMap ("Lightmap (RGB)", 2D) = "black" { LightmapMode }
}
SubShader
{
Pass
{
Name "BASE"
Tags {"LightMode" = "Always"}
BindChannels {
Bind "Vertex", vertex
Bind "texcoord1", texcoord0 // lightmap uses 2nd uv
Bind "texcoord", texcoord1 // main uses 1st uv
}
SetTexture [_LightMap] {
constantColor [_Color]
combine texture * constant
}
SetTexture [_MainTex] {
combine texture * previous
}
}
}
}
Now unless I’m mistaken, it seems what I need to do is implement your code snippet so that “CloudUV”, as you called it, would be stored in “texcoord1”? Or instead of binding I’m sort of at a loss how to go about this though. Or do I need to go a completely different way about it?
Yep - though maybe texcoord0? (going by the comments in that shader?)… but since that looks like fixed function I can’t help you there (yes, maybe time to stray into shaderlab
), I’ve only made the simplest of ffp shaders… but, you could easily do this directly to the mesh UV’s, which was actually the answer I recieved when asking here how to get basic UV scrolling shaders working in FFP.
Show us a screeny when you’ve got something! ![]()
Yeah, I suppose I could alter the UVs manually, but that seems awfully inefficient to do in-script if you have a large number of vertices in-scene.
I could use a lookup table and alter the object’s material color based on its position, but then I’d break iPhone batching, leading to tons of drawcalls…
Hey Brady,
Last September you put a post on the forums on how to get a projector to scroll.
Apparently, it worked out for you, but I’m having huge trouble following the solution in the forum post:
http://forum.unity3d.com/viewtopic.php?p=205335
I have no C# skills, so I’m trying to translate your code:
var proj;
var mat;
var scrollSpeed = 0.5;
function Start()
{
// Get the projector matrix...
var proj : Projector = GetComponent (Projector);
var mat = proj.material.GetMatrix("_Projector");
print(mat);
}
function Update()
{
mat.m13 += scrollSpeed * Time.deltaTime;
if(mat.m13 > 1.0)
{
mat.m13 = 0;
}
Shader.SetGlobalMatrix("_AnimatedProjector", mat);
}
Of course, the ‘m13’ part returns a null reference exception, since it isn’t defined. I don’t know where the ‘m13’ part comes from, what it does or how to define it.
Could you help?
Also, does this code only work with a specific type of shader only?
If so, what is the code for the shader?
If you could help me with this one, I’d REALLY appreciate it, I’ve been trying this for two days now and lost hope a little bit…
-TT
I’m no JS guru by any means, but I suspect it may have to do with the fact you’re using implicit typing? Try explicitly declaring the type of “mat” like so:
var mat : Matrix4x4;
At any rate, I’ve made this into a stand-alone MonoBehaviour that you should just be able to attach to your Projector and forget about it. If for some reason you’re going to call it from JS (which you shouldn’t need to since it is stand-alone), just put the script into your ‘Plugins’ folder so it gets compiled before any JS. Also, this is an updated version which also allows you to control the “scale” of the projected image. Try values between 0.2 and 1.0 to get a feel for how the scaling works. And for nice, slow, subtle moving clouds, I use 0.03 for the speed.
// A script to scroll a projected image.
// Copyright 2009 Above and Beyond Software. All rights reserved
using UnityEngine;
using System.Collections;
public class ProjectorScroller : MonoBehaviour
{
public float scrollSpeed; // Speed to scroll
public float scale;
protected Matrix4x4 _Projector;
// Use this for initialization
void Start ()
{
Projector p = (Projector) gameObject.GetComponent(typeof(Projector));
p.orthographic = true;
_Projector = p.material.GetMatrix("_Projector");
// Set the scale:
_Projector.m00 = 0.005f / scale;
_Projector.m12 = 0.005f / scale;
}
void Update()
{
_Projector.m13 += scrollSpeed * Time.deltaTime;
if (_Projector.m13 > 1f)
_Projector.m13 = 0;
Shader.SetGlobalMatrix("_AnimatedProjector", _Projector);
}
}
Just name the file “ProjectorScroller.cs” and drag it onto a projector and you should be good to go.
I’m not sure if you need a special shader other than the standard projector shader or not, but here’s what I’m using for mine, and I renamed it, so I probably modified it in some way, but I haven’t taken the time to compare it to see:
Shader "Projector/Additive, Animated" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_ShadowTex ("Cookie", 2D) = "" { TexGen ObjectLinear }
_FalloffTex ("FallOff", 2D) = "" { TexGen ObjectLinear }
}
Subshader {
Pass {
ZWrite off
//Fog { Color (1, 1, 1) }
ColorMask RGB
Blend DstColor One
SetTexture [_ShadowTex] {
constantColor [_Color]
combine constant * texture, ONE - texture
Matrix [_AnimatedProjector]
}
SetTexture [_FalloffTex] {
constantColor (0,0,0,0)
combine previous lerp (texture) constant
Matrix [_ProjectorClip]
}
}
}
}
I hope that helps! I’d love to see a screenshot of your game when you get this working. Thanks!
You totally saved my LIFE on this one!
Thanks so much for taking the time to help out!
I think the shader does matter, since the same script didn’t work on mine (particle/additive).
Script works perfectly, I might just try and remake it in JavaScript, but either way it works now.
Attached a screenshot for ya – thanks for the help!
-TT
Yeah, particle/additive won’t work. It has to be a projector shader for sure.
Hey, seeing that this is an under water scene, you ought to see about getting some perlin textures that look like caustics, then change the frames out progressively by re-assigning the “cookie” texture. I’ve never used a movie texture before so I don’t know if that would work as a cookie or not, but that would look awesome.
Anyway, it looks great and I’m glad I was able to help. ![]()
Great idea!
I’ll look into it!
Will probably need some good compression to keep the movie file small for the web-version, but that’s no problem.
Thanks for all the help!!
-TT
Tornado, I have never written a line of JS in my life but I’m guessing that m13 is undefined because you redeclared your vars in the Start function. If you just change “var mat” to “mat” in the Start function it should work fine. I guess this is coming a bit late, but I figured if you were going to change the code to JS you might still be stuck on this.
Hey
I tryied to follow the discussion but had no success.
I’m very noob with Shaders, and I couldn’t load the scripts right so the projector would animate.
I created the ProjectorScroller.cs and added to my Shadow prefab (which is a Projector), created a new Shader and apllied it to the the projector material (the standard Blob Shadow material). When I play the scene, when the projector is called the light just explodes super bright, and I cannot see the projector itself.
Shader:
Shader "Projector/Additive, Animated" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_ShadowTex ("Cookie", 2D) = "" { TexGen ObjectLinear }
_FalloffTex ("FallOff", 2D) = "" { TexGen ObjectLinear }
}
Subshader {
Pass {
ZWrite off
//Fog { Color (1, 1, 1) }
ColorMask RGB
Blend DstColor One
SetTexture [_ShadowTex] {
constantColor [_Color]
combine constant * texture, ONE - texture
Matrix [_AnimatedProjector]
}
SetTexture [_FalloffTex] {
constantColor (0,0,0,0)
combine previous lerp (texture) constant
Matrix [_ProjectorClip]
}
}
}
}
I want to try this because it may be handy in my game, which involves clouds in different sizes with shadow projected (by a instantiated projector). I plan to add a wind effect to move clouds and their shadows, so this is where these scripts may be useful.
Thanks.
I’m not sure what to tell you as it sounds like you’re doing everything right from what I can tell. Be sure you’ve set a cookie texture, and perhaps see if it works if the projector is in the scene rather than instantiated from prefab just to see if there’s any difference maybe. Also remember that for it to work with ProjectorScroller.cs properly, the projector needs to be set to orthographic.