上海幕墙光评 | 基于Rhino的快速检测方法(rhino怎么检查模型破面)

玻璃的反光危害一直是现代玻璃幕墙建筑中重要的限制因素,上海光评的严苛审查也一直是建筑与幕墙设计师无法回避的节点本文中介绍了一种快速的幕墙眩光检测算法,以满足在项目前期快速检验眩光影响的需求以几乎实时响应的速度为建筑师与幕墙工程师的设计迭代提供反馈,让设计形成闭环,使参数更改不再盲目,而是有理可循。

#1背景介绍Introduction眩光是指人的视野中突然出现或长时间存在较强烈的明暗对比,使人无法适应的现象建筑眩光通常由建筑物表面反射的阳光或人造光线产生该反射可能由建筑物的玻璃面、金属面或其他材料的光滑表面产生,在极端情况下反射的光线可能非常强烈。

建筑物的眩光可能会导致附近的居民或司机产生视觉不适,甚至引发短暂失明或视网膜灼伤

实际生活中,许多高层玻璃幕墙建筑也由于受到周边居民的光污染投诉而无奈赔款,并对立面进行二次改造,给业主造成额外的大量支出在上海光评的严格要求下,上海的玻璃幕墙建筑普遍采用极低反射率的玻璃,以至于在外观上“发黑”,如下图展示的上海白玉兰广场。

为了避免类似问题,在将眩光及其他建筑物理问题纳入思考范畴之后,越来越多的建筑师在前期方案阶段就开始考虑遮阳条,玻璃透光率、反光率等设计参数,希望获得建筑性能与外观的平衡然而建筑自身及周边的光环境通常非常复杂,尤其是在复杂几何的情况下,常会有一些意想不到的情况。

在没有辅助分析的情况下,仅依照理论公式与规律进行尝试无异于闭着眼睛过河因此,除了正式的光评分析以外,常驻的、实时反馈的、可迭代的光环境分析也有必要

本文将简单介绍在设计软件Rhinoceros(以下简称Rhino)中实现这一分析的方法之所以选择Rhino,是因为其优秀的几何图形处理能力不同于一般的软件,在Rhino的帮助下,处理分析模型的时间大大缩短,同时对曲面建筑和复杂几何的适应性也更强。

在分析完成后,也可以借助Rhino的图形表达,快速地预览分析结果,实时追踪反射光线的光路,判断眩光的影响时间与范围

在该方法下,仅使用普通的台式电脑(以CPU为酷睿12代i9为例),在几十万多边形数的场景下,包含数十万条光线,分析全年365天,精确至每分钟,整体计算耗时仅为5分钟左右因此,本文中介绍的方法将可以在同场景下实现超长时间跨度覆盖、超高时间精度采样。

本文也会提供一些代码片段参考,编程语言为Python,为了降低阅读门槛,在使用Rhino的API(RhinoCommon)时将给出详细的解释说明本方法的计算公式遵守上海市环境保护局于2015年发布的《建筑玻璃幕墙光反射影响分析报告编制要求》

该要求说明了检验玻璃幕墙对周边敏感目标的光反射影响的方法,其中敏感目标包括住宅、学校、养老院、yi院以及道路交通干线其他详细要求将在下文按分析逻辑逐步介绍,未覆盖部分请见编制要求原文,本文中不再赘述#2逻辑分析

Logics为了模拟玻璃幕墙间的多次光线反射并确定眩光影响的时间与位置,通常需要采用光线追踪(Ray-Tracing)技术由于眩光分析的潜在敏感视角已经确定(附近敏感建筑的窗户、道路上汽车的挡风玻璃),分析中需要摆放的观察视角也是确定的。

因此,同市面上主流的3D游戏一样,为了节省运算时间,眩光分析算法普遍采用反向光追(backward Ray-Tracing)。以此仅计算最终会进入视野的光线,而不会进入视野的光线则不计算。

如上图所示,进行反向光追时,光路从观察视角出发,在反射面反弹后回归天空以分析一处居民区窗户为例,完整的分析过程可以分为以下几个步骤:搭建场景模型,确定分析视角的位置(窗户),为不同区域附加立面材料并为材料设置。

反射方程(reflection equation);根据项目的经纬度,计算不同时间下的太阳方位,建立太阳矩阵(solar matrix);根据分析所需的视野角度,从镜头发射光线;任由光线在场景内弹射直至回归天空或能量耗尽,记录回归天空的光线尾部的方向向量;

