0
$\begingroup$

I want to know if there is a method for figuring out how much light is incident on a mesh using python. Some function for it that I can use or any leads on how can I implement this function will help a lot.

(the setting is such that there is a room that has mesh walls and floor and some lights. I want to quantify just how much of the floor is lit)

TIA

$\endgroup$
5
  • $\begingroup$ For which renderer? What kind of lighting? A simple point lamp? $\endgroup$
    – Leander
    Commented Apr 22, 2019 at 19:13
  • $\begingroup$ (please correct me if i say something wrong, im new to blender) cycles renderer, might shift to game. as for the light, point lamp; yes $\endgroup$
    – Sami
    Commented Apr 22, 2019 at 19:20
  • $\begingroup$ Am I making sense? $\endgroup$
    – Sami
    Commented Apr 22, 2019 at 19:40
  • $\begingroup$ Yes, I added the cycles tag. $\endgroup$
    – Leander
    Commented Apr 22, 2019 at 19:41
  • $\begingroup$ I have added an answer which would work with game like shading. If you wanted to use cycles engine, my best bet would be to use the baking functionality. For each triangle face unwrap it onto a temporary image, bake the diffuse light, read the pixels off the image and calculate the intensity from that. $\endgroup$
    – Leander
    Commented Apr 22, 2019 at 21:27

1 Answer 1

1
$\begingroup$

Using non PBR raytracing

For multiple spot lamps.

You can simply rewrite a raytracer (e.g. here) which checks for rays from the surface to the point lamps. I will demonstrate the steps in a simplified way.

Get a triangulated bmesh object from the active object. The bmesh is a python module with which we can access vertices, edges and faces.

import bpy
import bmesh

def get_active_bmesh():
    if bpy.context.object == None or bpy.context.object.type != 'MESH':
        return None
    me = bpy.context.object.data

    if me.is_editmode:
        bm = bmesh.from_edit_mesh(me)
    else:
        bm = bmesh.new()
        bm.from_mesh(me)

    bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method=0, ngon_method=0)
    return bm

We also need to generate sample points for the faces. For that we use the triangulated faces. Luckily, bmfaces already have a builtin area calculation, if we wanted to get the relative amount of light (I will only get the absolute amount though). For the random points on the triangle we can use the method of 4.2 in this paper, mentioned in this question.

Next, we select a triangle with probability proportional to its area by generating a random number between 0 and the total cumulative area and performing a binary search on the array of cumulative areas. For each selected triangle with vertices (A, B, C), we construct a point on its surface by generating two random numbers, r1 and r2, between 0 and 1, and evaluating the following equation:
P = (1 − √r1) A + √r1(1 − r2) B + √r1r2 C Intuitively, √r1 sets the percentage from vertex A to the opposing edge, while r2 represents the percentage along that edge (see Figure 4). Taking the square-root of r1 gives a uniform random point with respect to surface area.

The equation translated into python

import math
import random

def get_sample_point(bmface):
    assert len(bmface.verts) == 3
    A, B, C = [bmface.verts[i].co for i in range(3)]
    r1, r2 = [random.uniform(0, 1) for i in range(2)]
    P = (1 - math.sqrt(r1)) * A + math.sqrt(r1) * (1 - r2) * B + math.sqrt(r1) * r2 * C
    return P

To get the light intensity of a single sample point we need to cast a ray from the point of the sample in direction of the lamp. If the ray doesn't hit anything between the sample point and the lamp, then we can use the lamps intensity. I've implemented a simple linear falloff of a point light. This has to be adjusted for different falloff types. The following function returns the intensity at a sample point.

def get_light_intensity(origin, scn, lamps):
    intensity = 0
    for lamp in lamps:
        lamp_co = lamp.matrix_world.to_translation()
        direction = (lamp_co - origin)
        result, location, normal, index, object, matrix = scn.ray_cast(origin, direction.normalized())

        if not result or (location - origin).length > direction.length:
            # didn't hit anything, so add the lights intensity
            distance = (lamp_co - origin).length
            # inverse linear falloff
            intensity += max(0, lamp.data.energy-(distance/lamp.data.distance/2))
    return intensity

Now we can use this function. First, we get the lamps and a bmesh object from the active object. If there was a mesh selected, we have to call faces.ensure_lookup_table() on it to access the faces. Then, we loop through the faces and for each face

  • get sample points
  • calculate the light intensity at the sample points
  • add the relative face intensity to the overall intensity
SAMPLE_POINT_COUNT = 50
RANDOM_SEED = 1

random.seed(RANDOM_SEED)
scn = bpy.context.scene
lamps = [ob for ob in scn.objects if ob.type == 'LAMP' and ob.data.type == 'POINT']
intensity = 0

bm = get_active_bmesh()
if bm != None:
    bm.faces.ensure_lookup_table()

    for bmface in bm.faces:
        coords = [get_sample_point(bmface) for i in range(SAMPLE_POINT_COUNT)]
        face_intensity = 0
        for c in coords:
            face_intensity += get_light_intensity(c, scn, lamps)
        intensity = face_intensity / SAMPLE_POINT_COUNT

    print(intensity)

Finally, I print the result.

Some quick tests seem to show correct results.

values

The todo things would be to support the different falloffs of point lights and/or add other types of lights (like sunlight). The single float output is also not the best visualization.

$\endgroup$
5
  • $\begingroup$ Thank you so much for your response! I'm having a little difficulty in understanding whats happening. Placing 2 lamps (one at origin and one very far off from my actual mesh) gives me the same result as placing two lamps somewhere directly above the mesh. Can you please tell me how can i fix this? What i want is the effect of a lamp placed far off from my actual mesh to be negligible. Also for a single lamp, its position does not seem to effect the intensity even though intuitively it should. BTW, is it an issue if what i see isnt very black when there are no lights in the scene? $\endgroup$
    – Sami
    Commented Apr 23, 2019 at 19:52
  • $\begingroup$ Hi, I'll try to help you as best as I can. (1) Is it an issue if what I see isn't very black when there are no lights in the scene? You must be using cycles. Switch to internal, as in my example file to access the simple falloff methods for point light. (2) Far and close lamps give the same results. Not intended. Is it actually completely the same output or just a tiny difference? I'm accessing the Blender-Internal properties of point lamps lamp.data.energy and lamp.data.distance. Switch to internal and modify the distance to a small value. $\endgroup$
    – Leander
    Commented Apr 23, 2019 at 20:21
  • $\begingroup$ 1) By internal you mean blender render or something else? (everything is entirely dark in an environment without lights when i use blender render) 2)Its completely same. Again, what do you mean exactly by "switch to internal"? $\endgroup$
    – Sami
    Commented Apr 24, 2019 at 4:57
  • $\begingroup$ Changing lamp.data.energy and lamp.data.distance has no effect on the intensity. Turing on ray shadow does make everything look like it should but intensity is still the same. $\endgroup$
    – Sami
    Commented Apr 24, 2019 at 6:37
  • $\begingroup$ @Leander Could this solve this problem here? blender.stackexchange.com/questions/282161/… $\endgroup$
    – Harry McKenzie
    Commented Dec 27, 2022 at 8:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .