Class BaseShader

3DS Max Plug-In SDK

Class BaseShader

See Also: Class SpecialFX, Class ShaderParamDlg, Class ShadeContext, Class IllumParams, Class IMtlParams, Class StdMat2, Class Mtl, Class Color, Class ILoad, Class ISave.

class BaseShader : public SpecialFX

Description:

This class is available in release 3.0 and later only.

This is one of the base classes for the creation of Shaders which plug-in to the Standard material. Note: Developers should derive their plug-in Shader from Class Shader rather than this class directly since otherwise the interactive renderer won't know how to render the Shader in the viewports.

Developers of this plug-in type need to understand how the Standard material and the Shader work together.

Every material has a Shader. The Shader is the piece of code which controls how light is reflected off the surface. The Standard material is basically the mapping mechanism. It handles all the texturing for the material. It also manages the user interface. This simplifies things so the Shader plug-in only needs to worry about the interaction of light on the surface.

Prior to release 3 developers could write Material plug-ins that performed their own shading, however ths was usually a major programming task. Release 3 provides the simpler Shader plug-in that would benefit from sharing all the common capabilities. The Standard material, with its 'blind' texturing mechanism, makes this possible. It doesn't know what it is texturing -- it simply texturing 'stuff'. The shader names the channels (map), fills in the initial values, specifies if they are a single channel (mono) or a triple channel (color). The Standard material handles the rest including managing the user interface.

Most of the code in a Shader has to do with supplying this information to a Standard material. The values are passed and received back in class IllumParams. There is a single method in a shader which actually does the shading. This is the Illum() method.

Plug-In Information:

Class Defined In SHADER.H

Super Class ID SHADER_CLASS_ID

Standard File Name Extension DLB

Extra Include File Needed SHADERS.H

Methods Groups:

The hyperlinks below take you to the start of groups of related methods within the class:

Mapping Channel Access

Dialog (Rollup) Access

Illumination Related Methods

Standard Parameter Access

Compositing Method

Load / Save Methods

Methods:

public:

Dialog (Rollup) Access

Prototype:

virtual ShaderParamDlg* CreateParamDialog(HWND hOldRollup, HWND hwMtlEdit, IMtlParams *imp, StdMtl2* theMtl, int rollupOpen, int n=0)=0;

Remarks:

This method creates and returns a pointer to a ShaderParamDlg object and puts up the dialog which lets the user edit the Shader's parameters.

Parameters:

HWND hOldRollup

The window handle of the old rollup. If non-NULL the IMtlParams method ReplaceRollup method is usually used instead of AddRollup() to present the rollup.

HWND hwMtlEdit

The window handle of the material editor.

IMtlParams *imp

The interface pointer for calling methods in 3ds max.

StdMtl2* theMtl

Points to the Standard material being edited.

int rollupOpen

TRUE to have the UI rollup open; FALSE if closed.

int n=0

This parameter is available in release 4.0 and later only.

Specifies the number of the rollup to create. Reserved for future use with multiple rollups.

Prototype:

virtual ShaderParamDlg* GetParamDlg(int n=0)=0;

Remarks:

Returns a pointer to the ShaderParamDlg object which manages the user interface.

Parameters:

int n=0

This parameter is available in release 4.0 and later only.

Specifies the rollup to get ShaderParamDlg for. Reserved for future use with multiple rollups.

Prototype:

virtual void SetParamDlg(ShaderParamDlg* newDlg, int n=0)=0;

Remarks:

Sets the ShaderParamDlg object which manages the user interface to the one passed.

Parameters:

ShaderParamDlg* newDlg

Points to the new ShaderParamDlg object.

int n=0

This parameter is available in release 4.0 and later only.

Specifies the rollup to set ShaderParamDlg for. Reserved for future use with multiple rollups.

 

Standard Parameter Access

Prototype:

virtual ULONG SupportStdParams()=0;

Remarks:

Returns a value which indicates which of the standard parameters are supported.

Return Value:

See List of Shader Standard Parameter Flags.

Prototype:

virtual void ConvertParamBlk(ParamBlockDescID *descOld, int oldCount, IParamBlock *oldPB)=0;

Remarks:

This method is only required for R2.5 shaders to convert the previous Standard material parameter blocks to the current version.

Parameters:

ParamBlockDescID *descOld

Points to the old parameter block descriptor.

int oldCount

The number in the array of parameters above.

IParamBlock *oldPB

Points to the old parameter block.

Prototype:

virtual int NParamDlgs();