将光线的尾向量与太阳矩阵匹配,如匹配成功,同步矩阵对应位置的时间并计算光线亮度;各窗户回收统计光线的匹配时间与亮度数据,形成全年结果,输出报告。

完整程序的流程如下图所示:

#3详细过程Details#3-1 场景模型Rhino中有多种几何类型可以表达面这一概念,包括surface,brep,mesh,SubD经过多次尝试,mesh相关功能的便利性与计算速度最能满足快速检测的需求。

根据经验,为了提高分析的精确度,在划分Mesh网格时,以下图所示的矩形网格为最佳

用于提取网格中一个矩形面的位置与法向量信息的采样点可以设置在矩形面的中心居民楼窗户上分析视野的采样点可以根据真实的人视高度进行调整以下代码将演示如何获取一个mesh face的中心点与其中心位置法向量,用到了命名空间。

Rhino.Geometry.Mesh与Rhino.Geometry.MeshFace的相关方法假设程序中存在一个已知的Mesh,需要提取其每一个面的中心点pt与法向量vectdefget_face_center。

(mesh, idx):    face = mesh.Faces[idx]if face.IsQuad: #Face is a quad        p = mesh.PointAt(idx, 0.5

, 0, 0.5, 0)        v = mesh.NormalAt(idx, 0.5, 0, 0.5, 0)else: #Face is a triangle        p = mesh.PointAt(idx,

0.33, 0.33, 0.34, 0)        v = mesh.NormalAt(idx, 0.33, 0.33, 0.34, 0)return p, v#---/Main/---for idx_face

in range(mesh.Faces.Count):    (pt, vect) = get_face_center(mesh, idx_face)程序中对四边形与三角形面进行了区分,其区别在于不同边数的面获取其

重心坐标(barycentric coordinates)中心点的参数不同示例中的三角形面仅为接近中心点,此处无需特别精确#3-2 反射方程#3-2.1玻璃上海光评中的光反射仅涉及玻璃的反射在一般分析中,可以将玻璃看作完全光滑的材料,因此可以假设仅存在镜面反射,其反射方程符合菲涅尔公式(Fresnel formula)。

如下图所示,假设存在入射光线IO,其入射角为;反射光线为OR,其出射角为;折射光线为OT,其出射角为。

假设光路从空气传播到玻璃,空气的折射率为(约为1),玻璃的折射率为(普通玻璃约为1.52)由于空气与玻璃均为介电质(dielectric),因此根据斯涅尔定律(Snells law),入射角与折射角的关系为:。

将该等式变换后可得,在已知入射角的情况下,折射角为:将光线拆解为s-偏振与p-偏振两部分,根据菲涅尔公式,在介电质(空气)-介电质(玻璃)交界面下,这两种偏振态部分的反射率分别为:关于光的偏振态与其反射、折射的内容此处不做过多展开。

考虑到在单次分析中该反射率计算可能会重复触发几百、几千万次,因此可以将这两个公式进一步简化为:该简化公式的计算结果与上方的完整公式完全一致由于上海幕墙光评仅分析日光造成的眩光效果,而日光属于自然光,任何一种方向的震动都不比其他方向更占优势。

因此,可以将光路整体的等效反射率简单地看成s-偏振与p-偏振的平均:在使用简化公式时,在入射角为0°时(入射光垂直玻璃),简化公式的分母为0为避免程序报错,可以使用完整公式在入射角为0°时的简化:最后,不同于非透明材料,折射进玻璃的光在穿过玻璃后将到达玻璃的背面(第2个空气-玻璃交界面),此时将发生第二次反射。

因此,对单块玻璃而言,其整体的反射率为其正反两面反射多次迭代的效果,可通过以下公式近似求得:

对多块玻璃的情况(如中空玻璃),可以通过其他公式或从软件WINDOW中直接调取预设的玻璃组合在不同入射角下的反射率数据,此处不做过多展开在分析眩光问题时,需要考虑的是室外可见光波段反射率,而非全波段反射率。

下方为以上单块玻璃反射率计算的代码形式在实际应用中,将带有反射率的材料包装成一个单独的类(class),以实现根

classMaterial:#---/Private methods/---def__init__(self, txt, num):        self.tag = txt        self._refract = num

def__repr__(self):returnmaterial % self.tag#---/Public methods/---defreflect(self, angle_i):Reflect based on Fresnel

