Texture fire
From Winamp Developer Wiki
Here we'll create a pretty realistic fire effect using 2 textures and some color based uv translation.
Contents |
The Textures
Here are the two textures that we'll use for this effect. Save them to your Milkdrop2\textures folder.
Per-Frame Code
The only important thing we need to do here is set up some q variables to use in the warp shader. In all we're going to need 6 variables, so lets set those up and go through what they do,
q1 = time*0.44; q2 = time*0.32; q3 = time*0.285;
q4 = 0.2*sin(time*0.4); q5 = 0.2*cos(time*0.634); q6 = -0.2*sin(time*0.58);
The first 3 q variables will steadily increase at different rates without end. The last 3 will oscillate between -0.2..0.2 at different rates. We'll investigate what all these variables are for in the warp shader section.
Warp Shader
The brunt of this effect will be done in the warp shader. Our goal is to create a fire effect by adding several instances of the displacement texture together. For this to work, each instance of the texture needs to be moved to a different position than the others, otherwise the light and dark areas wouldn't move. First we'll set up the custom textures by adding these lines above the shader body:
sampler sampler_fire_base; sampler sampler_fire_dis4;
Next we need to sampler the base texture to use as, well, a base. We can store this into ret right away, or store it in a temporary float3 variable. Lets just put it into ret:
ret = tex2D(sampler_fire_base, uv);
Now the real meat of the effect. We're going to set up a new float3 variable called fire that will add 3 instances of the displacement texture together. At the end we'll have a black and white texture with quite a lot of white/grey areas along with some areas that are still black. The key to all of this is that we'll use the q variables from the per-frame code to move the 3 instances of the texture in different ways, which will create a pretty random looking fire effect:
float3 fire = tex2D(sampler_fire_dis4, float2(uv.x+q4, uv.y+q1)).xyz; fire += tex2D(sampler_fire_dis4, float2(uv.x+q5, uv.y+q2)).xyz; fire += tex2D(sampler_fire_dis4, float2(uv.x+q6, uv.y+q3)).xyz;
Notice that the first 3 q variables (q1,q2,q3) are all added to the uv.y coordinate, while the last 3 (q4,q5,q6) are all added to the uv.x coordinate. Remember that q1,q2 and q3 all increase linearly at different rates, in the warp shader this has the effect of scrolling each texture upward on the y axis at different speeds. What does fire always do? It moves upward :)
To give the fire some horizontal difference along the x axis too we add the q4,q5 and q6 variables to the uv.x coordinate. We want this to be an oscillating motion, because if we used linearly increasing values the fire would look like it's being pushed to the left constantly. Not very realistic unless you're in a very windy place. By oscillating the motion at different rates we create horizontal movement that looks a lot more random.
But we're not done with the warp shader yet. We still need to add the final displacement texture to the base texture,
ret += fire;
which makes some areas brighter than the rest. As a final tweak, we're going to have the fire fade away as it nears the top of the display window. Originally I did this with a very awkward set of alpha textures, but Nitorami pointed out a much easier way:
ret *= uv_orig.y;
Remember at the top of the window uv_orig.y is 0, at the bottom it's 1, and in between it grows from 0..1. Exactly what we need. At the top of the window ret is multiplied by 0, making it black, and as we get closer to the bottom it gets brighter until it reaches full brightness at the bottom of the window. The effect isn't nearly strong enough to give a realistic look, but we're going to fix that in the composite shader.
Here's the finished warp shader code:
sampler sampler_fire_base;
sampler sampler_fire_dis4;
shader_body
{
ret = tex2D(sampler_fire_base, uv).xyz;
float3 fire = tex2D(sampler_fire_dis4, float2(uv.x+q4, uv.y+q1)).xyz;
fire += tex2D(sampler_fire_dis4, float2(uv.x+q5, uv.y+q2)).xyz;
fire += tex2D(sampler_fire_dis4, float2(uv.x+q6, uv.y+q3)).xyz;
ret += fire;
ret *= uv_orig.y;
ret *= 1;
}
Composite Shader
Here we apply a few finishing touches to give the fire effect a more realistic look and better movement. We're going to start by creating a new float2 that we'll call sam, for no particular reason, and setting it to sampler_main:
float2 sam = tex2D(sampler_main, uv).xy;
We've used the .xy swivel to specify that we're interested in the red and green channels only, but we could leave them out altogether. Although tex2D returns a float3, when assigning it to a float2 Milkdrop is smart enough to simply throw away the blue channel instead of crying about a type mismatch. Next we'll assign ret to sampler_main as well, to give us a base to work with:
ret = tex2D(sampler_main, uv);
Now we're going to modify sam to make it a little smoother and a lot less saturated:
sam -= GetBlur1(uv)-GetBlur2(uv); sam *= 0.1;
We can think of the first line sort of like a Gaussian Blur filter. The second line is necessary because otherwise the effect will be far too intense (try commenting it out after we have everything done and you'll see what I mean).
Next we'll set up a new float3 variable, reta, which is going to be assigned sampler_main with a color based translation on the uv coordinates:
float3 reta = tex2D(sampler_main, float2(uv.x+sam.x, uv.y-sam.y) ).xyz;
Anywhere that we have a lot of red uv.x is going to get shifted quite a bit to the right. Where we have a lot of green uv.y will be shifted upward. The effect of this will be to give the fire effect movement that actually looks like something fire would do. It will swirl around gently as it rises and behave as if it's being blown around randomly by wind. But in order to get to that point, we need to integrate reta with ret in a way that will give us this effect, enter the next line:
ret = (reta.x * reta) * (reta.x * ret);
Ok, lets figure out what's going on here. Inside the first brackets we're multiplying reta by its own red channel, to darken some areas of the fire. In the second set of brackets we're multiplying ret by reta's red channel, this is again just to darken certain areas of the fire. Lastly we're multiplying these two things together. Keep in mind, because we haven't manipulated these color values up to this point all of them are in a range between 0..1. Any time you multiply a fraction with another fraction, you end up with a third fraction that's smaller than both of them. All of these multiplications we're doing have the effect of making some areas of the fire darker because we're pushing the color values closer to 0 with each multiplication.
A great way to see what's happening here is to examine just the red channel of ret and reta with ret = reta.x; and ret = ret.x;, then look at which areas will be reduced in brightness in the equation above. Any white area will remain pretty much unaffected, and areas darker than white will be made much darker after the 3 multiplications above.
One final tweak and we're done,
ret *= 2;
just to give the fire a little more brightness.
Here's the full composite shader code:
shader_body
{
float2 sam = tex2D(sampler_main, uv);
ret = tex2D(sampler_main, uv);
sam -= GetBlur1(uv)-GetBlur2(uv);
sam *=0.1;
float3 reta = tex2D(sampler_main, float2(uv.x+sam.x, uv.y-sam.y) ).xyz;
ret = reta.x * reta * (reta.x)*ret;
ret *= 2;
}