Remarks:

This method is available in release 4.0 and later only.

Returns the number of rollups this shader is requesting.

Default Implementation:

{ return 1; }

Illumination Related Methods

Prototype:

virtual void Illum(ShadeContext &sc, IllumParams &ip)=0;

Remarks:

This is the illumination function for the Shader.

Developers will find it very helpful to review the Mtl::Shade() method of the Standard material. This is the main method of the material which computes the color of the point being rendered. This code is available in \MAXSDK\SAMPLES\MATERIALS\STDMTL2.CPP. This code shows how the Standard calls Shader::GetIllumParams(), sets up mapping channels, calls this Illum() method, and calls the Shader::CombineComponents() method when all done.

Parameters:

ShadeContext &sc

The ShadeContext which provides information on the pixel being shaded.

IllumParams &ip

The object whose data members provide communication between 3ds max and the shader. Input values are read from here and output values are stored here. Note that the XOut (ambIllumout, etc) data members of this class are initialized to 0 before the first call to this method.

The input to this method has the textured illumination parameters, the bump perturbed normal, the view vector, the raw (unattenuated) colors in the reflection and refraction directions, etc.

Sample Code:

Below is a brief analysis of the standard Blinn shader Illum() method. This is the standard 'computer graphics look' type shader supplied with 3ds max. The entire method follows:

void Blinn2::Illum(ShadeContext &sc, IllumParams &ip) {

 LightDesc *l;

 Color lightCol;

 

 // Blinn style phong

 BOOL is_shiny= (ip.channels[ID_SS].r > 0.0f) ? 1:0;

 double phExp = pow(2.0, ip.channels[ID_SH].r * 10.0) * 4.0;

 

 for (int i=0; i<sc.nLights; i++) {

  l = sc.Light(i);

  register float NL, diffCoef;

  Point3 L;

  if (l->Illuminate(sc,ip.N,lightCol,L,NL,diffCoef)) {

   if (l->ambientOnly) {

    ip.ambIllumOut += lightCol;

    continue;

    }

   if (NL<=0.0f)

    continue;

 

   // diffuse

   if (l->affectDiffuse)

    ip.diffIllumOut += diffCoef * lightCol;

 

   // specular (Phong2)

   if (is_shiny&&l->affectSpecular) {

    Point3 H = Normalize(L-ip.V);

    float c = DotProd(ip.N,H); 

    if (c>0.0f) {

     if (softThresh!=0.0 && diffCoef<softThresh) {

      c *= Soften(diffCoef/softThresh);

      }

     c = (float)pow((double)c, phExp);

     ip.specIllumOut += c * ip.channels[ID_SS].r * lightCol;

     }

    }

   }

  }

 

 // Apply mono self illumination

 if ( ! selfIllumClrOn ){

  float si = ip.channels[ID_SI].r;

  ip.diffIllumOut = (si>=1.0f) ? Color(1.0f,1.0f,1.0f) :

    ip.diffIllumOut * (1.0f-si) + si;

 } else {

  // colored self illum,

  ip.selfIllumOut += ip.channels[ID_SI];

 }

 // now we can multiply by the clrs,

 ip.ambIllumOut *= ip.channels[ID_AM];

 ip.diffIllumOut *= ip.channels[ID_DI];

 ip.specIllumOut *= ip.channels[ID_SP];

 

 // the following is applicable only in R4

int chan = ip.stdIDToChannel[ ID_RL ];

 ShadeTransmission(sc, ip, ip.channels[chan], ip.refractAmt);

 chan = ip.stdIDToChannel[ ID_RR ];

 ShadeReflection( sc, ip, ip.channels[chan] );

 CombineComponents( sc, ip );

}

Some of the key parts of this method are discussed below:

The is_shiny line sets a boolean based on if the Shader has a shininess setting > 0. Note that the Blinn shader uses the same channel ordering as the original Standard material so it can index its channels using the standard ID ID_SS.

BOOL is_shiny= (ip.channels[ID_SS].r > 0.0f) ? 1:0;

Next the 'Phong Exponent' is computed. This is just a function that is used to give a certain look. It uses 2^(Shinniness *10) * 4. This calculation simply 'feels right' and gives a good look.

double phExp = pow(2.0, ip.channels[ID_SH].r * 10.0) * 4.0;

The following loop sums up the effect of each light on this point on surface.

