M2/Rendering: Difference between revisions
m (→Blending Modes) |
m (→Blending Modes) |
||
(138 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
== M2Element == | |||
== | An <tt>M2</tt> is rendered as a series of <tt>M2Element</tt>s which are constructed at runtime in <tt>CM2SceneRender::BeginDraw()</tt>, and sorted prior to drawing. | ||
=== Element Alpha === | |||
For a given <tt>M2Element</tt>, whole-element alpha is calculated by multiplying the following things together: | |||
* <tt>batch->color.alpha</tt> is the <tt>M2Batch</tt> animated color track alpha value (if present) | |||
* <tt>batch->textureWeight</tt> is the <tt>M2Batch</tt> animated texture weight track value (if present); if <tt>batch->textureCount</tt> is > 1, only the first <tt>textureWeight</tt> value is used | |||
* <tt>m2->alpha</tt> is overall model opacity, which can be manipulated by things like distance fading and similar effects; default: <tt>1.0</tt> | |||
<syntaxhighlight lang="cpp"> | |||
element->alpha = batch->color.alpha * batch->textureWeight * m2->alpha; | |||
</syntaxhighlight> | |||
=== Element Sorting === | |||
TODO | |||
== Blending Modes == | |||
The full blending state table for can be found on the [[Rendering]] page. | |||
=== M2BLEND === | |||
In | In World of Warcraft, the value listed in <tt>M2Material->blendMode</tt> is not a direct lookup in the <tt>[[Rendering#EGxBlend|EGxBlend]]</tt> state table. The value is translated as outlined below. | ||
Blend modes 3 and 7 are not used by any model in 3.3.5. | |||
{| class="wikitable sortable" | {| class="wikitable sortable" | ||
|- | |- | ||
! | ! Idx | ||
! | ! M2BLEND | ||
! | ! EGxBlend Idx | ||
! | ! EGxBlend | ||
|- | |||
| <tt>0</tt> | |||
| <tt>M2BLEND_OPAQUE</tt> | |||
| <tt>0</tt> | |||
| <tt>GxBlend_Opaque</tt> | |||
|- | |- | ||
| | | <tt>1</tt> | ||
| | | <tt>M2BLEND_ALPHA_KEY</tt> | ||
| | | <tt>1</tt> | ||
| | | <tt>GxBlend_AlphaKey</tt> | ||
| | |- | ||
| | | <tt>2</tt> | ||
| | | <tt>M2BLEND_ALPHA</tt> | ||
| <tt>2</tt> | |||
| <tt>GxBlend_Alpha</tt> | |||
|- | |||
| <tt>3</tt> | |||
| <tt>M2BLEND_NO_ALPHA_ADD</tt> | |||
| <tt>10</tt> | |||
| <tt>GxBlend_NoAlphaAdd</tt> | |||
|- | |||
| <tt>4</tt> | |||
| <tt>M2BLEND_ADD</tt> | |||
| <tt>3</tt> | |||
| <tt>GxBlend_Add</tt> | |||
|- | |||
| <tt>5</tt> | |||
| <tt>M2BLEND_MOD</tt> | |||
| <tt>4</tt> | |||
| <tt>GxBlend_Mod</tt> | |||
|- | |- | ||
| | | <tt>6</tt> | ||
| | | <tt>M2BLEND_MOD2X</tt> | ||
| | | <tt>5</tt> | ||
| | | <tt>GxBlend_Mod2x</tt> | ||
|- | |- | ||
| | | <tt>7</tt> | ||
| | | <tt>M2BLEND_BlendAdd</tt> | ||
| | | <tt>13</tt> | ||
| | | <tt>GxBlend_BlendAdd</tt> | ||
| | |} | ||
| | == Combiners == | ||
=== M2COMBINER === | |||
This enum maps 1:1 against <tt>EGxTexBlend</tt>. | |||
{| class="wikitable sortable" | |||
|- | |- | ||
! Idx | |||
! M2COMBINER | |||
|- | |- | ||
| | | <tt>0</tt> | ||
| | | <tt>M2COMBINER_OPAQUE</tt> | ||
|- | |- | ||
| | | <tt>1</tt> | ||
| | | <tt>M2COMBINER_MOD</tt> | ||
|- | |- | ||
| | | <tt>2</tt> | ||
| | | <tt>M2COMBINER_DECAL</tt> | ||
|- | |- | ||
| | | <tt>3</tt> | ||
| | | <tt>M2COMBINER_ADD</tt> | ||
|- | |- | ||
| | | <tt>4</tt> | ||
| | | <tt>M2COMBINER_MOD2X</tt> | ||
|- | |- | ||
| | | <tt>5</tt> | ||
| | | <tt>M2COMBINER_FADE</tt> | ||
|- | |- | ||
| | | <tt>6</tt> | ||
| | | <tt>M2COMBINER_MOD2X_NA</tt> | ||
|- | |- | ||
| | | <tt>7</tt> | ||
| | | <tt>M2COMBINER_ADD_NA</tt> | ||
|- | |- | ||
| | | <tt>7</tt> | ||
| | | <tt>M2COMBINER_OP_MASK</tt> | ||
|- | |- | ||
| | | <tt>8</tt> | ||
| | | <tt>M2COMBINER_ENVMAP</tt> | ||
|} | |} | ||
== Fog Modes == | == Fog Modes == | ||
Line 142: | Line 124: | ||
Because blending modes adjust how color is applied during a draw call, fog color often has to be modified accordingly. The client maps blending modes to fog modes using <tt>s_fogModeList</tt>. | Because blending modes adjust how color is applied during a draw call, fog color often has to be modified accordingly. The client maps blending modes to fog modes using <tt>s_fogModeList</tt>. | ||
<tt>M2Material</tt> flag <tt>0x02</tt> causes fog mode <tt>0</tt> (disabled) to always be selected, regardless of blending mode. | |||
In certain cases (possibly when the mesh or the camera is underwater), <tt>s_blendDisableUWFog</tt> may force fog mode <tt>0</tt> (disabled) to be selected based on blending mode. As of Mists of Pandaria, it appears this applies to <tt>Blend_Add</tt> and <tt>Blend_NoAlphaAdd</tt>. | |||
The following fog modes were taken from Mists of Pandaria (build 15662). | The following fog modes were taken from Mists of Pandaria (build 15662). | ||
Line 157: | Line 141: | ||
|- | |- | ||
| 1 | | 1 | ||
| | | Use fog color (without changes) | ||
| <tt>Blend_Opaque</tt>, <tt>Blend_AlphaKey</tt>, <tt>Blend_Alpha</tt> | | <tt>Blend_Opaque</tt>, <tt>Blend_AlphaKey</tt>, <tt>Blend_Alpha</tt> | ||
|- | |- | ||
Line 173: | Line 157: | ||
|- | |- | ||
| 5 | | 5 | ||
| ? submerged camera related | | ? submerged camera related; use <tt>ViewSettings::s_fogInfo</tt> 2 or 3 | ||
| - | | - | ||
|- | |- | ||
| 6 | | 6 | ||
| ? liquid plane related | | ? liquid plane related; use <tt>ViewSettings::s_fogInfo</tt> 3 or use <tt>CM2Lighting</tt> fog | ||
| - | | - | ||
|} | |} | ||
Line 185: | Line 169: | ||
Similar to fog modes above, blending modes also impact lighting. The client maps blending modes to lighting modes using <tt>s_shadedList</tt>. | Similar to fog modes above, blending modes also impact lighting. The client maps blending modes to lighting modes using <tt>s_shadedList</tt>. | ||
<tt>M2Material</tt> flag <tt>0x01</tt> always causes lighting mode <tt>0</tt> (disabled) to be selected. | |||
The following lighting modes were taken from Mists of Pandaria (build 15662). | The following lighting modes were taken from Mists of Pandaria (build 15662). | ||
Line 196: | Line 180: | ||
|- | |- | ||
| 0 | | 0 | ||
| Disable lighting logic in shader | | Disable lighting logic in shader, including local lights | ||
| <tt>Blend_Mod</tt>, <tt>Blend_Mod2x</tt> | | <tt>Blend_Mod</tt>, <tt>Blend_Mod2x</tt> | ||
|- | |- | ||
Line 203: | Line 187: | ||
| <tt>Blend_Opaque</tt>, <tt>Blend_AlphaKey</tt>, <tt>Blend_Alpha</tt>, <tt>Blend_Add</tt>, possibly: <tt>Blend_NoAlphaAdd</tt> | | <tt>Blend_Opaque</tt>, <tt>Blend_AlphaKey</tt>, <tt>Blend_Alpha</tt>, <tt>Blend_Add</tt>, possibly: <tt>Blend_NoAlphaAdd</tt> | ||
|} | |} | ||
== Alpha Testing == | |||
Alpha testing is enabled for most or all <tt>M2Element</tt>s being rendered. The alpha comparison function is always <tt>D3DCMP_GREATEREQUAL</tt>, which discards pixels with alpha values lower than the provided <tt>alphaRef</tt>. | |||
For <tt>Blend_AlphaKey</tt> blending, the <tt>alphaRef</tt> value starts at a baseline, and is multiplied by the <tt>M2Element->alpha</tt>. This multiplication prevents inappropriate pixel discards during things like fades for distance culling. See the M2Element alpha section above for details on what can modify <tt>M2Element</tt> alpha. | |||
For all other blending modes (ie <tt>!(Blend_AlphaKey)</tt>, the <tt>alphaRef</tt> value is constant. | |||
{| class="wikitable" | |||
|- | |||
! Blending Mode | |||
!colspan="2"|alphaRef | |||
|- | |||
| <tt>!(Blend_AlphaKey)</tt> | |||
| All | |||
| <tt>1.0 / 255.0</tt> (constant) | |||
|- | |||
|rowspan="2"|<tt>Blend_AlphaKey</tt> | |||
| >= Cata | |||
| <tt>(128.0 / 255.0) * element->alpha</tt> | |||
|- | |||
| <= WotLK | |||
| <tt>(224.0 / 255.0) * element->alpha</tt> | |||
|} | |||
== Shading == | |||
=== Vertex Shaders === | |||
TODO | |||
=== Pixel Shaders === | |||
The following pixel shaders are used to blend mesh color and textures together, prior to drawing the relevant geometry to the framebuffer. | |||
See the [[Rendering#Texture_Blending|texture blending section]] on the [[Rendering|main rendering page]] for more information on the intent and history of the logic performed in the pixel shaders. | |||
As with the other shaders used by the game, the M2 pixel shaders are housed within [[BLS|BLS files]] in the <tt>shaders</tt> directory in the game data. | |||
{| class="wikitable sortable" | |||
|- | |||
! Shader Name | |||
! # Tex | |||
! RGB Logic | |||
! Alpha Logic | |||
|- | |||
| <tt>Combiners_Add</tt> | |||
| <tt>1</tt> | |||
| <tt>out.rgb = in.rgb + tex0.rgb;</tt> | |||
| <tt>out.a = in.a + tex0.a;</tt> | |||
|- | |||
| <tt>Combiners_Decal</tt> | |||
| <tt>1</tt> | |||
| <tt>out.rgb = mix(in.rgb, tex0.rgb, in.a);</tt> | |||
| <tt>out.a = in.a;</tt> | |||
|- | |||
| <tt>Combiners_Fade</tt> | |||
| <tt>1</tt> | |||
| <tt>out.rgb = mix(tex0.rgb, in.rgb, in.a);</tt> | |||
| <tt>out.a = in.a;</tt> | |||
|- | |||
| <tt>Combiners_Mod</tt> | |||
| <tt>1</tt> | |||
| <tt>out.rgb = in.rgb * tex0.rgb;</tt> | |||
| <tt>out.a = in.a * tex0.a;</tt> | |||
|- | |||
| <tt>Combiners_Mod2x</tt> | |||
| <tt>1</tt> | |||
| <tt>out.rgb = in.rgb * tex0.rgb * 2.0;</tt> | |||
| <tt>out.a = in.a * tex0.a * 2.0;</tt> | |||
|- | |||
| <tt>Combiners_Opaque</tt> | |||
| <tt>1</tt> | |||
| <tt>out.rgb = in.rgb * tex0.rgb;</tt> | |||
| <tt>out.a = in.a;</tt> | |||
|- | |||
| <tt>Combiners_Add_Add</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb + tex0.rgb) + tex1.rgb;</tt> | |||
| <tt>out.a = (in.a + tex0.a) + tex1.a;</tt> | |||
|- | |||
| <tt>Combiners_Add_Mod</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb + tex0.rgb) * tex1.rgb;</tt> | |||
| <tt>out.a = (in.a + tex0.a) * tex1.a;</tt> | |||
|- | |||
| <tt>Combiners_Add_Mod2x</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb + tex0.rgb) * tex1.rgb * 2.0;</tt> | |||
| <tt>out.a = (in.a + tex0.a) * tex1.a * 2.0;</tt> | |||
|- | |||
| <tt>Combiners_Add_Opaque</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb + tex0.rgb) * tex1.rgb;</tt> | |||
| <tt>out.a = in.a + tex0.a;</tt> | |||
|- | |||
| <tt>Combiners_Mod_Add</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) + tex1.rgb;</tt> | |||
| <tt>out.a = (in.a * tex0.a) + tex1.a;</tt> | |||
|- | |||
| <tt>Combiners_Mod_AddNA</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) + tex1.rgb;</tt> | |||
| <tt>out.a = in.a * tex0.a;</tt> | |||
|- | |||
| <tt>Combiners_Mod_Mod</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb;</tt> | |||
| <tt>out.a = (in.a * tex0.a) * tex1.a;</tt> | |||
|- | |||
| <tt>Combiners_Mod_Mod2x</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0;</tt> | |||
| <tt>out.a = (in.a * tex0.a) * tex1.a * 2.0;</tt> | |||
|- | |||
| <tt>Combiners_Mod_Mod2xNA</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0;</tt> | |||
| <tt>out.a = in.a * tex0.a;</tt> | |||
|- | |||
| <tt>Combiners_Mod_Opaque</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb;</tt> | |||
| <tt>out.a = in.a * tex0.a;</tt> | |||
|- | |||
| <tt>Combiners_Mod2x_Add</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * 2.0 + tex1.rgb;</tt> | |||
| <tt>out.a = tex0.a * 2.0 + tex1.a;</tt> | |||
|- | |||
| <tt>Combiners_Mod2x_Mod2x</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 4.0;</tt> | |||
| <tt>out.a = tex0.a * tex1.a * 4.0;</tt> | |||
|- | |||
| <tt>Combiners_Mod2x_Opaque</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0;</tt> | |||
| <tt>out.a = tex0.a * 2.0;</tt> | |||
|- | |||
| <tt>Combiners_Opaque_Add</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) + tex1.rgb;</tt> | |||
| <tt>out.a = in.a + tex1.a;</tt> | |||
|- | |||
| <tt>Combiners_Opaque_AddAlpha</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) + (tex1.rgb * tex1.a);</tt> | |||
| <tt>out.a = in.a;</tt> | |||
|- | |||
| <tt>Combiners_Opaque_AddAlpha_Alpha</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) + (tex1.rgb * tex1.a * tex0.a);</tt> | |||
| <tt>out.a = in.a;</tt> | |||
|- | |||
| <tt>Combiners_Opaque_AddNA</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) + tex1.rgb;</tt> | |||
| <tt>out.a = in.a;</tt> | |||
|- | |||
| <tt>Combiners_Opaque_Mod</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb;</tt> | |||
| <tt>out.a = in.a * tex1.a;</tt> | |||
|- | |||
| <tt>Combiners_Opaque_Mod2x</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0;</tt> | |||
| <tt>out.a = in.a * tex1.a * 2.0;</tt> | |||
|- | |||
| <tt>Combiners_Opaque_Mod2xNA</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0;</tt> | |||
| <tt>out.a = in.a;</tt> | |||
|- | |||
| <tt>Combiners_Opaque_Mod2xNA_Alpha</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * mix(tex1.rgb * 2.0, vec3(1.0), tex0.a);</tt> | |||
| <tt>out.a = in.a;</tt> | |||
|- | |||
| <tt>Combiners_Opaque_Opaque</tt> | |||
| <tt>2</tt> | |||
| <tt>out.rgb = (in.rgb * tex0.rgb) * tex1.rgb;</tt> | |||
| <tt>out.a = in.a;</tt> | |||
|} | |||
''' Table Notes ''' | |||
* <tt>tex0.rgba</tt>, <tt>tex1.rgba</tt>, etc are <tt>vec4</tt>s containing sampled textures | |||
* <tt>in.rgba</tt> is a <tt>vec4</tt> containing the input color for the pixel shader, which comes from the output of the vertex shader | |||
* <tt>out.rgba</tt> is a <tt>vec4</tt> containing the result of texture blending | |||
* # Tex shows the number of textures required for the given shader | |||
* In the interest of clarity / brevity, no lighting or fogging operations are shown | |||
== Particle Emitters == | |||
=== CParticleMat === | |||
<tt>CParticleMat</tt> structs are set up by the client at runtime, based on the data present in the <tt>M2Particle</tt> structs in the M2 data files. | |||
The structure is quite simple: | |||
<syntaxhighlight lang="cpp"> | |||
struct CParticleMat { | |||
uint32_t blending; | |||
// flags | |||
__int32 enableLighting : 1; | |||
__int32 enableFog : 1; | |||
__int32 enableDepthWrites : 1; | |||
}; | |||
</syntaxhighlight> | |||
==== Blending Type ==== | |||
<tt>CParticleMat->blending</tt> is set to the result of the lookup of <tt>M2Particle->blendingType</tt> in <tt>s_gxBlend[]</tt>. The value translates to a mode from <tt>EGxBlend</tt> (pixel blending modes). | |||
In more recent clients, <tt>s_gxBlend[]</tt> maps 1:1 to <tt>EGxBlend</tt>. In older clients, <tt>s_gxBlend</tt> can be a bit different. [[Rendering#Pixel_Blending|Learn more about pixel blending]]. | |||
==== Flags ==== | |||
<tt>0.5.3.3368</tt> through <tt>3.3.5.12340</tt>: | |||
{| class="wikitable sortable" | |||
|- | |||
! Value | |||
! Flag | |||
|- | |||
| <tt>0x1</tt> | |||
| <tt>enableLighting</tt> | |||
|- | |||
| <tt>0x2</tt> | |||
| <tt>enableFog</tt> | |||
|- | |||
| <tt>0x4</tt> | |||
| <tt>enableDepthWrites</tt> | |||
|} | |||
==== Logic ==== | |||
3.3.5a: | |||
<syntaxhighlight lang="cpp"> | |||
CParticleMat newMat; | |||
newMat->enableLighting = 1; | |||
newMat->enableFog = 1; | |||
newMat->enableDepthWrites = 1; | |||
EGxBlend gxBlend = s_gxBlend[m2Particle->blendingType]; | |||
newMat->blending = gxBlend; | |||
switch (gxBlend) { | |||
case GxBlend_Opaque: | |||
case GxBlend_AlphaKey: | |||
newMat->enableDepthWrites = 1; | |||
break; | |||
case GxBlend_Alpha: | |||
case GxBlend_Mod: | |||
case GxBlend_Mod2x: | |||
case GxBlend_NoAlphaAdd: | |||
newMat->enableDepthWrites = 0; | |||
break; | |||
default: | |||
break; | |||
} | |||
</syntaxhighlight> |
Latest revision as of 03:58, 29 June 2023
M2Element
An M2 is rendered as a series of M2Elements which are constructed at runtime in CM2SceneRender::BeginDraw(), and sorted prior to drawing.
Element Alpha
For a given M2Element, whole-element alpha is calculated by multiplying the following things together:
- batch->color.alpha is the M2Batch animated color track alpha value (if present)
- batch->textureWeight is the M2Batch animated texture weight track value (if present); if batch->textureCount is > 1, only the first textureWeight value is used
- m2->alpha is overall model opacity, which can be manipulated by things like distance fading and similar effects; default: 1.0
element->alpha = batch->color.alpha * batch->textureWeight * m2->alpha;
Element Sorting
TODO
Blending Modes
The full blending state table for can be found on the Rendering page.
M2BLEND
In World of Warcraft, the value listed in M2Material->blendMode is not a direct lookup in the EGxBlend state table. The value is translated as outlined below. Blend modes 3 and 7 are not used by any model in 3.3.5.
Idx | M2BLEND | EGxBlend Idx | EGxBlend |
---|---|---|---|
0 | M2BLEND_OPAQUE | 0 | GxBlend_Opaque |
1 | M2BLEND_ALPHA_KEY | 1 | GxBlend_AlphaKey |
2 | M2BLEND_ALPHA | 2 | GxBlend_Alpha |
3 | M2BLEND_NO_ALPHA_ADD | 10 | GxBlend_NoAlphaAdd |
4 | M2BLEND_ADD | 3 | GxBlend_Add |
5 | M2BLEND_MOD | 4 | GxBlend_Mod |
6 | M2BLEND_MOD2X | 5 | GxBlend_Mod2x |
7 | M2BLEND_BlendAdd | 13 | GxBlend_BlendAdd |
Combiners
M2COMBINER
This enum maps 1:1 against EGxTexBlend.
Idx | M2COMBINER |
---|---|
0 | M2COMBINER_OPAQUE |
1 | M2COMBINER_MOD |
2 | M2COMBINER_DECAL |
3 | M2COMBINER_ADD |
4 | M2COMBINER_MOD2X |
5 | M2COMBINER_FADE |
6 | M2COMBINER_MOD2X_NA |
7 | M2COMBINER_ADD_NA |
7 | M2COMBINER_OP_MASK |
8 | M2COMBINER_ENVMAP |
Fog Modes
Because blending modes adjust how color is applied during a draw call, fog color often has to be modified accordingly. The client maps blending modes to fog modes using s_fogModeList.
M2Material flag 0x02 causes fog mode 0 (disabled) to always be selected, regardless of blending mode.
In certain cases (possibly when the mesh or the camera is underwater), s_blendDisableUWFog may force fog mode 0 (disabled) to be selected based on blending mode. As of Mists of Pandaria, it appears this applies to Blend_Add and Blend_NoAlphaAdd.
The following fog modes were taken from Mists of Pandaria (build 15662).
Fog Mode | Action | Blending Modes |
---|---|---|
0 | Disable fog logic in shader | - |
1 | Use fog color (without changes) | Blend_Opaque, Blend_AlphaKey, Blend_Alpha |
2 | Override fog color to 0x000000 (black) | Blend_Add, possibly: Blend_NoAlphaAdd |
3 | Override fog color to 0xFFFFFF (white) | Blend_Mod |
4 | Override fog color to 0x808080 (half white) | Blend_Mod2x |
5 | ? submerged camera related; use ViewSettings::s_fogInfo 2 or 3 | - |
6 | ? liquid plane related; use ViewSettings::s_fogInfo 3 or use CM2Lighting fog | - |
Lighting Modes
Similar to fog modes above, blending modes also impact lighting. The client maps blending modes to lighting modes using s_shadedList.
M2Material flag 0x01 always causes lighting mode 0 (disabled) to be selected.
The following lighting modes were taken from Mists of Pandaria (build 15662).
Lighting Mode | Action | Blending Modes |
---|---|---|
0 | Disable lighting logic in shader, including local lights | Blend_Mod, Blend_Mod2x |
1 | Enable lighting logic in shader | Blend_Opaque, Blend_AlphaKey, Blend_Alpha, Blend_Add, possibly: Blend_NoAlphaAdd |
Alpha Testing
Alpha testing is enabled for most or all M2Elements being rendered. The alpha comparison function is always D3DCMP_GREATEREQUAL, which discards pixels with alpha values lower than the provided alphaRef.
For Blend_AlphaKey blending, the alphaRef value starts at a baseline, and is multiplied by the M2Element->alpha. This multiplication prevents inappropriate pixel discards during things like fades for distance culling. See the M2Element alpha section above for details on what can modify M2Element alpha.
For all other blending modes (ie !(Blend_AlphaKey), the alphaRef value is constant.
Blending Mode | alphaRef | |
---|---|---|
!(Blend_AlphaKey) | All | 1.0 / 255.0 (constant) |
Blend_AlphaKey | >= Cata | (128.0 / 255.0) * element->alpha |
<= WotLK | (224.0 / 255.0) * element->alpha |
Shading
Vertex Shaders
TODO
Pixel Shaders
The following pixel shaders are used to blend mesh color and textures together, prior to drawing the relevant geometry to the framebuffer.
See the texture blending section on the main rendering page for more information on the intent and history of the logic performed in the pixel shaders.
As with the other shaders used by the game, the M2 pixel shaders are housed within BLS files in the shaders directory in the game data.
Shader Name | # Tex | RGB Logic | Alpha Logic |
---|---|---|---|
Combiners_Add | 1 | out.rgb = in.rgb + tex0.rgb; | out.a = in.a + tex0.a; |
Combiners_Decal | 1 | out.rgb = mix(in.rgb, tex0.rgb, in.a); | out.a = in.a; |
Combiners_Fade | 1 | out.rgb = mix(tex0.rgb, in.rgb, in.a); | out.a = in.a; |
Combiners_Mod | 1 | out.rgb = in.rgb * tex0.rgb; | out.a = in.a * tex0.a; |
Combiners_Mod2x | 1 | out.rgb = in.rgb * tex0.rgb * 2.0; | out.a = in.a * tex0.a * 2.0; |
Combiners_Opaque | 1 | out.rgb = in.rgb * tex0.rgb; | out.a = in.a; |
Combiners_Add_Add | 2 | out.rgb = (in.rgb + tex0.rgb) + tex1.rgb; | out.a = (in.a + tex0.a) + tex1.a; |
Combiners_Add_Mod | 2 | out.rgb = (in.rgb + tex0.rgb) * tex1.rgb; | out.a = (in.a + tex0.a) * tex1.a; |
Combiners_Add_Mod2x | 2 | out.rgb = (in.rgb + tex0.rgb) * tex1.rgb * 2.0; | out.a = (in.a + tex0.a) * tex1.a * 2.0; |
Combiners_Add_Opaque | 2 | out.rgb = (in.rgb + tex0.rgb) * tex1.rgb; | out.a = in.a + tex0.a; |
Combiners_Mod_Add | 2 | out.rgb = (in.rgb * tex0.rgb) + tex1.rgb; | out.a = (in.a * tex0.a) + tex1.a; |
Combiners_Mod_AddNA | 2 | out.rgb = (in.rgb * tex0.rgb) + tex1.rgb; | out.a = in.a * tex0.a; |
Combiners_Mod_Mod | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb; | out.a = (in.a * tex0.a) * tex1.a; |
Combiners_Mod_Mod2x | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0; | out.a = (in.a * tex0.a) * tex1.a * 2.0; |
Combiners_Mod_Mod2xNA | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0; | out.a = in.a * tex0.a; |
Combiners_Mod_Opaque | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb; | out.a = in.a * tex0.a; |
Combiners_Mod2x_Add | 2 | out.rgb = (in.rgb * tex0.rgb) * 2.0 + tex1.rgb; | out.a = tex0.a * 2.0 + tex1.a; |
Combiners_Mod2x_Mod2x | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 4.0; | out.a = tex0.a * tex1.a * 4.0; |
Combiners_Mod2x_Opaque | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0; | out.a = tex0.a * 2.0; |
Combiners_Opaque_Add | 2 | out.rgb = (in.rgb * tex0.rgb) + tex1.rgb; | out.a = in.a + tex1.a; |
Combiners_Opaque_AddAlpha | 2 | out.rgb = (in.rgb * tex0.rgb) + (tex1.rgb * tex1.a); | out.a = in.a; |
Combiners_Opaque_AddAlpha_Alpha | 2 | out.rgb = (in.rgb * tex0.rgb) + (tex1.rgb * tex1.a * tex0.a); | out.a = in.a; |
Combiners_Opaque_AddNA | 2 | out.rgb = (in.rgb * tex0.rgb) + tex1.rgb; | out.a = in.a; |
Combiners_Opaque_Mod | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb; | out.a = in.a * tex1.a; |
Combiners_Opaque_Mod2x | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0; | out.a = in.a * tex1.a * 2.0; |
Combiners_Opaque_Mod2xNA | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb * 2.0; | out.a = in.a; |
Combiners_Opaque_Mod2xNA_Alpha | 2 | out.rgb = (in.rgb * tex0.rgb) * mix(tex1.rgb * 2.0, vec3(1.0), tex0.a); | out.a = in.a; |
Combiners_Opaque_Opaque | 2 | out.rgb = (in.rgb * tex0.rgb) * tex1.rgb; | out.a = in.a; |
Table Notes
- tex0.rgba, tex1.rgba, etc are vec4s containing sampled textures
- in.rgba is a vec4 containing the input color for the pixel shader, which comes from the output of the vertex shader
- out.rgba is a vec4 containing the result of texture blending
- # Tex shows the number of textures required for the given shader
- In the interest of clarity / brevity, no lighting or fogging operations are shown
Particle Emitters
CParticleMat
CParticleMat structs are set up by the client at runtime, based on the data present in the M2Particle structs in the M2 data files.
The structure is quite simple:
struct CParticleMat {
uint32_t blending;
// flags
__int32 enableLighting : 1;
__int32 enableFog : 1;
__int32 enableDepthWrites : 1;
};
Blending Type
CParticleMat->blending is set to the result of the lookup of M2Particle->blendingType in s_gxBlend[]. The value translates to a mode from EGxBlend (pixel blending modes).
In more recent clients, s_gxBlend[] maps 1:1 to EGxBlend. In older clients, s_gxBlend can be a bit different. Learn more about pixel blending.
Flags
0.5.3.3368 through 3.3.5.12340:
Value | Flag |
---|---|
0x1 | enableLighting |
0x2 | enableFog |
0x4 | enableDepthWrites |
Logic
3.3.5a:
CParticleMat newMat;
newMat->enableLighting = 1;
newMat->enableFog = 1;
newMat->enableDepthWrites = 1;
EGxBlend gxBlend = s_gxBlend[m2Particle->blendingType];
newMat->blending = gxBlend;
switch (gxBlend) {
case GxBlend_Opaque:
case GxBlend_AlphaKey:
newMat->enableDepthWrites = 1;
break;
case GxBlend_Alpha:
case GxBlend_Mod:
case GxBlend_Mod2x:
case GxBlend_NoAlphaAdd:
newMat->enableDepthWrites = 0;
break;
default:
break;
}