n_i = 1        n_t = self._refractif angle_i == 0: #Check for normal            R = pow((n_i-n_t)/(n_i+n_t),

2)else: #Incident angle is not 0            angle_t = asin(n_i/n_t*sin(angle_i))#-/Make equation shorter/-

t1 = angle_i - angle_t            t2 = angle_i + angle_t#-/---------------------/-            R_s = pow(sin(t1)/sin(t2),

2)            R_p = pow(tan(t1)/tan(t2), 2)            R = 0.5 * (R_s + R_p)        R_all = 2*R / (1+R)

return R_all#---/Main/---IOR = 1.52#refraction index of typical glassglass = Material(glass, IOR)#-/Calculate reflection from 0 to 90 degree/-

for angle_deg in range(91):    angle_rad = radians(angle_deg)    r = glass.reflect(angle_rad)以上程序计算的结果可以打印出下图所示的曲线:

从图中可以看出,当入射角为0°时(入射光线垂直玻璃),单块玻璃正反两面造成的室外可见光反射率总和约为8%,符合一般玻璃厂商提供的数据当入射角为90°时(入射光线平行于玻璃),反射率为100%(可以理解成光与玻璃擦肩而过)。

同时可以发现,当入射光线位于0°-50°的区间内时,反射率几乎稳定保持在8%而在眩光分析时,自身建筑对较远方建筑产生眩光影响时多为太阳高度角较低的时候在考虑竖直玻璃的时候,产生眩光影响时多为光线的入射角较小的时候,也就是反射率。

稳定的区间因此,在实际应用中,为了节省计算时间,部分分析软件没有套用菲涅尔公式,而是直接用常数替代当使用这一方式时,代码可以简化为:classMaterial:#---/Private methods/---。

def__init__(self, txt, num):        self.tag = txt        self._reflectance = numdef__repr__(self):return

material % self.tag#---/Public methods/---defreflect(self):Reflect as constantreturn self._reflectance

#---/Main/---R = 0.08#reflectance of typical glassglass = Material(glass_constant, R)#-/Calculate reflection/-

r = glass.reflect()该方法在某些极端情况下算出的光线亮度可能会偏暗,但在普遍情况下确实可以在保证相似结果的同时简化计算至于使用何种方式全凭个人,此处不予置评#3-2.2金属由于上海光评只包括玻璃幕墙,因此仅了解上一节介绍的公式就可以制作分析算法。

但为了强调上文所述的公式不可以简单挪用到金属面板的分析中这件事,此处特意

他的实数部分为为,对应其介电常数(refraction index即上文中介电质材料的折射率);他的虚数部分为,对应其消光系数(extinction coefficient)此时,空气与金属的交界面为介电质-导体交界面,此时光线的入射角与折射角的关系为:。

由此可得,s-偏振与p-偏振的反射率分别为:这些公式比较难代入到程序中进行计算,因此在实际应用中可以使用专门针对金属材质的逼近公式,如下所示:该逼近公式并不完全精确,且不适用非金属的材质,需要注意下方为代码示例:。

xt, num1, num2)

:        self.tag = txt        self._refract_real = num1        self._refrect_img = num2def__repr__(self)

:returnmaterial % self.tag#---/Public methods/---defreflect(self, angle_i):Reflect based on Fresnel

n_i = 1        n_t = self._refract_real        k_t = self._refrect_img#-/Make equation shorter/-

t1 = pow(n_t, 2) + pow(k_t, 2)        t2 = 2 * n_t * cos(angle_i)        t3 = pow(cos(angle_i),

2)#-/---------------------/-        R_s = (t1-t2+t3) / (t1+t2+t3)        R_p = (t1*t3-t2+1) / (t1*t3+t2+

1)        R = 0.5 * (R_s + R_p)return R#---/Main/---IOR_real = 2.17#refraction indexIOR_img = 9#extinction coefficient

metal = Material(metal, IOR_real, IOR_img)#-/Calculate reflection from 0 to 90 degree/-for angle_deg

in range(91):    angle_rad = radians(angle_deg)    r = metal.reflect(angle_rad)其计算结果打印出的曲线为:

符合一个抛光金属面的特性(示例中的参数仅供参考)#3-3 太阳方位#3-3.1基础计算上海光评2015版的编制要求中规定分析应精确至1分钟,计时采用真太阳时(apparent solar time)为了获取每分钟下太阳在天空中的位置,需要计算当前时间的太阳高度角(solar altitude angle)与太阳方位角(solar azimuth angle)。

具体的定义可以自行查询,此处不做展开,其计算公式如下:其中:h——太阳高度角(°)A——太阳方位角(°)——地理纬度(°)——太阳赤纬(°)t——太阳时角(°)此处岔开话题,两个角度的计算中,太阳高度角的计算没有任何争议,因为根据高度角的定义,其理论的数学极限仅为-90°至90°,实际每个地区根据纬度不同,真实的范围更小。

在此区间内,高度角与其正弦值仅存在唯一对应,可以单纯通过正弦公式求得而太阳方位角的数学极限为-180°至180°,单纯通过正弦值无法确定它的象限或许存在根据当前时间来确定象限的办法(这可能是编制要求仅给出了正弦公式的原因),但在查阅资料后发现,许多其他规范(如。

ASHRAE)中补充了余弦公式来帮助确定象限:回到原话题,地理纬度直接代入项目所在地的纬度太阳赤纬(solar declination angle)为太阳光线的方向(即地日连线)与地球赤道平面的夹角

这一角度全年变化。

在2015版的编制要求中可以查询到该角度在全年中每天的数据,可以直接将表格中的数字代入到程序中如果奉行原教旨主义,也可以考虑用公式计算:其中:——太阳赤纬(°)N——平年中的天数,1月1号为1,12月31号为365。

该公式仅计算平年时的赤纬角,而编制要求中给出的角度为天文台实际观测4年(3平年+1闰年)的数据的平均值,因此存在一定误差(最大约为1.4°)下方代码示例中采用的为王炳忠教授于《太阳辐射计算讲座》中提到的考虑闰年的算法,其误差约为0.1°,此处暂且按下不表。

太阳时角(solar hour angle)是当地经线所在的平面与太阳光方向的夹角,随着一天中地球的自转而变动根据编制要求,太阳时角(solar hour angle)的计算公式为:其中:t——太阳时角(°)。

n——时间(小时,真太阳时)以上为上海光评要求使用的计算太阳方位的内容,代码示例将整合到下一节中展示#3-2.2进阶计算以下为进阶的内容,仅为编写上海光评算法可以不了解,并跳至下一节(03-4 初始化光线)。

如果稍微了解太阳方位或天文相关的计算可以发现,上一节中的计算公式甚至不需要引入当地的经度这是因为编制要求中做了一项十分巧妙的设定:计算与分析采用真太阳时换言之,上海光评所要求的时间单位,并不是我们日常生活中熟知的时间单位。

要理解这一点,首先需要了解不同的计时方式以宇宙中遥远的“固定”的恒星为参照,由于恒星非常遥远,使得地球绕日公转造成地球的位置变化可以忽略不记(其实太阳也在动,暂且按下不表)可以理解成地球以整个宇宙的绝对坐标系为参照,记录地球绕自转轴自转一周的时间目前称为。

恒星日(stellar day),即地球切实自转了360°所需的时间;以地球北半球的平春分点(mean vernal equinox),即黄道(ecliptic)与天赤道(celestial equator)

的升交点的平均值为参考,记录地球自转一周的时间也称为恒星日(sidereal day),英文不同,以下用英文区分受地球自转轴进动和章动的影响,这个点在一直变化(春分点西移)由于sidereal day是平均后的结果,因此一个sidereal day并不切实反映地球自转一周的时间。

但因为这个计时方式方便天文观测,因此比stellar day更普及或许可以暴力地理解成stellar day是科技发展后更加精确的sidereal day一个sidereal day大约比一个stellar day短8.4 毫秒。

以太阳光的方向(即地日连线方向)为参照,地球自转一周的时间称为一个太阳日(solar day)由于地球离太阳比较近(相对其他恒星),地球绕日公转所带来的角度偏差就非常明显如下图所示,当地球旋转360°后,一天过去,地球在绕日公转轨道上行进了一段距离,因此,原本朝向太阳的位置此时并没有朝向太阳,地球仍需要额外自转一段时间,才能让昨天朝向太阳的位置重新朝向太阳。

整个补足的过程需要约4分钟,补完这段距离后,才称为一个太阳日

由于太阳对地球的影响更加大,因此除了一些特殊行业,地球上目前普遍采用太阳时而此处才是要强调的重点,太阳时又一分为二,分为真太阳时与平太阳时如下图所示,地球的绕日轨道是一个椭圆,且并不相对太阳对称,存在近日点(perihelion)

