VTK:实现光照效果,从一根线到一个面

2022-02-17 00:37:36 浏览数 (2)

  导读:很多地方需要查看光照度什么的,目前也有很多软件来处理。今天就做了一个关于光照效果的内容。从早上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()

0 人点赞