Single Pass Ambient Occlusion

Feb 15, 2009 by     No Comments    Posted under: 3dsMax, Technical Research, Tips and Tricks

Most people will be aware of the use of the Mental Ray Ambient Occlusion (AO) shader by now and will have read the tutorials scattered around the interwebulator. The question is – Now that the A&D material features ambient occlusion, do you still need to use the old AO shader on it’s own any more?

As great as the A&D shader is, you might not need a material with this many features, or you might want to just add AO to a model without changing the material you already have. Without the need to add a reflection pass into the equation, the process of rendering an occlusion pass is a way of adding an extra layer of soft shadow areas, and can be combined with either a diffuse pass or an ambient color pass depending on the effect required –

This is combined by overlaying the separate occlusion pass with the multiply screen mode in Photoshop or After Effects/Combustion.

You can obviously add a bitmap texture to the light channel of the AO material, but you would lose any specular shading that the standard material provides. Before the A&D material was launched, you could get specular shading and AO by putting a ‘Material to shader’ (MTS) in the bright channel, and then placing your standard material in this. However, it wasn’t a perfect solution, for the following reasons –

  • navigating through the material was problematic as the MTS shader sort of stopped the material editor as clicking on the assigned material presented you with the assign new material box, not the parameters to edit it.
  • There was no way of previewing the texture in the viewport, leading to an instanced texture in the contour channel which had to be turned off to render but was visible in the viewport.
  • Most importantly, there was no actual control of the intensity of the AO effect. You could of course use the parameters within the material (spread, Falloff) to curtail the degree of occlusion but there were times when we wanted to have per-object control of the effect, like what the A&D material offers as well as not necessarily wanting 100% of the occluded solution. (one thing about using the AO shader as a light shader springs up is that there settings are the same for every object)

The solution cropped up to use the new Composite material that ported with 3ds Max 2008.

This material allows you to set up complex overlay materials like you would in Photoshop, with options for the layer modes too. This means that you can keep a standard material and just add this pass, technically like you would when you composite them together in Photoshop.

The best part is that not only to you have other overlay methods to experiment with, you can also change the intensity of the Occlusion effect without losing the spread and falloff values that you like.

This image below was rendered using this technique, and has no shadows, Just Ambient Occlusion.

Controlling Node Visibility with Custom Attributes

Jan 20, 2009 by     No Comments    Posted under: 3dsMax, Characters, Maxscript, Rigging

This was a small attribute inspired after reading Paul Neale’s article on Weak Referencing in 3dsMax. It allows you to store references to nodes within the attribute and then globally control their visibility in a scene. The only thing to note is that obviously, if you are hiding a node that the attribute is on, you will no longer have the dialog showing but you can just place this on a root node or dummy object too. On the visibility attribute, use the set button to specify the nodes you want to control the visibility of. There are three separate groups for mesh, rig and control objects.

Weak References are great for character animators because you can store a load of information about objects in a rig without having the issue of referring to it explicitly by name, nor do you have dependency issues as it stores the node with some jiggery pokery between the RefTargMonitorRefMaker and NodeTransformMonitor classes. Without going into it anymore than that, you can specify a reference in a custom attribute and refer to it via Maxscript like CustomAttribute.Weasel_RootBone.node. This will return the node.

You specify a Weak Reference by using a parameter block in the custom attribute. This must be set to the #maxobject type. If you want to store an array of nodes you should use the #maxobjecttab type. In the reset character utility above i have used a #maxobject to store the rootnode, and a #maxobjecttab to store the other bones.

Look at the code below and you will see the parameter declared in the first section of the attribute. You can obviously set all these things via script but these utilities are a more manageable front end in case you need to add anything to the character afterwards.

parameters main
RigVisNodes type:#MaxObjectTab tabSize:0 tabSizeVariable:true
MeshVisNodes type:#MaxObjectTab tabSize:0 tabSizeVariable:true
CtrlObjsNodes type:#MaxObjectTab tabSize:0 tabSizeVariable:true
HideCVisUI type:#boolean default:false
RigVisHidden type:#boolean default:false
MeshVisHidden type:#boolean default:false
CtrlObjsVisHidden type:#boolean default:false