与远日点(aphelion)。在该轨道离心率与倾角的共同作用下,在一年中地球绕日公转的角速度与线速度均不固定,此处不过多展开。

真太阳时记录的是当地所在的经线与太阳的真实关系,因此真太阳时每天的长度都不相同,如此一来不方便日常生活因此产生了平太阳时,即真太阳时的平均长度,这也就是日常生活中钟表所使用的时间(1平太阳日=24h)如要将平太阳时转换为真太阳时,就需要人为修正不均匀公转造成的差异。

这一差异的变化非常复杂,但可以用三角函数来绘制逼近曲线(此处参考ASHRAE):其中:ET——时间修正值(分钟)N——平年中的天数,1月1号为1,12月31号为365该修正值在一年中的数值如下图所示:

但这并不是全部,由于真太阳时为项目所在地的经线与太阳的关系;而钟表时间是当地的时区采用的平均时间,是当地时区中央经线对应的平太阳时要修正真太阳时与钟表时之间的差异,项目所在地的经度与中央经线的经度间的差异也应弥补。

其中的关系如下图所示:

以上所有的转换,最终都反映在太阳时角的计算中即计算太阳时角时需输入真太阳时,而真太阳时由钟表时转换所得以此才能最终得到钟表上的时间与天空中太阳位置的对应关系最终的公式如下:其中:H——太阳时角(°)AST——真太阳时(小时)

LST——当地标准钟表时(小时,中国为北京时间)LON——项目所在地的经度(°)LSM——当前时区的中央经线经度(°,东八区为120°E)ET——时间修正值(分钟)整合上述所有的公式,其代码形式如下:

m datetime

(year, month, day)

:try:        day_now = date(year, month, day)        day_start = date(year, 1, 1)        day_delta = day_now - day_start

num = day_delta.days + 1return numexcept:returnNonedefget_declination(n, year):    n_0 = 79.6764

+ 0.2422 * (year - 1985) - floor((year - 1985) / 4)    t = n - n_0    theta = 2 * pi * t / 365.2422    delta_degree = (

0.3723 + 23.2567*sin(theta) + 0.1149*sin(2*theta) - 0.1712*sin(3*theta) - 0.758*cos(theta) + 0.3656*cos(

2*theta) + 0.0201*cos(3*theta))    delta = radians(delta_degree)return deltadefget_equation_time(n):    Tau = radians(

360 * (n - 1) / 365)    ET = 2.2918 * (0.0075 + 0.1868*cos(Tau) - 3.2077*sin(Tau) -1.4615*cos(2*Tau) -

4.089*sin(2*Tau))return ETdefget_hour_angle(LST, LON, LSM, ET):    AST = LST + ET/60 + (LON - LSM)/15

H = radians(15 * (AST - 12))return Hdefget_solar_position(lat, declin, h):    lat = radians(lat)#---/Find the solar altitude angle in radians/---

sin_alt = sin(lat) * sin(declin) + cos(lat) * cos(declin) * cos(h)    alt_rad = asin(sin_alt)#---/Find the solar azimuth angle in radians/---

sin_azi = cos(declin) * sin(h) / cos(alt_rad)    cos_azi = ((cos(h) * cos(declin) * sin(lat) -             sin(declin) * cos(lat)) / cos(alt_rad))

sun_vect = rg.Vector3d(-sin_azi, -cos_azi, 0)    azi_rad = rg.Vector3d.VectorAngle(-rg.Vector3d.YAxis, sun_vect,

rg.Plane(rg.Point3d.Origin, -rg.Vector3d.ZAxis))if azi_rad > pi:        azi_rad -= pi *

2#---/treat the results/---    pln_ref = rg.Plane(rg.Point3d.Origin, sun_vect, rg.Vector3d.ZAxis)    sun_vect.Rotate(alt_rad, pln_ref.ZAxis)

sun_vect.Unitize()    alt_deg = degrees(alt_rad)    azi_deg = degrees(azi_rad)return alt_deg, azi_deg, sun_vect

#----------///Main///----------#---/Calculate constant values/---LSM = 15 * TimeZone#---/Iterate the months/---

Alts = []Azis = []Vects = []for mon in Month:#---/Iterate the days/---for day in Day:#---/Calculate the solar decilination angle at the day/---

