Analyzing Viewport Lighting [3DS Max 2014]


#1

Hello!
I have a question concerning the lighting in the viewport. Is it possible to somehow get the RGB Values or just the brightness of every Face of an object by script?
As shown in the screenshot here I would like to have all those brightness values in realtime, but I dont find a way to get them. I somehow need to perform the calculations done for basic lighting rendering. Can you help me out ? Is it possible? Do you know what I mean? Sorry it is not so easy to describe. I just want to have the average brightness of every Face in a list in realtime. So i have a realtime list with the values corresponding to the face color as shown in the viewport. (see screenshot)

Thank you


#2

What material/s and light sources will you use?


#3

I dont even assign a specific material to the object. I just give the object a white color from the object color palette.

For the lighting I plan to use lights from the standard selection basically i wanted to use target spots and omni lights.


#4

What you are trying to do isn’t an easy task.

Here is a very simple script, based on the SVG renderer shown in the MXS help, which is missing many things in order to give accurate values. I’ve removed the specular values as they didn’t play well, but you can add them, just look at the MXS help.

Other than that, you could grab the viewport image and averagea few sample colors from each face to get the average color, but it has other problems like hiding everything except the geometry when capturing the viewport. Could use the CreatePreview() function to grab the viewport and hide everything, but you need to save, load and parse the image, which might not be very fast for real time.

Last alternative would be to create a more advanced renderer.Perhaps there are some methods in the SDK to do what you need, but I am not aware of any.


(
    
    try destroydialog ::RO_DISPLAY_FACES_COLORS catch()
    
    rollout RO_DISPLAY_FACES_COLORS "Faces Colors" width:172 height:112
    (
        colorPicker  cp1  "Material Color:" pos:[8, 8] height:16 color:white
        radioButtons rdo1 "Display:"        pos:[8,32] labels:#("RGB","H","S","B") columns:4
        checkbutton  btn1 "Enable"          pos:[8,72] width:154 height:28
        
        global GW_DisplayFacesColors
        
        local GetfaceNormal = polyop.getfaceNormal
        local GetFaceCenter = polyop.getFaceCenter
        local node

        fn CalculateFacesColors obj =
        (
            viewTM = viewport.getTM()
            viewTM.row4 = [0,0,0]
            viewPos = (inverse (viewport.getTM())).row4

            fillColor = cp1.color

            result = for f = 1 to obj.numfaces collect
            (
                faceNormal = normalize (GetfaceNormal obj f)
                
                if (faceNormal*viewTM).z > 0 then
                (
                    faceCenter = GetFaceCenter obj f
                    viewVector = normalize (viewPos - faceCenter)
                    faceColor = black
                    
                    for k in lights where classof k != targetobject do
                    (
                        lightDir = normalize (k.pos - faceCenter)
                        
                        diffuse = amax ((dot lightDir faceNormal)*k.multiplier) 0
                        faceColor += fillColor * k.color * diffuse
                    )
                    
                    faceColor.r = amin (int faceColor.r) 255
                    faceColor.g = amin (int faceColor.g) 255
                    faceColor.b = amin (int faceColor.b) 255
                    
                    #(faceCenter, faceColor)
                )else(
                    dontcollect
                )
            )
            
            return result
        )
        
        fn GW_DisplayFacesColors =
        (
            data = CalculateFacesColors node
            gw.setTransform (matrix3 1)
            
            for j in data do
            (
                val = case rdo1.state of
                (
                    1: j[2] as point3
                    2: j[2].h
                    3: j[2].s
                    4: j[2].v
                )
                gw.text j[1] (val as string) color:black
                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
        (
            unregisterRedrawViewsCallback GW_DisplayFacesColors
            if arg do
            (
                if selection.count == 1 and iskindof selection[1] editable_poly then
                (
                    if lights.count == 0 then
                    (
                        node = undefined
                        btn1.checked = false
                        messagebox "There must be at least one light source in the scene"
                    )else(
                        node = selection[1]
                        node.wirecolor = cp1.color
                        if classof node.mat == standardmaterial do node.mat.diffuse = cp1.color
                        registerRedrawViewsCallback GW_DisplayFacesColors
                    )
                )else(
                    node = undefined
                    btn1.checked = false
                    messagebox "Select an object"
                )
            )
            completeredraw()
        )
        
        on rdo1 changed arg do completeredraw()
        
        on cp1 changed arg do
        (
            if node != undefined do
            (
                node.wirecolor = arg
                if classof node.mat == standardmaterial do node.mat.diffuse = arg
            )
            completeredraw()
        )

    )
    
    createdialog RO_DISPLAY_FACES_COLORS
)


#5

Here is another version using the viewport image to average pixels colors.

Currently the sample pixels are the number of face verts + 1, and they are at the average position between each vertex and the face center, but you can modify it to use more samples and a better distribution if you need.


(
    
    try destroydialog ::RO_DISPLAY_FACES_COLORS catch()
    
    rollout RO_DISPLAY_FACES_COLORS "Faces Colors" width:172 height:88
    (
        radioButtons rdo1 "Display:" pos:[8,8] labels:#("RGB","H","S","B") columns:4
        checkbutton  btn1 "Enable"   pos:[8,48] 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 data = #()
        
        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 = int (faceColor.r/numverts)
                    faceColor.g = int (faceColor.g/numverts)
                    faceColor.b = int (faceColor.b/numverts)
                    
                    #(faceCenter, faceColor)
                )else(
                    dontcollect
                )
            )
            
            free dib
            
            return result
        )
        
        on clock tick do
        (
            data = CalculateFacesColors()
        )
        
        fn GW_DisplayFacesColors =
        (
            for j in data 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:black
                --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
        (
            unregisterRedrawViewsCallback GW_DisplayFacesColors
            if arg then
            (
                if selection.count == 1 and iskindof selection[1] editable_poly then
                (
                    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()
        
    )
    
    createdialog RO_DISPLAY_FACES_COLORS
)


#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?