Analyzing Viewport Lighting [3DS Max 2014]


#6

wow ! thank you so much. I am now trying to find out if it is possible to get the brightness of the faces which are on the backside of the object also. I have to take some time to understand your script!
Thank you again. thats much more than I expected as answer!!!


perhaps it is possible to use some cameras around the object which always see every face together to use the script for every camera to get every faces brightness.
The only drawback for this is that the viewport default lighting is not working because the camera has its own standard light as the viewport has so there is a different lighting in viewport and in the cameras perspective


#7

This line of the code is a “quick backface culling”. It does not take into account the camera FOV, so if you use it in an orthographic view you’ll see faces are correctly isolated, but not in perspective or camera views.

if (faceNormal*viewTM).z > 0 then

If you remove that line, all faces will be parsed.

Unfortunately that won’t work with the script that uses the viewport image to get the colors.
But you can modify the first code to use some sample points and average them, same as does the second code.
The first one only gets the color at the center of the face, but if you use more samples you’ll get a more accurate value.

Regarding the default viewports lighting, the script does not work with them and their parameters are unknown, and I could not find anything in the SDK that would help to reproduce them. Using the menu command to create the default lights does not create the same lights as the default ones, or I was unable to set the scale value properly.

Perhaps you can experiment a little to re-create what the default lights are, or perhaps someone else already knows it.


#8

I am just trying around with the first script. But the colors shown arent right.
The second script would be perfect if I somehow could apply it to two camera views.
So i put to cameras which have all faces in their view and then i set one viewport section for every camera. and the script analyses the orthographic camera views.
Do you know if there is a way to do what the second script is doing with camera views instead of the active viewport?

and… where do you know all these coding tricks from ?
like this one :
result = for f = 1 to obj.numfaces collect

Best wishes and thanks alot,
Flub


#9

Oops… doublepost…


#10

Do you have an example I can reproduce to see where it fails?

The faces almost never have a flat color, at best they have a soft gradient shading, and so that’s why you need to average a few samples to get a better result. The more samples the more accurate the result, and the first code currently does only get the color at the center of the face.

Also, note that the color of the backfaces will be wrong if you leave the code as it is now and remove the backface culling, as the color depends on the light and view angle. So in order to get the color of the backfaces you also need to change the viewing angle for those faces.

If you will use 2 or more fixed cameras, you can get the view vector from the camera direction.

However, the first code does not take into account the ambient and specular values, so it might produce wrong results as it is.

A sample scene would be useful to understand exactly what you want to do.

Regarding the second code, there is no way that I know to get the viewport image from a none active viewport, other than capturing the whole screen and cropping the viewport. I haven’t tried it, but I think with the SDK you can do it.

As far as learning MXS, this website, ScriptSpot and the MXS help are the best resources. It is also good to read things that are not of your interest, even if you don’t understand them. If you always eat apples you’ll never know how an orange tastes. If one day you decide to eat an orange, you may also one day invent the “apple & orange” juice.


#11

sorry for the delay.
i used the svg script from the reference as it is the same method to get the colors like in your first script and it is better to show in a rendering.
It does not have to be 100% precise and the middle of the face is perfect for my case. But as you can see the values differ very much in the script rendering.
Perhaps its just the factor of ambient lighting which needs to be added I look around how this might work.
Why does the lighting depend on the vewing angle? A very matt surface without reflections should not change depending on perspective if I imagine a real world scenario.


thank alot.


#12

The SVG renderer does not take into account the light multiplier, but I added it to the script. I suppose you are not using a multiplier of 1.0, and so the render comes out different. The first script should work though.

You can modify the SVG code by multiplying the resulting color by the light multiplier.

Note that the script does also not take into account the ambient light, but it wouldn’t be hard to add it.

Regarding the viewing and light angles, I meant if you are using specular and glossiness values other than 0.0. If you only use the diffuse value, the color shouldn’t change much, perhaps you would see a very soft gradient. You could also turn off affect specular in the light parameters just to be sure it is not affecting the material.

What values do you get with the first script for that same example? They should be lighter than the SVG image.

As I mentioned earlier, it would be useful a sample scene, so I can reproduce the results you get, because in my test, it seems to
work well.