is_dd_valid = True        Declins = []for year in Year:            doy = get_annual_day(year, mon, day)

if doy:                declin = get_declination(doy, year)                Declins.append(declin)else:

is_dd_valid = Falsebreakif is_dd_valid:            declin_mean = sum(Declins) / len(Declins)

ET = get_equation_time(doy)#---/Iterate the hours/---for hour in Hour:#---/Iterate the minute/---

for min in Minute:                 hour_lst = hour + min/60                    ang_hour = get_hour_angle(hour_lst,

Longitude, LSM, ET)                    [alt, azi, vect] = get_solar_position(Latitude,

                                                  declin, ang_hour)if alt > 10: #Only collect daytime

Alts.append(alt)                        Azis.append(azi)                        Vects.append(vect)

程序中迭代了4年的太阳赤纬角度,包含了一个闰年,并求取了平均值其中变量Latitude,Longitude,TimeZone,Year,Month,Day,Hour,Minute需要输入而输出值为Alts

,Azis与Vects,分别对应各时间下太阳的高度角,方位角与太阳方向向量在计算太阳方向向量时,调取了Rhino API中的命名空间Rhino.Geometry.Vector3d下的方法整合所有太阳方向向量,即可获得用于后期比对的太阳矩阵。

#3-4 初始化光线上海光评中对于光线亮度的评判标准根据光线的入射角不同而变化,其具体情况如下图所:

其中入射角的定义为经玻璃反射后的太阳光线与人眼水平视线所在平面的夹角以侧视图的视角来看,入射光线为上下各自15°与30°的范围;在俯视图中,入射光线的范围为前方180°的完整扇形(考虑一般居民楼的情况下光线不可能从玻璃后面射入)。

以此范围扫掠所得的空间即为光评中需要考虑的入射光范围,如下图所示:

因此,在反向光追的工作流程中,仅需要在上述的空间

--------///Public method definitions///----------defget_Vects(vect_ini, pto, deg_start, deg_end)

:    Vects = []for deg in range(deg_start, deg_end):        vect_col = _rotate_vect(vect_ini, deg, rg.Vector3d.XAxis)

for deg_sub in range(-89, 90):            vect = _rotate_vect(vect_col, deg_sub, rg.Vector3d.ZAxis)            Vects.append(vect)

return Vects#----------///Private method definitions///----------def_rotate_vect(vect_in, deg, axis):

vect = rg.Vector3d(vect_in)    rad = radians(deg)    vect.Rotate(rad, axis)return vect#----------///Main///----------

vect_Y = rg.Vector3d.YAxisVects_30_b = get_Vects(vect_Y, rg.Point3d.Origin, -30, -14)Vects_15 = get_Vects(vect_Y, rg.Point3d.Origin,

-14, 15)Vects_30 = get_Vects(vect_Y, rg.Point3d.Origin, 15, 31)Vects_30.extend(Vects_30_b)该程序生成的初始向量集合中,每个向量间的夹角为1°,集合中的总向量个数为10,919个。

即居民楼(举例)的每个窗户需要检测约1万个方向的光线当然,这一夹角也可以根据想要的精度与计算机的算力调整程序中生成的光线集合以世界坐标系Y轴作为基准朝向在使用时,需要根据窗户对应的水平法向量将光线集合翻转过去,推荐使用。

Rhino.Geometry.Transform.PlaneToPlane()来完成这一操作#3-5 光线发射与反弹光线初始的向量方向定义好,并将光线集合翻转至窗户的位置与朝向后,即可开始光线追踪在Rhino中进行光线与场景的碰撞检测时推荐使用。

Rhino.Geometry.Intersect命名空间下的Intersection.MeshRay()方法,可以检测出一条射线与场景中模型的第一个相交点得益于mesh精简的数据结构与Rhino底层C++算法超高的运行速度,碰撞检测可以在几乎瞬间完成。

同一命名空间下还有一个Intersection.Rayshoot()方法,可以一口气完成光线的多段弹射但由于本算法的基础逻辑需要让光线分次弹射,并在每次弹射时同步反射面的信息,再根据光线内残留的能量实时回收光线,因此仍推荐。

MeshRay()方法光线的整体弹射可以分为推进(forwarding)和反射(reflecting)两个部分在推进时,需要根据光线此时出发点的位置与当前的方向向量初始化一个Ray3d实例,再将该实例与场景的。

