1

I am trying to write a small Python script in Blender which should be able to detect whether a surface of a mesh is hit by the light or not.

I wrote this code based on some replies and comments. In the scene, you can see two cubes and a light (sun). Obviously, the ray hits first the larger cube, meaning that the smaller cube is shadow. I also get a correct result in terms of location of the hit. But how can I automatically tell that the smaller cube is shadowed? An idea could be to measure the distance between the source and the obstacle (larger cube), and since this is smaller than the distance between the source and the target (smaller cube), I can assume that the smaller cube is in shadow. However, I was just wondering if this makes sense, because I saw other answers where this approach of measuring the distance did not seem to be implemented (e.g. https://blender.stackexchange.com/a/269656/52670)

import bpy

bpy.ops.mesh.primitive_cube_add(enter_editmode=False, align='WORLD', location=(0, 0, 8), scale=(4, 4, 4)) bpy.ops.mesh.primitive_cube_add(enter_editmode=False, align='WORLD', location=(0, 0, 1), scale=(1, 1, 1)) bpy.ops.object.light_add(type='SUN', align='WORLD', location=(0,0, 40), scale=(1, 1, 1))

loc_origin=bpy.data.objects['Sun'].location loc_target=bpy.data.objects['Cube.001'].location direction = target-origin

scene = bpy.context.scene depsgraph = bpy.context.evaluated_depsgraph_get() result = scene.ray_cast(depsgraph, origin, direction)

print(result[0]) print(result[1])

Alex Mekx
  • 117
  • 7
  • 1
    Hello, could you elaborate upon your use case ? I think you should look into raycasting – Gorgious Jul 20 '22 at 12:15
  • Hey. In my scene, I have a mesh (for the moment a simple cube) and a light object. When I go to "rendered" in viewport shading, I can see which surfaces of my cube are hit by the light, and which surfaces are shadowed. I would like to write a Python script that can detect automatically this. Ideally, the script should simply output for example "0" if the surface is shadowed, and "1" if the surface is illuminated. – Alex Mekx Jul 20 '22 at 12:25
  • 2
    Are you using Eevee or Cycles (Or another engine) ? – Gorgious Jul 20 '22 at 13:07
  • I can see that Eevee is selected as default engine. – Alex Mekx Jul 20 '22 at 13:16
  • You answered what you'd want the detection to detect, but how are you going to apply it? That helps us ensure that a solution we give isn't overkill or not enough for your application. – S. Magnusson Jul 25 '22 at 12:31
  • I want to apply this for a 3D city model made of hundreds of buildings. Each building is represented by a simple cube mesh (6 faces). – Alex Mekx Jul 25 '22 at 13:29

1 Answers1

2

As suggested, I would recommend raytracing. I'd recommend the code recently shared in this answer as a place to start: https://blender.stackexchange.com/a/269656/52670 - instead of "occluded objects from the camera", you want "faces occluded from the location of the light." So, replace the camera's world location with the light's world location. Granted, this will only test visibility based on the position of the light, not the light itself. It is precise, but only as precise as the rays you give it.

The critical function - scene.ray_cast() - is documented here: https://docs.blender.org/api/current/bpy.types.Scene.html?highlight=ray_cast#bpy.types.Scene.ray_cast You will likely want the object as well as the face index that the function gives back to you:

faces_hit = [] # dictionary of objects and the faces that were hit

within loop iterating over rays hitting object from light

is_hit, _, _, face_index, obj, _ = bpy.context.scene.ray_cast(bpy.context.view_layer.depsgraph, bpy.data.objects['My_Light'].location, dir)

if is_hit and obj.name = 'MyObjectName': faces_hit.append(face_index)

after loop, use the face indices as you'd like

print('Was it hit:', is_hit) print(face_index) print(obj.name + 'was hit first')

Regarding your additional question:

But how can I automatically tell that the smaller cube is shadowed?

You answered your own question here:

Obviously, the ray hits first the larger cube, meaning that the smaller cube is [in] shadow.

Since you never hit the smaller cube, it's likely that it is in shadow. You can get which object is hit from the ray_cast function, as shown in my update code sample.

As to detect whether the cube is shadowed from all light rays of a given light - you just need to shoot more rays in more directions. Try shooting them toward each face, vertex, etc. Again, this is not taking the actual rendered lighting into account, but comparing distances and line intersections. And I edited my code sample to show that ray_cast tells you can see which object was hit :) If you shoot twenty rays and an object was never hit - it's likely that it is in shadow. This is an automatic solution, but its precision and accuracy is up to you.

The distance measuring is already done as part of the ray_cast - from the origin, it shoots a ray and determines what it hits first - anything behind that first object is not hit - and retrieves the data, (hit result (boolean), hit location, hit normal, index of face hit, object that was hit, hit object matrix), as shown in the documentation I linked.

S. Magnusson
  • 1,503
  • 6
  • 16
  • 1
    Hey, I looked at your suggestions, and I tried to write a code (see edits in the question), but there is something not clear about how the ray_cast works. Regarding your questions, it is actually enough to export a 0-1 output. That is my final goal. I don't need to do anything else (at least in Blender). – Alex Mekx Jul 27 '22 at 11:37
  • Okay, I just wanted to clarify, as additional application may change how the problem is solved. Updated my answer to your question. – S. Magnusson Jul 27 '22 at 15:26
  • 1
    Thanks a lot :) – Alex Mekx Jul 27 '22 at 16:58