# VTK: achieve lighting effect, from a line to a face

Posted by dysonline on Thu, 17 Feb 2022 01:41:42 +0100

Guide: many places need to check the illumination, and there are many software to deal with it. Today I made a content about lighting effect. From 10 a.m. to 3:30 p.m., start writing headlines. About that long. There was no rest or meal at noon.

Content:

2. My Lighting class

3. Basic parameter setting

4. Grid import and basic initialization (update_grid function)

Update light function (u#5)

6. Set a field to display the effect of light (update_shadow function)

7. Shiny appearance and visualization of results

8. End (with all codes attached)

A simple operation, if there is no change, repeated 10000 times, is still just an operation.

A simple operation, if there are two variables, repeated 10000 times, is a system.

There is a case in pyvita. Find an example of starting from a point and intersecting with the grid.

As shown in the figure, the blue one is a line, and the intersection with the sphere is displayed by points.

In this simple case, we can analyze what we need to know in order to obtain the intersection on the surface.

(1) a starting point is required

start

(2) need an end point,

end，

(3) a straight line is required,

(4) one face needs to be defined

(5) find the intersection

Let's take a look at the code of this simple case:

```　　import pyvista as pv
import numpy as np
# Create source to ray trace
# Define line segment
start=[0, 0, 0]
stop=[0.25, 1, 0.5]
# Perform ray trace
points, ind=sphere.ray_trace(start, stop)
# # Create geometry to represent ray trace
ray=pv.Line(start, stop)
intersection=pv.PolyData(points)
#
# # Render the result
p=pv.Plotter()
show_edges=True, opacity=0.5, color="w",
lighting=False, label="Test Mesh")
point_size=25, label="Intersection Points")
p()```

In fact, the four lines 5, 8, 9 and 12 complete the main operations.

Line 5: defines a spherical mesh

Line 8-9: defines the start and end points

Line 12,: ray_ The trace function gets the intersection.

There are some realistic contents behind.

2. My Lighting class

I constructed a class and operated it completely according to the simple case above. Form the effect of the light in the figure below.

The blue circle behind is the display of the end point.

Let's first look at the basic structure:

In addition to some basic parameters, there are only three steps, which correspond to the above three functions:

update_grid

update_lights

3. Basic parameter setting

In__ init__ In the function, we define nine parameters.

Input parameters

Source represents the coordinates of the point light source

Angle represents the angle at which the point light diffuses to form a cone

Direction represents the direction of the light center and is a unit vector

seeds is a number of parameters needed to obtain the coordinates of the light end point

In addition to these four, the instance variables of the class are increased

Grid is used to store grid data

distance sets the length of the light cone

nlights records the number of rays

seed_polydata is visual data that stores light spots

In order to calculate the distance between two points, I defined a mag function

```　　@staticmethod
def mag(start,end):
s=(start[0] - end[0]) ** 2 + (start[1] - end[1]) ** 2 + (start[2] - end[2]) ** 2
return s ** 0.5```

There should be similar functions in the library, but I'm too lazy to check.

There are two more attributes

```　　@property
def source_polydata(self):
return pv.PolyData(self.source)
@property
return np.tan(self.angle) * self.distance```

The first one is to generate visual data of point light source

Second, aim_radius is the radius of the end point of the target.

After starting from the point light source, we hash the points on the bottom to determine the number of rays.

4. Grid import and basic initialization (update_grid function)

First, we have to deal with the grid. And initialize some Game download Data, such as distance.

Let's first look at the function code:

```　　def update_grid(self,filename):
xmin,xmax,ymin,ymax,zmin,zmax=self.grid.bounds
start=[xmin,ymin,zmin]
end=[xmax,ymax,zmax]
dis=self.mag(start,end)
p=[[xmin, ymin, zmin],
[xmin, ymin, zmax],
[xmin, ymax, zmin],
[xmin, ymax, zmax],
[xmax, ymin, zmin],
[xmax, ymin, zmax],
[xmax, ymax, zmin],
[xmax, ymax, zmax]
]
for i in p:
self.distance=max(dis, self.mag(self.source, i))
self.distance=self.distance*1.05```

(2) calculate the distance to determine that the light emitted by the point light source intersects all positions of the grid

5. Set the light (update_lights function)

That is to say, the data we need to extract. Take a look at the code:

```　　def update_lights(self):
# Generate face
# Terminal Center
end_center=[self.source[0] + self.distance * self.direction[0],
self.source[1] + self.distance * self.direction[1],
self.source[2] + self.distance * self.direction[2]
]
plane=pv.Plane(center=end_center, direction=self.direction,
i_resolution=self.seeds, j_resolution=self.seeds)
# Define end point
stops=[]
for point in plane.points:
x1=point[0]
y1=point[1]
z1=point[2]
stops.append([x1, y1, z1])
self.nlights=len(stops)
self.lights=stops
self.seed_polydata=pv.PolyData(stops)```

(1) generate a plane data centered on the bottom center.

(2) extract the data of points belonging to the ball

(3) generate a polydata for visualization

6. Set a field to display the effect of light (update_shadow function)

The name may not be very good. The word shadow is used in the place where the light shines.

Also, look at the code first:

```　　def update_shadow(self):
for i in range(self.nlights):
point, ind=self.grid.ray_trace(self.source, self.lights[i])
if len(point) < 1:
pass
else:
a=self.mag(self.source, point[0])
end=ind[0]
for i in point:
if self.mag(self.source, i) < a:
a=self.mag(self.source, i)
end=ind
else:
end=ind[0]

(1) generate an id list, which is generated by ray_trace generates a list of face numbers.

At the same time, it should be noted that ray_trace will intersect more than two points with the surface. We choose the nearest one because the light will not pass through the object.

(2) generate a scalar attribute of shadow. The face in the list is assigned 10 and the others are 0

7. Shiny appearance and visualization of results

Finally, we actually wrote an update function.

```　　def update(self,filename):
self.update_grid(filename)
self.update_lights()

Mainly for the convenience of writing.

Let's take a look at the way we call:

```　　if __name__=='__main__':
source=[3000,0,-2000]
angle=20
direction=[-0.3,0.1,1]
seeds=100
light=Lighting(source,angle,direction,seeds)
light.update('car.stl')
p=pv.Plotter()
#
p()```

Set four parameters, call the update function at the same time, and read the car STL file. And show it.

The result is the effect as we said above.

8. Conclusion

A small function, put in practical application, will have a lot of incredible things. In fact, many times, we don't use so much knowledge. More likely is the form of organization.

I'm Dr. Zhang Lin. please remember to pay attention. If you are interested in anything, you can also reply in the message area.

Good luck!

Finally, attach all codes:

```　　import pyvista as pv
import numpy as np
class Lighting:
def __init__(self, source, angle, direction, seeds=10):
self.direction=direction
self.source=source
self.angle=angle/180*np.pi
self.seeds=seeds
# Determine the distance based on the entered grid
self.grid=None
self.distance=None
# Generate light seed
self.nlights=None
self.lights=None
self.seed_polydata=None
@staticmethod
def mag(start,end):
s=(start[0] - end[0]) ** 2 + (start[1] - end[1]) ** 2 + (start[2] - end[2]) ** 2
return s ** 0.5
@property
def source_polydata(self):
return pv.PolyData(self.source)
@property
return np.tan(self.angle) * self.distance
def update_grid(self,filename):
xmin,xmax,ymin,ymax,zmin,zmax=self.grid.bounds
start=[xmin,ymin,zmin]
end=[xmax,ymax,zmax]
dis=self.mag(start,end)
p=[[xmin, ymin, zmin],
[xmin, ymin, zmax],
[xmin, ymax, zmin],
[xmin, ymax, zmax],
[xmax, ymin, zmin],
[xmax, ymin, zmax],
[xmax, ymax, zmin],
[xmax, ymax, zmax]
]
for i in p:
self.distance=max(dis, self.mag(self.source, i))
self.distance=self.distance*1.05
def update_lights(self):
# Generate face
# Terminal Center
end_center=[self.source[0] + self.distance * self.direction[0],
self.source[1] + self.distance * self.direction[1],
self.source[2] + self.distance * self.direction[2]
]
plane=pv.Plane(center=end_center, direction=self.direction,
i_resolution=self.seeds, j_resolution=self.seeds)
# Define end point
stops=[]
for point in plane.points:
x1=point[0]
y1=point[1]
z1=point[2]
stops.append([x1, y1, z1])
self.nlights=len(stops)
self.lights=stops
self.seed_polydata=pv.PolyData(stops)
for i in range(self.nlights):
point, ind=self.grid.ray_trace(self.source, self.lights[i])
if len(point) < 1:
pass
else:
a=self.mag(self.source, point[0])
end=ind[0]
for i in point:
if self.mag(self.source, i) < a:
a=self.mag(self.source, i)
end=ind
else:
end=ind[0]
def update(self,filename):
self.update_grid(filename)
self.update_lights()
if __name__=='__main__':
source=[3000,0,-2000]
angle=20
direction=[-0.3,0.1,1]
seeds=100
light=Lighting(source,angle,direction,seeds)
light.update('car.stl')
p=pv.Plotter()