I’m working on a Ruby tool that modifies faces. Before altering the physical face an interactive preview is shown. To make the preview look more 3D-dimensional and better fit into the SketchUp UX I want to shade the face color just as SketchUp shades faces but have little success. Has anyone done this before?
My tests show the following:
Primary colors and secondary colors keep hue when shaded.
Other colors change hue slightly (!) meaning the shading isn’t applied to the lightness in HLS space or B in HBS space. Or maybe it is but the hue changes when rounding the values when converting to and from RGB space.
Colors stay the same regardless of shading when Shadow Light is set to 0 and Shadow Dark is set to 80.
The Shadow Light and Shadow Dark value can’t be altered when not using sun for shading but the constant values seem to correspond to 81 and 20 with use sun for shading.
When using Sun for Shading the shading differs between faces facing the sun directly and faces with the normal being perpendicular to the sun vector. When the face faces away from the sun the shading is identical regardless of how much it faces away from the sun.
This is the code I’ve got so far:
# Returns Float in interval 0.0 to 1.0 of how much face should be shaded,
# 0.0 being the darkest and 1.0 lightest.
def shade_value(normal, view)
reference =
if view.model.shadow_info["UseSunForAllShading"]
view.model.shadow_info["SunDirection"]
else
(view.camera.eye - view.camera.target).normalize
end
[normal % reference, 0].max
end
# Select a face and run:
model = Sketchup.active_model
shade_value(model.selection.first.normal, model.active_view)
Does anyone know how to go from here? Getting the RGB color from a face is fairly easy but how do I apply the shade_value to the color?
Shade test.skp (171.9 KB)
If anyone is interested this is the model I’ve conducted the tests on. I’ve aligned the camera in the second scene and the component shown in that scene with the sun vector to easier conduct tests.
I haven’t tested the code myself but I think each RGB channel is multiplied by
0.2 + dark + shade*light
where dark, shade and light are all in the range of 0 to 1, and then capped to 255. Capping individual channels would explain why the hue changes when the color is tinted very light.
If I’m not mistaken any value for dark and 0 for light makes the color consistent regardless of shading as observed. A dark value of 0.8 and a light value still at 0 would show the color on screen exactly as defined in the Material window, as observed.
I haven’t written the actual code and maybe light and dark values need to be remapped from an interval of 0 to 100 when taken from the ShadowInfo but I think I have found the formula!
A few years ago when I was researching SUbD I did something like this. I was experimenting with subdivisions in pure Ruby and I was looking for ways to make the subdivision preview faster.
I extracted the preview tool from my experiment. (It won’t run without tweaks, but you can find a lot of logic that might be of use.)
get_ambient computes the ambient light based on shadow info and some magic values from SketchUp. su_shading.rb · GitHub
update is what I called whenever the scene changed (view or model). It recomputes all the GL data needed to draw and cache it. Each face with similar color is grouped in an attempt to make drawing faster. su_shading.rb · GitHub
update_outlines will compute the Outline edges. (Called when the current Style have Outlines enabled): su_shading.rb · GitHub
draw will draw all the geometry based on the current rendering options.
Note that to draw edges they are offset slightly towards the camera in order to avoid z-fighting. This is currently done in the draw method - but could and probably should be cached as well.
I think I noticed this behavior in SketchUp’s native drawing even before I started writing plugins. It’s also the reason why edges sometimes bleed through faces when they are placed really close behind relative to the distance to the camera. I didn’t get around to do it myself until yesterday though . My upcoming plugin may be the one that feels most native to SketchUp of all my plugins yet .
I’ve successfully managed to replicate SketchUp’s own shading!
I’ve also written a really simple test tool that draws points on face centroids with the same color as the face is drawn with. Points are drawn on all faces but can’t be seen except when they represent a face further back, when they don’t fit into the face or when there is a shadow on the face.
Here is my code, including the test tool:
module FaceShading
# Get color of face (not shaded).
# If face has texture, simply return the average color.
# For now material inherited from parent group/component is ignored.
#
# face - A Face Entity.
#
# Returns Color object.
def self.face_color(face)
if view_back_face?(face)
if face.back_material
face.back_material.color
else
face.model.rendering_options["FaceBackColor"]
end
else
if face.material
face.material.color
else
face.model.rendering_options["FaceFrontColor"]
end
end
end
# Shade a color as it would be shaded if drawn to a face with a certain normal
# and shown in a certain view.
#
# color - Color object to base shaded color of.
# normal - Vector3d.
# view - View object.
def self.shade_color(color, normal, view)
si = view.model.shadow_info
light = sun_for_shading?(si) ? si["Light"]/100.0 : 0.81
dark = sun_for_shading?(si) ? si["Dark"]/100.0 : 0.2
shading = shade_value(normal, view)
shift = 0.2 + dark + shading*light
Sketchup::Color.new(*color.to_a[0, 3].map { |c| [(c*shift).to_i, 255].min })
end
# Get the shaded color of a face.
#
# face - A Face.
#
# Returns Color.
def self.shaded_face_color(face)
shade_color(face_color(face), face.normal, face.model.active_view)
end
# Check what side of face is being viewed.
# Assume face is in same coordinate system as camera.
#
# face - A Face entity.
# camera - A Camera object (default: the camera of the same model fce is in).
#
# Returns Boolean.
def self.view_back_face?(face, camera = nil)
camera ||= face.model.active_view.camera
(camera.eye - camera.eye.project_to_plane(face.plane)) % face.normal < 0
end
private
# Returns Float in interval 0.0 to 1.0 of how much face should be shaded,
# 0.0 being the darkest and 1.0 lightest.
#
# normal - Normal Vector3d.
# view - Sketchup::View object.
#
# Examples
#
# model = Sketchup.active_model
# shade_value(model.selection.first.normal, model.active_view)
#
# Returns float.
def self.shade_value(normal, view)
si = view.model.shadow_info
reference =
if sun_for_shading?(si)
si["SunDirection"]
else
(view.camera.eye - view.camera.target).normalize
end
value = normal % reference
sun_for_shading?(si) ? [value, 0].max : value.abs
end
def self.sun_for_shading?(si)
# If shadows are enabled SketchUp uses sun for shading regardless of
# the UseSunForAllShading setting.
si["UseSunForAllShading"] || si["DisplayShadows"]
end
end
# Test code to see that calculated color really matches the one SketchUp
# uses for face shading.
#
# If customs hading code is successful the points drawn by TestTool should being
# invisible unless they represent a face hidden behind another face or cover
# their faces' edges.
class TestTool
def activate
Sketchup.active_model.active_view.invalidate
end
def draw(view)
view.model.active_entities.each do |face|
next unless face.is_a?(Sketchup::Face)
view.draw_points([face.bounds.center], 10, 2, FaceShading.shaded_face_color(face))
end
end
def resume(view)
view.invalidate
end
end
Sketchup.active_model.select_tool(TestTool.new)
By the way does anyone know some quick way to disable any shading in SketchUp? For now it is possible to set time to midnight, set both light and shadow to 100 and enable sun for shading.
But maybe it is possible just to set some single option somewhere in order to achieve the same result. It is rarely needed but sometimes for some orthogonal scenes it is necessary to display a color of each face as is regardless of its orientation relatively to a camera view direction or sun rays direction.
Yes, for some reason dark should be 80 (not 100 as I expected) in order to display colors as in the Materials. I was wondering if there is a faster way to achieve the same result (like some option anywhere in display styles, which just turns off any shadows and displays original color of faces without changing of shadows settings). I thought such option could be useful if any.
An extra 20 is added internally in SketchUp, otherwise a face not hit by the sun would be pitch black. That’s why the slider needs to be at 80 to add up to 100. I don’t think there is a faster way to set this up. If there is a renderingoption for it it’s not exposed to the UI anyway.