Cookbook recipe #4: part 1
Determining the material channel the instance of a shader is in
This is actually quite easy but there are a few things that you need to do, hence this page, which demonstrates code needed to solve this problem.
Note: this page only applies to the standard/physical renderers, not Redshift.
This is part 1 of the recipe: with this link you can go to part 2
Why would you want to know this? There are several possible reasons. Suppose you have a shader which can be used in more than one material channel - say, colour and displacement. If it's in a colour channel, perhaps you want certain interface elements to be available, but these shouldn't be available in an instance of the shader in the displacement channel. Or, at render time you might want the shader to do something differently if it's in an alpha channel rather than in the colour channel.
Which channel at render time?
We'll deal with this question first, because it's a simple one to solve. As an example, let's imagine there is an interface element which returns a colour. We've obtained the colour from the interface, probably in the InitRender() function, and stored it in a class-level variable so it can be used in the Output() function of the shader. If the shader is in the colour channel, we want Output( ) to use the actual colour, but if the shader is in the alpha channel we need it to return a greyscale version of the colour.
We know that Output() is passed a ChannelData structure which has a member called 'texflag'. Using a macro called GET_TEX_CHANNEL we can find out which channel is currently being rendered. The code would look like this:
// we already have the colour in a class-level variable called 'm_color'
Vector color_to_use;
Int32 texflag = cd->texflag;
if (GET_TEX_CHANNEL(texflag) == CHANNEL_ALPHA)
{
// convert to greyscale
Float brightness = (m_color.x + m_color.y + m_color.z) * 0.333333;
color_to_use = Vector(brightness);
}
else
{
// we are in some other channel than alpha
// of course, we could test for other specific channels and do something else if required
// otherwise, we just use the original colour
color_to_use = m_color;
}
// now do something with the colour
// ....
So, this is simple: just check which channel is being rendered and do whatever is needed for that channel.
Which channel outside of rendering?
This is a little trickier. In the case of disabling certain interface elements depending on the channel holding the shader, we would probably do this in GetDEnabling(), but then we won't have a ChannelData to work with. Finding the channel a shader is not quite the same as simply determining if the shader is in a particular channel, but in most cases the simple solution is all we need. The way it could work is like this:
- if we have a pointer to the shader, we can find its parent material
- if we have the material, we can get a pointer to any specific channel we are interested in
- if we have a pointer to a channel, we can find if the shader being tested is in that channel
So, we need a function which takes a pointer to the shader and the ID value of whatever channel we want to check, and returns a Bool which will be true if the shader is in that channel and false if it is not. The function could look like this:
Bool OurShaderClass::IsInChannel(GeListNode* node, Int32 chnID) const
{
BaseList2D* blist = nullptr;
BaseMaterial* mat = nullptr;
BaseChannel* channel = nullptr;
BaseShader* shd = nullptr;
Bool success = false;
// 'node' is a pointer to the shader, perhaps obtained from GetDEnabling()
// get the parent object of the shader
blist = (static_cast<BaseList2D*>(node))->GetMain();
// is this BaseList2D a material?
if (blist != nullptr && blist->GetType() == Mmaterial)
{
// get a pointer to the material
mat = static_cast<BaseMaterial*>(blist);
// get the required channel, which was passed into the function as chnID
channel = mat->GetChannel(chnID);
// get the shader in the channel if there is one - it might be empty
if (channel != nullptr)
{
shd = channel->GetShader();
if (shd != nullptr)
{
// first, is this a shader of our type and then if it is, is it the correct instance - that is, the one passed in to the function
if (shd->GetType() == OUR_SHADER_ID)
{
if (shd == node)
success = true; // it's the correct instance of our shader and it's in this channel
else
success = false; // yes, it is our type of shader, but it isn't the correct instance
}
else
{
// it isn't our type of shader at all
success = false;
}
}
else
{
// there is no shader in this channel, so our shader cannot be in it
success = false;
}
}
}
return success;
}
So now, we can call this function from anywhere we have a pointer to the shader - could be GetDEnabling(), GetDDescription(), Message(), InitRender(), etc. - and find out if the shader is in the channel we want to know about. It might work like this:
Bool OurShaderClass::GetDEnabling(const GeListNode* node, const DescID& id, const GeData& t_data, DESCFLAGS_ENABLE flags, const BaseContainer* itemdesc) const
{
// we want to know if this shader is in the alpha channel or not
// we could test any channel instead of or in addition to alpha
Bool inAlphaChannel = false;
// nasty hack here because 'node' is const, but we aren't going to change anything with the pointer so it should be safe
GeListNode* node2 = const_cast<GeListNode*>(node);
// is the shader in the alpha channel?
inAlphaChannel = IsInChannel(node2, CHANNEL_ALPHA);
switch (id[0].id)
{
case ELEMENT_TO_CHANGE:
// this element will be enabled as long as it isn't in the alpha channel; it will be disabled if it's in alpha
return inAlphaChannel == false;
break;
}
return true;
}
All this does is test if the shader is in the alpha channel and if it is, disables the description element. If the material contains two instances of the shader, one in the alpha channel and the other in colour, GetDEnabling() will be called for each instance and the disabling/enabling of the element will be handled correctly.
This works fine, but there is one problem. We only check to see if the channel contains the correct instance of our shader, we don't care if it's something else. That's okay, unless the 'something else' is a Layer shader. If it is, then it might contain an instance of our shader, so that needs to be checked. How to do that is in part 2 of this recipe.
Page last updated June 1st 2025