初出茅庐篇【葬龙谷】【主线】葬龙谷之变对话三个NPC 熟悉操作 靠近马车。第一个选项 走为上策 和 出言相劝 都不影响后续。【梧桐村】经脉：前期主 任、冲（快速提升战力）前期瑜伽经心法需3000+气血50身法，战斗技巧：尽量不要被包围，攻击背面和侧面伤害更高，背击+命中。优先获取和请教心法 属性经脉丹优先给主角，主角战力傲视群雄以后再培养队友。【主线】初入梧桐村[江小彤]好感+30（30）拿江宅内 右侧 宝箱。出门左侧宝箱 【一周年礼包】打开获得【小熊猫坐骑】【宗师书札】【金元宝】【五转洗髓丹】【五品宁神丹】【逸剑风云决】把【宗师手札】【五转洗髓丹】吃了，【逸剑风云决】穿上。按J打开经脉图，选择任、冲脉点满前三个黄点，冲脉第三个暂时点不满。按B打开背包点击左下角坐骑切换到【DLC海豹坐骑】先不去找[小彤]往右走，按E消耗精力（左上角绿条是精力值）采集路过的所有资源（红蓝荧光点靠近可采集）。左下[樵夫]棚屋内宝箱。【支线】北望对话[赵启志]选择 肯定能，北方已经不打仗了。钓鱼点钓五条草鱼也可以去清河村找渔夫购买左侧[赵氏]货摊上方隐藏宝箱对话[赵氏]【支线】百晓生-无妄之灾对话[百晓生]选择 如何化解 付钱。【支线】铁匠的烦心事对话[铁匠] 选择 行吧，旁边的宝箱开了，前往钓鱼点对话李寡妇，在回复铁匠。郎中家左下棚屋宝箱左侧宝箱。继续往左走遇到[小彤]，打开背包把刚获得的装备穿上，切磋[小彤]装备金钱镖，走到左侧猴子处战斗帮小彤抢回木剑回到江吟风宅，获得【基础心法秘籍】，点满【基础心法】继续点经脉 把其他脉络前三个先点满，再点任脉和冲脉【主线】解毒之物-药材找[孙郎中]取药材，采集五份半边莲回复[孙郎中]【支线】枯藤老树昏鸦继续与[孙郎中]交谈 选择 趁着还硬朗，回去看看吧有后续获得【辛金丸】【乙木丹】，直接吃了【支线】慈母手中线对话[裁缝] 接取任务【支线】无妄蛇灾李村长 选择 义不容辞【支线】樵夫的斧子【支线】村民的困惑对话张樵夫 选择 好，我这就去找铁匠对话后再回复樵夫 选择 重新锻造一番吧，好吧再找铁匠，交谈两次 选择樵夫或许有办法回复樵夫，交谈两次。再去找郎中，去刚才抢木剑的地方。选项任意，下不下猴子都死再回复郎中完成任务。接着前往江宅右侧回复江吟风【主线】解毒之物-高粱酒前往客栈触发剧情，再去村口找卫霍,送礼到20选择切磋。邀请[卫霍]入队。解锁成就：招募卫霍[卫霍]好感+40（60）【支线】卫霍的承诺【支线】山猪为患送礼[卫霍]加好感（＞60），选择 帮他 。切磋[卫霍]【蟠龙棍法秘籍】，菜谱功法全部学习。【主线】最后一样解毒之物回江宅找江吟风。请教卫霍钓鱼、制衣以及 【江湖心法】【镇山心法】精力不够直接在床上休息，升满心法，继续点前三个经脉黄点。到桌边喝药。【存档30】根据获取江吟风教的不同武学到碗子山头领宝箱获不同取功法秘籍【霹雳霸刀】【化功绵掌】【惊雷剑法】只能获得其中一本所以需要读档收集后山山洞前找江吟风，选择 明白了 剑系武器装备纯钢剑 武学笔记吃了，靠近木桩，这场输赢无所谓。升满刚才学会的剑法，进入山洞。右侧宝箱【一转气血丹】，深处还有一个宝箱。吃【一转气血丹】，给卫霍换品级低品质好的装备。第一场战斗：有镇山用镇山，先杀小怪再杀BOSS。第二场战斗：直接往后稍稍让江吟风打就行。【主线】山洞大战战斗胜利后江吟风加入队伍。把江吟风身上除武器外所有装备换下来自己穿上。自身除周年饰品其他都换六品装备。前往洞口，开战选择回合敌人各自为战我们只需逐个击破即可由于没有额外击杀经验所以直接让江吟风打行了，我们在旁边喊加油。战胜后获得 【基础轻功秘籍】、【碧水剑法秘籍】、【五虎断门刀秘籍】、【幻阴掌秘籍】【支线】无妄蛇灾对话村长把任务交了前往后山山洞旁祭拜牢江。【主线】洛村寻人菜谱、武学全部学习，轻功升满，卫霍升级【镇山心法】，卫霍经脉也是先点3个黄点。出村前往洛村（M查看地点名字）【洛村】【支线】养生秘术对话魏老 把任务接了【支线】农忙对话杨婶 收割麦子回复杨婶。【支线】野猪进村对话柳氏柳氏旁边宝箱开了，对话野猪战斗后回复柳氏。【二转气血丹】* 2解锁成就：宝箱达人【支线】百晓生-桃花劫对话百晓生 选择 信他 付钱王大嘴后面的宝箱开了。进客栈对话掌柜，二楼逗猫 解锁成就：大橘为重王石头旁边宝箱开了交易王石头购买 【基础拳掌秘籍】、【基础刀法秘籍】、【基础剑法秘籍】【支线】蜃楼幻境左侧出村 前往平康城，路过驿站顺便开一下（后续可以和其他驿站互相传送）。一周目玩家无法触发蜃楼幻境。【平康城】【支线】千金难求醉仙楼 成生 选择 买客栈里右侧宝箱开了。【支线】同情心出门右下角小雨 选择 帮她先进武馆 右上宝箱开了进入药铺内左侧位置触发钱袋回去找小雨发现不见了，选择 还是算了（有后续）。【支线】化缘对话空闻 选择 给他水囊 赞同他 获得【一转洗髓丹】空闻身后宝箱开了【支线】千金蟾衣对话王员外 选择 听听看【支线】灭门之案-说客对话张捕快 选择 好奇询问 很感兴趣 没问题进当铺 左侧宝箱开了，交谈钱掌柜 选择以理服人（一周目先不去找成生，暂时打不过）交易钱掌柜 购买 龙颈笛、碧玉兰。（后续任务道具）【支线】百晓生-命中贵人对话百晓生 付钱（祸、桃花、贵人都在碗子山，之后无法再算命）王有财旁边宝箱对话铁匠、裁缝学习锻造制衣博坊院子里右侧墙边有隐藏宝箱【一转洗髓丹】前往宋宅对话宋仵作 选择 劝说 继续劝说 极力劝说 我这就去对话铁匠。前往洛村，背包里图纸全部学习了，【二转气血丹、一转洗髓丹】吃了，往右前往碗子山。【碗子山】前往碗子山 立刻迎战，2连战后宇文逸习得 【五灵心经·残】把地牢左侧宝箱【巨木丹】【巨木丹】吃了，【五灵心经·残】拉满（上限3级） 经脉点了，右侧出牢房。打赢红衣少女。【心火丹、一转洗髓丹】红衣少女加入队伍。【主线】狂风寨救人一路向右把资源点都采了，精石 至少采五个。道中资源点左侧隐藏宝箱【一转聚灵丹】右下 头目 房子内侧宝箱山贼头领 右上宝箱到吊桥前触发剧情后往右下地牢走战斗后 牢房右侧宝箱上方过桥剧情战斗后休整一下状态给主角和卫霍换好装备 卫霍心法点满 经脉同主角点法主角吃完丹药，再往右侧深处走触发战斗胜利后右侧宝箱 【惊雷剑法秘籍】此处【临时存档】并读取【存档30】直接顺着主线拿完另外两本。根据获取江吟风教的不同武学到碗子山头领宝箱获不同取功法秘籍【霹雳霸刀】【化功绵掌】【惊雷剑法】（只能获得其中一本所以需要读档收集）图鉴开完后在读取【临时存档】（所谓的临时存档意思是用完之后可以随便删除）【惊雷剑法】直接学点满。把红衣少女装备换下来，前往右下打开牢房红衣少女离队 【玄火刀法秘籍】带孩子们回洛村。【洛村】【主线】武当求助获得坐骑，换回DLC海豹坐骑（加成高），全村好感+20，客栈免费。请教卫霍制衣、钓鱼 江湖枪棍。【支线】重回碗子山对话白锦，战斗后 选择就依此计。【白锦】好感+10（10）切磋 老赵 没药。到碗子山击杀五个山贼，回复白锦 选择 告诉他【白锦】好感+30（40）秘籍全部学习 武学点书籍吃了，轻身术点满。再回到碗子山打boss的位置和白锦一起战斗胜利后获得 【双龙斩秘籍】、【天龙心法秘籍】战后选择 金银（兵刃、珠宝七品装备没用）【白锦】好感+0（40）【平康城】前往平康城找铁匠，回复宋仵作，宋仵作暂时入队。切磋宋仵作【锐金丹】对话李捕快获得【二转洗髓丹】（新版本增加了三色大阵练级点所以不用再带抄书匠了）（抄书匠指的是不上场留在队伍里撰写武学点的队友）对话宋仵作 选择：窒息？ 帮忙找线索找李捕快、陈麟熙、幻紫交谈。（暂时中断，之后再来）【支线】五仙花酿对话幻紫 询问小彤下落 包在我身上前往野猪林。【野猪林】资源采了（地黄、铁木、补骨脂）中间宝箱，右上宝箱，上方宝箱，左下宝箱打狼后获得【一转洗髓丹】、【一转气血丹】、【江湖心法秘籍】。丹药吃了 经脉点了，去左边打野猪王 山贼2连战，出林子往右下莲心湖。【莲心湖】来到莲心湖 剧情战斗后获得 【黑风刀法秘籍】、【中品混元丹】、【中品凝元丹】【李元兴】好感+10（10）资源全采了（玉髓矿可以开出任何矿石），绕着陆龟走左侧钓鱼点开图鉴全钓了【鲢鱼、鲈鱼、黄花鱼、大闸蟹】最下方宝箱【支线】莲心会晤中央触发莲心会晤。解锁成就：莲湖会晤（进武当后无法触发）【冷无情】好感+5（5）【支钱】养生秘术前往洛村，找魏老完成任务获得 【养心秘术秘籍】前往梧桐村，对话村长获得 【镇山心法秘籍】卫霍 力道+5 根骨+15 身法+10 内功+20再邀请卫霍，回复裁缝。前往武当 武当驿站顺便开了。
传功弟子篇【蔓阴林】左侧双生荼蘼触发剧情【莲花掌秘籍】回复玉伯，前往天鸣村。【天鸣村】【主线】筹备寿礼对话村长。【支线】丐帮之法对话刘玄。右下前往紫竹林。【紫竹林】【支线】玉容皓皓竹林右下侧拿到凤冠霞披。【支线】丐帮之法左上山坡 触发剧情。【主线】筹备寿礼刷完10个玄火力士回复 村长【支线】丐帮之法对话刘玄。前往南渝村【南渝村】【支线】玉容皓皓回复玉伯。荀杳杳好感+20（当前30）查看未署名的信。对话玉伯。前往名剑山庄。【名剑山庄】【支线】净衣派对话[欧阳献公]。【存档24】【支线】绝响难求前往莫邪阁 选择 找到琴谱，再来探望欧阳雪。前往姑苏客栈对话[掌柜]。姑苏城东找姑[苏小贩]。前往素节林【素节林】黑蛇王旁边宝箱，瀑布右边密道宝箱【巨木丹】【支线】绝响难求前往右下瀑布【龙泣剑法秘籍】欧阳雪好感+20（80）前往谭城【谭城】斗笠客战斗胜利获得【缠丝擒拿手秘籍】【支线】丐帮之法回复龚进。回复左江龙。【支线】净衣派回复乌天瘤【二转洗髓丹】【支线】拨云见日对话李元兴。前往谭城客栈。前往青萝山。【青萝山】【支线】拨云见日山顶遇到李元兴。对话荀杳杳把信给她荀杳杳好感+10（当前40）对话李元兴 选择 做出决定。回到丐帮【丐帮】【支线】丐帮大战对话[李元兴] 选择 做好准备。战斗后获得【神龙掌法秘籍】、【五罗青烟秘籍】、【擒龙功秘籍】传送到天鸣村。【天鸣村】【支线】丐帮大战先往下走找[荀杳杳][荀杳杳]好感+10（50）对话 [燕未还] 选择 已做好准备，击败黑白无常获得【丧哭棒】* 2[李元兴]好感+10（20）[荀杳杳]好感+0（50）[燕未还]好感+5（30）回丐帮对话[李元兴]【睡梦罗汉棍秘籍】，【青玉令（李元兴专属）】[李元兴]好感+10（当前30）送礼[李元兴]好感度（>=80）邀请[李元兴]切磋[李元兴]【一转气血丹】出谭城【三转洗髓丹】，【骸骨钉秘籍】、【腐尸毒秘籍】前往幽云泽渡口。
精英弟子篇【武当】【主线】武当大比找[清虚]获得【流云剑法秘籍】对话[元启]。回弟子房休息。武当大比获胜后【三转气血丹】、【三转洗髓丹】习得【太极神功】【主线】祭拜故人[元鸣]、[莫弃]、[莫问]加入队伍。切磋[元鸣]【一转洗髓丹】请教[莫问]【真武剑气】现在开始先不做主线，前往天剑宗。【天剑宗】切磋[肖剑声]【剑心诀秘籍】、【乙木丹】前往蔓阴林。【蔓阴林】【支线】飞蝎使中路往下直走触发[褚无救]剧情战斗。第一场优先击杀[褚无救]。【三转洗髓丹】、【辛金丸】第二场还是先解决[褚无救]。【瘟疱掌秘籍】打完返回大地图。[娜乌]好感+20（20）自动传送到北山村。【北山村】对话[顾思归] 选择 学习。【白鹤归】[顾思归]好感+20（20）前往少林寺。【少林寺】对话[涤罪僧]，离开少林。【存档27】【支线】韶华轻掷韶华轻掷分支：救[孔亮]：[孔亮]入队成就，请教【天罡刀法】。不救[孔亮]：等到支线甘泉苦水完成后去神鹰门可以获得【漫天花雨秘籍】救不救不影响后续主线，漫天花雨可以继承。对话[涤罪僧]选择 帮助孔亮，战斗后获得 【血河神鉴秘籍】、【神鹰指法秘籍】、【黄龙刀法秘籍】[涤罪僧]好感+10（10）若不帮助[孔亮]则[冷鹰]好感+10前往谭城。【谭城】切磋[张判龙]【锐金丹】客栈切磋[哑头陀]【参茸养身汤菜谱】【支线】酒鬼乞丐对话[小狗儿]选择 帮助对话[龚进] 【莲花棒法秘籍】晋庸右上角邀请[孔亮]请教[孔亮] 【天罡刀法】前往丐帮。【丐帮】切磋[侯九]【佛跳墙菜谱】前往雷家村。【雷家村】切磋[冷无情]【二转气血丹】【支线】雷家村对话[冷无情]选择 前来相助 略有耳闻。询问所有村民。回报[冷无情]选择 准备好了。【连鼓山】走至中央触发剧情，打完回雷家村找[冷无情]。【雷家村】两场战斗，第一场较难，先杀[覆海蛟]，再打[叶飞] 【天龙斩秘籍】。【桃花林】【支线】反攻前奏对话[冷无情]选择 休整充沛。前往雷家村调查，再回桃花林对话。前往双河村铲除暗探[简希]、[郝任]、[陆虢]。前往连鼓山。【连鼓山】支援[仇无道]、[斩无刑]、[冷无情]【乌角巾精良图纸】【乌角巾】是目前最好的装备前往山顶，战斗后获得【玄鸟剑势秘籍】，【极品混元丹】、【极品烈阳丹】【三转洗髓丹】，返回雷家村。前往莲心湖。【莲心湖】【支线】娄赴召的归宿走至湖中心，战斗后获得【刀山火海势秘籍】、【玄火掌法秘籍】邀请[叶飞]切磋[叶飞]【雁行功秘籍】前往武当。【武当】请教[道玄]【炼丹】，【四象无极功】切磋[道玄]【极品血灵丹】、【九炼熊蛇丸】、【三转洗髓丹】、【三转气血丹】、【极品纯阳丹丹方】切磋[道通]【癸水丹】切磋[道微]【二转洗髓丹】、【没药】送礼[道微]好感度(>=80)后续拿【归元刀法秘籍】前往姑苏。【姑苏城】提升司马铃练度：刀法：恶鬼刀法 | 狂风刀法 | 刀山火海势 | 霹雳霸刀轻功：雁行功 | 轻身术 | 武当轻功心法：叶家心法 | 吐纳法 | 瑜伽经 | 四关十二元经经脉：阴点出神藏>阳点满>任点满【支线】姑苏曼舞送礼[司马铃]好感度(>=80)前往客栈。二连战后获得【太华十二剑】、【追命三仙剑秘籍】[司马铃] 力道+15 根骨+5 身法+10 内力+5[司马铃]习得【白帝剑器·灵】请教[司马铃]【白帝剑器·灵】前往梧桐村。【梧桐村】对话[村长] 询问小彤去向祭拜[江吟风]。【主线】姑苏雨夜前往姑苏城。【姑苏城】前往渡口触发剧情，再前往客栈。雨夜渡口右侧宝箱，武馆宝箱，侯九宝箱。过桥 战斗胜利获得【恶鬼心法秘籍】传送至名剑山庄。【名剑山庄】早晨【主线】品剑大会客房出来右侧假山宝箱，莫邪阁宝箱，东边假山宝箱，[欧阳小武]下方宝箱【流水丹】名剑山庄钓点【鳜鱼 鲤鱼 海参 鲍鱼 冰鲤】请教[欧阳剑英]【锻造】，【飘雪穿云剑】、【鲸息功】、【混元神剑功】切磋[欧阳剑英]【天羽奇剑秘籍】对话 [莫问]、[莫弃]、[元鸣]。三场战斗获胜【武岳枪法秘籍】、【玄焰身法秘籍】傍晚客房出来右侧假山宝箱【名剑心法秘籍】，剑心亭右侧宝箱。前往剑心亭 选择 有点压抑欧阳雪好感+10（10）询问琴曲【五灵心经·残】提升至6级夜晚欧阳雪入队，他的武器【素弦】卸下来开一下图鉴莫邪阁前方石桌宝箱【中品纯阳丹】，客房出来右侧假山宝箱【名剑剑法秘籍】元鸣左侧宝箱前往渡口划船至姑苏。【姑苏城】[花灯小贩]背后宝箱【心火丹】、【上品纯阳丹丹方】说书的右侧宝箱[穷书生]右上栏杆内角隐藏宝箱【辛金丸】对话[花灯小贩] 选择 【华 灯 映 雪】[欧阳雪]好感+10（20）【姑苏灯会】百味馄饨对话[萧大爷]吃馄饨 选择安慰【萧家食录】（不要用掉后续有任务）、【二十四气馄饨】【姑苏灯会】街头卖艺对话[江飞鱼]选择 江飞鱼。【姑苏灯会】茶摊说书可以都听一遍（真假参半）。【姑苏灯会】霓裳羽衣最后对话[穷书生]选择 帮忙[欧阳雪]好感+20（40）传送回名剑山庄。【名剑山庄】回弟子房前遇到[元鸣]，回客房休息。第二天再次前往剑坪，战斗胜利获得【玄焰身法秘籍】【血鹰喙秘籍】【醉八仙棍秘籍】【韦陀掌秘籍】【玄火心法秘籍】【湛卢剑】[瑶姬]好感+20（30）装备湛卢，右下石桌前捡到冷鹰玉佩【丧魂穿心钉秘籍】[冷鹰]好感+10（10）若不救[孔亮]此时为20切磋[欧阳雪]【一转聚灵丹】切磋[瑶姬]【野火燎原势秘籍】切磋[欧阳衡]【龙泣剑法秘籍】【一转气血丹】【阮师刀精良图纸】请教[欧阳衡]【制衣】【锻造】【天外飞仙剑】【天羽飞踪】切磋[清霄]【泰阿剑精良图纸】【三转聚灵丹】切磋[左江龙]【犀皮兽纹甲精良图纸】【二转聚灵丹】切磋[圆觉]【千叶如来手秘籍】【龙爪手秘籍】【三转气血丹】切磋[刘雄]【三转洗髓丹】【承影剑精良图纸】请教[刘雄]【钓鱼】【三才刀法】【破军刀法】【舍身刀】【圆月天刀】【先天一气功】前往泰阿堂，泰阿堂右侧宝箱。主线暂时不做现在，不要对话[莫问]、[欧阳雪]、[瑶姬]三人。带步微月前往洗剑池有额外剧情前往姑苏【姑苏城】【支线】寒泉铸剑对话铁匠刘成 选择 继续询问打造【泰阿剑】 给宇文逸穿。离开姑苏城。【中原大地图】姑苏城外切磋[谢学士]【一转洗髓丹】切磋[莫燕飞]【二转洗髓丹】虎威镖局外切磋[赵虎]【心火丹】切磋[张龙]【心火丹】切磋[童彪]【锐金丹】谭城外切磋[张志豪]【二转洗髓丹】切磋[董老倌]【一转洗髓丹】武当山外切磋[元真]【绵掌秘籍】【一转洗髓丹】少林寺外切磋[慧净]【韦陀掌秘籍】【一转洗髓丹】主角丹药、洗髓丹全吃。现在开始所有获得的洗髓丹给上官虹吃，属性丹药还是主角吃。相关成就 宗师之友：一名队友六脉全满前往少林寺。【少林寺】切磋[圆悟]【金钟罩秘籍】【七杀拳套精良图纸】【三转聚灵丹】【锦斓袈裟】请教[圆悟]【易筋经】宇文逸请教完易筋经 心法换成 四象无极功 | 五灵心经残 | 易筋经 | 混元神剑功切磋[圆苦]【般若金刚掌秘籍】【七星拳套精良图纸】切磋[圆难]【大日如来棍秘籍】【方天画戟精良图纸】【三转洗髓丹】前往武当【武当】切磋[清虚]【太极拳秘籍】【太极剑法秘籍】【玄虚刀法秘籍】【太极神功秘籍】【三转气血丹】请教[清虚]【梯云纵】前往名剑山庄。【名剑山庄】制作【犀皮兽纹甲】给全队穿（上阵成员），回姑苏对话铁匠完成寒泉铸剑。对话[瑶姬] 选择 叫名字[瑶姬]好感+10（40）对话[欧阳雪]【天羽奇剑秘籍】【存档26】莫问相关好感图鉴分支，应该会搭配瑶姬、欧阳雪好感分支一起使用。对话[莫问] 选择 赞同[莫问]好感+10（90）回客房休息。前往姑苏城【姑苏城】【支线】寒泉铸剑对话铁匠完成寒泉铸剑。前往幽云泽渡口。【幽云泽渡口】左上角[霸刀秦烈] 选择 不救（重要选项）神秘少女好感+20（20）到码头传送到北山村，前往樊城客栈。【樊城】【支线】烟霞剑客战斗后 选择 去[霍长青]好感+20（20）前往程家村【恶鬼杖法秘籍】[霍长青]好感+60（80）再回樊城，对话邀请[霍长青]入队【烟霞剑法秘籍】[霍长青]好感+20（100）前往平康城。【平康城】【支线】小倩去[王钧]家 触发小倩 选择 拔刀相助，他们介意你的出身，尽管说吧。对话[王庆]，前往洛村。【洛村】【支线】谋生之道对话[王钧]，[王氏] 选择 帮忙想办法。对话[铁匠]、[郎中]、[杨婶]，回复[王氏]。吃掉桌上的面。带[王钧]去找客栈掌柜，回来对话[王氏]【雪花芙蓉】【参茸二鲜汤菜谱】任务完成后可以找王钧蹭饭，但需要自备食材前往津鲤村。对话[小倩]，前往平康城对话王家护院，再回津鲤村对话[小倩]【流水丹】前往无名岛，触发目前队友所有剧情【无名岛】[宇文逸]、[上官虹]、[霍长青] 剧情：剑法精通+20[顾思归]剧情：武学点+1000前往峋谷关。【峋谷关】切磋[晁鸿熙]【一转洗髓丹】右下宝箱，孙婆婆旁边宝箱。路上行走的卫兵对话（萧家食谱相关任务）【支线】镖局客卿对话[陈镖头]。[陈镖头]下方隐藏宝箱【二转洗髓丹】前往 将军府 选择 夜探将军府。前往客栈休息。峋谷关夜前往将军府左侧房间，选择 突出重围。前往城东寻找[徐海]，选择 上前阻止【极品混元丹】 2、【极品烈阳丹】 2，【破阵枪法秘籍】返回将军府，面见[黄将军]出言安慰。切磋[黄宗政]【三清铃精良图纸】【支线】筹备军资对话[陈主簿] 选择 我来帮忙。切磋[陈主簿]【一转聚灵丹】【中品烈阳丹丹方】出将军府选择 先找瑶姬。[瑶姬]好感+20（60）【支线】赵二磊的家书对话[赵二磊] 选择 我帮你寄。【支线】筹备药材对话[朴世人] 选择 主动帮忙。三清铃可以制作给队友左侧出峋谷关到大地图切磋[守卫]【一转洗髓丹】【极品清灵丹丹方】先不去辽城，前往蜃楼海域。【蜃楼海域】右上角辰砂矿左侧草丛宝箱【壬水丹】，【 陨铁矿】* 2蜃楼海域钓鱼点【刀鱼 银鱼 鲥鱼 紫贝 海蜇 赤鳞鱼】刷[林天寒]【锦镧袈裟精良图纸】【别角晚水、魏紫】（这个看脸吧，这两奇珍先刷到可以在南疆前获得九州决）关外地图刷BOSS、资源点、武学点：蜃楼海域-龙门峡-驼铃丘-子河谷-天山挖陨铁矿【龙门峡】左侧底下宝箱，右侧底下宝箱。右侧出口出去。前往临昌，驿站顺便开了。【驼铃丘】道上宝箱【寒铁箍精良图纸】，玉髓矿全挖了。前往玄火教。【玄火教】右侧柱子宝箱。前往子河谷。【子河谷】门口宝箱【极品混元丹、极品烈阳丹】内部地图右下宝箱【三转洗髓丹】前往天山，豫村驿站开了。【天山】两处陨铁矿挖了。天山钓鱼点【黄鱼 银鱼 冰鲤 龙鱼 雪蛤】前往不风山。【辽城】李宅左侧宝箱，包氏篱笆宝箱。前往城东家中触发剧情，回忆中直接走左上找羊支线不用管。打赢[无名]获得【龙骨】【奔雷掌秘籍】暂时不做主线，因为去了黎城[瑶姬]会离队，准备去找[慕迟]。前往猴儿林左侧山谷，遇到去年花剧情后进入和璞山庄。[慕寒梅]好感+10（30）【和璞山庄】剧情对话后 [慕迟]好感+10（20）战斗结束后校场左上角 对话慕勇 开启三色大阵：简单难度通关奖励：【二转洗髓丹】1000武学点普通难度通关奖励：【三转洗髓丹】2500武学点困难难度通关奖励：【四转洗髓丹】5000武学点在这里刷武学点直到技能全点满。前往山庄左下：去找 琢玉楼掌事 可用10个玉髓矿换七品玉制武器（各武器一把）然后再对话可以制造珠光宝气武器需要【紫水晶、祖母绿、芙蓉石、田黄石、鸡血石各一份】（无人岛资源有收获基本不会缺）解锁各种珠光宝气武器图鉴，装备珠光宝气武器的时候要装备太极神功或青玉劲才能使用。慕严 低级宝石换高级宝石【支线】和璞山庄前往北边正厅赴宴。门口遇到慕迟 切磋切磋[慕迟]【顽石功残章】、【琢玉手残章】、琢玉手残章（可留给队友），顽石功一周目目前打不过可以略过，等之后绮罗筵再来赴宴后前往右侧东厢房休息。【支线】夤夜访客听见声响后前往校场跟[慕迟]打一场。[慕迟]好感+10（30）第二天前往校场对话后进玉阁找[慕迟]，再去左上逊雪阁找[慕寒梅]剧情后传送到右上角沙盘。【支线】沙盘演武对话[吕仙儿]开启沙盘。注意站在森林里+30%闪避。对话[慕艾]开启第二场仙人掌是森林，站在带药字的据点可以回血。对话[慕艾]开启第三场新兵种 骑兵 移动远攻击越高还有直线4格距离的二技能群伤。对面有箭塔可以引出范围来打。打完第三场需要休息一天再来。沙盘庭院右侧对话[慕智] 选择：正是，项羽，张良，周幽王，平原君，伍子胥，周厉王。获得【史记】沙盘庭院外左下角对话[慕信] 选择：正是，道天地将法，取用于敌，不战而屈人之兵，避实击虚，山地应靠近山谷驻扎，通形挂形支形等。获得【孙子兵法】对话[慕仁] 选择：正是，广开言路，勤俭节约，牺牲与忍耐，送子为质，秦国虎视眈眈，诸侯国联合反对。获得【战国策】再对话[慕艾] 选择： 国君支持，合众弱以敌一强，形篇 获得【武穆遗书】东厢房左下有个伙房对话伙房师傅 选择简单用食 可以恢复状态 获得随机buff。前往校场，2场剧情战斗后。左上角请教[慕寒梅]【花谢剑法】切磋[慕寒梅]【清明上河图】、【龙团凤饼】回东厢房休息，听到敲门声前往校场陪慕迟喝酒。[慕迟]好感+10（当前40）[慕迟]走后对话慕寒梅。[慕寒梅]好感+20再回房歇息。几天后，醒来先去右上角沙盘找[吕仙儿] 开启 第四场沙盘。把敌人引到沼泽里堵着打。打赢后获得【雷霆枪法】、【擂山锤秘籍】对话[慕艾] 开启 第五场沙盘。这场可以输，隔天再来就好打了。打完以后前往左上角逊雪阁找[叶云]剧情后，去校场南边找[慕寒梅]。前往平康城。【平康城】到客栈找门口行脚商买3份山药和蚕豆再对话[赛金花]。获得【翡翠豆糕菜谱】[慕寒梅]好感+10离开平康。剧情进墓园剧情。[慕寒梅]好感+20右上角墓碑 莲芯彩蛋离开墓园。【和璞山庄】回到和璞山庄剧情后第二天，出厢房对话右侧[吕仙儿]再去校场对话叶家兄妹。前往演武场，继续沙盘第五场。打完获得【慕家兵略】[慕艾]好感+50（50）切磋[慕艾]【穿云一枪秘籍】[吕仙儿]入队，前往校场找[叶云]。两场战斗后获得【十七路小擒拿秘籍】、【叶家枪法秘籍】、【青玉劲秘籍】，【四转洗髓丹】 * 2、【沥泉神枪精良图纸】[慕迟]好感+20（60）解锁成就：武家传承制作一把沥泉神枪开图鉴，需要【玄阳龙骨】前往玉阁寻找慕迟，对话右侧[扫地老人]。主角全属性+10。前往庄外桃园，遇到[醋小虾]。获得【五宝花蜜酒】先别喝掉后续去东海有大用主角与吕仙儿 气血真气各+600剧情回到校场和[慕迟]战斗胜利后主角习得【流云太极】，获得【青玉手秘籍】[慕迟]好感+20（80） 解锁成就：流云太极第二天校场剧情获得【一心戎衣】吕仙儿全属性+10。吕仙儿获得新形象。解锁成就：巾帼红妆疯乞丐卖给吕仙儿【穆家兵法】前往无名岛。【无名岛】[瑶姬]剧情。<绝不会忘记自己的责任>[吕仙儿]剧情：[吕仙儿]、[宇文逸] 枪棍精通+20前往黎城。【黎城】左上宝箱，客栈宝箱【主线】赶赴黎城[清霄]好感+5前往不风山【不风山】大地图切磋[独孤阳]【极品凝元丹丹方】上路道中宝箱，过桥宝箱【辛金丸】【遗书（任务）】猛虎堂屋内左侧宝箱【赤血珊瑚（任务）】右侧下山房子右侧宝箱，右侧下山山底宝箱。【主线】围攻不风山先走下路遇到逃兵选择 跟上去看看 饶他一命。开启宝箱【二转气血丹】，【玄焰身法秘籍】再回头走上路，山顶清剿天龙帮帮众。树上【梅枝】摘了猛虎堂搜刮一遍 书架、桌子、椅子，靠近字画 选择 插梅花。地道 道中宝箱 6【红烩盘龙菜谱】【二转聚灵丹】【一转洗髓丹】 2【丁火丹】【庚金丸】打败[曹煜天]【八方藏刀势秘籍】第二场坚持5回合就行。传送回黎城。【黎城】前往城门，先去一趟玄火教找瑶姬 选择 隐瞒身份（瑶姬好感+10（当前70）【支线】失而复得对话[成老] 选择 原物奉还【支线】遗书对话[骆娴] 选择 严词拒绝【什锦鹿茸羹菜谱】【主线】战后余波前往客栈休息回客栈休息，有步微月额外剧情传送回武当【武当】【支线】塞外风光白锦好感80自动在武当山门触发前往梧桐村【梧桐村】【支线】赵二磊的家书对话[赵氏]完成任务【一转洗髓丹】【双菌汤菜谱】前往峋谷关。【峋谷关】【支线】塞外风光前往将军府【临时存档】[白锦]2个支线图鉴【绝笔】、【盗取军情】前往 葬龙谷 寻找 [渊国传令兵] 再回将军府。【霹雳雷火弹秘籍】驿站前往 临昌 寻找 [白锦] 再回将军府。【天罗地网钉秘籍】两条路线都走一遍，接2.号路线流程继续游戏。【 临昌】切磋[慕容垂云]【流水丹】，【龙骨】、【没药】，一周目可以请教【化功绵掌】切磋[萧古里]【八宝亮银铲精良图纸】客栈二楼 对话[黄小邪]、[素素]（容易漏掉的NPC图鉴）切磋[黄小邪]【飞天夜叉面具精良图纸】【支线】排查奸细前往将军府找[黄宗政] 选择 义不容辞，寻找 [由归交]、[汪端]，回复[黄宗政]。[汪端]在客栈，[由归交]在有宝箱的楼顶城东对话兰儿有额外剧情。前往桃花林。【桃花林】【支线】再遇无情对话[冷无情]。[司马铃] 力道+15 根骨+5 身法+10 内功+5[上官虹] 力道+5 根骨+5 身法+15 内功+10前往威远镖局，对话[副总镖头]。前往姑苏城。【姑苏城】走到戏台对话[萧大爷] 如实相告【陨铁锭】【二十四气馄饨】【支线】再遇无情客栈对话[冷无情]、[掌柜]【五恒天经秘籍】【兵锋六要秘籍】【素质刀精良图纸】【金缕玉带精良图纸】[冷无情]好感+50（100）[冷无情]入队请教 [冷无情] 【森罗万象】，并装备森罗万象（身法+10，无论是否装备暗器）前往万劫窟。【万劫窟】下路磁石触发剧情[朱九戒]战斗后选择 询问原委 同意。前往深处把[盘山蟒]、[穿山蟒]解决了，再回复朱九戒。先不出万劫窟【存档25】瑶姬相关好感图鉴分支，应该会搭配欧阳雪好感分支一起使用。深处右侧石洞进入。[瑶姬]好感+20（90）石洞左侧宝箱【上品凝元丹丹方】【上品清灵丹丹方】【四转洗髓丹】前往武当，回复[道玄][道玄]好感+5把主角的 太极神功 换成 流云太极，流云太极是太极神功的上位心法。前往名剑山庄【名剑山庄】前往客房休息。【五灵心经·残】 改名 【风云诀·残】[欧阳雪]好感+20（60）升满【风云诀·残】【主线】叛徒打开【清虚的信】前往威远镖局。【威远镖局】选择 好言相劝。前往饿虎坡【饿虎坡】道中触发[元鸣]剧情，回武当。【武当】找[清虚]复命【支线】故人再问一下[清虚] 莫问去哪了回弟子房休息一晚。【门派日常】炼丹术（二）对话[道玄]，直接交药材。【上品清灵丹】【上品清灵丹丹方】道微好感送到80，接道微任务。【门派日常】搜集矿石对话[道微]，直接交乌金矿1个。【武器随便选，不如锻造】[道微]好感+5【门派日常】清剿饿虎坡对话[道微]找[元风]接任务：【门派日常】搜集药材熊胆3个。【上品纯阳丹】* 2【门派日常】垂钓精通七鳃鳗1个【门派日常】收集猴儿酒猴儿酒3个，交给[莫弃]。做完前往 祖师殿，触发祭拜[元鸣]剧情 选择 去。【门派日常】送信少林前往少林【少林寺】【门派日常】送信少林交给少林寺[圆觉]。前往谭城。【谭城】遇到[莫问][莫问]好感+10（100）【支线】伤魂调·序曲前往丐帮总舵【丐帮总舵】【支线】伤魂调·序曲选择 质问乌天瘤。对话[李元兴]一次，再出丐帮不要直接让其安排休息否则坏结局【支线】镇派棒法对话[侯九]。[侯九]好感+10【支线】丐帮之法对话[左江龙]【支线】净衣派对话[乌天瘤] 选择 答应。【支线】玉容皓皓先前往客栈触发[玉伯]任务，【支线】丐帮之法再找[龚进]对话 选择 都清楚了。前往南渝村。【南渝村】【支线】玉容皓皓对话 [玉伯]。前往饿虎坡【饿虎坡】【门派日常】清剿饿虎坡饿虎坡刷完10个贼寇前往武当。【武当】【门派日常】清剿饿虎坡对话[道微]交任务【归元刀法秘籍】【门派日常】送信少林对话[元风]交任务[清虚]好感+10【主线】晋升传功弟子对话[道通]晋升传功弟子。[道通]好感+5[宇文逸]、[莫问] 习得 【四象无极功】找[道妙]交谈 选择 药材【门派日常】惩奸除恶对话[元启]选择 请师兄吩咐，去万劫窟击杀boss震山龙前往蔓阴林。
Luna作为一套比较出名的Lua与C++绑定的框架，以其简洁的代码且贴近面向对象的书写方式，与游戏脚本的需求重合性较高，因此被广泛应用在游戏行业中。然而比较有趣的是，虽然该框架用途广泛，但是很少人知道这套框架的作者，甚至不知道框架的名字。2003年，Lenny Palozzi在Lua社区中发表了名为：《A template class for binding C++ to Lua》的文章。文章中，作者介绍了一种C++与Lua绑定的新框架，并将这个框架命名为Luna。文中提出了几个思路：将构造函数以className为key存到全局表中，当用户调用className()时，也就会显式调用C++的构造函数在构造函数中，为用户生成一个userdata，将类对象指针存入其中，同时将类的函数指针以函数名为key也存入其中，返回该userdata给用户在lua中使用，作为Lua曾的类对象。当用户以面向对象的方式，对该类对象执行.操作时，会触发C++中的函数，C++会从table取出类指针以及函数指针，以正确的执行类成员函数。这些也就是Luna的核心思维，后来很多人尝试对Luna框架进行了优化（包括之后会提到的Lunar），但是最终都没有完全脱离这套模式。《A template class for binding C++ to Lua》一文虽然已经写得足够详细，但是涉及的Lua版本过低，且其代码泛化不足。Lua5作为用途最广泛的版本，大部分人自然更需要以Lua5版本的Luna。5年后的2008年，《Simpler Cpp Binding》这篇文章应运而生。算法本文主要以《Simpler Cpp Binding》作为分析的参考。定义定义类名类名之后需要注册到全局表中，让Lua使用者能调用到构造函数static const char className[]; const char Account::className[] = "Account";定义需要导出的函数为了安全性与实用性，并不需要把所有C++函数都导出给Lua使用者使用#define method(class, name) {#name, &class::name} Luna<Account>::RegType Account::methods[] = { method(Account, deposit), method(Account, withdraw), method(Account, balance), {0,0} };RegType是一个结构体数组，以{函数名，函数指针}的形式保存所有需要导出的函数。注册面向对象的类，实际上是一组数据（变量）和定义在这种数据上的操作方法（函数）组成的。Luna也是参照这一理念涉及的，它将函数放在全局对象上，而数据放在一个table中交给用户使用。注册Lua类对象（数据）luaL_newmetatable(L, T::className); lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methods); lua_settable(L, metatable); // hide metatable from Lua getmetatable() lua_pushliteral(L, "__index"); lua_pushvalue(L, methods); lua_settable(L, metatable); lua_pushliteral(L, "__tostring"); lua_pushcfunction(L, tostring_T); lua_settable(L, metatable); lua_pushliteral(L, "__gc"); lua_pushcfunction(L, gc_T); lua_settable(L, metatable);metatable重定向了table中一些重要的元操作，将会作为Lua类对象（实际上是一个userdata）的主元表，其具体结构如下：$\text{metatable} \begin{cases} \text{__metatable = method} \\ \text{__index = method} \\ \text{__tostring = tostring_T} \\ \text{__gc = gc_T} \end{cases}$注册全局对象（函数）lua_newtable(L); int methods = lua_gettop(L); lua_pushstring(L, T::className); lua_pushvalue(L, methods); lua_settable(L, LUA_GLOBALSINDEX); lua_newtable(L); // mt for method table int mt = lua_gettop(L); lua_pushliteral(L, "__call"); lua_pushcfunction(L, new_T); lua_pushliteral(L, "new"); lua_pushvalue(L, -2); // dup new_T function lua_settable(L, methods); // add new_T to method table lua_settable(L, mt); // mt.__call = new_T lua_setmetatable(L, methods); // fill method table with methods from class T for (RegType *l = T::methods; l->name; l++) { /* edited by Snaily: shouldn't it be const RegType *l ... ? */ lua_pushstring(L, l->name); lua_pushlightuserdata(L, (void*)l); lua_pushcclosure(L, thunk, 1); lua_settable(L, methods); }method以函数名为key, 函数指针的闭包为value，包含了所有需要用到的函数信息，挂在全局表的className下。其具体结构如下：$\text{method} \begin{cases} \text{new = new_T} \\ \text{__metatable = mt } \lbrace \text{__call = new_T} \\ ... \\ \text{function} \\ ... \end{cases}$调用创建Lua类对象创建对象通过.new()进行，实际是上从全局表中取到中存的method这个table，并执行method中的new函数，这个函数映射到了C++中的new_T。static int new_T(lua_State *L) { lua_remove(L, 1); // use classname:new(), instead of classname.new() T *obj = new T(L); // call constructor for T objects userdataType *ud = static_cast<userdataType*>(lua_newuserdata(L, sizeof(userdataType))); ud->pT = obj; // store pointer to object in userdata luaL_getmetatable(L, T::className); // lookup metatable in Lua registry lua_setmetatable(L, -2); return 1; // userdata containing pointer to T object }new_T函数去创建一个新的C++对象，并把指针存入一个userdata中，将metatable作为该userdata的主元表返回给调用者，该userdata即为Lua层面的类对象。调用Lua类方法调用类方法通过.进行，该操作会触发原表中对应的闭包，该闭包会以函数指针为upvalue执行thunk函数。static int thunk(lua_State *L) { // stack has userdata, followed by method args T *obj = check(L, 1); // get 'self', or if you prefer, 'this' lua_remove(L, 1); // remove self so member function args start at index 1 // get member function from upvalue RegType *l = static_cast<RegType*>(lua_touserdata(L, lua_upvalueindex(1))); return (obj->*(l->mfunc))(L); // call member function }thunk函数中，C++取得userdata中保存的类指针，同时从upvalue上拿到函数指针，也就可以执行该C++类对象中的对应成员函数。优化双向调用Luna一个很明显的缺陷就是其调用单向性，只支持从Lua调用C++接口，无法从C++层主动触发Lua函数。2016年出现了一篇名为《Cpp Binding With Lunar》的文章，文中在Luna的基础上加入了从C++调用Lua的流程，并将自己这套优化后的Luna框架叫做Lunar。为了达到这个目的，作者加了两个接口Push和Call，从今天的眼光看来，这两个函数实现都比较简单。Push接口，允许C++直接把已经存在的类对象推给Lua，无需再通过Lua的new接口创建。static int push(lua_State *L, T *obj, bool gc=false) { if (!obj) { lua_pushnil(L); return 0; } luaL_getmetatable(L, T::className); // lookup metatable in Lua registry if (lua_isnil(L, -1)) luaL_error(L, "%s missing metatable", T::className); int mt = lua_gettop(L); subtable(L, mt, "userdata", "v"); userdataType *ud = static_cast<userdataType*>(pushuserdata(L, obj, sizeof(userdataType))); if (ud) { ud->pT = obj; // store pointer to object in userdata lua_pushvalue(L, mt); lua_setmetatable(L, -2); if (gc == false) { lua_checkstack(L, 3); subtable(L, mt, "do not trash", "k"); lua_pushvalue(L, -2); lua_pushboolean(L, 1); lua_settable(L, -3); lua_pop(L, 1); } } lua_replace(L, mt); lua_settop(L, mt); return mt; // index of userdata containing pointer to T object }除开一些参数合法性的检查，函数主要作用就是把传进来的obj指针生成一个带主元表的userdata放到栈上。Call接口，允许C++直接调用堆栈上userdata中注册的函数（既可以是C++导出的，也可以是Lua脚本里自行声明的） static int call(lua_State *L, const char *method, int nargs=0, int nresults=LUA_MULTRET, int errfunc=0) { int base = lua_gettop(L) - nargs; // userdata index if (!luaL_checkudata(L, base, T::className)) { lua_settop(L, base-1); // drop userdata and args lua_pushfstring(L, "not a valid %s userdata", T::className); return -1; } lua_pushstring(L, method); // method name lua_gettable(L, base); // get method from userdata if (lua_isnil(L, -1)) { // no method? lua_settop(L, base-1); // drop userdata and args lua_pushfstring(L, "%s missing method '%s'", T::className, method); return -1; } lua_insert(L, base); // put method under userdata, args int status = lua_pcall(L, 1+nargs, nresults, errfunc); // call method if (status) { const char *msg = lua_tostring(L, -1); if (msg == NULL) msg = "(error with no message)"; lua_pushfstring(L, "%s:%s status = %d\n%s", T::className, method, status, msg); lua_remove(L, base); // remove old message return -1; } return lua_gettop(L) - base + 1; // number of results }同样的，忽略掉参数合法性检查后，该函数主要作用无非是通过lua_pcall调用userdata上的特定函数。当然，事物的出现并被大家所接受总自然有其合理性，否则以新增2函数=新增1r来算，创造一个lunarrrrrrrr框架对大家来说并不是什么难事（笑
目标文件代码文件经过编译器编译、链接器链接后，将会生成目标文件。与认知不同的是，即使是用于执行的可执行目标文件，都并非单纯由指令序列组成。所有的目标必须包含一系列供操作系统加载器使用的结构信息，这个结构信息由操作系统进行约定，因此不同操作系统的结构信息各异。比较主流的目标文件格式如下表：操作系统文件格式MAGICLinuxELFELFWindowsPEMZMacMach-O0xfeedface/0xfeedfacfELFExecutable and linking formatELF是Linux系统下一种常见的目标文件，有三种主要类型可执行文件用于提供程序的进程映像，加载到内存执行可重定位文件用于与其他目标文件链接，一起创建可执行文件或共享目标文件共享目标文件用于与其他可重定位目标文件和共享文件链接成其他目标文件，或与可执行文件组合创建进程映像。在看文件结构的详细定义之前，我们先动手编译一个简单的目标文件，亲自察看其文件内容并配合结构定义能更方便的理解。为了更直观的理解，程序显然越简单越好。比如下面这个就很不错：int main() {}gcc编译得到.out文件后，我们使用readelf来实际查看其文件内容gcc -march=i686 -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc a.cpp readelf -a a.outELF Header在文件开头描述了整个文件的组织，结构如下struct elfhdr { uint magic; // must equal ELF_MAGIC uchar elf[12]; ushort type; ushort machine; uint version; uint entry; // 程序入口的虚拟地址 uint phoff; // program header 表的位置偏移 uint shoff; uint flags; ushort ehsize; ushort phentsize; ushort phnum; //program header表中的入口数目 ushort shentsize; ushort shnum; ushort shstrndx; };而在我们的目标文件中，ELF Header长这个样子ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x1070 Start of program headers: 52 (bytes into file) Start of section headers: 16020 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 12 Size of section headers: 40 (bytes) Number of section headers: 33 Section header string table index: 32可以发现，ELF Header实际上是该文件的大体信息，包括Magic，文件类型，入口地址等等。如果是可执行文件，紧接着Header将会是Program Header数组，结构如下struct proghdr { uint type; // 段类型 uint offset; // 段相对文件头的偏移值 uint va; // 段的第一个字节将被放到内存中的虚拟地址 uint pa; uint filesz; uint memsz; // 段在内存映像中占用的字节数 uint flags; uint align; };每个程序结构描述了一个段或者系统准备执行所必需的其它信息。每个段包含一个或者多个 “节区”（section） ，也就是“段内容”（Segment Contents）我们的目标文件Program Headers如下Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x00000034 0x00000034 0x00180 0x00180 R 0x4 INTERP 0x0001b4 0x000001b4 0x000001b4 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x00000000 0x00000000 0x003d4 0x003d4 R 0x1000 LOAD 0x001000 0x00001000 0x00001000 0x00254 0x00254 R E 0x1000 LOAD 0x002000 0x00002000 0x00002000 0x00164 0x00164 R 0x1000 LOAD 0x002edc 0x00003edc 0x00003edc 0x0012c 0x00130 RW 0x1000 DYNAMIC 0x002ee4 0x00003ee4 0x00003ee4 0x000f8 0x000f8 RW 0x4 NOTE 0x0001c8 0x000001c8 0x000001c8 0x00060 0x00060 R 0x4 GNU_PROPERTY 0x0001ec 0x000001ec 0x000001ec 0x0001c 0x0001c R 0x4 GNU_EH_FRAME 0x002008 0x00002008 0x00002008 0x0004c 0x0004c R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 GNU_RELRO 0x002edc 0x00003edc 0x00003edc 0x00124 0x00124 R 0x1PEPortable application首先要说的是，PE这个“可移植可执行”中的“可移植”，并不是现在大家所理解的跨平台性。在我看来，这个可移植应该理解为从低版本操作系统到高版本操作系统的可移植性（比如为16位DOS编译的目标文件，依旧能在32位Windows上执行）。也正是因为这个“可移植”，Windows的目标文件比Linux复杂了不少。MS-DOS年代的目标文件由马克·茨柏克沃斯基（Mark Zbikowski）主持开发，因此他使用了自己名字缩写作为MS-DOS目标文件的MAGIC。到了Windows年代，考虑到DOS-MZ文件格式的兼容性，MZ这个MAGIC需予以保留。而作为新的操作系统，自然要支持包含新特性的PE目标文件格式。微软使用了一个取巧的方法，它在MZ文件头之后，又增加了一个可选的COFF文件头，当文件含有这个文件头时，意味该目标文件为PE格式，操作系统将会使用PE的方式去解析。与ELF一样，PE也有三种主要的目标文件可执行文件(.exe)静态链接库(.lib)动态链接库(.dll)与ELF一样，我们在看PE文件结构的详细定义之前，我们先动手编译一个只含有空白main函数的目标文件。windows主要使用cl指令来编译，而使用dumpbin来实际查看其文件内容gcc -march=i686 -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc a.cpp readelf -a a.outMZ Header如下struct mzhdr { char signature[2] = "MZ"; short lastsize; short nblocks; short nreloc; short hdrsize; short minalloc; short maxalloc; void *ss; void *sp; short checksum; void *ip; void *cs; short relocpos; short noverlay; short reserved1[4]; short oem_id; short oem_info; short reserved2[10]; long e_lfanew; }在我们的目标文件中，通过dumpbin能看到以下信息FILE HEADER VALUES 14C machine (x86) 4 number of sections 5F93F9FA time date stamp Sat Oct 24 17:55:06 2020 0 file pointer to symbol table 0 number of symbols E0 size of optional header 102 characteristics Executable 32 bit word machine
调用约定调用约定(Calling Conventions)指的是为了函数调用能顺利进行，被调用者与调用者达成的一系列包括参数传递与名字修饰在内的约定。总的来说有下面几种：参数传递约定极微参数或复杂参数独立部分的分配顺序参数是如何被传递的（放置在堆栈上，或是寄存器中，亦或两者混合）被调用者应保存调用者的哪个寄存器调用函数时如何为任务准备堆栈，以及任务完成如何恢复名字修饰约定x86架构微处理器有多种调用约定(Calling Conventions)，不同语言，甚至不同的编译器都有不同的编译选项以便开发者选择。出于方便，本文仅从GCC的角度进行分析。参数传递约定为了能更直观的观察各个不同调用约定产生的汇编代码的区别，需要关闭编译优化，同时也不需要去搜索标准系统目录，我们选择如下编译选项：gcc -march=i686 -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinccdelcallC声明调用cdel(C declaration)即C声明，是C语言实际上的默认标准。主要约定如下：函数实参从右至左依次压栈函数结果保存在EAX中（16位及8位程序分别为AX， AL）调用结束后，栈数据不会清理，调用者需按自己需要进行处理。所编译的代码:int __attribute__((__cdelcall__)) cdelfunc(int x, int y, int z) { return x + y + z; } int main() { cdelfunc(1, 2, 3); }经过objdump -S可以比较得到如下的汇编代码（无关代码已经忽略）000011ad <_Z8cdelfunciii>: int __attribute__((__cdelcall__)) cdelfunc(int x, int y, int z) { return x + y + z; } 11b1: 55 push %ebp 11b2: 89 e5 mov %esp,%ebp 11b4: 8b 55 08 mov 0x8(%ebp),%edx 11b7: 8b 45 0c mov 0xc(%ebp),%eax 11ba: 01 c2 add %eax,%edx 11bc: 8b 45 10 mov 0x10(%ebp),%eax 11bf: 01 d0 add %edx,%eax 11c1: 5d pop %ebp 11c2: c3 ret 000011c3 : 11c7: 55 push %ebp 11c8: 89 e5 mov %esp,%ebp 11ca: 83 ec 0c sub $0xc,%esp 11cd: c7 44 24 08 03 00 00 movl $0x3,0x8(%esp) 11d5: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp) 11dd: c7 04 24 01 00 00 00 movl $0x1,(%esp) 11e4: e8 c4 ff ff ff call 11ad <_Z8cdelfunciii> 11e9: b8 00 00 00 00 mov $0x0,%eax该调用过程分为以下三步：先看调用者main函数，由于3个int长度为12字节，调用者在栈上开辟了12字节的空间（ESP - 0xC），然后依次存入3, 2, 1。接着调用_Z7stdfunciii。被调用者_Z7stdfunciii将调用者的EBP压栈，然后从栈上0x8, 0xC, 0x10分别取出参数，运算后存入EAX。第三步为隐藏操作，由于函数使用指令ret返回，CPU不会对栈进行任何处理，栈上依然保留着之前的参数，此时应由调用者进行手动处理。stdcall标准调用函数实参从右至左依次压栈函数结果保存在EAX中（16位及8位程序分别为AX， AL）调用结束后，栈数据将会由CPU进行清理。类似的，我们使用__stdcall__替换__cdelcall__，将会得到如下的汇编代码：000011ad <_Z7stdfunciii>: int __attribute__((__stdcall__)) stdfunc(int x, int y, int z) { return x + y + z; } 11ad: f3 0f 1e fb endbr32 11b1: 55 push %ebp 11b2: 89 e5 mov %esp,%ebp 11b4: 8b 55 08 mov 0x8(%ebp),%edx 11b7: 8b 45 0c mov 0xc(%ebp),%eax 11ba: 01 c2 add %eax,%edx 11bc: 8b 45 10 mov 0x10(%ebp),%eax 11bf: 01 d0 add %edx,%eax 11c1: 5d pop %ebp 11c2: c2 0c 00 ret $0xc 000011c5 : 11c5: f3 0f 1e fb endbr32 11c9: 55 push %ebp 11ca: 89 e5 mov %esp,%ebp 11cc: 83 ec 0c sub $0xc,%esp 11cf: c7 44 24 08 03 00 00 movl $0x3,0x8(%esp) 11d7: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp) 11df: c7 04 24 01 00 00 00 movl $0x1,(%esp) 11e6: e8 c2 ff ff ff call 11ad <_Z7stdfunciii> 11eb: 83 ec 0c sub $0xc,%esp 11ee: b8 00 00 00 00 mov $0x0,%eax可以发现，stdcall与cdelcall最大的不同的是函数使用ret 0xC返回。这条指令的含义是：通知CPU清理栈上的0xC数据（也就是CPU会自行帮你进行ESP + 0xC的操作）fastcall快速调用函数前两个参数通过寄存器ECX与EDX传递，其他参数从左至右通过栈传递函数结果保存在EAX中（16位及8位程序分别为AX， AL）调用结束后，栈数据将会由CPU进行清理。000011ad <_Z8fastfunciii>: int __attribute__((__fastcall__)) fastfunc(int x, int y, int z) { return x + y + z; } 11b1: 55 push %ebp 11b2: 89 e5 mov %esp,%ebp 11b4: 83 ec 08 sub $0x8,%esp 11b7: 89 4d fc mov %ecx,-0x4(%ebp) 11ba: 89 55 f8 mov %edx,-0x8(%ebp) 11bd: 8b 55 fc mov -0x4(%ebp),%edx 11c0: 8b 45 f8 mov -0x8(%ebp),%eax 11c3: 01 c2 add %eax,%edx 11c5: 8b 45 08 mov 0x8(%ebp),%eax 11c8: 01 d0 add %edx,%eax 11ca: c9 leave 11cb: c2 04 00 ret $0x4 000011ce : 11d2: 55 push %ebp 11d3: 89 e5 mov %esp,%ebp 11d5: 83 ec 04 sub $0x4,%esp 11d8: c7 04 24 03 00 00 00 movl $0x3,(%esp) 11df: ba 02 00 00 00 mov $0x2,%edx 11e4: b9 01 00 00 00 mov $0x1,%ecx 11e9: e8 bf ff ff ff call 11ad <_Z8fastfunciii> 11ee: 83 ec 04 sub $0x4,%esp 11f1: b8 00 00 00 00 mov $0x0,%eaxfastcall与cdelcall有了比较大的区别，该调用使用了ECX与EDX两个寄存器进行前两个参数的传递，只将第三个参数压栈。寄存器操作相比栈操作（栈的本质是主存）存在指数级的速度差别。在只有1-2个参数，且函数内部指令较少时，该调用能带来较为可观的速度优化，也算对得起fast这个名字。
Atmosphère是Nintendo Switch一款很有意思的定制固件，法语意为大气层。由Exosphère（散逸层）、Stratosphère（平流层）等等几个不同的组件组成，每个组件对应着大气层的组成部分，负责执行Nintendo Switch不同的系统功能。Atmosphère的组件包括：Fusée 一个自定义引导程序。Exosphère（散逸层） 功能齐全的定制安全监视器。Stratosphère（平流层） 一套定制系统模块。Thermosphère（热层） 一种基于管理程序的emuNAND实现。该组件尚未实装。Troposphère（对流层） Horizon OS的应用程序级补丁。该组件尚未实装。FuseeFusée（不要与FuséeGelée混淆）是启动Atmosphère并取代Nintendo的Package1loader/bootloader所需的自定义引导程序。它目前使用Tegra X1 RCM漏洞来运行。Fusée分为两个独立的部分：fusée-primary和fusée-secondary。这是由于RCM漏洞限制了发送到设备上的payload的文件大小。截至2018年6月，新发售的Switch系统使用了ipatched启动芯片而让Fusée（或任何需要FuséeGelée漏洞的有效载荷）无法工作。所有ipatched系统共享HAC-S-JXE-C3产品代码。虽然Fusée无法在这些ipatched设备上运行，但他们仍然使用固件4.1.0，这很容易受到即将推出的DéjaVu软件攻击。但请注意，如果您在其中任何一个ipatched设备上更新了高于4.1.0的固件，那么安装Atmosphère或运行任何自制程序的可能性几乎不存在。此外，据悉名为“Mariko”的Switch的硬件版本正在开发中。虽然目前仍未在商店中看到过这样的单元，但预计任天堂会默默地推出它们。Mariko单元很可能会修复目前用于访问CFW的FuséeGelée的启动芯片漏洞，并可能拥有自己的专有引导程序。Fusse-PrimaryFusée-primary是从外部设备发送到交换机的payload文件（fusee-primary.bin）。一旦发送，fusée-primary会在Switch的SD卡加载fusée-secondary之前进行初步准备。可以通过Switch SD卡上的BCT.ini文件配置Fusée-primary。Fusse-SecondaryFusée-secondary是一个payload文件，保留在Switch的SD卡（fusee-secondary.bin）的根目录下。它在fusée-primary完成后自动启动，并负责为将来运行的环境（如自制菜单）准备Switch的硬件。 Fusée-secondary还负责验证和启动Exosphère。Fusée-secondary包含各种.kip模块。这些模块可以修改操作系统中的现有功能，也可以添加新功能。Fusée还能够链接其他payload，例如Linux。ExosphèreExosphère是Arm的TrustZone（TZ）的重新实现，也称为安全监视器（Secure_Monitor.bin）。它具有Switch处理器上可用的最高权限模式，并且可以访问控制台上的所有内容。Exosphère可能会在Jamais Vu和DéjaVu即将推出的Switch软件漏洞中扮演重要角色，它允许用户在Fusée-Gélee补丁（ipatched）Switch控制台上启动Atmosphère，并且还可以直接启动来自Switch本身的CFW，而不使用任何类型的外部设备（例如计算机或RCM夹具），只要它们位于足够低的系统固件上。TrustZone/Secure MonitorTrustZone负责Switch上的所有加密操作。它运行方式背后的想法是所有密钥都保留在TrustZone中，而用户空间只能获得它们的“句柄”。这将确保keydata永远不会泄漏并保持安全。它还有一些更多的职责，例如电源管理，提供随机数源，以及提供对存储在熔丝中的各种信息的访问。扩展Exosphère目前只包含一个扩展，一个SMC允许自制软件查找当前运行的Atmosphère版本，以便找出哪些拓展是允许使用的。StratosphèreStratosphère允许自定义Horizon OS和Switch内核。它包括扩展内核并提供新功能的自定义系统模块。它还包括重新实现的加载器系统模块来Hook重要的系统操作。Stratosphère包含的系统模块包括：boot 该模块启动系统并初始化硬件。creport 重新实现任天堂的崩溃报告系统。将所有错误日志转储到SD卡，而不是将它们保存到NAND并将它们发送到Nintendo。fs_mitm 此模块可以记录，拒绝，延迟，替换和重定向对文件系统发出的任何请求。loader 允许修改未存储在内核中的二进制代码。pm 重新实现任天堂的流程管理器。sm 重新实现任天堂的服务管理器。ThermosphèreThermosphère是基于emuNAND实现的管理程序。 emuNAND是Switch内部存储器（sysNAND）上固件的副本，通常安装在外部SD卡上。emuNAND完全独立于sysNAND运行。 这允许人们安全地制作或测试各种修改和自制软件，而不需要通过测试emuNAND上的内容来恢复其NAND备份，并在完成后切换回sysNAND。对于过去的任天堂系统（如3DS），甚至可以使用emuNAND将系统更新到最新固件，同时保持sysNAND的较低版本，然而由于任天堂在Swtich使用efuse技术来进行主系统的更新，这在Switch上可能更难做到。Thermosphère目前计划包含在Atmosphère的1.0版本中。TroposphèreTroposphère包含对操作系统的各种应用程序级修改，例如直接从homemenu启动homebrew或执行cheat/gameshark代码，类似于Luma3DS。Troposphère尚未在Atmosphère实装。
我们提供了从辐射度方程进行即时渲染的基本步骤。直接在纹理化场景描述上操作，非常高效且简单的算法可以生成照片级真实图像，而无需任何有限元核心或对基础积分方程进行离散化处理。通过利用图形硬件，利用用于解决全局光照问题的准随机游走确定性技术以及抖动低差异采样的新方法获得仅需数秒钟的渲染速率。分类和学科描述：I.3.3 [计算机图形学]：图片/图像生成 - 抗锯齿| 位图和帧缓冲操作| 显示算法| 查看算法;I.3.7 [计算机图形学]：三维图形和现实 - 动画|颜色，阴影，阴影和纹理|光能传递关键词：辐射方程，辐射率，阴影，蒙特卡罗积分，准蒙特卡罗积分，准随机游走，抖动低差异采样，硬件，累积缓冲区，实时渲染算法，照片写实。1 介绍提供一个写实场景描述，这个场景由辐射度积分方程[Kaj86]渲染而产生写实图像。在漫反射的前提下，得到弗雷德霍姆积分方程近似解的最常用方法是辐射度算法。 在经典算法[CW93]中，辐射度积分方程的核心是通过投影到某些有限基上，得到为场景元素数量的二次方的形状因子矩阵。 对于它的稀疏表示，已经引入了具有阶梯型基函数的阶梯型方法。 尽管如此，这些Galerkin算法需要存储积分方程的内核和离散解。除了用于高度复杂精确网格生成的阴影表示[LTG92]外，这样的投影还引入了离散化误差。在蒙特卡罗仿真领域中，不需要内核离散化的算法也是可行的，使用仅需要把解投影到有限基上的随机积分方案。与随机渐进法相似，在[Kerl96b]中，渐进性粒子仿真方案基于导入低差异采样。这个渐进性方案在略微优越的速率下平滑收敛，而且与随机算法相比没有变化。在双向路径追踪算法[LW93, VG94]中，尽管辐射度方程解的离散情况已经被避免，但其渲染时间远没有实时性。另外一个方面，图形学硬件能够实时通过扩展光源[HA 90, Hei 91, SKvW 92]照亮和遮蔽贴图场景。在我们新方法中，我们将光线确定性粒子仿真（即准随机游走原理）的优点与可用硬件功能结合，始终由辐射度方程进行渲染，既不使用核心也不使用积分方程解的中间解的投影，导致过程非常快速，健壮和直接。在介绍之后，论文的第二节中，我们简要的阐述全局光照问题的数学模型。第三节解释新的渲染过程及的准蒙特卡罗积分、准随机游走原理的基本技术。第四节，我们支持基本算法的一些扩展，包括基于抖动低差异采样的抗锯齿，镜面反射以及实时应用程序的改进，第五节讨论了该算法。最后一节得出结论，并指出未来研究的方向。2 全局光照我们的眼睛能感受到来自每单位角每单位面积功率的辐射。在真空中，辐射度$L$满足辐射方程[Kaj86]：$$L(y,\vec{w}_{r})=L_{e}(y,\vec{w}_{r})+\int_{\Omega}f_{r}(\vec{w}_{i},y, \vec{w}_{r})L(h(y,\vec{w}_{i}),-\vec{w}_{i})cos\theta_{i}dw_{i}\qquad$$其中$\Omega$是单位半球上垂直于$y$点表面的所有方向$\vec{w}=(\Omega,\phi)$的集合。$S$是作为边界表示而建模的场景表面。$h$函数返回当从$y$点出发向$\vec{\omega}_{i}$方向发射一条射线击中的第一个点。术语$cos\theta_{i}$表示将入射光线垂直投射到表面，$\theta_{i}$是$y$表面法线与入射光线$\vec{\omega}_{i}$之间的夹角。点$y$朝$\vec{w}_{r}$的辐射度$L$就是源辐射度$L_{e}$与反射辐射度之和。我们可以使用符号简写为：$$ L=L_{e}+T_{f_{r}}L $$双向反射分布函数$f_{r}$考虑了颜色和光泽度等表面特性。在一般设置中，这个函数取决于辐射的入射方向$\vec{w_{i}}$和反射方向$\vec{w_{r}}$以及基准点$y$。在辐射度设置中，$f_{r}=f_{d}(y):=\frac{\rho_{d}(y)}{\pi}$仅限于漫反射，然后辐射也会变得各向同性：$$L(y)=L_{\omega}(y)+\frac{\rho_{d}(y)}{\pi}\int_{\Omega}L(h(y,\vec{\omega}_{i})) cos\theta_{i}d\omega_{i}\qquad $$$\rho_{d}(y)$是漫反射贴图的反射性。给定一个四元组，全局光照问题由以下计算函数得到：$$<L,\Psi>:=\int_{S}\int_{\Omega}L(y,\vec{\omega})\Psi(y,\vec{\omega})cos\theta d\omega dy$$与辐射度设置相关，也与全辐射等式相关。检测器功能有多种选择，例如，在经典或分层辐射度方法[CW93]中使用的有限向量空间的正交基向量之和。我们不离散化积分方程的解，而是必须将其渲染到一个单独的通道中，我们直接选择下面这个方程来检测穿过焦点针孔摄像机看到的图像矩阵的像素点$P_{mn}$的平均辐射。 $$\Psi_{mn}(y,\vec{\omega}):=\frac{\delta(\vec{\omega}-\vec{\omega}_{y_{f}})}{cos\theta}\frac{1}{|P_{mn}|}\chi_{P_{mn}}(h(y,\vec{\omega}))$$ $y_{f}\cdot\vec{\omega}_{y_{f}}:=P-y_{f}$是在$P_{mn}$穿过$y_{f}$情况下点$P$的方向。$\chi_{P_{mn}}$是像素对应的特征函数，$\delta$则是克罗内克函数。
1 游戏CG技术的挑战写实的CG影像表现对于增强模拟现实世界的游戏环境中的沉浸感是必不可少的。特别是在高品质游戏制作中，改进图形表现使之成为成为大吸引力点的机会很多，如何做出让玩家第一眼看到就惊讶无比的影像已经成为了一个重要的技术课题。图1 Agni's Philosophy例如，SQUARE ENIX在2012年举办的E3这个世界上最大的游戏展上，公开的《Agni's Philosophy》实时CG技术演示。如图1的截图所示，实时计算影像已经接近电影质量，各公司和各研究机构正在进行各种研究和开发，以进一步提高性能。在游戏中开发CG技术的困难在于，它必须在每个游戏场景中实现健壮的计算，同时确保有限的计算资源之间的响应。与电影等影视产品不同的是，在游戏中，不仅仅是角色操作和相机操作，包括故事的速度和顺序之类的掌控权通常都留在玩家手中。出于这个原因，无论玩家视角如何，无论玩家进行了什么操作，都必须要能立即显示高品质的图像。此外，游戏制作所需的金钱和时间成本随着每一代硬件产品而呈指数级增长，尤其是处理60fps全高清视频的高品质游戏时， 为了避免在屏幕上出现锯齿，必须要生成高精度的多边形模型，纹理，动画等，这是造成开发周期延长和成本增加的原因之一。减少数据制作所需成本是各公司的共同课题， 给予依赖于设计经验的传统工作流程以技术支持，或是能够全自动生成一些数据的系统也已经被开发出来。本文介绍了几种应用于游戏的CG技术。首先，我们描述被称为实时全局光照（Global Illumination）的渲染技术。接下来，我将从物理模拟开始到统计分析方法的引入为止，总结CG动画技术的最新发展趋势。最后，我们介绍一种称为程序技术（Procedural Technologies），可动态生成游戏实时处理的数据的最新技术，并总结今后的展望。2 写实渲染的挑战2.1 图形硬件的应用在当前的游戏图像表示中，通过利用称为GPU（图形处理单元）专用于3D图形处理的并行计算单元已经开发了用于实时可编程地渲染CG场景的技术。在传统的CG系统 ，CG是利用硬件固有机能计算出来的，但是在GPU中，开发人员可以自由地编程控制绘图功能。尤其3D对象的质感表现依靠着GPU的登场得到了飞速发展。例如，为了再现人体皮肤的纹理，也在使用诸如在皮肤内模拟漫反射光的技术。2.2 基于即时辐射度技术（Instant Radiosity）的实时全局光照在此之前的技术中，确定对象颜色的时候，只处理从光源直接入射的光线。即不考虑来自其它物体的反射光和物体内部的透射光的影响。而用于模拟这些对象之间的光的相互反射和传播的技术被称为全局光照（GI）。为了实现精确的GI，需要模拟光传播的各种路径，其计算量通常很大。例如，以追踪方法和光子映射方法为代表的典型算法，为了得到物理上精确的结果，渲染一张静态画需要几秒到几个小时的计算时间。因此，GI限于应用于诸如电影等视频作品。然而，在近年来的高品质游戏中，GI和类似技术的引入变得不可或缺。出于这个原因，适合于游戏机的有限计算资源，特别是GPU计算体系结构的近似算法的正在持续不断的开发。图3 使用立即辐射度的实时全局照明在这种情况下，即时辐射度技术作为一种可以在游戏中得到实际应用的技术而受到关注。在即时辐射度技术中，如图2所示，在场景中放置一个接近物体表面反射间接光的颜色和强度的虚拟点光源，并且在渲染计算时，除了实际光源以外，还需要来自所有虚拟点光源的直接入射光进行对象的颜色计算。例如，通过在红色物体附近放置发射红光的虚拟点光源，近似模拟来自对象的反射光。一旦确定了虚拟点光源的亮度，颜色和排列，剩下的渲染过程就可以在一般的GPU计算管线上执行，因此它与现有的硬件兼容。而且，通过调整虚拟点光源的数量，可以容易地权衡计算速度和质量进行优化。图3显示了基于立即辐射度技术在GPU中实装的实时GI渲染结果，这表明即使使用现有的硬件配置，也可以获得足够高质量的影像。如上所述，即时辐射度技术具有优良的特性，因此预计可应用于次时代游戏。3 动画技术的发展伴随着纹理表现对GPU的使用和GI技术的发展，背景影像和场景纹理变得难以与现实区分。但是，如果角色像机器人或玩偶一样移动，整个影像的质量反而会使游戏的世界观崩塌。因此近年来，游戏场景的构成对象，特别是模仿人类模型角色的动画控制技术成了瞩目的焦点。3.1 使用数据交换和转换的经典技术在游戏中的传统动画技术中，通过在短时间切换数据进行回放，生成对应玩家操作的一系列动画。在许多高品质游戏中，会通过动作捕捉设备进行三维测量，以创建高度逼真的动画数据。然而，在简单地再现记录数据的技术中，可以在游戏中呈现的动画变化是有限的。在游戏中，多次重复显示相同的动作，重复播放相同的动画，会降低玩家的兴致。此外，影像要展示足够的说服力，显示与地形和环境相适应的动画是不可或缺的。简而言之，我们需要一种技术，它能高速生成与游戏进行时对应而且变化丰富的动画。3.2 物理仿真的应用物理仿真技术正在被广泛使用于具有表现力的动画计算技术中。例如，基于物理定律来模拟包括毛发和衣服等物件、水和烟等流体、物体破坏等行为在内的对象，可以在不预先准备动画数据的情况下再现真实对象的行为。除了提高视频质量外，物理仿真还有诸多与游戏设计直接相关的作品发布，展示了其广泛的适用性。另一方面，由设计师指导物理仿真往往是一项非常困难的任务，例如，当制作长发在风中卷起的动画时，需要考虑如何指导诸如控制头发剪影形状等细节来反映角色的感觉和场景的意图。虽然为了在物理仿真中获得所需的结果，不可能改变头发的运动方程，但是许多参数，例如头发的材料和风的强度等，必须以反复试验的方式进行调整。而且，利用当前的技术，几乎不可能模拟角色的活跃行为。例如，角色丧失心智倒下的样子确实可以根据诸如重力的物理定律来计算得到，但即使在最单纯的行走运动中，也无法建立能够完全描述主动人体运动的数学模型。此外，如果将性别，个性，情感等不确定因素包含进去，则人体运动仿真将会是一个完全未知的领域。针对这些问题，引入人体运动的测量数据以便提高物理仿真质量，以及通过先进的优化，使设计师得以进行仿真设计的技术都在研究中。这其中许多仍处于学术研究的阶段，但如果实现更强大和更有效的算法，它将可以很好地应用于游戏中。3.3 应用统计处理的动画合成随着动作捕捉技术的普及，可以轻松测量大量的三维人体运动数据。通过对大量运动数据进行统计分析，实时生成自然的动画的技术得到了广泛的发展。例如，应用贝叶斯统计量在保持动画的统计性质的同时进行数据压缩，高速搜索相似动作自动生成满足任意约束条件数据等等，这些方法已经得到了广泛的应用。特别地，作为近年来游戏产品中实用化程度不断提高的技术，运用散射数据插值方法的动画生成技术不断得到发展。在该技术中，通过使用表示运动特性的少量控制参数，达到计算多个动画数据加权插值的目的。例如，对以不同的速度和行进方向行走动画进行插值时，不必直接操纵加权百分比，而是在满足指定速度和指定方向的前提下进行最优化统计，来间接控制动画。图4 使用散射数据插值生成非平坦地形行走动画在图4的示例中，使用在不同高度的台阶上的行走动画简单数据，基于在角色的行进路线上检测到的地形来应用散射数据插值，可以做到实时计算任意非平坦地形上的行走动画。4 对程序技术的期望为了减少用人工制作数据的成本，统称为程序技术的技术群被给予了很高的期待。它指的是基于一些程序计算算法，动态计算游戏场景构成数据的一般方法。例如，被称为程序噪音的技术，是通过操作二维傅立叶级数参数，生成各种类噪声纹理图像。此外，傅立叶级数在合成周期性物体动画合成和效果音合成等领域得到了广泛利用。另外，植物的枝条分叉形态已知可以通过被称为L-系统的简单语法规则来记述，该技术已被应用于动态生成森林和草原场景。这样，经典的程序技术方法，通过调整相对简单的数学模型参数，获可以得生成无穷变化对象的方法。近年来，已经开发了一种从对象数据上获取特有背景知识和少许简单数据，就能够在保持计算自由度的同时进行更高质量数据合成的方法。举一个形状建模的例子，最新的GPU都配备了被称为镶嵌单元的功能，他可以程序化地操控多边形的位置和数量，而这些多边形构成了3D对象。通过使用镶嵌单元，设计者不需要事先制作所有对象的形状数据，根据给定的算法动态计算，通过操作少许参数，就可以生成出丰富的变化。如图5所示怪兽角色，设计者在制作过程中事先合成少许几乎没有波动的、低分辨率的模型，在游戏实时运行时增删多边形数量的同时，通过程序将详细的凹凸形状添加到物体表面。此外，仅在角色放缩的时候对多边形进行细致分割等，可以实时优化计算量与质量之间的权衡。目前的技术，很多的详细的形状信息仍然由高级设计师手动设定，但我们认为它会向前发展，变成可以基于任何数学模型和规则的自动生成机制。5 技术元素和游戏引擎的进步如以上所述，游戏CG的技术要素组成是非常复杂的，每个技术都需要相关的深度专业知识。事实上，在海外的游戏制作现场，活跃着很多获得计算机科学博士学位的计算机人才，技术的精锐化变得越来越受欢迎。在另一方面，一般的制作者学习技术变得越来越困难，即便对于与游戏的基本乐趣无关的部分也是如此，被迫付出巨大成本的情况时有发生。基于这些情况，人们开发出了许多称为游戏中间件和游戏引擎的制作平台。游戏引擎是一个集成开发环境，可以轻松处理复杂的游戏技术，通过充分利用它们，制作人可以专注于游戏设计和故事等内容制作。6 将来的展望直到目前为，大多数游戏CG技术都被几年前的离线技术所主导，它们通过硬件的进化和高速化近似手法实现实时计算。因此，通过观看当前的CG视频作品，将能够在一定程度上预测未来游戏CG的姿态。另一方面，游戏是一个直到最后都由玩家操作进行干预的交互式应用程序，即使在游戏CG中，玩家的操作体验也是很重要的。例如，考虑角色全身浸在水中，衣服被浸湿而变得难以移动的游戏设计。这个时候，如果能适当的表现出湿润的头发和衣服的质感，那么游戏设计将给与人很强的说服力。换句话说，游戏CG是支持游戏设计的一个要素，也被认为是今后制作者扩展自由度，进一步激发新的游戏设计的发展方向。其中，特别是以程序技术为代表的实时数据生成技术和物理仿真技术，可以成为游戏空间和玩家达到高度交互的基础，有待今后的进一步研究和开发。
[译]QSplat:大型网格的多分辨率点渲染系统
3D扫描技术的进步使数亿多边形网格的实际创建得以实现。用于显示、简化渐进式网格传输的传统算法对于这种尺寸的数据集而言是不切实际的。我们定义了一个用于表示和逐步显示这些网格的系统，这些网格将基于边界球体的多分辨率层次结构与基于点的渲染系统结合在一起。专门使用一个数据结构用于视锥截取，背面剔除，细节层次选择和渲染。这种表示方法非常紧凑，可以快速计算，适用于大型数据集。我们是为了用于大型3D数字化项目而编写的，它可以快速启动，保持用户可设置的交互式帧速率，而不管对象的复杂程度或相机位置如何，在运动过程中产生合理的图像质量，并在空闲时逐渐细化为高的最终图像质量。我们已经在包含数亿样本的扫描模型上演示了该系统。分类和学科描述：I.3.3 [计算机图形]：图片/图像生成 - 显示算法;I.3.5 [计算机图形]：计算几何和对象建模 - 曲线，曲面，实体和对象表示;I.3.6 [计算机图形]：方法和技术 - 图形数据结构和数据类型。关键词：渲染系统，空间数据结构，细节层次算法，压缩算法1 简介计算机图形学最近的一个重大趋势是在渲染过程中向着使用实时对象采样表现方式转变。这种趋势的一个例子是越来越多地使用三维扫描系统，它可以生成物理对象三维采样模型。然而，3D扫描仪的一个问题是如何处理他们生产的大量数据。在过去几年中，3D扫描系统的硬件和软件组件的改进已经将实际可获得的扫描网格大小增加到数亿个样本。当前工作站不能实时显示这种尺寸的网格，传统网格简化和逐步显示算法的运行时间和空间需求使得这些方法对于包含超过几百万个样本的扫描网格来说不切实际。而且，许多这样的技术着重于优化单个边和顶点的布局，每个顶点将会消耗大量精力。然而，扫描的数据通常具有大量的顶点，并且由于噪声的原因，它们的位置往往不够精确。这揭示存在一种替代方法，这种方法将个别点视为相对不重要，这样对原始网格而言实际上降低了消耗。最近采用这种范式的研究包括Krishnamurthy和Levoy的样条拟合系统[Krishnamurthy 96]，Curless和Levoy[的距离图像合并系统Curless 96]，以及基于八叉树粒子的Yemez和Schmitt渲染系统[Yemez 99]。这些算法不会精确处理范围数据，实际上也不会保留原始网格的任何样本的3D位置。遵循这种低基本成本算法的趋势，我们开发了一种用于大型网格交互式显示的新算法。这个被称为QSplat的算法是在一个大型3D数字化项目[Levoy 00]的过程中设计的。由于它使用基于遍历边界球体层次结构的简单渲染算法，因此适合浏览此项目中生成的模型，该模型包含1亿至10亿个样本。此外，QSplat不保持输入网格的连通性（在扫描固有数据时仅能用于解决深度不连续性而没有其他意义），而是依赖于基于点的表示和图示渲染(splat rendering)。因此，我们的系统比类似的基于多边形的系统具有更低的预处理和渲染成本。在本文中，我们将介绍QSplat数据结构和渲染算法，并讨论一些权衡和设计决策，以使其适用于大型网格。我们描述系统的渲染性能，并讨论它的预处理成本。最后，我们考虑它与以前用于显示大型网格的算法的关系，并描述用于渲染其他类型大型几何数据集的一些未来扩展。2 QSplat数据结构和算法QSplat使用层次结构的边界球体[Rubin 80，Arvo 89]进行可见性剔除，细节层次控制和渲染。树的每个节点都包含球体中心和半径，法线，普通锥体的宽度[Shirman 93]以及可选的颜色。可以从多边形，体素或点云中生成这样的边界球体层次结构，但对于我们的应用程序，我们只需要一种用于从三角形网格生成层次结构的算法。层次结构被进行预处理，然后写入磁盘。2.1 渲染算法一旦构建了层次结构，将使用以下算法进行显示：TraverseHierarchy(node) { if (node not visible) skip this branch of the tree else if (node is a leaf node) draw a splat else if (benefit of recursing further is too low) draw a splat else for each child in children(node) TraverseHierarchy(child) }我们现在详细研究这个基本算法的几个阶段。可见性剔除: 当我们递归边界球体层次时，我们剔除不可见的节点。通过测试每个球体相对视锥体的位面来进行视锥剔除。如果球体位于外部，则它和其子树将被丢弃并且不会被进一步处理。如果球体完全位于平截头体的内部，不会尝试对该节点的子节点进行进一步的视锥体剔除。我们还将在渲染过程中使用存储在每个节点的法线和法线锥来进行执行背面剔除。如果锥面完全离开观察者，则丢弃该节点及其子树。我们还将检测锥体完全指向观察者的情况，并将其子树标记为不适合进行背面剔除。确定何时进行递归: QSplat使用启发式算法，递归次数取决于屏幕上的投影大小。也就是说，如果投影到观察面上的球体的面积超过阈值，则对节点进行细分。递归的中值可以调整帧与帧之间的间隔来保持用户选择的帧速率。我们目前使用一个简单的反馈方案，通过前一帧中从实际到预期的渲染时间的比率来调整阈值区域。Funkhouser和Séquin已经展示了一种预测性的LOD控制算法[Funkhouser 93]，可以缩短渲染时间的帧间变化。但是，我们没有实现这一点。我们还没有导入任何平滑过渡算法用于做过模型细节层次改变，例如Hoppe渐进网格系统[Hoppe 98]中的地形。鉴于我们为应用程序中经典快速变化和提炼选择了最适度变化方案，我们尚未发现光滑视觉效果的缺失;但是，其他应用程序或许会从平滑过渡中受益。尽管屏幕空间区域是LOD控制最流行的指标，其他启发式算法[Duchaineau 97，Hoppe 97]已经用于确定递归的深度。在我们系统的框架内，可以在高曲率区域（使用标准锥体宽度）或在屏幕的中央“中心凹”区域中结合围绕边（使用每个节点的法线）进一步制定递归的规则（只使用投影位置）。上述帧率控制的实现将会在模型的交互操作期间使用。一旦用户停止移动鼠标，我们相继用较小的阈值重绘场景，直到达到一个像素的大小为止。 图1显示了QSplat在几个细化层次上呈现的示例场景。绘制Splats: 一旦我们已经到达叶节点或者决定停止递归，我们绘制一个代表当前球体的图示[Westover 89]。图示的大小基于当前球体的投影直径，其颜色是根据基于当前每球法线和颜色的光照计算获得的。使用启用Z缓冲来绘制Splats以解决遮挡问题。我们在3.3节讨论每个图示的形状。2.2 预处理算法我们的预处理算法从待编码模型的三角形网格的表现开始。尽管可以直接从点云建立QSplat层次结构，但从网格开始，可以轻松计算每个节点的法线。如果我们没有网格，我们将不得不通过将平面拟合到每个点周围小邻域中的顶点来计算法线。从网格开始，还可以将球体大小分配给输入顶点（它们成为我们的边界球体层次结构中的叶节点），以便在渲染过程中不留下任何空洞。为了保证这一点，必须选择尺寸，使得如果两个顶点通过原始网格的边连接，那么放置在这些顶点处的球体的大小足以相交。我们当前的算法使得球体的大小等于接触该顶点的所有三角形的边界球体的最大尺寸。这是一种保守的方法 - 它可能会导致球体太大，但保证不留下任何空隙。一旦我们分配了叶球大小，我们使用以下算法来构建树的其余部分：BuildTree(vertices[begin..end]) { if (begin == end) return Sphere(vertices[begin]) else midpoint = PartitionAlongLongestAxis(vertices[begin..end]) leftsubtree = BuildTree(vertices[begin..midpoint]) rightsubtree = BuildTree(vertices[midpoint+1..end]) return BoundingSphere(leftsubtree, rightsubtree) }该算法沿着其边界框的最长轴分割顶点集合，递归计算两个子树并找到两个子球体的边界球来构建树。随着树的建立，内部节点上的每个顶点属性（例如法线和颜色）被预设为子树中这些属性的平均值。当递归到达单个顶点时，我们只需创建一个球体，其中心是顶点的位置。由于树的总大小取决于每个节点的分支因子，因此我们组合树中的节点将平均分支因子增加到大约4。这减少了内部节点的数量，从而减少了树的存储需求。如3.1节所述，预处理的最后一步是量化每个节点的所有属性。3 设计决策和权衡现在让我们考虑一下在进行QSplat时做出的一些决定，这些决定将使算法适用于大型扫描数据集的可视化应用。我们将描述如何权衡量化文件布局，图示形状以及那些受到快速渲染和紧凑表现额外影响的选择。3.1 节点布局和量化图2c中显示了边界层次结构中每个节点的布局。节点包含球体相对于其父体的位置和大小，法线，法线锥体的宽度，可选颜色以及用于表示树结构的几个比特位。我们在3.2节讨论树的结构和文件内节点的布局。位置和半径:每个球体在边界球体层级中的位置和半径以其父节点为参考系进行编码（使用相对值）。为了节省空间，这些量被量化为13个值。也就是说，一个球体的半径可以在其父部的半径的1/13至13/13范围内，并且球体的中心相对于其父体的中心的偏移量（在X，Y和Z）是父球面的直径的1/ 13的倍数。量化从上到下进行，子球体的位置和大小相对于其父体的量化位置进行编码; 因此，量化误差不会沿着网格传播。为了保证量化过程不会引入任何空洞，量化半径总是四舍五入到最接近的可表示值，以确保量化球体完全包围真实球体。请注意，并非所有的$13^{4}$的可能的组合(x, y, z)中心偏移和半径比都是有效的，因为许多结果的子球体并不被父球体包住。实际上，只有7621个可能的组合是有效的，这意味着我们可以仅使用13位（使用查找表）对量化的位置和半径进行编码。对于半径为1的父球体，这种编码方案组成的子球体中,（x，y和z）会有0.04平均量化误差，半径会有0.15的平均误差。半径误差之所以大于位置误差，是因为半径首先根据球体位置的量化误差来增加（以确保量化球体包围真实球体），而且总是向上取表示值。当然我们可以通过不要求量化的球体完全包围原始图像来获得较低的半径量化误差。但是，这样做会导致量化后球体不再相交的可能性。这可能会在我们的渲染中产生空洞。通过增量编码来表示诸如球体位置等几何量的想法，实质上扩展了层次结构中各个层次之间的数量位，这意味着与传统的网格压缩方法不同，它依赖于沿网格边缘的某些路径编码时的顶点位置[Taubin 98]。实际上，这种“分层三角形编码”更接近用于Eck等人的多分辨率分析[Eck 95]中的几何形状的小波表示。我们每个节点13比特位的空间需求比state-of-the-art传统压缩方法更有竞争力。依靠顶点位置的初始量化优势，它们平均每个节点有9-15比特。然而，这并不是一个完全有效的比较，因为传统的几何压缩方法也表示网格连通性（我们不需要表示），并且我们每个节点的13位还包含球体半径。渲染过程中，每个节点的位置和半径都可以实时解码。因此，我们的数据结构不仅在磁盘上紧凑，而且在渲染过程中需要的内存少于在渲染前必须解压缩数据的方法。法线: 每个节点的法线量化存储为14位。可表示法线在立方体6个面的每个面上，各对应5252网格上的点，这样可以更均匀的弯曲向样品空间。在渲染过程中使用查找表来解码可表示法线。在实践中只使用5252*6 =16224种不同的法线（导致大约0.01弧度的平均量化误差）在漫反射组成中不产生可见的伪影，但是在低曲率的宽广区域内的镜面反射周围可见一些带状伪影。通过移动到每一条相对于父球体进行增量编码的法线上，可以消除这些伪影，并且实现更好的压缩。当然，这会增加算法内部循环的计算复杂度，这是一种时间-空间的权衡。与节点位置范围不同，法线空间是有界的，因此固定的量化表就足以编码任意场景的正则表达式。因此，此时我们选择了对法线使用固定量化，这只需要在运行时查找单个表。随着处理器速度的增加，我们预计增量量化方案将变得更具吸引力。颜色: 当前存储的颜色量化为5-6-5共16位。正常情况下，颜色使用增量编码会节省空间，但在运行时会更加昂贵。法线锥体: 经过一些实验后，我们决定将每个节点的法向锥体的宽度量化为2比特位。四个表示的值对应于锥体，其半角具有正弦1/16，4/16，9/16，和16/16。在典型的数据集上，使用这种量化的正常圆锥体进行背面剔除时丢弃的节点中，超过90％是使用精确的正常圆锥体宽度也会被剔除的节点。请注意，我们在表示法线锥体宽度时总是保守的，所以我们从未丢弃应该显示的几何体。与法线和颜色一样，标准锥体宽度可以相对于父节点处的宽度来表示，但这会降低渲染速度。3.2 文件布局和指针边界球体层次的节点以宽度优先的顺序排列（内存和磁盘）。这样做的主要结果是文件的第一部分包含低分辨率的整个网格。因此，我们只需要读取文件的第一部分便足以使用低分辨率来可视化模型; 随着从磁盘读取文件的更多信息，我们将能看到更多细节。我们目前使用操作系统提供的内存映射作为工作集管理的基础，因此当用户查看时，模型的给定部分的高分辨率数据将从磁盘读取。这种渐进式加载对于大型模型的可用性而言有非常重要的意义，因为从磁盘加载整个数据集的时间可能需要几分钟。不过由于数据是根据需要加载的，当用户首次放大模型的某个区域时，渲染性能会降低 – 帧速率控制方法基于反馈导致帧速率出现问题。然而，接触模型相同区域的后续帧会全速渲染。[Funkhouser 92，Funkhouser 96，Aliaga 99]提出了一种推测预取方法来减少这种变动，但是我们目前没有实现这一点。已经提出了几种无指针方案用于树编码，包括线性八叉树和基于完全树的方法[Samet 90]。但是，这些数据结构对我们的应用来说不合适。线性八叉树和相关想法需要遍历整个树来恢复其结构，这在我们的系统中是不切实际的。基于完整树的数据结构可以用于部分遍历，但是因为我们用来生成树的算法是基于轴对齐的二等分，所以我们不能保证结果树会完整和平衡。此外，修改预处理算法生成完整的树并不是必需的，因为在每个子树中放入相同数量的顶点可能会使分裂平面偏离中心。尽管我们不能为我们的树使用无指针编码，但我们至少应该尽量减少所需指针的数量。考虑到我们按照广度优先顺序存储树，树中的每个兄弟组（即，单个父球体的子节点）都有一个指针指向这些节点的子节点就足够了。而且，如果这些球体都没有子节点（即它们都是叶节点），那么该指针就不是必需的。使用这种方案，大约总存储成本的8％到10％用于指针，我们认为这些指针足够小，以至于我们没有寻求更复杂的方法来进一步降低指针成本。为了能够遍历树，我们在每个节点上存储两位编码节点的子节点数的位（0,2,3或4个子节点 - 不允许有单个子节点），同时使用一个比特位来指示该节点的左右子节点是否是叶节点。现在可以将树的总存储需求计算为树中的节点数乘以每个节点的成本，再加上由于指针引起的开销。对于平均分支因子为3.5的树，节点总数将是叶节点数量的1.4倍，从而使整个树的净存储需求大约为9字节*叶节点数，如果不存储颜色则为6字节。3.3 图示形状用于表示渲染点样本的内核选择可能会对最终图像的质量产生重大影响。最简单，最快的选项是非抗锯齿OpenGL点，它被视为一个正方形。第二种选择是一个不透明的圆，它可以作为一组小三角形呈现，或者作为在大多数OpenGL实现中成本较低，作为单个纹理映射的多边形。另一种选择模糊点，拥有以高斯或某种近似值进行径向衰弱的透明通道。我们使用的特定近似值是在图示的标准半径中不透明度下降到1/2的样条函数。后两个选项绘制速度较慢，因为它们需要向图形管道发送更多数据。此外，由于混合和Z缓冲之间的相互作用，绘制高斯图示需要特别注意绘制图示的顺序。Levoy和Whited在纯软件的渲染器[Levoy 85]中讨论了这个问题；他们提出了一种基于桶的方法，以确保遮挡和混合均正确发生。在OpenGL中，我们可以使用多通道渲染来实现正确的行为。在第一通道中，通过$z_{0}值$来描述与观察者相距的偏离深度，我们只渲染深度缓冲区。对于第二通道，我们关闭深度偏移并将其叠加渲染到颜色缓冲区中，并且不进行深度更新。这将具有在表面深度范围$z_{0}内将所有图示混合在一起的效果，同时保持正确的遮挡。图3比较了图示内核的这三种选择。由于每个内核的每个图示绘图时间对于每个内核都不相同，因此我们在不同的图示大小和不变的运行时间下进行比较。关于图示形状另外一个需要决定的选项，图示总为圆形（或OpenGL点的平方）或是总为椭圆形。在后一种情况下，每个节点处的法线用于确定椭圆的偏心率和方向。当法线指向观察者时，图示将是圆形的。否则，每个椭圆的短轴将沿着法线的投影指向观察平面，并且短轴与长轴的比率将等于n v，其中n是图示法线，v是指向观察者的向量。与圆形图示相比，这提高了轮廓边缘的质量，减少了噪音和增厚。我们在图4中比较了圆形和椭圆形图示的使用。因为我们构造了我们的边界球体层次结构，使得沿着连续表面放置的球体永远不会留下空洞，所以我们可以保证正方形和圆形内核总是会可以无空洞再构造。我们对高斯内核的近似也保证在起始于连续曲面的区域产生完全不透明度。当我们移动到椭圆内核时，我们不能再保证无空洞的再构造，因为法线不需要沿着表面连续。实际上，当使用椭圆形内核时，我们偶尔会看到空洞，特别是在轮廓边缘附近。我们发现，限制椭圆的最大缩短率（例如，将主轴与次轴的最大比率限制为10）实际上填充了所有这些空洞。3.4 基于点的系统的结果点渲染对于具有统一尺寸几何细节的物体，以及在不需要以比样本间距更高的分辨率观察模型的应用中，对于具有均匀尺寸几何细节的物体最为有效。如果模型具有大的，平坦的或巧妙的曲面，那么多边形模型可以更紧凑和更快地绘制。同样，如果需要放大以使样本间距比像素大小更大，多边形可提供更高的视觉质量，特别是在尖锐的边缘和角落附近。图5显示了基于点和多边形的渲染之间的比较。QSplat的开发旨在将扫描模型可视化，其中包含大量精细细节，并且接近扫描分辨率。我们使用体积范围图像处理（VRIP）系统[Curless 96]将原始扫描合并到我们的最终模型中，并使用行进立方体算法[Cline 88]提取多边形网格。由于后者生成的样本具有均匀的空间，所以点渲染非常适合我们的应用领域。对于具有较大平滑区域的场景，我们预计相对于基于多边形的系统，QSplat效果会较差。但是，如果大型多边形被切块，那么所得模型的视觉质量仍然很好，就像在REYES架构中一样[Cook 87]。对于包含高频细节和大平坦区域的应用，混合点/多边形方案可能是合适的选择。4 性能如第3节所述，交互性的目标决定了我们系统的许多设计决策。除此之外，我们还通过多种方式优化了实施，以增加可视化模型的大小。4.1 渲染性能我们系统中的大部分渲染时间都花费在遍历层次结构的内部循环中，计算每个节点的位置和半径，执行可见性剔除，并决定是绘制点还是进一步递归。这个内部循环被调整来消除昂贵的操作，特别是在树的较低级别时。例如，我们不在树的低层执行精确的透视分割，当节点的屏幕空间大小达到几个像素时切换到近似值。因此，一旦从磁盘读取数据，平均而言，我们的算法可以在SGI Onyx2上每秒渲染1.5至2.5百万点。确切的速率取决于缓存效果（例如，我们发现当工作在L2高速缓存环境下，速度会有提升）以及树中哪些级别的数据被剔除。我们的显示速率可以与Hoppe报道的每秒48万个多边形（在相同的硬件上）进行比较，因为他为ROAM系统实现了渐进网格[Hoppe 98]或每秒18万个多边形[Duchaineau 97]。对于我们的应用，我们通常使用5-10赫兹的帧速率，这意味着我们在交互式渲染过程中每帧画出200到30万点。请注意，与上述两个系统不同，QSplat没有明确使用帧到帧的一致性，例如缓存可能可见的初始化列表。图6总结了QSplat的渲染性能。我们算法的简单性使其非常适合在低端机器上实施。作为一个极端的例子，我们在没有3D图形硬件（366 MHz Intel Pentium II处理器，128 MB内存）的笔记本电脑上实现了QSplat。由于渲染是用软件来执行的，系统是有限制的。对于500x500的典型窗口大小和5 Hz的帧速率，实现可以每秒钟运行250到400千点，每秒填充速率为4000万像素，并且通常每帧可以绘制50到70个splats 。在这个情况下，这个实现仍然很好用。尽管目前大多数桌面系统都具有3D图形硬件，但对于便携式和手持式系统以及数字电视机顶盒等应用来说，情况并非如此。4.2 预处理性能虽然预处理时间并不像渲染时间那么重要，但对于非常大的网格的实际可视化仍然很重要。Hoppe报告10个小时作为20万个顶点渐进网格的预处理时间[Hoppe 97]。Luebke和Erikson报告121秒作为实施分层动态简化的281,000个版本的预处理时间[Luebke 97]。相比之下，我们对20万顶点的预处理时间在5秒以内（在同一硬件上）。图6给出了本文数字中所用模型的预处理时间和空间需求的一些统计数据。我们可以比较预处理时间的另一类算法是用于网格简化和抽取的算法。 尽管这些算法与QSplat的目标不同，但它们通常也用于生成多分辨率表示或简化网格以进行显示。 Lindstrom和Turk发表了几个最近的网格简化方法的比较[Lindstrom 98]。 他们报告30秒到45分钟之间的时间，以简化35000个顶点的兔子网格。 文件没有考虑的一种方法是基于体素的简化Rossignac和Borrel [Rossignac 93]，它在与Lindstrom和Turk使用的硬件相同的硬件上花费了一秒钟时间。 我们对这个网格的预处理时间是0.6秒。 因此，我们的算法比大多数当前网格抽取算法快得多，并且与Rossignac和Borrel方法相比具有竞争性。5 前期工作以前用于表示和显示大型模型的方法可以分为点渲染，可见性剔除，细节层级控制和几何压缩。5.1 点渲染传统上计算机图形系统使用三角形作为渲染原语。为了减少包含大量几何图形场景的三角形的设置和光栅化成本，已经提出了许多更简单的基元。使用点作为连续曲面的显示基元由Levoy和Whitted [Levoy 85]引入，最近又由Grossman和Dally [Grossman 98]重新研究。点渲染已被纳入到商业产品中 - 例如，Animatek鱼子酱系统在视频游戏[Animatek]中将点渲染用于动画角色。粒子也被用于更特殊的环境中，如渲染火焰，烟雾和树木[Csuri 79，Reeves 83，Max 95]。数据结构来实现空间空间区域的压缩，Laur和Hanrahan已经研究了使用八分音符表示的音量的等级划分[Laur 91]。 尽管splatting最适合在投影体素大小在像素大小的情况下使用，但也已经检验过在其他体系中的有效性。 Cline等人提出的微分立方体算法适用于体素大于像素的情况。对于小于像素的体素，Swan等人[Cline 88]已经提出了正确生成反锯齿结果的算法[Swan 97]。5.2 可见性剔除大量计算机图形系统中出现了诸如QSplat所使用的视锥和背面剔除算法。基于数据结构（如oc树）的层次平截体剔除一直是大多数渲染大型场景系统的标准功能[Samet 90]。基元的背面剔除通常在硬件中实现，Kumar和Manocha提出了基于法线圆锥的分层背面剔除算法[Kumar 96]。另一类可见性剔除算法包括用于遮挡剔除的方法。Greene等人描述一种通用算法，用于丢弃被更紧密的几何体使用分层Z缓冲区阻塞的基元[Greene 93]。如果场景高度结构化，也可以使用其他更专用的遮挡算法。例如，建筑飞行系统通常使用单元和门户的概念来剔除整个不可见的房间[Teller 91]。 QSplat目前不执行任何种类的遮挡剔除，它将为查看我们正在考虑的扫描模型提供最小的益处。 然而，遮挡剔除对于更复杂深度的场景是一个有用的补充。5.3 细节控制的级别在低倍率下渲染大型数据集通常会导致原始像素小于输出设备像素。为了尽量减少这些情况下的渲染时间，最好切换到更低分辨率的数据集，并使用更接近输出显示分辨率的基元。在LOD算法中，可以将那些以离散细节层次存储整个对象的算法与执行更细粒度LOD控制的方法区分开来。后一类中的算法可以连续控制图元的数量，最大限度地减少“弹出”伪影，并且通常会改变整个场景中的细节水平，以补偿透视投影的变化放大率。多分辨率分析将一个对象表示为“基本网格”，并将一系列校正值存储为小波系数[Eck 95]。Certain等人已经实现了基于多分辨率网格的实时查看器，该网格可以选择任意数量的要使用的小波系数，因此可以绘制任意数量的多边形网格[Certain 96]。他们的观察对象还包括诸如逐行传输和几何和颜色的小波系数分离集等功能。渐进网格使用基础网格和一系列顶点拆分操作[Hoppe 96]。渐进网格已被纳入实时查看器中，该查看器对数百万个多边形场景的实时飞行进行视点相关改进[Hoppe 97，Hoppe 98]。观察者不仅可以选择任意数量的多边形进行绘制，还可以将对象的不同部分细化为不同的分辨率。其他最近允许在整个场景中改变水平的系统包括ROAM 地形渲染系统[Duchaineau 97]和LDI树[Chang 99]。QSplat中LOD控制的实现与这些系统具有相同的目标，允许根据投影的屏幕尺寸在整个场景中细节级别平滑地变化。5.4 几何压缩几何压缩的目标是减少大网格的存储和内存要求以及传输成本。Deering提出了一个压缩网格连通性，顶点位置，颜色和法线的系统，后来在硬件中实现[Deering 95]。最近的研究，如Taubin和Rossignac 的拓扑外科方案，已经集中在降低表示网格连通性和改善顶点位置压缩的成本[Taubin 98]。Pajarola和Rossignac对渐进网格进行了压缩，产生了一种将细节层次控制和渐进式细化与紧凑表示相结合的方案[Pajarola 99]。然而，他们的算法比QSplat具有更高的预处理和解码成本。6 结论和未来工作QSplat系统演示了大型模型的实时渐进渲染。QSplat的体系结构与最先进的逐行显示算法的渲染速度相匹配，预处理时间可与当前最快的网格化抽取器相媲美，实现了接近当前几何压缩技术的压缩比。由于QSplat查看器非常轻便，可以在低成本硬件上实现，因此我们认为它有可能允许在以前不实用的应用程序中使用3D渲染，例如在博物馆中内置自助服务终端。1999年5月6日，我们在佛罗伦萨Medici教堂的一台计算机上安装了QSplat，显示我们部分完成的Dawn雕像计算机模型，并让游客玩耍（见图7）。先前介绍的几种技术可以纳入当前的QSplat框架中，以使其更加节省时间和空间：霍夫曼编码[Huffman 52]或其他无损压缩方案可用于使当前表示更加紧凑。这对于通过低带宽通信链路进行离线存储或传输非常有用，但是需要在渲染之前对模型进行解压缩。对于渲染速度比紧密表示更重要的情况，可以通过消除球体位置和大小的压缩和增量编码（如3.1节所述）来加速算法，并将这些数量简单地存储为彩车。此外，正常的口罩和能见度掩模（如Grossman和Dally使用的口罩和能见度掩模）可以在有大量大面积封闭的情况下加速渲染[Zhang 97，Grossman 98]。速度的进一步提高可以通过并行渲染算法来实现，将树的一部分分配给不同的处理器。虽然我们在本文中报告了单处理器的结果，但我们已经可以通过将网格分解为多个tile来并行化我们的预处理算法。进一步的分析对于理解QSplat的时间一致性和缓存行为是必要的。对地形渲染和建筑漫游系统[Funkhouser 96]中的帧速率控制和工作集管理技术进行了大量的系统研究，这些算法将提高用户与QSplat交互的平滑度。以下是帕特COM的图形中结合了不同种类的算法QSplat方法未来研究的潜在领域：QSplat使用的边界球体层次结构非常适合作为光线追踪的加速度数据结构。潜在地，这可以用于高质量渲染，并具有以QSplat格式存储的模型的高级渲染效果。实例化将很容易融入我们的基于树的数据结构和渲染算法，大大减少了许多程序化定义场景的内存需求。这可以被认为是一种依赖视图的精灵的新形式，允许在场景中的多个位置有效地包含几何图形。除法线和颜色以外的项目可以存储在每个节点。透明度（alpha），BRDF和BTDF将是明显的候选对象，这会增加QSplat可以表现的视觉复杂度，使其具有与现代体积渲染器相似的功能[Kajiya 89]。更复杂的对象，如光场，依赖视图的纹理，空间变化的BRDF以及分层深度图像也可能存储在每个节点上，从而创建点渲染系统和当代基于图像的渲染器的混合。