导读:很多地方需要查看光照度什么的,目前也有很多软件来处理。今天就做了一个关于光照效果的内容。从早上10点开始,到下午3点半,开始写头条号。大概就这么长时间。中午没有休息,没有吃饭。
内容:
1、从一个最简单的案例说起
2、我的Lighting类
3、基本参数设置
4、网格导入和基本初始化(update_grid函数)
5、设置光线(update_lights函数)
6、设置一个场来显示光的作用(update_shadow函数)
7、结果的闪亮登场 可视化
8、结束(附全部代码)
一个简单的操作,如果没有变化,重复一万次,也还是只是一个操作。
一个简单的操作,如果有2个变量,重复一万次,就是一个系统。
1、从一个简单的案例说起
pyvita里有一个案例。寻找从一个点出发与网格相交点的例子。
如图,蓝色的是一根线,与球面相交用点来显示。
这个简单的案例,我们可以先分析一下,为了获得面上的交点,我们需要知道什么。
(1)需要一个起始点
start
(2)需要一个终点,
end,
(3)需要一条直线,
(4)需要明确一个面
(5)求交点
我们看一下这个简单案例的代码:
代码语言:javascript复制 import pyvista as pv
import numpy as np
# Create source to ray trace
sphere=pv.Sphere(radius=0.85)
# 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()
p.add_mesh(sphere,
show_edges=True, opacity=0.5, color="w",
lighting=False, label="Test Mesh")
p.add_mesh(ray, color="blue", line_width=5, label="Ray Segment")
p.add_mesh(intersection, color="maroon",
point_size=25, label="Intersection Points")
p.add_legend()
p()
其实,5,8,9,12这四行就完成了主要的操作。
Line 5:定义了一个球面网格
Line 8-9:定义了起点和终点
Line 12,:ray_trace函数获取交点。
后面都是一些现实的内容。
2、我的Lighting类
我构造了一个类,完全按上面的简单的案例来操作。形成下图的光的效果。
后面蓝色的圆,是终点的显示。
我们先看一下基本结构:
除了一些基本的参数之外,只有三个步骤,也就对应上面的三个函数:
update_grid
update_lights
update_shadow
3、基本参数设置
在__init__函数里,我们定义了9个参数。
输入参数
source 表示点光源的坐标
angle 表示了,点光源扩散的角度,形成一个锥
direction 表示光中心的方向,是一个单位向量
seeds 是为了获取光终点坐标需要的一个数量的参数
除了这四个之外,类的实例变量增加了
grid 用于存储网格数据
distance 设置光锥的长度
nlights 记录光线的个数
seed_polydata 是存储光点的可视化的数据
为了计算两个点之间的距离,我定义了个mag函数
代码语言:javascript复制 @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
库里应该是有类似的函数的,不过我就懒得去查了。
还有两个属性
代码语言:javascript复制 @property
def source_polydata(self):
return pv.PolyData(self.source)
@property
def aim_radius(self):
return np.tan(self.angle) * self.distance
第一个生成点光源的可视化数据
第二个,aim_radius是目标终点的半径。
我们是在从点光源出发之后的,底面上散列点,来确定光线个数的。
4、网格导入和基本初始化(update_grid函数)
第一件事,我们得处理一下网格。并且初始化一些游戏下载数据,比如distance之类的。
我们先看一下函数代码:
代码语言:javascript复制 def update_grid(self,filename):
self.grid=pv.read(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
(1)从文件读取网格
(2)计算距离,以确定点光源发出的光线和网格的所有的位置要相交
5、设置光线(update_lights函数)
也就是说我们需要提取那些终点的数据。看一下代码:
代码语言:javascript复制 def update_lights(self):
# 生成面
# 终点中心
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_size=2 * self.aim_radius, j_size=2 * self.aim_radius,
i_resolution=self.seeds, j_resolution=self.seeds)
# 定义终点
stops=[]
for point in plane.points:
x1=point[0]
y1=point[1]
z1=point[2]
if self.mag(point, end_center) < self.aim_radius:
stops.append([x1, y1, z1])
self.nlights=len(stops)
self.lights=stops
self.seed_polydata=pv.PolyData(stops)
(1)生成一个以底面中心为中心的平面数据。
(2)提取出属于球内的点的数据
(3)生成一个polydata数据,用于可视化
6、设置一个场来显示光的作用(update_shadow函数)
这个名字可能不是很好,光照的地方居然用了shadow这个单词。
同样先看代码:
代码语言:javascript复制 def update_shadow(self):
shadow_cell_ids=[]
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]
shadow_cell_ids.append(end)
# 创建shadow cell属性
shadow=np.zeros((self.grid.n_cells,))
for i in shadow_cell_ids:
shadow[i]=10
self.grid.cell_arrays['shadow']=shadow
(1)生成一个id列表,这个id是由ray_trace生成的,是面的编号列表。
同时,需要说明的是,ray_trace会和面交两个以上的点,我们选择那些最近的,因为光线不会穿越物体。
(2)生成一个shadow的标量属性,在列表内的面,赋值为10,其他为0.
7、结果的闪亮登场 可视化
最后,我们其实综合写了一个update函数。
代码语言:javascript复制 def update(self,filename):
self.update_grid(filename)
self.update_lights()
self.update_shadow()
主要是为了书写方便。
看一下我们调用的方式:
代码语言:javascript复制 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.add_background_image('road.jpg')
#
p.add_mesh(light.source_polydata, color="yellow", point_size=20, render_points_as_spheres=True)
p.add_mesh(light.seed_polydata, color="blue", line_width=5, label="Ray Segment")
p.add_mesh(light.grid, scalars='shadow')
# p.add_legend()
p()
设置了四个参数,同时调用update函数,读取了car.stl文件。并且显示出来。
结果就是如上面我们说的那样的效果了。
8、结论
一个小小的功能,放在实际应用中,都会有很多不可思议的东西。其实很多时候,我们所用到的知识并不是那么多。更多的可能是组织形式。
我是张麟博士,大家记得关注,如果大家对什么感兴趣,也可以在留言区回复。
祝好!
最后附上所有的代码:
代码语言:javascript复制 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
# 根据输入的网格确定距离
self.grid=None
self.distance=None
# 生成光源种子
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
def aim_radius(self):
return np.tan(self.angle) * self.distance
def update_grid(self,filename):
self.grid=pv.read(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):
# 生成面
# 终点中心
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_size=2 * self.aim_radius, j_size=2 * self.aim_radius,
i_resolution=self.seeds, j_resolution=self.seeds)
# 定义终点
stops=[]
for point in plane.points:
x1=point[0]
y1=point[1]
z1=point[2]
if self.mag(point, end_center) < self.aim_radius:
stops.append([x1, y1, z1])
self.nlights=len(stops)
self.lights=stops
self.seed_polydata=pv.PolyData(stops)
def update_shadow(self):
shadow_cell_ids=[]
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]
shadow_cell_ids.append(end)
# 创建shadow cell属性
shadow=np.zeros((self.grid.n_cells,))
for i in shadow_cell_ids:
shadow[i]=10
self.grid.cell_arrays['shadow']=shadow
def update(self,filename):
self.update_grid(filename)
self.update_lights()
self.update_shadow()
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.add_background_image('road.jpg')
#
p.add_mesh(light.source_polydata, color="yellow", point_size=20, render_points_as_spheres=True)
p.add_mesh(light.seed_polydata, color="blue", line_width=5, label="Ray Segment")
p.add_mesh(light.grid, scalars='shadow')
# p.add_legend()
p()