for (int i=0; i<sc.nLights; i++) {

Inside the loop, the light descriptor is grabbed from the ShadeContext:

  l = sc.Light(i);

The LightDesc::Illuminate() method is then called to compute some data:

  if (l->Illuminate(sc,ip.N,lightCol,L,NL,diffCoef)) {

To Illuminate() is passed the ShadeContext (sc), and the normal to the surface (ip.N) (pointing away from the surface point).

The method returns the light color (lightCol), light vector (L) (which points from the surface point to the light), the dot product of N and L (NL) and the diffuse coefficient (diffCoef). The diffuse coefficient is similar to NL but has the atmosphere between the light and surface point taken into account as well.

The next piece of code checks if the light figures into the computations:

   if (NL<=0.0f)

    continue;

If NL<0 then the cosine of the vectors is greater than 90 degrees. This means the light is looking at the back of the surface and is therefore not to be considered.

The next statement checks if the light affect the diffuse channel (lights may toggle on or off their ability to affect the diffuse and specular channels.)

   if (l->affectDiffuse)

    ip.diffIllumOut += diffCoef * lightCol;

If the light affects the diffuse channel then the diffuse illumination output component of the IllumParams is added to. This is done by multiplying the diffuse coefficient (returned by LightDesc::Illuminate()) times the light color (also returned by LightDesc::Illuminate()). Notice that the diffIllumOut is being accumulated with each pass of the light loop.

The next section of code involves the specular component. If the light is shiny, and it affects the specular channel a vector H is computed.

   if (is_shiny&&l->affectSpecular) {

Note the following about this H vector computation. Most vectors are considered pointing from the point on the surface. The view vector (IllumParams::V) does not follow this convention. It points from the 'eye' towards the surface. Thus it's reversed from the usual convention.

H is computed to be the average of L and V. This is (L+V)/2. Since we normalize this we don't have to divide by the 2. So, if V followed the standard convention this would be simply L+V. Since it doesn't it is L+(-ip.V) or L-ip.V.

    Point3 H = Normalize(L-ip.V);

Next the dot product of N and H is computed and stored in c. When you take the dot product of two normalized vectors what is returned is the cosine of the angle between the vectors.

    float c = DotProd(ip.N,H); 

If c>0 and the diffuse coefficient is less than the soften threshold then c is modified by a 'softening' curve.

    if (c>0.0f) {

     if (softThresh!=0.0 && diffCoef<softThresh) {

      c *= Soften(diffCoef/softThresh);

      }

Note that the Soften() function is defined in \MAXSDK\SAMPLES\MATERIALS\SHADERUTIL.CPP and developers can copy this code.

     c = (float)pow((double)c, phExp);

Next, c is raised to the power of the Phong exponent. This is effectively taking a cosine (a smooth S curve) and raising it to a power. As it is raised to a power it becomes a sharper and sharper S curve. This is where the shape of the highlight curve in the Materials Editor UI comes from.

That completes the pre computations for the specular function. Then c is multiplied by the specular strength (ip.channels[ID_SS].r) times the light color (lightCol). The result is summed in specular illumination out (ip.specIllumOut).

     ip.specIllumOut += c * ip.channels[ID_SS].r * lightCol;

     }

    }

   }

That completes the light loop. It happens over and over for each light.

Next the self illumunation is computed. If the Self Illumination Color is not on, then the original code for doing mono self illumination is used.

 // Apply mono self illumination

 if ( ! selfIllumClrOn ){

  float si = ip.channels[ID_SI].r;

  ip.diffIllumOut = (si>=1.0f) ? Color(1.0f,1.0f,1.0f) :

    ip.diffIllumOut * (1.0f-si) + si;

 } else {

Otherwise the self illumination color is summed in to the Self Illumination Out (ip.selfIllumOut).

  // colored self illum,

  ip.selfIllumOut += ip.channels[ID_SI];

 }

Then, we multiply by the colors for ambient, diffuse and specular.

 ip.ambIllumOut *= ip.channels[ID_AM];

 ip.diffIllumOut *= ip.channels[ID_DI];

 ip.specIllumOut *= ip.channels[ID_SP];

The results are ambIllumOut, diffIllumOut, and specIllumOut. Note that these components are not summed. In R3 and earlier these results would be returned to the Standard material. However, R4 introduces a couple extra steps.

Finally, we call ShadeTransmission() and ShadeReflection() to apply the transmission/refraction and reflection models. The core implementation of 3ds max provides the standard models, but the shader can override these methods to compute its own models.

int chan = ip.stdIDToChannel[ ID_RL ];

 ShadeTransmission(sc, ip, ip.channels[chan], ip.refractAmt);

 chan = ip.stdIDToChannel[ ID_RR ];

 ShadeReflection( sc, ip, ip.channels[chan] );

 

In R4, It is a shader’s responsibility to combine the components of the shading process prior to exiting Illum() (in R3, this was the responsibility of the Standard material). In order to combine these values together to produce the final color for that point on the surface (IllumParams.finalC), the shader should call CombineComponents() method. The Shader base class provides a default implementation which simply sums everything together and multiplies by the opacity.

 virtual void CombineComponents( IllumParams& ip )

 {

  ip.finalC = ip.finalOpac *

   (ip.ambIllumOut + ip.diffIllumOut + ip.selfIllumOut)

   + ip.specIllumOut + ip.reflIllumOut + ip.transIllumOut ;

}

Prototype:

virtual void AffectReflection(ShadeContext &sc, IllumParams &ip, Color &rcol)=0;

Remarks:

Note: This method has been superceded by ShadeReflection() and is obsolete in release 4.0 and later.

This method provides the shader with an opportunity to affect the reflection code.

Parameters:

ShadeContext &sc

The ShadeContext which provides information on the pixel being shaded.

IllumParams &ip

The object whose data members provide communication between 3ds max and the shader.

Color &rcol

The input reflection color is passed in here and the resulting 'affected' color is stored here.

Sample Code:

A simple example like Phong does the following:

void AffectReflection(ShadeContext &sc, IllumParams &ip, Color &rcol)

{

rcol *= ip.channels[ID_SP];

};

If a color can affect the reflection of light off a surface than it can usually affect the reflection of other things off a surface. Thus some shaders influence the reflection color using the specular color and specular level. For instance the Multi Layer Shader does the following:

#define DEFAULT_GLOSS2 0.03f 

void MultiLayerShader::AffectReflection(ShadeContext &sc, IllumParams &ip, Color &rcol)

{

 float axy = DEFAULT_GLOSS2;

 float norm = 1.0f / (4.0f * PI * axy );

 rcol *= ip.channels[_SPECLEV1].r * ip.channels[_SPECCLR1] * norm;

}

Prototype:

virtual void GetIllumParams(IllumParams* ip)=0;

Remarks:

This method updates the channels (as well as other) data member of the IllumParams object passed to it with the local variables of the material for possible mapping prior to being given to the Shader's Illum() method. The shader plug-in copies the state of all its parameters (at their current animation state) into the data members of the IllumParams passed.

Parameters:

IllumParams* ip

Points to the IllumParams to update.

Prototype:

virtual void ShadeReflection(ShadeContext &sc, IllumParams &ip, Color &mapClr);

Remarks:

This method is available in release 4.0 and later only.

Compute the reflected color from the sc, ip, and reflection map (or ray) color. The core implementation of this provides the standard 3ds max reflection model. To support the standard reflection model, a shader may call this default implementation.

Parameters:

ShadeContext& sc

The context which provides information on the pixel being shaded.

IllumParams& ip

The object whose data members provide communication between 3ds max and the shader.

Color &mapClr

The input reflection (or ray) color is passed in here and the resulting 'affected' color is stored here.

Prototype:

virtual void ShadeTransmission(ShadeContext &sc, IllumParams &ip, Color &mapClr, float amount);

Remarks:

This method is available in release 4.0 and later only.

Compute the transmission/refraction color for the sample.. The core implementation of this provides the standard 3ds max reflection model. To support the standard transmission/refraction model, a shader may call this default implementation.

Parameters:

ShadeContext& sc

The context which provides information on the pixel being shaded.

IllumParams& ip

The object whose data members provide communication between 3ds max and the shader.

Color &mapClr

The input refraction (or ray) color is passed in here and the resulting 'affected' color is stored here.

float amount

The level of the amount spinner for the refraction channel.

Mapping Channel Access

Prototype:

virtual long nTexChannelsSupported()=0;

Remarks:

Returns the number of texture map map channels supported by this Shader.

Prototype:

virtual TSTR GetTexChannelName(long nTextureChan)=0;

Remarks:

Returns the name of the specified texture map channel.

Parameters:

long nTextureChan

The zero based index of the texture map channel whose name is returned.

Prototype:

virtual TSTR GetTexChannelInternalName(long nTextureChan);

Remarks:

Returns the internal name of the specified texture map. The Standard material uses this to get the fixed, parsable internal name for each texture channel it defines.

Parameters:

long nTextureChan

The zero based index of the texture map whose name is returned.

Default Implementation:

{ return GetTexChannelName(nTextureChan); }

Prototype:

virtual long ChannelType(long nTextureChan)=0;

Remarks:

Returns the channel type for the specified texture map channel. There are four channels which are part of the Material which are not specific to the Shader. All other channels are defined by the Shader (what they are and what they are called.) The four which are not the province of the Shader are Bump, Reflection, Refraction and Displacement. For example, Displacement mapping is really a geometry operation and not a shading one. The channel type returned from this method indicates if the specified channel is one of these, or if it is a monochrome channel, a color channel, or is not a supported channel.

Parameters:

long nTextureChan

The zero based index of the texture map whose name is returned.

Return Value:

Texture channel type flags. One or more of the following values:

UNSUPPORTED_CHANNEL

Indicates the channel is not supported (is not used).

CLR_CHANNEL

A color channel. The Color.r, Color.g and Color.b parameters are used.

MONO_CHANNEL 

A monochrome channel. Only the Color.r is used.

BUMP_CHANNEL

The bump mapping channel.

REFL_CHANNEL

The reflection channel.

REFR_CHANNEL

The refraction channel.

DISP_CHANNEL

The displacement channel.

ELIMINATE_CHANNEL

Indicates that the channel is not supported. For example, a certain Shader might not support displacement mapping for some reason. If it didn't, it could use this channel type to eliminate the support of displacement mapping for itself. It would be as if displacement mapping was not included in the material. None of the 3ds max shaders use this.

SKIP_CHANNELS

This is used internally to indicate that the channels to be skipped.

Prototype:

virtual long StdIDToChannel(long stdID)=0;

Remarks:

Returns the index of this Shader's channels which corresponds to the specified Standard materials texture map ID. This allows the Shader to arrange its channels in any order it wants in the IllumParams::channels array but enables the Standard material to access specific ones it needs (for instance the Bump channel or Reflection channel).

Parameters:

long stdID

The ID whose corresponding channel to return. See List of Texture Map Indices.

Return Value:

The zero based index of the channel. If there is not a corresponding channel return -1.

Sample Code:

This can be handled similar to below where an array is initialized with the values of this plug-in shader's channels that correspond to each of the standard channels. Then this method just returns the correspond index from the array.

static int stdIDToChannel[N_ID_CHANNELS] = {

0, 1, 2, 5, 4, -1, 7, 8, 9, 10, 11, 12

};

long StdIDToChannel(long stdID){ return stdIDToChannel[stdID]; }

Prototype:

virtual void Reset()=0;

Remarks:

This method is called when the Shader is first activated in the dropdown list of Shader choices. The Shader should reset itself to its default values.

Compositing Method

Prototype:

virtual void CombineComponents(ShadeContext &sc, IllumParams& ip);

Remarks:

This method does the final compositing of the various illumination components. A default implementation is provided which simply adds the components together. Developers who want to do other more specialized composition can override this method. For example, a certain Shader might want to composited highlights over the underlying diffuse component since the light is reflected and the diffuse color wouldn't fully show through. Such a Shader would provide its own version of this method.

Parameters:

ShadeContext &sc

The ShadeContext which provides information on the pixel being shaded.

IllumParams& ip

The illumination parameters to composite and store.

Default Implementation:

virtual void CombineComponents(IllumParams& ip)

{

 ip.finalC = ip.finalOpac *

  (ip.ambIllumOut + ip.diffIllumOut + ip.selfIllumOut)

  + ip.specIllumOut + ip.reflIllumOut + ip.transIllumOut ;

}

Prototype:

virtual ULONG GetRequirements(int subMtlNum)=0;

Remarks:

Returns the requirements of the Shader for the specified sub-material. Many objects in the rendering pipeline use the requirements to tell the renderer what data needs to be available. The Shader's requirements are OR'd with the combined map requirements and returned to the renderer via the Stdmtl2's GetRequirements() function.

Parameters:

int subMtlNum

This parameter is not used.

Return Value:

One or more of the following flags:

See List of Material Requirement Flags.

Load / Save Methods

Prototype:

IOResult Save(ISave *isave)=0;

Remarks:

Saves the plug-in's name. This should be called at the start of a plug-in's Save() method.

Parameters:

ISave *isave

An interface for saving data.

Prototype:

IOResult Load(ILoad *iload)=0;

Remarks:

Loads the plug-in's name. This should be called at the start of a plug-in's Load() method.

Parameters:

ILoad *iload

An interface for loading data.