Difference between revisions of "Talk:EffectShader"
imported>DavidJCobb |
DavidJCobb (talk | contribs) (→On blending: new section) |
||
(5 intermediate revisions by 2 users not shown) | |||
Line 65: | Line 65: | ||
Mind, I'm only testing edge effects right now. [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 2016-11-01T22:12:19 (EDT) | Mind, I'm only testing edge effects right now. [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 2016-11-01T22:12:19 (EDT) | ||
:Okay, further testing confirms the current behavior at least for edge effects: if ''Full Alpha Ratio'' is less than ''Persistent Alpha Ratio'', then ''Full Alpha Time'' will still be used, but at the end of the ''Full Alpha Time'', the shader will instantly switch to the ''Persistent Alpha Ratio'' without fading (''Alpha Fade Out Time'' is not used for a transition). Haven't tested what happens if you specify an ''Alpha Fade In Time''. Bloody hell. [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 2016-11-01T22:38:52 (EDT) | |||
== Notes on particle shaders == | == Notes on particle shaders == | ||
Line 75: | Line 77: | ||
It appears that using the blend modes "Source Alpha" for source and "Dest Inverted Alpha" for destination can cause first-person arms to turn pitch-black. [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 2016-04-17T02:22:19 (EDT) | It appears that using the blend modes "Source Alpha" for source and "Dest Inverted Alpha" for destination can cause first-person arms to turn pitch-black. [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 2016-04-17T02:22:19 (EDT) | ||
== Projected UVs == | |||
Testing indicates that when projected UVs are enabled, the way the texture is projected onto the target object depends on ''both'' the camera position ''and'' the target object's texture mapping. | |||
Consider a texture consisting of diagonal (45-degree) lines overtop a transparent background. When you use this texture on an EffectShader that doesn't have Projected UVs, you will see it wrap around the target object, conforming to its shape. You might ''expect'' that when Projected UVs are used, the EffectShader does not conform to the target object's shape: you might expect to see diagonal, 45-degree lines in-game, as if there were a flat overlay exactly covering the area of the screen where the object is rendered, and you might expect the angle, position, and size of these lines to remain constant as the camera is moved or rotated. This is not what occurs. Instead, Projected UVs cause the texture to still conform to the target object's shape, while ''also'' warping and distorting dramatically as the camera moves. | |||
Here's one such diagonal stripe texture applied to a bench in Whiterun's Hall of the Dead. You can see that rather than doing anything useful, it just distorts around the shape of the object, becoming horribly warped. It's even worse if you look at the back of the bench (where the stripes run the wrong way while still being in "half screen-space") or if you look at the bench from above. | |||
[[File:Projected UVs example.png|500px]] | |||
So that's not great. In fact, with it behaving that way, I can't even really imagine how this option could ever actually be reliable or useful. [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 2020-08-12T17:58:06 (EDT) | |||
:Yeah, no, [https://www.youtube.com/watch?v=hXepCugf5gk this feature is a train wreck]. The, ah, gentle animation in that video is UV scrolling using EffectShader settings; the, uh, everything else is Projected UVs. How could this ever possibly be useful to anyone? [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 2020-08-12T19:33:27 (EDT) | |||
== Reverse-engineering == | |||
The fade timers for EffectShader effects appear to be used as follows. This function is located at 0x00497A70 in Skyrim Classic, and it appears to return a single value that should be added to the current fade opacity. | |||
<syntaxhighlight lang="cpp" line>/*static*/ float TESEffectShader::ComputeFades( | |||
float previous_alpha, | |||
float Arg2, // probably a "delta time" float, i.e. seconds per frame | |||
float time_since_start, | |||
bool Arg4, // probably an "is fading out" bool | |||
// | |||
// shader settings: | |||
// | |||
float fade_in_time, | |||
float fade_out_time, | |||
float full_time, | |||
float alpha_full, | |||
float alpha_persistent, | |||
float min_fade_rate | |||
) { | |||
if (Arg4) { | |||
float end_of_full_time = fade_in_time + full_time; | |||
if (end_of_full_time < time_since_start) { | |||
// | |||
// We have passed the end of the Full Alpha period. | |||
// | |||
if (0.0 <= fade_out_time) { | |||
// | |||
// This effect shader has a defined fade-out time. | |||
// | |||
float alpha = alpha_persistent; | |||
if (alpha == 0) | |||
// | |||
// If the shader has a 0.0 persistent alpha, use its full alpha as the | |||
// persistent alpha. | |||
// | |||
alpha == alpha_full; | |||
// | |||
float fade_rate = fade_out_time / alpha; | |||
if (min_fade_rate > fade_rate) | |||
fade_rate = min_fade_rate; | |||
float result = previous_alpha - (fade_rate * Arg2); | |||
if (result >= 0.0) | |||
return result; | |||
return 0.0F; | |||
} | |||
// | |||
// This effect shader has a negative fade-out time, so drop to zero alpha | |||
// instantly. | |||
// | |||
return 0.0F; | |||
} | |||
} | |||
if (time_since_start < fade_in_time) { | |||
// | |||
// We're fading in to Full Alpha. | |||
// | |||
float a = fade_in_time / Arg2; | |||
float result = (a * alpha_full) + previous_alpha; | |||
if (result > alpha_full) | |||
return alpha_full; | |||
return result; | |||
} | |||
if (full_time > time_since_start - fade_in_time) { | |||
// | |||
// We're in Full Alpha time. | |||
// | |||
return alpha_full; | |||
} | |||
if (fade_out_time > full_time + time_since_start) { | |||
// | |||
// We're past Full Alpha time, and are in Fade Out time. Opacity should fade from | |||
// the Full Alpha ratio to the Persistent Alpha ratio. | |||
// | |||
float alpha_span = alpha_full - alpha_persistent; | |||
float fade_rate = (Arg2 / fade_out_time) * alpha_span; | |||
float result = (double)previous_alpha - (float)fade_rate; // Bethesda casts | |||
return std::max(alpha_persistent, result); | |||
} | |||
return alpha_persistent; | |||
}</syntaxhighlight> | |||
This means that the timers work as follows: | |||
* Fade in from zero alpha to ''Full Alpha Ratio'' over ''Fade-In Time'' seconds. | |||
* Stay at ''Full Alpha Ratio'' for ''Full Alpha Time'' seconds. | |||
* Fade down from ''Full Alpha Ratio'' to ''Persistent Alpha Ratio'' for ''Fade-Out Time'' seconds. If the alpha value falls below ''Persistent Alpha Ratio'', force it to ''Persistent Alpha Ratio''. | |||
* Stay at ''Persistent Alpha Ratio'' until the shader is removed. | |||
* If ''Full Alpha Time'' is negative, then drop to zero alpha immediately. Otherwise, continue. | |||
* Fade out from ''Persistent Alpha Ratio'', if it's non-zero, or ''Full Alpha Ratio'' otherwise; we will call this value the ''Prior Alpha''. The fade rate is (''Fade Out Time'' / ''Prior Alpha'') per second(?) unless that value falls below a set minimum rate, in which case that minimum rate is used instead. | |||
** The minimum setting is whichever is relevant among the following [[Game Settings|game settings]]: | |||
*** fEffectShaderParticleMinFadeRate | |||
*** fEffectShaderFillEdgeMinFadeRate | |||
Some edge-cases: | |||
* If the shader is removed while still in ''Full Alpha Time'', then based on the subroutine above, it would wait for that time to expire, and only begin animating out as normal after ''Full Alpha Time'' is up. | |||
== On blending == | |||
I believe the descriptions of the blend modes are wrong, and should be: | |||
* '''Source Alpha:''' The pixel currently being blended, times the source alpha. | |||
* '''Source Inverted Alpha:''' The pixel currently being blended, times (1 - the source alpha). | |||
* '''Destination Alpha:''' The pixel currently being blended, times the destination alpha. | |||
* '''Destination Inverted Alpha:''' The pixel currently being blended, times (1 - the destination alpha). | |||
The article suggests the following as equal to a typical alpha blend: | |||
* '''Source Blend Mode:''' Source Alpha | |||
* '''Dest Blend Mode:''' Source Inverted Alpha | |||
* '''Blend Operation:''' Add | |||
The equation for a typical alpha blend, assuming a fully-opaque destination, is <math>\alpha A + (1-\alpha)B</math>, where <math>\alpha</math> is the source alpha, <math>A</math> is the source ("over") color, and <math>B</math> is the destination ("under") color. If Source Inverted Alpha was always the source color times <math>1 - \alpha</math>, then we'd just be blending the source (EffectShader) color with itself (<math>\alpha A + (1-\alpha)A = (1-\alpha+\alpha)A = A</math>); but if instead it's ''the color being blended'' times <math>1 - \alpha</math>, then when it's used for the Destination Blend Mode, it'd produce <math>(1-\alpha)B</math>, which would indeed match the equation for alpha blending of a fully-opaque destination pixel. | |||
(I'm not sure if it's possible to handle blending of a semitranslucent shader onto a semitranslucent destination; but then, membrane EffectShaders are known not to work on alpha-blended (as opposed to alpha-tested) meshes anyway.) | |||
I'm updating the article accordingly. [[User:DavidJCobb|DavidJCobb]] ([[User talk:DavidJCobb|talk]]) 21:36, 20 August 2024 (EDT) |
Latest revision as of 20:36, 20 August 2024
Revised the presentation[edit source]
In my quest to understand the EffectShader form, I found myself looking back at this page very often, only to drown in a sea of text every time. To improve the situation, I figured it would be better to drop the "list item; name - description" format in favor of a spacier list of options with indented descriptions. After my edits, the list looks more like a nix* manual page - a format that has proven solid for over forty years. Hopefully others will find it more readable too and start contributing info to relevant sections Tox2ik (talk) 2013-01-25T14:44:44 (EST)
Incomplete information[edit source]
This page is missing a lot of information! I think it's a real shame that such a powerful tool remain undocumented, especially when people spend (impression from looking over the oblivion and skyrim forums) hours on end playing with these settings.
To someone with little experience in (TES) modding, the option such as "Texture (U,V)" makes little to no sense. Having even an inaccurate empirical account of what this does would be useful - instead of not having the option on the page at all.
On a similar note, what is the difference between `Fill Texture' and `Palette Texture'?
What is the difference between `Fill / Texture' and `Edge Effect' and how do the different pulsation settings interfere / override each other? They both seem to appear on the outskirt of a mesh, so why do we even need an edge effect?
I feel uncomfortable contributing to the page because of inadequate experience, but find it rather disappointing to see this powerful feature neglected. Surely, after looking at vanilla shaders for a weekend or two, I'll be able to tell what a shader will look like by just looking at these parameters. But many already do, and the info is not here! *sadface*
Last question:
Is the claim seen on the front page about "Z Test Function" having wall-hack capabilities true? If so, under which circumstances?
Tox2ik (talk) 2013-01-25T15:29:24 (EST)
Particle Shader Finish Effect[edit source]
Anyone know what exactly controls the "finish" effect of an EffectShader? For instance, any of the core alteration spells (Stoneflesh, Oakflesh, etcetera) all will play a brief particle shader when the spell is cast, then they have only a membrane shader playing while the effect is active, but then when the effect finishes, another particle shader effect (similar to, but not exactly the same as) the original particle shader casting effect appears. What options in the Particle Shader settings (or elsewhere) control this behavior? -- Egocarib (talk) 2013-09-14T11:41:23 (EDT)
Notes on membrane shaders' values[edit source]
The documentation on this page is very, very, very lacking. I'd edit it, but it's very difficult to test things for sure.
My tests indicate the following behavior:
- Upon being applied, the shader's alpha will fade in from zero to Full Alpha Ratio over Alpha Fade In Time seconds.
- The shader will then remain at Full Alpha Ratio for Full Alpha Time seconds.
- The shader will then fade back down to Persistent Alpha Ratio over ??? seconds (presumably the Alpha Fade Out Time is used for this as well).
- Upon being removed, the shader's alpha will fade out from its current alpha to zero over Alpha Fade Out Time seconds.
- The shader's alpha will also pulse by Alpha Pulse Amplitude at Alpha Pulse Frequency; this stacks with the previously mentioned behavior.
In other words,
- The alpha pulses over the entire time that the shader is active; these modifications stack with fades.
- Alpha Fade In Time and Alpha Fade Out Time determine how quickly the shader starts up and shuts down.
- The shader will only ever be at Full Alpha Ratio once.
- Use the fade times to control how quickly the shader applies and leaves. Use the pulse settings to animate the shader while it's there.
DavidJCobb (talk) 2015-04-11T17:20:35 (EDT)
More research and testing appears to indicate that the "source pixel" is the EffectShader's texture and color, while the "destination pixel" is the texture and color of the target object. It's difficult to know for sure, though.
I've been running my tests using an EffectShader with an opaque solid white fill texture. Here's what I'm seeing:
- No change when Source Blend is Zero, Dest Blend is One, and Operation is Max.
- Solid white when Source Blend is One, Dest Blend is One, and Operation is Add.
- Solid black when Source Blend is Zero, Dest Blend is Dest Alpha, and Operation is Add.
- The Z-Test Function appears to be totally broken; the "Greater Than or Equal" value does not show through walls as described; remarks found around the web indicate that it used to work back in Oblivion. NIF files have a similar feature (a flag available on NiAlphaProperty) which is also broken, so odds are good that Bethesda broke something deep within the graphics engine.
- Objects that use emissive glows but no textures appear to be totally unaffected by EffectShader membrane options.
Man, it sure would be nice to know what we're blending. :\ DavidJCobb (talk) 2015-04-12T22:26:30 (EDT)
Shaders do indeed fade from Full Alpha Ratio to Persistent Alpha Ratio over Alpha Fade Out Time seconds. However, there's a notable exception: it seems like if Persistent Alpha Ratio is greater than Full Alpha Ratio, the shader will immediately begin at the Persistent Alpha Ratio. Though this behavior is implied by the names of the settings ("full," as in "max," alpha ratio), one would expect it to start at Full Alpha Ratio and fade into Persistent Alpha Ratio regardless of the difference; and indeed that would be the more useful behavior.
Mind, I'm only testing edge effects right now. DavidJCobb (talk) 2016-11-01T22:12:19 (EDT)
- Okay, further testing confirms the current behavior at least for edge effects: if Full Alpha Ratio is less than Persistent Alpha Ratio, then Full Alpha Time will still be used, but at the end of the Full Alpha Time, the shader will instantly switch to the Persistent Alpha Ratio without fading (Alpha Fade Out Time is not used for a transition). Haven't tested what happens if you specify an Alpha Fade In Time. Bloody hell. DavidJCobb (talk) 2016-11-01T22:38:52 (EDT)
Notes on particle shaders[edit source]
Particles appear to be emitted from any NiNode in the Actor's 3D skeleton that has collision data of some kind attached. If a NiNode and its parent both have collision data, particles should be emitted along the line between these two nodes. NiNodes without collision are almost always ignored by the particle system. DavidJCobb (talk) 2015-04-30T00:53:37 (EDT)
- All named elements in the skeleton must have unique names. DavidJCobb (talk) 2015-04-30T01:52:53 (EDT)
Pitch-black arms in first person[edit source]
It appears that using the blend modes "Source Alpha" for source and "Dest Inverted Alpha" for destination can cause first-person arms to turn pitch-black. DavidJCobb (talk) 2016-04-17T02:22:19 (EDT)
Projected UVs[edit source]
Testing indicates that when projected UVs are enabled, the way the texture is projected onto the target object depends on both the camera position and the target object's texture mapping.
Consider a texture consisting of diagonal (45-degree) lines overtop a transparent background. When you use this texture on an EffectShader that doesn't have Projected UVs, you will see it wrap around the target object, conforming to its shape. You might expect that when Projected UVs are used, the EffectShader does not conform to the target object's shape: you might expect to see diagonal, 45-degree lines in-game, as if there were a flat overlay exactly covering the area of the screen where the object is rendered, and you might expect the angle, position, and size of these lines to remain constant as the camera is moved or rotated. This is not what occurs. Instead, Projected UVs cause the texture to still conform to the target object's shape, while also warping and distorting dramatically as the camera moves.
Here's one such diagonal stripe texture applied to a bench in Whiterun's Hall of the Dead. You can see that rather than doing anything useful, it just distorts around the shape of the object, becoming horribly warped. It's even worse if you look at the back of the bench (where the stripes run the wrong way while still being in "half screen-space") or if you look at the bench from above.
So that's not great. In fact, with it behaving that way, I can't even really imagine how this option could ever actually be reliable or useful. DavidJCobb (talk) 2020-08-12T17:58:06 (EDT)
- Yeah, no, this feature is a train wreck. The, ah, gentle animation in that video is UV scrolling using EffectShader settings; the, uh, everything else is Projected UVs. How could this ever possibly be useful to anyone? DavidJCobb (talk) 2020-08-12T19:33:27 (EDT)
Reverse-engineering[edit source]
The fade timers for EffectShader effects appear to be used as follows. This function is located at 0x00497A70 in Skyrim Classic, and it appears to return a single value that should be added to the current fade opacity.
/*static*/ float TESEffectShader::ComputeFades(
float previous_alpha,
float Arg2, // probably a "delta time" float, i.e. seconds per frame
float time_since_start,
bool Arg4, // probably an "is fading out" bool
//
// shader settings:
//
float fade_in_time,
float fade_out_time,
float full_time,
float alpha_full,
float alpha_persistent,
float min_fade_rate
) {
if (Arg4) {
float end_of_full_time = fade_in_time + full_time;
if (end_of_full_time < time_since_start) {
//
// We have passed the end of the Full Alpha period.
//
if (0.0 <= fade_out_time) {
//
// This effect shader has a defined fade-out time.
//
float alpha = alpha_persistent;
if (alpha == 0)
//
// If the shader has a 0.0 persistent alpha, use its full alpha as the
// persistent alpha.
//
alpha == alpha_full;
//
float fade_rate = fade_out_time / alpha;
if (min_fade_rate > fade_rate)
fade_rate = min_fade_rate;
float result = previous_alpha - (fade_rate * Arg2);
if (result >= 0.0)
return result;
return 0.0F;
}
//
// This effect shader has a negative fade-out time, so drop to zero alpha
// instantly.
//
return 0.0F;
}
}
if (time_since_start < fade_in_time) {
//
// We're fading in to Full Alpha.
//
float a = fade_in_time / Arg2;
float result = (a * alpha_full) + previous_alpha;
if (result > alpha_full)
return alpha_full;
return result;
}
if (full_time > time_since_start - fade_in_time) {
//
// We're in Full Alpha time.
//
return alpha_full;
}
if (fade_out_time > full_time + time_since_start) {
//
// We're past Full Alpha time, and are in Fade Out time. Opacity should fade from
// the Full Alpha ratio to the Persistent Alpha ratio.
//
float alpha_span = alpha_full - alpha_persistent;
float fade_rate = (Arg2 / fade_out_time) * alpha_span;
float result = (double)previous_alpha - (float)fade_rate; // Bethesda casts
return std::max(alpha_persistent, result);
}
return alpha_persistent;
}
This means that the timers work as follows:
- Fade in from zero alpha to Full Alpha Ratio over Fade-In Time seconds.
- Stay at Full Alpha Ratio for Full Alpha Time seconds.
- Fade down from Full Alpha Ratio to Persistent Alpha Ratio for Fade-Out Time seconds. If the alpha value falls below Persistent Alpha Ratio, force it to Persistent Alpha Ratio.
- Stay at Persistent Alpha Ratio until the shader is removed.
- If Full Alpha Time is negative, then drop to zero alpha immediately. Otherwise, continue.
- Fade out from Persistent Alpha Ratio, if it's non-zero, or Full Alpha Ratio otherwise; we will call this value the Prior Alpha. The fade rate is (Fade Out Time / Prior Alpha) per second(?) unless that value falls below a set minimum rate, in which case that minimum rate is used instead.
- The minimum setting is whichever is relevant among the following game settings:
- fEffectShaderParticleMinFadeRate
- fEffectShaderFillEdgeMinFadeRate
- The minimum setting is whichever is relevant among the following game settings:
Some edge-cases:
- If the shader is removed while still in Full Alpha Time, then based on the subroutine above, it would wait for that time to expire, and only begin animating out as normal after Full Alpha Time is up.
On blending[edit source]
I believe the descriptions of the blend modes are wrong, and should be:
- Source Alpha: The pixel currently being blended, times the source alpha.
- Source Inverted Alpha: The pixel currently being blended, times (1 - the source alpha).
- Destination Alpha: The pixel currently being blended, times the destination alpha.
- Destination Inverted Alpha: The pixel currently being blended, times (1 - the destination alpha).
The article suggests the following as equal to a typical alpha blend:
- Source Blend Mode: Source Alpha
- Dest Blend Mode: Source Inverted Alpha
- Blend Operation: Add
The equation for a typical alpha blend, assuming a fully-opaque destination, is , where is the source alpha, is the source ("over") color, and is the destination ("under") color. If Source Inverted Alpha was always the source color times , then we'd just be blending the source (EffectShader) color with itself (); but if instead it's the color being blended times , then when it's used for the Destination Blend Mode, it'd produce , which would indeed match the equation for alpha blending of a fully-opaque destination pixel.
(I'm not sure if it's possible to handle blending of a semitranslucent shader onto a semitranslucent destination; but then, membrane EffectShaders are known not to work on alpha-blended (as opposed to alpha-tested) meshes anyway.)
I'm updating the article accordingly. DavidJCobb (talk) 21:36, 20 August 2024 (EDT)