Once you have the UI working as you like, you can alter these to any purpose you want. I have added a turbosmooth control that allows you to globally set the viewport/render state of the added nodes, as well as a skin pose utility that takes a custom rig and stores allows you to reset the whole rig back to a default position. In the case of storing the visibility nodes, it goes like this –

if VisObjs != undefined do
MeshVisNodes = #() -- resets the #maxobjecttab array
for i in Visobjs do append MeshVisNodes(nodeTransformMonitor node:i forwardTransformChangeMsgs:false)

It is worth researching the types available in the parameter block, as others can be very useful also. I have used the #boolean option to store the UI state of the attribute and this means once minimized, it stays like that when run subsequent times. This keeps everything local to the attribute and eliminates the need for ini files to store settings.

Using the boolean switch could just as easily be applied to the state of a checkbutton – swapping of materials or changing the spline base recomb option on a hair and fur modifier for example. This state is stored within the attribute.

Improvements you can make

Currently, the attribute doesn’t check if the node has been hidden manually. This could be done with a callback but I haven’t implemented it yet. The problem is that you might have to press the button again to toggle the objectset to the state you want.

To add to an object

Select it and run the script, it will apply it to the currently selected stack item.

Where to look in the Maxscript Help for further information

Scripted Plug-in Clauses

Weak References To Nodes in the Expression/Script Controller


Visibility Tracks and Mapped Functions

Jan 14, 2009 by     8 Comments    Posted under: 3dsMax, Maxscript

Some methods are useful and can get overlooked when perusing the MXS help. For me, one of them is the use of a mapped function in maxscript.

Mapped functions act in the same way as a normal function but instead of manually specifying a loop for each element of an array or collection, the loop is performed automatically when a collection or array of values is passed as the first argument of a function.

There is still nothing wrong with performing a function on each element in turn, (and I frequently do), but I wanted to highlight this as an option that can added to the scripting toolkit.

The example is used in this visibility script. I needed something that i could set the object visibility quickly without having to open the curve editor each time. Therefore, if an object did not have a visibility track, it should be added.




mapped fn setviskey objs visval intan outtan =
if (getVisController objs) == undefined do VisibilityAssistant.adddviscontroller objs
local tvis = objs.visibility.controller
addnewkey tvis currenttime
keyind = getKeyIndex tvis currenttime
tvis.keys[keyind].value = visval
tvis.keys[keyind].inTangentType = intan
tvis.keys[keyind].outTangentType = outtan

The mapped function is passed the objs array (via selection as array) and then loops through each element. There is no need to specify a for/loop.

In theory this should be more efficient and faster, but for something like this it really doesnt make any difference.

The other thing of mild interest in the script is a custom trackbar filter. It allows you to only see visibility keys on the trackbar. Feel free to look through the code. You’ll also see the script is contained within a Struct. This allows you to specify one global variable and call many methods from it, including the rollout creation.

download mapped functions script

Using Base64 encoding in 3dsMax

Dec 17, 2008 by     8 Comments    Posted under: 3dsMax, DotNet, Imaging, Maxscript

If you have ever received an email and instead of your normal information for pharmaceutical-related special offers and personal member enhancement, you get a jumble of nonsense, you’re probably already aware of what a Base64 encoded string looks like. Email clients use MIME to transfer messages and attachments, and one way to break up things like images so that it can be sent is Base64 encoding.


Despite looking like the sound you make when trapping your plums in the fridge door, Base64 encoded strings can be used to represent images and sounds and deployed with scripts to avoid the need for external linked dependencies.

DotNet Provides some easy methods to do this within the framework, so here are some functions for converting images to Base64 within 3dsmax below.

