6

I need to know how light is reflected on the objects given a fixed spot as light source. The best way would be to render a laser beam, however such laser beam is not easy to design/render and heavily depends on the rendering engine. So I need a more easy possibility to check. Do you have an idea how this could be achieved?

I am using Blender 2.8.

Thomas
  • 299
  • 5
  • 18
  • 1
    Are you using 2.8 or 2.79. How would you handle refraction, diffuse, rough glossy? Anything where a beam would "split". – Leander Aug 30 '19 at 07:41
  • 1
    A laser beam should not be visible until it get reflect by something. The dust in air is the most common thing that "catch" laser. What you looking for might be the volume rendering and small angle spot light. Both are pretty complicated in CG. – HikariTW Aug 30 '19 at 08:18
  • 2
    Searching on BSE might give you some ideas https://blender.stackexchange.com/search?q=laser – rob Aug 30 '19 at 08:26

2 Answers2

20

If I understood, you are looking for a representation of a single ray, without the use of render engines.

You can create geometry with python, which is raycasted. I modified the script from this laser question to create an object, which contains the laser as geometry.

raycast

Start with the first "hit" point at (0, 0, 0) and the casting direction (0, 0, 1) in the objects orientation. The following steps are

  1. Use the scenes ray_cast method to cast a ray. Get the hit position and the normal of the hit face.
  2. If the normal is on the side of the incoming ray, reverse it. The resulting angle between direction and normal ray will be < 90°.
  3. Get the difference between direction and normal ray. Rotate the direction ray twice by this difference. The direction ray is now "mirrored" along the inversed normal ray.
  4. Inverse the direction ray. This is the new direction ray in which direction we cast from the hit position.
import bpy
import bmesh
import mathutils

EPSILON = 0.00001
MAXIMUM_ITERATIONS = 50
OBJECT_NAME = 'light_plane'

def get_create_object(scene):
    ob = scene.objects.get(OBJECT_NAME)
    if ob == None:
        me = bpy.data.meshes.new("me")
        ob = bpy.data.objects.new(OBJECT_NAME, me)
        scene.collection.objects.link(ob)
    return ob

def do_raycast(scene):
    object = get_create_object(scene)
    points = [object.location]

    _, rot, _ = object.matrix_world.decompose()
    direction = mathutils.Vector((0, 0, 1))
    direction.rotate(rot)


    for i in range(MAXIMUM_ITERATIONS):
        origin = points[-1] + direction * EPSILON
        result = scene.ray_cast(scene.view_layers[0], origin=origin, direction = direction)
        hit, location, normal, index, ob, matrix = result
        if not hit:
            break

        if normal.dot(direction) < 0:
            normal *= -1

        rot_dif = direction.rotation_difference(normal)
        direction.rotate(rot_dif)
        direction.rotate(rot_dif)
        direction *= -1
        points.append(location)

    points.append(points[-1] + direction*10)
    bm = bmesh.new()
    [bm.verts.new(pt) for pt in points]
    bm.verts.ensure_lookup_table()
    for i in range(len(bm.verts) - 1):
        bm.edges.new((bm.verts[i], bm.verts[i + 1]))

    bmesh.ops.transform(bm, matrix = object.matrix_world.inverted(), verts = bm.verts)
    bm.to_mesh(object.data)
    bm.free()

for h in bpy.app.handlers.depsgraph_update_post:
    bpy.app.handlers.depsgraph_update_post.remove(h) 
bpy.app.handlers.depsgraph_update_post.append(do_raycast)

In 2.9+ the Scene.ray_cast method takes different arguments. Replace the line with this for 2.9+

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

Leander
  • 26,725
  • 2
  • 44
  • 105
  • I'm not sure how to implement your script in my scene. It's created a light_plane object but I don't see the ray. I'm using Blender 2.93

    Edit: I see that it's working in 2.81 in fact, so perhaps nevermind

    – Lukas Kawalec Sep 01 '21 at 17:23
  • 1
    @LukasKawalec I added two lines at the bottom of the answer. – Leander Sep 01 '21 at 18:46
  • May be relevant https://blender.stackexchange.com/questions/91161/calculating-estimating-the-normal-at-any-location-on-a-face – batFINGER Sep 01 '21 at 18:50
0

Several rays shot from roughly same position I don't have enough reputation to comment on the Leander post. I've modified the script to have several rays. It's naive and rusty, but there's an important detail - if you get your evaluated_depsgraph_get() every time you call a ray, you'll get stack overflow in Blender. The rays on the screenshot are displaced manually, script doesn't do that. Trees models by reymarch. Other than that fix, the rest modification is pretty trivial:

import bpy
import bmesh
import mathutils

EPSILON = 0.001 MAXIMUM_ITERATIONS = 4

OBJECTS_AMOUNT = 9

def get_object(scene, name): ob = scene.objects.get(name) return ob

def do_raycast(scene, graph, object): #object = get_create_object(scene) points = [object.location]

_, rot, _ = object.matrix_world.decompose()
direction = mathutils.Vector((0, 0, 1))
direction.rotate(rot)


for i in range(MAXIMUM_ITERATIONS):
    origin = points[-1] + direction * EPSILON
    #graph = bpy.context.evaluated_depsgraph_get()
    result = scene.ray_cast(graph, origin=origin, direction = direction)
    hit, location, normal, index, ob, matrix = result
    if not hit:
        break

    if normal.dot(direction) &lt; 0:
        normal *= -1

    rot_dif = direction.rotation_difference(normal)
    direction.rotate(rot_dif)
    direction.rotate(rot_dif)
    direction *= -1
    points.append(location)

points.append(points[-1] + direction*10)
bm = bmesh.new()
[bm.verts.new(pt) for pt in points]
bm.verts.ensure_lookup_table()
for i in range(len(bm.verts) - 1):
    bm.edges.new((bm.verts[i], bm.verts[i + 1]))

bmesh.ops.transform(bm, matrix = object.matrix_world.inverted(), verts = bm.verts)
bm.to_mesh(object.data)
bm.free()

def raycast_all(scene): graph = bpy.context.evaluated_depsgraph_get() for i in range(1,OBJECTS_AMOUNT+1): ob = get_object(scene, 'ray' + str(i)) if ob is not None: do_raycast(scene, graph, ob)

for h in bpy.app.handlers.depsgraph_update_post: bpy.app.handlers.depsgraph_update_post.remove(h) bpy.app.handlers.depsgraph_update_post.append(raycast_all)