[Closed] Analyzing Viewport Lighting [3DS Max 2014]
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
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.
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
)
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
)
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
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.
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
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.