mesh进行MeshRay()检测测试会返还光线此次行进的长度,若该长度为正数,则表明光线碰到了物体;若返还的长度为负无穷,则证明光线没有碰到任何物体对于碰到物体的光线,可以根据其返还长度更新新的起始位置,并进入反射程序;对于没碰到物体的光线,需要将其终止并回收。

ard(self, mesh):#-/Prepare the basic forward recipe/-

adjust = TOL        Idcs = clr.StrongBox[Array[int]]()        Lst_idx = []        pt = self._Pts[

-1]        vect = self._Vects[-1]        pt_start = pt + vect * adjust#-/Initiate the ray and shoot/-

ray = rg.Ray3d(pt_start, vect)        dist = rg.Intersect.Intersection.MeshRay(mesh, ray, Idcs)

if dist > 0: #Hit something#-/Turn to the terminating routine/-            pt_new = pt_start + vect * dist

Lst_idx.extend(list(Idcs.Value))else: #Hit nothing#-/Turn to the terminating routine/-            self._status =

-1#Ray get lost#-/Add a tail to the ray/-            dist = 20            pt_new = pt_start + vect * dist

return pt_new, Lst_idx#---/Main/---TOL = sc.doc.ModelAbsoluteTolerance在实际应用中,应当将光线制作成一个专门的类,用以存储信息以及编写相关的功能。

上文的代码示例就将推进功能做成了光线这个类的其中一个方法其中self._Pts是一个list,用于存放光线每次发射的起点;self._Vects则用于存放每次发射的方向向量当光线反射过一次后,此时的出发点正好位于。

MeshFace的表面,此时如果直接再次发射,光线可能会被mesh困住。因此可以看到程序中每次在发射前,会稍稍将光线往前推一小步,推的距离为模型中的当前最小长度精度TOL。

完成了推进并成功击中场景的光线将进入反射程序在反射程序中,根据之前StrongBox收集的MeshFace索引号,对应MeshFace将被找到,并提取其对应的材料属性和法向量这些信息在之前的03-1小节中已经提取完成,并制成了一个客制化的类。

Custom_Scene这个类的实例scene负责向光线转递场景的一切信息光线同步完信息后,若材料为不反光材料,则光线终止并回收;若材料为玻璃,则根据其反射率进行能量衰减,并根据其法向量进行方向翻转以下为代码示例:。

iom the scene archive by indices/-

vect = rg.Vector3d.Zero        Lst_tag = []for idx in Lst:            panel = scene.get_panel(idx)

vect_panel = panel.get_norm()            vect += vect_panel            tag = panel.get_tag()

ifnot tag in Lst_tag:                Lst_tag.append(tag)        vect.Unitize()        tag = Lst_tag[0

]return vect, tagdefis_valid(self):if self._status == 1and self._portion[-1] > 0.001:returnTrueelse:return

Falsedefreflect(self, mesh, pt, Lst_idx):if self.is_valid():#-/The ray is on going or is waiting to be initiated/-

#-/Attract information from the panels/-                [vect_norm, tag] = self._sync_panel(Lst_idx, self._Vects[

-1])#-/Calculate the incident angle/-                pln = rg.Plane(pt, vect_norm, -self._Vects[-1])                rad = rg.Vector3d.VectorAngle(vect_norm, -self._Vects[

-1], pln)                reflect = scene.calc_reflection(tag, rad)                self._portion.append(self._portion[

-1] * reflect)if self.is_valid():#-/Reflection/-                    vect_cross = rg.Vector3d.CrossProduct(vect_norm,

-self._Vects[-1])                    pln = rg.Plane(pt, vect_norm, vect_cross)

trans = rg.Transform.Mirror(pln)                    vect_reflect = rg.Vector3d(-self._Vects[

-1])                    vect_reflect.Transform(trans)#-/Record this reflection for the next forward/-

self._Vects.append(vect_reflect)#-/Record the last forward                self._Pts.append(pt)

self._Idcs_face.append(Lst_idx)else:#-/The ray get lost in the last forwarding/-#-/Terminating the ray with appending the last points/-