#13

the multiplier is 1.0, the values I get from script one are wrong…
here is my testfile https://app.box.com/s/md4y9kzikt0p60ypc5ivrud5uhnmok0h


#14

Your scene is gamma corrected so you should correct the output colors as well.
The formula for gamma correction is:

color = 255 * (color/255.0)^(1/2.2)

#15


#16

Besides Gamma correction, your scene also has Exposure Control, so you must use the same exposure algorithm to correct the output colors.


#17

Considering the amount of variables you need to handle, based on your scene, I think it would be faster to just use the viewports images to get the colors instead of building a pseudo render engine. Not the best approach, but see if this code helps you.

You need to select 2 viewports (for front and back faces) as you described, and then enable it.

Keep in mind that any other visible stuff in the scene can alter the colors, so you better hide everything you can before getting the image. You could add some code to hide things like grid, lights, cameras, etc. to the code if you want.

Hope it helps.


(

    try destroydialog ::RO_DISPLAY_FACES_COLORS catch()

    rollout RO_DISPLAY_FACES_COLORS "Faces Colors" width:172 height:124
    (
        button       bt_vp1 "Set View 1" pos:[ 8,8] width:72 height:32
        button       bt_vp2 "Set View 2" pos:[88,8] width:72 height:32
        
        radioButtons rdo1 "Display:" pos:[8,48] labels:#("RGB","H","S","B") columns:4
        checkbutton  btn1 "Enable"   pos:[8,88] width:154 height:28
        timer        clock interval:20 active:false

        global GW_DisplayFacesColors

        local GetFaceVerts = polyop.getfaceverts
        local GetfaceNormal = polyop.getfaceNormal
        local GetFaceCenter = polyop.getFaceCenter
        local GetVert = polyop.getvert
        local node
        local vp_front = undefined
        local vp_back  = undefined
        local data_front = #()
        local data_back  = #()
        local ViewPanelHwnd = 0
        
        fn GetViewportsPanelHWND =
        (
            for j in (windows.getchildrenhwnd #max) where j[4] == "ViewPanel" do exit with j[1]
        )

        fn CalculateFacesColors =
        (
            dib = gw.getViewportDib()

            viewTM = viewport.getTM()
            viewTM.row4 = [0,0,0]

            gw.setTransform (matrix3 1)

            result = for f = 1 to node.numfaces collect
            (
                faceNormal = normalize (GetfaceNormal node f)

                if (faceNormal*viewTM).z > 0 then
                (
                    faceCenter = GetFaceCenter node f
                    fverts = GetFaceVerts node f

                    verts = for j in fverts collect (faceCenter + (GetVert node j)) / 2.0
                    append verts faceCenter
                    numverts = verts.count

                    faceColor = black

                    for j in verts do
                    (
                        px = gw.transPoint j
                        if px.x > 1 and px.y < dib.width do
                        (
                            pcolor = (getpixels dib [px[1], px[2]] 1)[1]
                            if pcolor != undefined then faceColor += pcolor else numverts -= 1
                        )
                    )
                    
                    faceColor.r = faceColor.r/numverts
                    faceColor.g = faceColor.g/numverts
                    faceColor.b = faceColor.b/numverts
                    
                    if iDisplayGamma.colorCorrectionMode == #gamma do
                    (
                        faceColor.r = 255 * (faceColor.r / 255.0)^(1/IDisplayGamma.gamma)
                        faceColor.g = 255 * (faceColor.g / 255.0)^(1/IDisplayGamma.gamma)
                        faceColor.b = 255 * (faceColor.b / 255.0)^(1/IDisplayGamma.gamma)
                    )
                    
                    faceColor.r = int faceColor.r
                    faceColor.g = int faceColor.g
                    faceColor.b = int faceColor.b
                    
                    #(faceCenter, faceColor)
                )else(
                    dontcollect
                )
            )

            free dib

            return result
        )

        on clock tick do
        (
            if viewport.numViews < vp_front or viewport.numViews < vp_back then
            (
                data_front = CalculateFacesColors()
                data_back = #()
            )else(
                activeVP = viewport.activeViewport
                windows.sendmessage ViewPanelHwnd 11 0 0    -- redraw off
                
                viewport.ActiveViewportEx vp_front
                data_front = CalculateFacesColors()
                
                viewport.ActiveViewportEx vp_back
                data_back = CalculateFacesColors()
                
                viewport.ActiveViewportEx activeVP
                windows.sendmessage ViewPanelHwnd 11 1 0    -- redraw on
            )
        )

        fn GW_DisplayFacesColors =
        (
            for j in data_front do
            (
                val = case rdo1.state of
                (
                    1: j[2] as point3
                    2: int j[2].h
                    3: int j[2].s
                    4: int j[2].v
                )
                gw.text j[1] (val as string) color:blue
            )
            
            for j in data_back do
            (
                val = case rdo1.state of
                (
                    1: j[2] as point3
                    2: int j[2].h
                    3: int j[2].s
                    4: int j[2].v
                )
                gw.text j[1] (val as string) color:red
            )
            gw.enlargeUpdateRect #whole
        )

        on RO_DISPLAY_FACES_COLORS open do
        (
            unregisterRedrawViewsCallback GW_DisplayFacesColors
            completeredraw()
        )

        on RO_DISPLAY_FACES_COLORS close do
        (
            unregisterRedrawViewsCallback GW_DisplayFacesColors
            completeredraw()
        )

        on btn1 changed arg do
        (
            if vp_front == undefined or vp_back == undefined do return messagebox "Select View 1 and 2"
            
            unregisterRedrawViewsCallback GW_DisplayFacesColors
            if arg then
            (
                if selection.count == 1 and iskindof selection[1] editable_poly then
                (
                    ViewPanelHwnd = GetViewportsPanelHWND()
                    node = selection[1]
                    clock.tick()
                    clock.active = true
                    registerRedrawViewsCallback GW_DisplayFacesColors
                )else(
                    node = undefined
                    btn1.checked = false
                    clock.active = false
                    messagebox "Select an object"
                )
            )else(
                clock.active = false
            )
            completeredraw()
        )

        on rdo1 changed arg do completeredraw()
        
        on bt_vp1 pressed do
        (
            vp_front = viewport.activeViewport
            bt_vp1.text = vp_front as string
        )
        
        on bt_vp2 pressed do
        (
            vp_back = viewport.activeViewport
            bt_vp2.text = vp_back as string
        )

    )

    createdialog RO_DISPLAY_FACES_COLORS
)


#18

wow Thank you thats much more than i expected.
It is almost done. Now the last problem is that the User interface is flickering and partly unusable when the activeViewport changed all the time mhh.
I tried to find the exposure control algorithm but i had no luck so far. Do you have any Plan on where to look for it?
What all of this is for : The Project am on is about transferring a virtual light situation onto a real object by measuring-> setting the light on each face.


#19

If the screen flickers then the WM_SETREDRAW message mightnot be working on your system. On my end I see no flickering in any Max version I could test, and I can navigate the viewports normally.

I tested it on Windows 7, perhaps you are using Windows 10?You’ll need to find the correct message in order to set the redraw on/off.

Other than that, the script is like 90% done if you wouldlike to avoid any “hack” using messages, you could use the C# API, which allows you to get the BID of a non-active viewport.

Regarding the Exposure algorithm, I would try the SDKexamples, but I think it is not there. Perhaps you should open a different thread about the Logarithmic Exposure Control and how to do it in MXS? It seems it is a custom algorithm so is not going to be so easy to re-build. The Brightness and Contrast parameters could be just standard, but I don’t know how the other values scale the colors.


#20

I also use win 7 with the following .net versions installed :


They flickering is there I can see the yellow border of the active viewport switch between the viewports. I cannot really interact with the toolbar on the bottom right to turn around or so.
which .net version do you use?


#21

I don’t know what can cause the flickering on your end, but I don’t think is .Net related. The script just sends a standard Windows message. Perhaps is video card or driver related, I can’t tell.
You could try with windows.postMessage() instead of sendmessage().
Or try sending just the messages to enable/disable the redraw and see if they work.


#22

you can really use the orbit tool while running the script?

I also tried to send the same message to ViewportControlPanel but then i couldnt even select the orbit tool anymore…
My earlier script has the same problem that I have to select the object to start the render to texture script.
If I only could find a way to do it in the background or to render to texture without selecting the object everything would work out.
The new “All prepared” option in the render to texture rollout was a hope but I dont know how to select in the script.


clearListener()
global nodeName = "right_perfect"
global DotNetForm
-- Check variable and close window if it exists
try DotNetForm.close() catch()
(    
    unregisterRedrawViewsCallback printTime
    global obj = getnodebyname nodeName
    -- Create a form
    global DotNetForm = DotNetObject "System.Windows.Forms.Form"
    DotNetForm.Size = (DotNetObject "Drawing.Size" 200 80)
    
    global ViewPanelHwnd = 0
    fn GetViewportsPanelHWND = ( for j in (windows.getchildrenhwnd #max) where j[4] == "ViewPanel" do exit with j[1] )
    ViewPanelHwnd = GetViewportsPanelHWND()
    
    global ViewPanelHwnd2 = 0
    fn GetViewportsPanelHWND2 = ( for j in (windows.getchildrenhwnd #max) where j[4] == "RollupPanel" do exit with j[1] )
    ViewPanelHwnd2 = GetViewportsPanelHWND2()
    
    -- create bakemap to use later
    global monitor = bitmap 14 14 color:green
    display monitor
    -- set render environment background to black
    execute "backgroundcolor = color 0 0 0"
    if (obj != undefined AND obj.name == nodeName) then (        
        local bakeMap = diffusemap()
        bakeMap.outputSzX = 16
        bakeMap.outputSzY = 16
        bakeMap.fileType = ".png"
        bakeMap.filterOn = false
        bakeMap.shadowsOn = on
        bakeMap.lightingon = on
        bakeMap.targetMapSlotName = "Diffuse"
        bakeMap.enabled = true
        obj.INodeBakeProperties.addBakeElement bakeMap
        obj.INodeBakeProperties.bakeEnabled = on
        obj.INodeBakeProperties.flags = 1
        obj.INodeBakeProperties.bakeChannel = 1
         obj.INodeBakeProperties.nDilations = 0
    )
    local myBitmap = render rendertype:#bakeSelected vfb:off progressBar:false outputSize:[16,16]
    
    fn renderFrame = (
        if (obj != undefined AND obj.name == nodeName) then (
            faceValues = #()
            selectedbefore = getCurrentSelection()
            
            windows.sendmessage ViewPanelHwnd 11 0 0    -- redraw off
            windows.sendmessage ViewPanelHwnd2 11 0 0    -- redraw off
            select obj
            myBitmap = render rendertype:#bakeSelected vfb:off progressBar:false outputSize:[16,16]

            if (selectedbefore != undefined) then ( select selectedbefore )
            windows.sendmessage ViewPanelHwnd 11 1 0    -- redraw on
            windows.sendmessage ViewPanelHwnd2 11 1 0    -- redraw off
            
            -- this is the first row which has only 6 fields
            thisRow = getPixels myBitmap [1,2] 6
            for color in thisRow do ( append faceValues color.r )
            for row=3 to 16 do (
            thisRow = getPixels myBitmap [1,row] 14 
                for color in thisRow do (
                    append faceValues color.r
                )    
            )
            for y = 1 to 14 do (
                for x = 1 to 14 do (
                    index = (y*x)
                    if index <= 188 then (
                        colorValue = faceValues[index] 
                        thisColor = color colorValue colorValue colorValue
                        -- append colors thisColor
                        colors = #(thisColor)
                        setPixels monitor [(x-1),(y-1)] colors
                    )
                )
            )
            close myBitmap
            -- copy myBitmap monitor
            display monitor 
            -- return faceValues
        )
    )
    
    local theTimer = dotNetObject "System.Windows.Forms.Timer"
    fn printTime = (
        values = renderFrame()
    )
    
    fn stopEverything = (
   unregisterRedrawViewsCallback printTime        
    )
    fn exitProgram = (
   unregisterRedrawViewsCallback printTime
        if (obj != undefined AND obj.name == nodeName) then (
              obj.iNodeBakeProperties.removeAllBakeElements()
        )
        free myBitmap
        undisplay monitor
    )

    -- Create a button
    local DotNetButton = DotNetObject "System.Windows.Forms.Button"
    DotNetButton.Text = "start"
    DotNetButton.Location = (DotNetObject "Drawing.Point" 10 10)

    -- Create another button
    local DotNetButton2 = DotNetObject "System.Windows.Forms.Button"
    DotNetButton2.Text = "stop"
    DotNetButton2.Location = (DotNetObject "Drawing.Point" 100 10)

    -- The "Click" event calls this function (see below)
    fn OnButtonClick s e =(
        if (s.Text == "start") then (
            registerRedrawViewsCallback printTime
        ) else (
            unregisterRedrawViewsCallback printTime
            stopEverything()
        )
    )

    -- Setup an event handler for both buttons
    dotNet.addEventHandler DotNetButton "Click" OnButtonClick
    dotNet.addEventHandler DotNetButton2 "Click" OnButtonClick
    dotNet.addEventHandler DotNetForm "Closing" exitProgram
   
    -- Add the buttons to the form
    DotNetForm.Controls.AddRange #(DotNetButton, DotNetButton2)
    DotNetForm.TopMost = true
    
    -- Show the form   
    DotNetForm.Show()
)

this somehow works but If i add or remove a light while it is running it is crashing .
when adding a light it first crashes if I leave the mouse button so while dragging the light into the scene everything still works even with two lights but as soon as i move my finger up (on mouse up event) I get an application error and max crashes


#23

I can use Max normally including any viewport navigation command, add/remove lights, modify the box, add/remove faces etc. and everything updates in real time, with no flickering, not even in the controls. Additionally, I can animate and play the animation while the tools is enabled and the values updates while playing.

The CPU usage keeps at 0 and the FPS does not slow down, which is surprising for a script that captures 100 images per second. I would expect at least a little drop in performance, but nothing changes on my end.

Using the render() function does cast a lot of notifications so I don’t think it is suitable for this task,

Using the Max .Net API should do it better. Here is all you need to implement it:


(
    gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
    ip  = gbl.COREInterface
    
    viewHwnd = dotnetobject "system.intptr" ip.ActiveViewExp.Hwnd
    viewExp = ip.GetViewExp viewHwnd
    GraphicsWindow = viewExp.Gw
    
    dib = GraphicsWindow.DIB
)

Grab each viewport hwnd and the just get the bitmap of them and find the pixels values. From my last script you can remove all the stuff to switch the active viewport and the redraw messages. With this, you should see no flickering at all. Don’t forget to dispose the returning bitmap once you don’t need it.


#24

cool thanks,
Ufff, I dont really know how to implement the code, did you write it or did you find it somewhere?
I tried to look at the dib data from the code like this but only got black screen:


gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
ip  = gbl.COREInterface

viewHwnd = dotnetobject "system.intptr" ip.ActiveViewExp.Hwnd
viewExp = ip.GetViewExp viewHwnd
GraphicsWindow = viewExp.Gw

dib = GraphicsWindow.DIB

testbmp = bitmap 1920 1024 
copy dib testbmp 
display testbmp


#25

I wrote the code. The bitmap you get is a .Net bitmap so it won’t display with the MXS display() function. You can either copy it to the clipboard (using .Net) and then getting the clipboard image from MXS and display it or use a .Net control to see it.


(
    gbl = (dotnetclass "Autodesk.Max.GlobalInterface").Instance
    ip  = gbl.COREInterface

    viewHwnd = dotnetobject "system.intptr" ip.ActiveViewExp.Hwnd
    viewExp = ip.GetViewExp viewHwnd
    GraphicsWindow = viewExp.Gw

    dib = GraphicsWindow.DIB
    
    rollout RO_VIEWER ""
    (
        dotNetControl dnc_img "PictureBox" pos:[8,8] width:dib.width height:dib.height
        
        on RO_VIEWER open do dnc_img.image = dib
    )
    
    createdialog RO_VIEWER width:(dib.width+16) height:(dib.height+16)
)