fn ConvertImageToBase64String filename =
if (doesfileexist filename) do
memstream = dotnetobject "System.IO.MemoryStream"
ImgLoaded = ImageClass.fromfile filename memstream ImgLoaded.rawformat
Base64string = ConvertClass.ToBase64String (memstream.ToArray())
return Base64String
fn ConvertBase64StringToImage string =
bytearr = convertclass.FromBase64String string
memstream = dotnetobject "System.IO.MemoryStream" bytearr
DecodedImg = ImageClass.fromstream memstream
return DecodedImg

I’ve added these into a utility with a few extras (namely functions to convert and play Wav files using Base64 encoding) You can download this script at the bottom of the page.

Grimlock says “SSdtIGdvbm5hIG9wZW4gYSBjYW4gb2YgV0hPT1BBU1Mgb24geW91IQ==”

If if you have ever read the “Bitmap Values” topic in the MXSHelp, you will be aware of this script –

b=selectbitmap() -- open image file browser
bname="bitmap_"+(getfilenamefile b.filename) -- build name from filename
w=b.width -- get properties of bitmap
format "----------nfn load_% = (n" bname -- start defining function
format "local %=bitmap % %n" bname w h -- create bitmap in function
-- write out a function that unpacks an integer into a pixel color
format "fn unpack val = for p in val collect (r=p/256^2; g=p/256-r*256; b=mod p 256; color r g b)n"
for r=0 to h-1 do -- for each row in the bitmap
-- have function write the column of pixels to the bitmap
( format "setpixels % [0,%] (unpack #(" bname r
pixels=getpixels b [0,r] w -- read in the column of pixels
for c=1 to w do -- loop through each pixel
( p=pixels[c] -- get the pixel
-- pack the pixel into an integer and write it out
format "%" (((p.r as integer)*256+(p.g as integer))*256+(p.b as integer))
if c != w then -- if not at end of data
format ", " -- write a comma
format "))n" -- else close out the line
format "return %n" bname -- function returns the bitmap
format ")n----------n" -- finish off function definition

Base64 is a DotNet method of performing the same thing, and instead of returning a max bitmap, it returns a Dotnet image.

This could probably benefit from being moved into a dedicated dotnet assembly, as I found with the color control a few weeks back, similar functions are much slower within max. Therefore if you are converting large images you might find the UI snagged up for a while.

Finally, to convert text to and from Base64, here’s a great site I found that will do it for you! –

download script

Sort by Hue, I should Coco

Nov 26, 2008 by     No Comments    Posted under: 3dsMax, DotNet, User Controls

Here is an update to the color chart article. Since custom UI controls are kinda my thang, I decided to make one for this. The above left is with the colors sorted by Hue (More about this below). On the right is the previous arrangement where they are sorted by name. The Hue sort isn’t perfect, but it does at least group similar colors together which makes it easier to pick rather than the random sort by name on the right. You pick colors generally by Hue, not by their name.

The advantages of building a custom control are a simpler maxscript implementation. As you can see below this is the code for building the ColorChart in 3ds Max –

dotnet.LoadAssembly "C:LoneRobotclasslibMaxColorControl.dll"
rollout ColorChart "LoneRobot MaxColorChart" width:340 height:610
dotNetControl LRColorChart "LoneRobot.UI.MaxColorControl" pos:[0,0] width:338 height:610
createdialog colorchart

Now that is minimal code! 😉

I was looking into using an IComparer interface for performing a Hue based color sort and found this article –

Thanks goes to Ruud van Eeghem for this posting.

Rather than writing one from scratch, I liked his GDI+ method of generating the color swatches, and since it was in C# I converted it to VB so that I could use it. Once that was done I added a few extra properties and methods to the control, namely taking the picked pixel color and checking to see if it was a ‘named’ dotnet color. Once I had this I made a composite control to extend the functionality and provide the clipboard method seen before. Bingo!

Update –

After a nights’ sleep I realized I wanted a more compact version. The control still displays the color name, but not the full maxscript string. The full string is still copied to the clipboard. You could register a dockable window for this. The new control is embedded into the same assembly, just change the dotnetcontrol declaration to “LoneRobot.UI.MaxColorControlMini”

download script