self._Pts.append(pt其中reflect()为主要的反射程序,sync_panel()负责让光线与场景交换信息,is_valid()用于确认光线是否仍然合法。

self._portion是一个list,用于存放光线每一段中残留能量的比例,初始为1(100%),每次反射后根据玻璃反射率减少,当值小于0.001(即光线能量仅剩出发时的千分之一)时认为光线已经没有意义,终止并回收(因为光路是可逆的,可以等效为如果一束光从太阳出发,沿相同路径进入相机,因为途中反弹太多次,早就不亮了)。

以上就是在Rhino中进行光线追踪需要使用的两个主要程序其他的辅助程序可以根据需要自行客制化#3-6 光线与太阳矩阵匹配当所有光线全部终止并回收后(一部分能量耗尽,一部分回归天空),就可以根据光线最后一次推进时的向量,与太阳矩阵匹配。

如下图所示,将在03-3小节中计算完的太阳向量从坐标轴原点打出,依照一定距离在天空形成点阵再将上一节中算完的光线向量也从原点打出,用同样的距离打在天空上

此时若要判断光线的终点是否与太阳点位有重合,可以使用一种经典的空间树形结构:R-tree关于R-tree本文不再过多展开,Rhino的Rhino.Geomtry命名空间下自带RTree这一类,可以直接使用。

在Rhino中的R-tree搜索支持两种方式,一种是使用Rhino.Geometry.BoundingBox,另一种是使用Rhino.Geometry.Sphere,此处使用Sphere更加契合主题在用球进行搜索时,球的直径可以用弧度角换算,为光线的向量提供1°的容差(03-4小节中生成光线时,每条光线相差1°),如上图所示。

以下为在Rhino中使用R-tree搜索的最简单的代码案例:import Rhino.Geometry as rg#---///Search method///---defSearchCallback(sender, e)

:    Lst_idx.append(e.Id)#---///Initiate the tree///---tree = rg.RTree()for idx, pt in enumerate(Lst_point):

tree.Insert(pt, idx)#---///Generate sphere for search///---sphere = rg.Sphere(pt_search, radius)#---///Search in R-tree///---

Lst_idx = []tree.Search(sphere, SearchCallback, Lst_idx)Lst_point为天空中太阳的点的集合,sphere为光线终点位置的搜索球,匹配成功后可以获得匹配点的索引号(即太阳的索引号)。

至于如何通过索引号获取对应的日期、太阳高度角信息,本就是基础的python编程内容,可以通过字典或自定义类轻易实现,本文中不再赘述#3-7 亮度计算2015版的编制规范中给出的光线亮度起算公式为:其中:

B——亮度()E——太阳光直射法线照度(lx)——室外可见光反射率——当时的太阳高度角式中的称为反射率,实际可由03-5小节中的self._portion[-1]的值代替,即光线此时残余能量的比例太阳高度角通过上一节中匹配的太阳信息给出。

最后再根据下图的的标准进行影响等级判定由于再03-4小节中,初始化的光线中15°的光线和30°的光线已经提前分为了两组,两组单独运算,此时不需要在判定角度

由于过于简单,此处不再提供代码示例#4Rhino中的多线程Multi-process in Rhino虽然MeshRay与RTree这些优秀的算法已经大大提升了运行速度,但在庞大的计算量下,使用单一线程的计算速度仍有可能不尽如人意。

nlib.paralleldefcalc(num):    reurn num + 1Lst_num = []

for num in range(9999):    Lst_num.append(num)Nums = ghpythonlib.parallel.run(calc, Lst_num, False)#5

总结Summary通过以上的方法,可以在Rhino中建立一个快速进行眩光分析的工具本文旨在让有进行相关分析需求的各位从业者了解类似算法的框架,明白发生了什么,需要做什么相关的代码也会陆续开源,以供提取RFR希望通过类似的交流,将一部分信息透明化、公开化,让大家共同提升。

CHECK OUT MORE ABOUT

RFR 精选 | 恒基·旭辉天地:漫步于2500个花钵组成的垂直峡谷RFR精选 | 全木结构建筑探索 - 上海诺华园区C12-1RFR精选 | 超高层建筑的“世界第一扭”RFR精选 | “山水城市”——双曲面玻璃幕墙的“极限挑战”

RFR探索 | 浦东花木10号幕墙重难点简析和VMU验证RFR精选 | 打造极致品质的办公建筑群 – 诺华园区系列之一RFR精选 | 超高层建筑的自然通风解决方案

主题测试文章,只做测试使用。发布者:打酱油的星星,转转请注明出处:http://www.gaofansheji.com/index.php?m=home&c=View&a=index&aid=4047

联系我们

在线咨询:点击这里给我发消息

邮件:787143156@qq.com