写在前面
笔记来源于官网文档Book for Application Developers中 visualization 一章,作适当整理和删减。
不同的图形驱动 (graphics driver) 的调用可能不同。如果读者参照的是最主流的下载教程,很有可能和笔者安装的一样的"OGL", 否则以下某些命令可能不适用。
另外对于相关内容已经有所了解的读者进行说明,在目录意指的范围内但是不在笔记包括范围内的部分:
- 自定义 Visualization Attributes
- 根据自定义Visualization Attributes进行着色、筛选,以及对应的FRED or HepRApp的使用
- 能够使用UI命令代替的代码实现
目录
Visusalization
从UI command 可以理解可视化的过程。其中有三个概念
- scene
- scence handler
- viewer
scene 是一堆3D数据,viewer是最终渲染图片的工具,而scence handler的作用就是将scene这堆原始数据转换为viwer能够处理的数据形式。对于Geant4而言,也可以说一个图形驱动( graphics driver) 就是一些scence handler和viewer。
所以可视化的最简单的思路就是
对应了
当然,实际的UI command就是
/vis/open OGL // 打开graphics driver
/vis/scene/create //第1步
/vis/scene/add/volume // 2
/vis/sceneHandler/attach // 3
/vis/viewer/refresh // 4
/vis/viewer/flush // 4 并声明渲染过程结束
一个最简单的 vis. mac也只需要以上6行代码。
以上是本文的核心内容,后文除Visuzalization attributies外,都是作为本节第2步 ”add to scene" 的补充和一些样式的调整。 Visualization attributes 需要额外说明是它和自定义HIt类的Draw方法是唯二两个使用代码来实现比UI命令更为方便的例子。
Visulization attributes
visualization attributes 通过代码来设置显然更为简便。
auto VisAtt = new G4VisAttributes();
VisAtt.SetColor(1.,0.,0.,1.); // red, green, blue, alpha
VisAtt.SetVisibility(true);
除了volume之外,一些其他对象也可以设置visualization attributes.
G4Circle circle(pos);
G4Square square(pos);
circle.SetVisAttributes(vis);
square.SetVisAttributes(vis);
Volume & Field
考虑Volume和Field的最简单的语句为
/vis/scene/add/volume
/vis/scene/add/magneticField
/vis/scene/add/electricField
而对于volume来说,还有特殊的UI命令
/vis/drawVolume
这个命令相当与将第1、2、3步骤整合起来相当于
/vis/scene/careate
/vis/scene/add/volume
/vis/cence/attach
如果没有参数,将会绘制 world 下的所有 volume, 如果带有world之外参数
/vis/drawVolume [physical-volume-name]
将会通过正则匹配所有符合要求的physical volume,以“Detector"为例,将会匹配到"Detector1", “Detector2”,…
drawVolume在参数化的volume渲染时需要处理大量的volume,降低运行效率,此时,可以设置
/vis/viewer/set/specialMeshRendering
提高运行效率
Trajectories
/vis/scene/add/trajectories
而trajectories是程序模拟过程中比较关心的作用过程,所有还有另外一些用法
- 额外参数
/vis/scene/add/trajectories [smooth/rich]
rich 会包含一些额外的信息,smooth只会保存和显示trajectory中的点。
- 累积
如果设置
/vis/scene/endOfEventAction accumulate [maxNumber]
新的Event会画在旧的Event上
如果
/vis/scene/endOfEventAction refresh
则会将旧的Event清除,再绘制新的Event
- 存储事件的刷新
如果每产生一个Trajectory就要立即渲染在输出中,大量的trajectory的处理的消耗较大。对于图形驱动(graphics driver) OGL 默认会在第100个Event结束的时候写入输出图形中,即
/vis/ogl/flushAt NthEvent 100
这种默认行为也可以修改,语法为
/vis/ogl/flushAt
<[endOfEvent|endOfRun|eachPrimitive|NthPrimitive|NthEvent|never]> <N>
或者更简单直接一点的方法,在run前不渲染,而run后再一齐处理
/vis/disable
/run/beamOn 10000
/vis/enable
/vis/reviewKeptEvents
Trajectory: 着色 (Style)
trajectory 的绘制样式会根据相应的 drawing model 来设定,通过以下命令来选择model
/vis/modeling/trajectories/select [model-name]
具体的model和相应的默认值可见此页
也可以使用命令查询
/vis/modeling/trajectories/list
也可以在原model的基础上自定义修改
/vis/modeling/trajectories/create/drawByCharge MyByCharge
/vis/modeling/trajectories/MyByCharge/set 0 white
/vis/modeling/trajectories/MyByCharge/set 1 green
/vis/modeling/trajectories/MyByCharge/set -1 red
drawByParticleId可以如以下修改
/vis/modeling/trajectories/create/drawByParticleID MyByParticle
/vis/modeling/trajectories/MyByParticle/set gamma green
/vis/modeling/trajectories/MyByParticle/set e- red
/vis/modeling/trajectories/MyByParticle/set proton yellow
/vis/modeling/trajectories/MyByParticle/set neutron white
此外,drawByAttributes是非常灵活的model,但是需要自定义相应的Visualization Attributes, 笔者暂时没有找到完整的相关案例,并且这个话题也是相对超出了笔者的需求,所以在这里不进行深入了。
Trajectory: 筛选 (Filtering)
具体的filter可见此页,处于笔记整理的完整性考虑,此处花费一些篇幅展示filtering的使用
# Create a particle filter. Configure to pass only gammas. Then
# invert to pass anything other than gammas. Set verbose printout,
# and then deactivate filter
/vis/filtering/trajectories/create/particleFilter
/vis/filtering/trajectories/particleFilter-0/add gamma
/vis/filtering/trajectories/particleFilter-0/invert true
/vis/filtering/trajectories/particleFilter-0/verbose true
/vis/filtering/trajectories/particleFilter-0/active false
# Create a charge filter. Configure to pass only neutral trajectories.
# Set verbose printout. Reset filter and reconfigure to pass only
# negatively charged trajectories.
/vis/filtering/trajectories/create/chargeFilter
/vis/filtering/trajectories/chargeFilter-0/add 0
/vis/filtering/trajectories/chargeFilter-0/verbose true
/vis/filtering/trajectories/chargeFilter-0/reset true
/vis/filtering/trajectories/chargeFilter-0/add -1
正如上一节,也有一种基于visualization Attributes进行筛选的实现方法,同样不在本文的范围之内。
另一种方法是通过/vis/reviewKeptEvents实现的,首先现在
可以在MyEventAction::EndOfEventAction中声明保留的Event
if ( some criterion ) {
G4EventManager::GetEventManager()->KeepTheCurrentEvent();
}
// or
if ( some criterion ) {
UImanager->ApplyCommand("/event/keepCurrentEvent");
}
然后在UI command使用
/vis/reviewKeptEvents
或者类似与draw/volume类似的
/vis/drawOnlyToBeKeptEvents
就可以只展示选择的Event的 trajectory.
Hits
需要在vis.mac中加入
/vis/scene/add/hits
并且需要在Hit类中设置可视化相关的信息,可以重写Hit类的draw方法
class MyHit:public G4VHit{
public:
//...
void Draw() override;
//...
}
void MyHit::Draw(){
G4Circle circle(Pos);
circle.SetVisAttributes(vis);
}
事实上关于Hit类重写实际操作比较复杂
G4THitsMap和G4THitsCollection是相互替代的关系,出现前者通常是由G4MultiFunctionalDetector创建的。
一个Event由许多HitCollection组成,每个具体的HitCollection需要通过对应的collectionID访问,而collectionID可以通过collecitonName或者HitCollection对象从G4SDManager中获得。
重定义Hit类需要在Hit类中显式地定义对应的Allocator和HitCollection,并在SensitiveDetecoter中显式地创建对应的HitCollection,加入HCofTHisEvent中,并显示地创建Hit对象再将其加入对应的HitCollection中,以Example B2a为例:
在Hit的头文件中
// header
class TrackerHit : public G4VHit
{
public:
TrackerHit() = default;
TrackerHit(const TrackerHit&) = default;
~TrackerHit() override = default;
// operators
TrackerHit& operator=(const TrackerHit&) = default;
G4bool operator==(const TrackerHit&) const;
inline void* operator new(size_t);
inline void operator delete(void*);
// methods from base class
void Draw() override;
void Print() override;
// Set methods
void SetTrackID (G4int track) { fTrackID = track; };
void SetPos (G4ThreeVector xyz){ fPos = xyz; };
// Get methods
G4int GetTrackID() const { return fTrackID; };
G4ThreeVector GetPos() const { return fPos; };
private:
G4int fTrackID = -1;
G4ThreeVector fPos;
};
using TrackerHitsCollection = G4THitsCollection<TrackerHit>;
extern G4ThreadLocal G4Allocator<TrackerHit>* TrackerHitAllocator;
inline void* TrackerHit::operator new(size_t)
{
if(!TrackerHitAllocator)
TrackerHitAllocator = new G4Allocator<TrackerHit>;
return (void *) TrackerHitAllocator->MallocSingle();
}
inline void TrackerHit::operator delete(void *hit)
{
TrackerHitAllocator->FreeSingle((TrackerHit*) hit);
}
以上除了Set method 和Get method 全是必要的部分,也可以看作很好复用的模板。而Hit的主文件只有Draw、Print和==的实现,相对比较简单
G4ThreadLocal G4Allocator<TrackerHit>* TrackerHitAllocator = nullptr;
G4bool TrackerHit::operator==(const TrackerHit& right) const
{
return ( this == &right ) ? true : false;
}
void TrackerHit::Draw()
{
// ...
}
void TrackerHit::Print()
{
// ...
}
创建HitCollection,创建Hit,将Hit放入HitCollection的过程在SensitiveDetector中完成。
// header
void Initialize(G4HCofThisEvent* hitCollection) override;
void EndOfEvent(G4HCofThisEvent* hitCollection) override;
TrackerHitsCollection* fHitsCollection = nullptr;
// implements
TrackerSD::TrackerSD(const G4String& name,
const G4String& hitsCollectionName)
: G4VSensitiveDetector(name)
{
collectionName.insert(hitsCollectionName); // 与HitCollection创建相关
}
void TrackerSD::Initialize(G4HCofThisEvent* hce)
{
// 创建HitCollection
fHitsCollection
= new TrackerHitsCollection(SensitiveDetectorName, collectionName[0]);
// 通过ID 将HitCollection加入HCofThisEvent中
G4int hcID
= G4SDManager::GetSDMpointer()->GetCollectionID(collectionName[0]);
// G4int hcID
// = G4SDManager::GetSDMpointer()->GetCollectionID(fHitsCollection);
hce->AddHitsCollection( hcID, fHitsCollection );
}
G4bool TrackerSD::ProcessHits(G4Step* aStep,
G4TouchableHistory*)
{
// 创建 Hit
auto newHit = new TrackerHit();
newHit->SetTrackID (aStep->GetTrack()->GetTrackID());
newHit->SetPos (aStep->GetPostStepPoint()->GetPosition());
// 将 Hit加入 HitCollection中
fHitsCollection->insert( newHit );
return true;
}
Culling & Cutting
culling 可以 不渲染特殊的一部分场景
# global
/vis/viewer/set/culling global [True/False]
# invisible
/vis/viewer/set/culling invisible [True/False]
/vis/viewer/set/culling density [True/False] [[value] [g/cm3][mg/cm3][kg/m3]]
/vis/viewer/set/culling coveredDaughters [True/False]
而 cutting 可以做一些切片
/vis/viewer/set/sectionPlane on 2.0 0.0 0.0 cm 1.0 0.0 0.0
第一组向量是切片底面的任意点坐标,第二组向量是切面底面的法向量,其模对应切片的宽度。
可以同时做多个切片,这些切片可以取并集(默认), 也可以取交集
/vis/viewer/set/cutawayMode add // default
/vis/viewer/set/cutawayMode multiply // optional
或者清除切片
/vis/viewer/clearCutawayPlanes
总结
根据以上内容,我们可以写一个比较容易解读的vis.mac
/vis/open OGL
/vis/scene/create
/vis/scene/add/volume
/vis/scene/add/hits
/vis/scene/add/trajectories smooth
/vis/scene/endOfEventAction accumulate 100
/vis/modeling/trajectories/create/drawByCharge
/vis/modeling/trajectories/select drawByCharge-0
/vis/filtering/trajectories/create/chargeFilter
/vis/filtering/trajectories/chargeFilter-0/add -1
/vis/sceneHandler/attach
/vis/viewer/flush
如果有需要,我们还可以调整trajectory处理的时机以优化渲染过程,略去一些不重要的volume或者在一些关键的部位作切片观察。
除了这些UI command, 我们还能够在代码中进行设置,自定义Hit类,调用Hit的Draw方法来实现Hits的可视化。