0%
高级程序员应具有的素质
发表于:
浏览:
作为高级程序员,以至于系统分析员,也就是对于一个程序项目的设计者而言,除了应该具备上述全部素质之外,还需要具备以下素质:
第一,需求分析能力
对于程序员而言,理解需求就可以完成合格的代码,但是对于研发项目的组织和管理者,他们不但要理解客户需求,更多时候还要自行制定一些需求,为什么这么说呢?
一般而言,进行研发任务,也许是客户提出需求,也许是市场和营销部门提出的需求,这时候对于研发部门,他们看到的不是一个完整的需求,通常而言,该需求仅仅是一些功能上的要求,或者更正规些,可能获得一个完整的用户视图;但是这都不够,因为客户由于非技术因素多一些,他们可能很难提出完整和清晰,或者说专业性的性能需求,但是对于项目组织者和规划者,他必须能够清醒认识到这些需求的存在并在完成需求分析报告的时候适当的提出,同时要完整和清晰的体现在设计说明书里面,以便于程序员编码时不会失去这些准则。
程序设计者必须正确理解用户需求所处的环境,并针对性做出需求的分析,举例而言,同样一个软件通过ASP租用方式发布和通过License方式发布,性能需求可能就是有区别的,前者强调的是更好的支撑能力和稳定性,而后者则可能更强调在各种平台下的普适性和安装使用的简捷性。
第二,项目设计方法和流程处理能力
程序设计者必须能够掌握不少于两到三种的项目设计方法(比如自顶至下的设计方法,比如快速原型法等等),并能够根据项目需求和资源搭配来选择合适的设计方法进行项目的整体设计。
设计方法上选择不当,就会耽误研发周期,浪费研发资源,甚至影响研发效果。
一个程序设计者还需要把很多功夫用在流程图的设计和处理上,他需要做数据流图以确立数据词典;他需要加工逻辑流图以形成整体的系统处理流程。
一个流程有问题的系统,就算代码多漂亮,每个模块多精致,也不会成为一个好的系统。当然,做好流程分析并选择好项目设计方法,都需要在需求分析能力上具有足够的把握。
第三,复用设计和模块化分解能力
这个似乎又是老调重谈,前面基本素质上不是已经说明了这个问题吗?
作为一个从事模块任务的程序员,他需要对他所面对的特定功能模块的复用性进行考虑,而作为一个系统分析人员,他要面对的问题复杂的多,需要对整体系统按照一种模块化的分析能力分解为很多可复用的功能模块和函数,并针对每一模块形成一个独立的设计需求。举个例子,好比是汽车生产,最早每辆汽车都是独立安装的,每个部件都是量身定做的,但是后来不一样了,机器化大生产了,一个汽车厂开始通过流水线来生产汽车,独立部件开始具有一定的复用性,在后来标准化成为大趋势,不同型号,品牌甚至不同厂商的汽车部件也可以进行方便的换装和升级,这时候,汽车生产的效率达到最大化。软件工程也是同样的道理,一个成熟的软件行业,在一些相关项目和系统中,不同的部件是可以随意换装的,比如微软的许多桌面软件,在很多操作模块(如打开文件,保存文件等等)都是复用的同一套功能模块,而这些接口又通过一些类库提供给了桌面应用程序开发者方便挂接,这就是复用化的模块设计明显的一个佐证。
将一个大型的,错综复杂的应用系统分解成一些相对独立的,具有高度复用性的,并能仅仅依靠几个参数完成数据联系的模块组合,是作为高级程序员和系统分析员一项最重要的工作,合适的项目设计方法,清晰的流程图,是实现这一目标的重要保证。
第四,整体项目评估能力
作为系统设计人员,必须能够从全局出发,对项目又整体的清醒认识,比如公司的资源配置是否合理和到位,比如工程进度安排是否能最大化体现效率又不至于无法按期完成。评估项目整体和各个模块的工作量,评估项目所需的资源,评估项目可能遇到的困难,都需要大量的经验积累,换言之,这是一种不断总结的累计才能达到的境界。在西方一些软件系统设计的带头人都是很年长的,比如4,50岁,甚至更老,他们在编码方面已经远远不如年轻人那样活络,但是就项目评估而言,他们几十年的经验积累就是最重要和宝贵的财富。中国缺这么一代程序员,主要还不是缺那种年纪的程序员,而是那种年纪的程序员基本上都是研究单位作出来的,都不是从专业的产品化软件研发作出来的,他们没有能积累那种产品化研发的经验,这也是没有办法的事情。
第五,团队组织管理能力
完成一个项目工程,需要团队的齐心协力,作为项目设计者或研发的主管人,就应当有能力最大化发挥团队的整体力量,技术管理由于其专业性质,不大同于一般的人事管理,因为这里面设计了一些技术性的指标和因素。
首先是工作的量化,没有量化就很难做到合适的绩效考核,而程序量化又不是简单的代码行数可以计算的,因此要求技术管理人员需要能真正评估一个模块的复杂性和工作量。
其次是对团队协作模式的调整,一般而言,程序开发的协作通常分为小组进行,小组有主程序员方式的,也有民主方式的,根据程序员之间的能力水平差距,以及根据项目研发的需求,选择合适的组队方式,并能将责权和成员的工作任务紧密结合,这样才能最大发挥组队的效率。
一个代码水平高的人,未必能成为一个合格的项目研发主管,这方面的能力欠缺往往是容易被忽视的。
综上可以看到,作为一个主管研发的负责人,一个项目设计者,所需要具备的素质和能力并不是程序代码编写的能力,当然一般情况下,一个程序员通过不断的总结提高达到了这种素质的时候,他所具有的代码编写能力也已经相当不简单了,但是请注意这里面的因果关系,一个高水平的项目设计者通常已经是代码编写相当优秀的人了,但是并不是一个代码相当优秀的程序员就可以胜任项目设计的工作,这里面存在的也不是智商和课本的问题,还是在于一个程序员在积累经验,逐步提升的时候没有意识到应当思考哪方面的东西,没有有意识的就项目的组织和复用设计进行揣摩,没有经常性的文档习惯和总结习惯,不改变这些,我们的合格的项目设计者还是非常欠缺。
另外,为防止有无聊的人和我较真,补充一点,本文针对目标是作商业化的软件项目和工程,那些科研机构的编程高手,比如算法高手,比如图象处理高手,他们的工作是研究课题而非直接完成商业软件(当然最终间接成为商业产品,比如微软研究院在作的研究课题),因此他们强调的素质可能是另外的东西,这些人(专家),并不能说是程序员,不能用程序员的标准去衡量。
此诗甚妙
一个犁牛半块田,收也凭天,荒也凭天
粗茶淡饭饱三餐,早也香甜,晚也香甜
布衣得暖胜丝绵,长也可穿,短也可穿
草舍茅屋有几间,行也安然,待也安然
雨过天青驾小船,鱼在一边,酒在一边
夜归儿女话灯前,今也有言,古也有言
日上三竿我独眠,谁是神仙,我是神仙
南山空谷书一卷,疯也痴癫,狂也痴癫
公布一个PPT0day
最近没啥好八卦的,拿这个来充数.HOHO.0day在文章最后面.
|=---------------------------------------------------------------------------=| |=----------------------=[ 专访wordexp ]=---------------------=| |=---------------------------------------------------------------------------=| |=---------------------------------------------------------------------------=| |=-------------------=[ By wordexp ]=------------------=| |=---------------------------------------------------------------------------=|
[目录] 1. 据您所知现在都还有哪些严重的0DAY没被公开? 2. 挖漏洞有什么窍门?可以具体谈谈fuzzer怎样构造样本么? 3. 请问您对0day市场有什么看法? 4. 您建立wordexp这个blog的初衷是什么?为什么叫wordexp,而不是pdfexp或者是exclexp? 5. 请问0day是咋来的? 6. 请问在安全圈子谁是您的偶像? 7. 请问溢出这面红旗还能打多久? 8. 您对我们杂志以及我们小组的发展有什么建议? 一、据您所知现在都还有哪些严重的0DAY没被公开? 主流应用软件方面目前微软公司的IE6/7和PPT 2003 SP3前一阵子就有EXP在外面跑了, adobe公司的FLASH产品中也有一个能被利用的漏洞,拿到的人应该也不少,往后的一到三个月 内就应该出补丁或是有相关新闻,当然以我们目前的视界能看到的只有很少很少的一部分,其 实国外的很多安全机构比如:idefense和zdi可以确定还有不少没被公开的漏洞,只是这些漏 洞可能并不是我们想象的那么通用,成功率也许有限。 其实经常听到朋友问这个问题,说白了就是个消息的打听,这个一方面要提高自已的敏感 度,注意随时关注国内外相关网站的新闻,比如NORTON和MCAFEE的网站经常有一些抓到的0DAY 样本的新闻,有时还有一些细节。还有就是消息的共享,你提前能知道消息并告知朋友,以后 也许人家也会这样对你。 二、挖漏洞有什么窍门?可以具体谈谈fuzzer怎样构造样本么? 这个问题太为难我了,很多人比我更有资格回答这个问题。我只是斗胆胡说几句。 要说窍门,应该是不同软件的洞窍门还不一样,然后还要看挖洞的目的,如果是为了出名 在bugtraq之类的邮件列表上能多露几次脸,那么可以尽可能的找那种用户少关注少的软件特 别是WEB脚本程序的洞。如果是公司有任务必须往CVE、MS上报多少条漏洞那么可以找大公司 的二三线产品的漏洞或是有一定用户数但版本很久不更新的软件的洞,而且这些洞是不一定 要可利用的。如果是为了混zdi、idefense那么可以把fuzz到的POC只要看起来有可能被利用 的都提交上去,也可以找默认情况下不支持的功能的洞,多少可以骗点钱。 如果是要挖卖得出去也能利用的洞,比较通用的一些窍门我能想到的是: 1. 找大众软件生僻功能,生僻协议/文件格式的洞 2. 找几乎没有文档化的功能的洞 3. 新版本软件为了向下兼容所支持的老协议/老文件格式的洞 4. 新版本软件增加的新功能/新格式 5. 不容易fuzz到的洞,比如数据是加密/压缩/编码过的,或是有验效的 6. 某软件某功能刚出了漏洞,马上测试其它同类软件同类功能是否有类似漏洞 7. 多分析老漏洞,善于总结前人挖漏洞的经验技巧,很多不同的洞其实都有相类似的发 掘方式和思路 第二个问题,我以文件型漏洞举例子,fuzz样本的构造,首先是按照上述几个窍门来生成 原始模版,这样相对可以弄出一些人家不太容易fuzz到的数据格式结构,当然在生成原始模版 的前期功课也是很花时间的。做好样本后就是写具体的fuzz程序, 如果对文件格式比较熟, 那么可以节约很多的时间,我比较喜欢的一个办法是fuzz某一些功能的洞,那么就先看格式, 把数据在文件中的位置先手工定位,然后小粒度的测试,要注意的是可能与某功能相关联的数 据比较杂乱数据很可能并不是连续存放的。一般1-4KB的数据要不了多少时间就可以手工测 试完毕。另外具体测试时,数据替换的长度(一次替换几个字节),替换的内容也是非常重要的。 为此我们将提供一个PPT 2003 sp3的“0day”poc,在这个“0day”中数据替换的步进就必须为1 字节,而且值也必须为一个固定的值才能触发出错。最后要注意的就是错误的捕捉,有些洞是 打开就退出进程,有些是打开要停顿一定时间才退出进程,有些是CPU 100%程序挂起,有些是 关闭时触发,甚至有些是文档打开后进行某种操作才会触发,当然还有一种情况进程不退出, 也没有提示,也不出错,象这种情况一般依靠进程/窗口/CPU来检测错误的fuzz就失效了。 三、请问您对0day市场有什么看法? 很复杂的一个圈子,搞技术的不搞技术的啥人都有,不过目前看来很多都是为了各种利益 混这个圈子。简单说就是: 池塘不大但人杂,水深。 四、您建立wordexp这个blog的初衷是什么?为什么叫wordexp,而不是pdfexp或者是exclexp? 初衷就是团队成员工作之余发发劳骚,聊聊八卦的地方,希望大家别见怪。另外这个名字 是因为我们几个人搞客户端的漏洞都比较多,所以随便就取了这么个名字。 五、请问0day是咋来的? 最初当然是某个人找出来的。从这个0DAY的发掘者到最终的用户中间可能会只有一层关 系,也可能会有N层关系,也许直到这个0DAY被补上,使用者也不知道洞是谁挖到的。下面举几 个例子吧: 情况一:A挖到一个0day,但对黑产没有了解或接触,或者也不想靠这个赚钱,或者觉得漏 洞不值钱,或者压根以为漏洞不能够被利用,那么A有可能把这个漏洞公开给类似PST的网站, 网站上的代码通常是POC或是只有一部分细节。这时黑产中的漏洞研究者B,很快会看到这个 消息,并且分析POC然后写出EXP。随后B再联系具体的使用者C或是自已使用。最终或是因为 这个0DAY的POC被公开,也或许因为EXP被杀毒软件公司抓到样本等等,软件厂商推出补丁。在 这个过程中B可能是一个人也可能是很多水平各不相同的人,所以公开的0DAY的EXP有时是千 差万别,有的好用,有的很差。 情况二:A是黑产中的一员,挖到一个0DAY并卖给X,或是接受使用者X的定制并找到0DAY, X偷偷的使用0DAY,这样的情况一般0DAY的生存期会比较长一些,因为这才算是真正的私洞,知 道的人不多。但是在X的使用中,EXP可能被别的黑产从业者Y抓到样本,然后Y把样本提供给技 术员T分析并重新写出EXP,而成果Y和T分享。这时T可能再次把EXP卖给其它的黑产使用者W, 同样X或Y在使用一段时间后也可能交换或者再出手给其它的买家,而且这个过程是可以无限 次重复的,当然时间越久知道的人越多,0DAY就越掉价。 情况三:白帽子A挖到一个0day,并提交给软件厂商B。假设A是个真真正正的白帽子,也假 设这个0day的确也只被A发现了,但0day到了厂商B那儿,最终会找公司内负责安全的部门对漏 洞进行研究,假设这个公司内部的研究者是C,C有可能在圈子中也有其它从事黑产的朋友或是 自已本身就偷偷的在参与黑产,那么在金钱或是感情的作用下C完全有可能违背道德,写出EXP 出售或是使用。象这种情况,0day可能刚出来没几天就被补上了,或者圈子里的很多人根本就 没机会见到0day,知道有这么个东东的时候早就被补上了。 情况四:软件厂商B在代码审计或软件测试过程中发现了漏洞,自已在新版本中偷偷的补 上了漏洞,但并没有在相对老的版本中打补丁,也没有公开任何细节和公告。 研究人员A通过 补丁比较,直接定位出老版本中的漏洞位置,然后动态调试找到触发方式,并写出EXP,由于很 多情况下老版本的软件反而用户更多,所以这样的0day还是有一定的价值。 情况五:研究员A挖到了一个Nday而这个Nday以前在圈内并不为人所知,或是研究员A研究 出某Nday的新利用方式,比如说可以和某某软件结合看起来和以前的EXP完全不一样,或是成 功率有很大的提高。研究员A以较低的价格出售给中间人B。B拿到EXP后,发现圈内还没人有, 成功率各方面也还不错,于是号称0day到处叫卖。如果买家发现问题,B就装傻说自已也上当 了。现在象B这样的人其实也是不少的, 因为很多最终用户对技术并不是特别懂,而且有些 Nday测试起来也不是那么简单,如果刚好EXP效果不错,可能就忽悠过去了。那么我们能看到 的情况就是,江湖传言又出了个0day,或是某某手上有0day,但等呀等就是见不到东东,最终传 言不了了之或是被人家揭发出来。 所以要搞到0day可以自已挖,可以补丁比较,也可以分析已公开信息快速写出EXP,可以买, 也可以换,不要命也可以偷抢骗,技术手段非技术手段都是可能的。也正是因为上述情况的多 样性,所以经常有不怎么搞技术的人,手上也有些0day。 六、请问在安全圈子谁是您的偶像? 我的偶象是那种啥技术不懂,还能发大财的,不PF不行。 七、请问溢出这面红旗还能打多久? 仅仅是溢出这块,我们团队里面意见也大不相同,另外几个成员还是比较乐观的,如果比 较全面的分析这个问题,首先要看站在什么人的角度来看这个问题,是黑产工作者是安全公司 还是软件生产商。假设以黑产工作者的角度来看,那么我是非常非常悲观的,因为溢出漏洞从 技术角度上说:有一个通用性和成功率的问题,直观的说就是有一个效果的问题,再深一点说 就是经济成本的问题。往后走溢出漏洞单从个数上说还是会有很多的。但是现在从编译器和 OS(/GS、/SafeSEH、/DYNAMICBASE、DEP、PEB随机等等)到CPU(NX),软件公司和硬件厂商已 经越来越关注安全问题,几十年来溢出漏洞最关键的命脉无非是数据能够被当做代码来执行, 以前这一点基本上不被软硬件厂商所重视,这几年来人家开始重视了,开始从体系上解决这个 问题,那么这个命脉也将因为各种防范检测技术的运用被卡得越来越死,另外现在很多软件也 有自动升级功能了。 往后走是个什么样的情况,我想应该是上面提到的各种技术随着新型CPU和OS的占有率越 来越高,被越来越多的应用。一个溢出漏洞的成功率将会大大下降,再加上主流软件公司的产 品也越来越安全,以后那种一个漏洞打天下的局面将会越来越少(现在黑产工作者的网马都是 漏洞合集了,无非就是提高成功率),具体的情况也许就是现在我有一个IE的0DAY,100个人看 也许能中10-20个,以后可能手上能用的就变成某个第三方控件的0DAY,100个人看网页就能中 1-2个吧。当你使用溢出漏洞的时间,人力,金钱成本和产出完全不成正比的时候,也基本上算 溢出这面红旗倒下的时候。 估计也就三四年以后,具体指标就是上面提到的各种检测技术的普及率,至少往后的发展 不会是车到山前必有路。如果把挖溢出漏洞当成一个产业,也就是个夕阳产业。 八、您对我们杂志以及我们小组的发展有什么建议? 不走商业路线是正确的,反正你们那群人也不差钱,就不定期搞搞科普工作吧,为普及中 国安全事业做点贡献,同时也可以锻炼你们各方面的能力,继续努力! -EOF-
某类领导
发表于:
更新于:
浏览:
某类领导
在我们眼里,他们没文化,没水平,没魄力,他一无是处
但是他对上级忠诚,唯命是从,鞍前马后,忠诚得像一条哈巴狗
凭借着这种忠诚,甚至是卑贱,他总是有骨头啃,总是能明目张胆地拿着鸡毛当令箭
这种人创造的,往往都是一种非常压抑的气氛
他不停地给你找事做,他尽可能剥夺你学习时间和上进的机会,他不希望任何他能影响的人,变得比他聪明
你若反对他,他就变成狼狗,悄悄地却狠狠地咬你,咬你的时候他还装作自己也很受伤的样子
若有告状的,上级领导就会出来说:“xx其实是个好人,他也不容易,相互理解支持嘛”
年底,xx必然有各种奖状,“勤勤恳恳、大胆泼辣、敢于管理”
等到某一天,xx求我办事
这事跟他切身利益直接相关,我一手把握的,我才突然感觉他很可怜
但是,谁又来可怜我呢
山不转路转,路不转水转,水不转人转,终于转到老子手上
呵呵,不符合游戏规则的,没办法处理的,统统法办
相信你肯定可以理解我的
就像当年我可以理解你的那样
组织,是有胸怀的
每一个人,总是要有觉悟和勇气为自己的所作所为付出代价的
不能把握的东西,你还不如不去占有
团队机能障碍
发表于:
浏览:
1.缺乏信任
该问题源于成员大都害怕成为别人攻击的对象。大家不愿意互相敞开心扉,承认自己的缺点和弱项,导致无法建立相互信任的基础
《荣枯鉴》白话版
《荣枯鉴》
冯道 著
东东枪 译
冯道 著
东东枪 译
·圆通卷一
善恶有名,智者不拘也。
善恶这种事儿都有个定义,但真正有智慧的人根本不理会。
天理有常,明者不弃也。
世上什么事儿都有个规律,真正的明白人不会随意视而不见、自以为是。
道之靡通,易者无虞也。
是不是合乎道理,顺应变化的人根本也不关心这个。
惜名者伤其名,惜身者全其身。
珍视名声者,名誉易损,爱惜自己者,保全自身。
名利无咎,逐之非罪,过乃人也。
名利不是坏事,追名逐利也不是罪过,以此为恶是人的主观想法而已。
君子非贵,小人非贱,贵贱莫以名世。
君子没什么可自以为尊贵的,小人也没什么低贱的,尊贵还是低贱都是虚名,不足以称道一世。
君子无得,小人无失,得失无由心也。
君子不一定就能得到什么,小人也不一定会失去什么,得到、失去,跟思想境界也毫无关系。
名者皆虚,利者惑人,人所难拒哉。
名誉都是虚的,利益才能真正诱人,是人就难以抗拒。
荣或为君子,枯必为小人。
事成显贵的,有可能会被传颂为君子;身败落魄的,必定会被当作小人。
君子无及,小人乃众,众不可敌矣。
因此,君子永远是少数(因为即使成功也不都是君子),小人永远是多数,多数人的力量,无可抗拒。
名可易事难易也,心可易命难易也,人不患君子,何患小人焉?
名誉容易转变,思想境界可以变化,但事情的成败、人生的命运,却难以更改。反正君子小人不是根本问题,你觉得当君子受苦没有什么不好,当小人来享福又有什么可怕的呢?反正名誉根本都不靠谱。
·闻达卷二
仕不计善恶,迁无论奸小。
做官不做官,升职不升职这类事情,根本不是以善还是恶、君子还是小人来作为判断标准的。
悦上者荣,悦下者蹇。
让上边的领导高兴的,日子必定好过;让下边的群众高兴的,碰到的困难却会更多。
君子悦下,上不惑名。
君子让下边的群众高兴,可上头的领导并不觉得你在群众里的好名声有什么意义。
小人悦上,下不惩恶。
小人讨好上头的领导,下边的群众可能会反感,但是,这样的反感并不能对你有任何损伤。
下以直为美,上以媚为忠。
下边的群众以道德高尚刚正不阿为美德,上头的领导会把谄媚顺应当做忠诚。
直而无媚,上疑也;媚而无直,下弃也。
道德高尚,但是毫不顺应领导,上头就该不信任你了;顺应领导,但是让下头人觉得你不地道,下头人就该反对你了。
上疑祸本,下弃毁誉,荣者皆有小人之谓,盖固本而舍末也。
可是,上头不信任你,你就完了——这是根本,下头反对你,顶多是给你来个坏名誉——无所谓的事儿。因此,那些功成名就大富大贵的人都难免被人称作小人,就是因为他们选择了保住根本,顾不得那些没有意义的名誉了。
富贵有常,其道乃实。
想富贵么?规律很简单:一切从实际出发。
福祸非命,其道乃察。
祸福其实并非命运注定,也有规律可循:看你是否善于观察和判断。
实不为虚名所羁,察不以奸行为耻。
一切从实际出发,就别受那些毫无实际意义的虚名的束缚。善于观察判断,就别老以坏人坏事为耻辱,那会搞得很不客观。
无羁无耻,荣之义也。
没有名誉的羁绊,没有耻辱感的束缚,你离显贵发达也就不远了。
求名者莫仕,位非名也。
要是真想追求名誉,你就别去当官,好名誉不是当官儿可以当出来的。
求官者莫名,德非荣也。
要是真想当官,你就别管什么名誉了,道德高尚跟富贵荣华是两码事儿。
君子言心,小人攻心,其道不同,其效自异哉。
君子讲的是道德、理念,小人则专门对付这种道德理念,价值观不同,行为不同,结果自然不同。
·解厄卷三
无忧则患烈也。
什么都不操心,你就离倒霉不远了。
忧国者失身,忧己者安命。
不过,操心根操心不一样。为国家操心的人,只能把自己搭上;为自己操心的人,却能乐享安然。
祸之人拒,然亦人纳;祸之人怨,然亦人遇。
祸事是人不想要的,但也是人自找的,人人都讨厌有祸事,但人人都难免碰上。
君子非恶,患事无休;小人不贤,余庆弗绝。
君子不做坏事儿,但倒霉事儿一个接一个,小人不干什么好事儿,却能够常年乐乐呵呵。
上不离心,非小人难为;下不结怨,非君子勿论。
让上头领导对自己完全放心,不是小人就难以做到;让下头群众不怨恨自己,不是君子您就别想了。
祸于上,无辩自罪者全。祸于下,争而罪人者免。
上头要找别扭的时候,只有装孙子认罪才能保全自己;要是下头起了负面意见,就只有抢先把责任推到别人头上,装好人,才能免去责任。
君子不党,其祸无援也。小人利交,其利人助也。
君子不爱拉帮结伙,所以,真有了祸事,没人会帮助君子。小人有一大帮酒肉朋友,成事解围,全都靠这帮人帮助。
君子不爱拉帮结伙,所以,真有了祸事,没人会帮助君子。小人有一大帮酒肉朋友,成事解围,全都靠这帮人帮助。
道义失之无惩,祸无解处必困,君子莫能改之,小人或可谅矣。
不讲道义不会造成什么实际的恶果,有了祸事没人帮可就必然倒霉。做君子倒了霉只能束手无措,所以,大家选择做小人,也就是可以理解的了。
·交结卷四
智不拒贤,明不远恶,善恶咸用也。
真正的聪明人不拒绝好人,也不疏远坏人,这两款都有用。
顺则为友,逆则为敌,敌友常易也。
都是拿顺应自己的人当朋友,拿反对自己的人当敌人——敌人朋友是随时可以转化的。
贵以识人者贵,贱以养奸者贱。
用尊重的态度看待别人的人,日后将发达显贵,用轻贱的态度对待别人,日后难免潦倒。
贵不自贵,贱不自贱,贵贱易焉。
显贵的人不自重,不知道摆出个尊贵的样子来,而潦倒的人不做轻贱别人的贱事儿,贵贱就该掉个儿了。
贵不贱人,贱不贵人,贵贱久焉。
显贵的人永远不轻视别人,潦倒的人永远不尊重别人,贵贱就这么一直下去了。
人冀人愚而自明,示人以愚,其谋乃大。
人盼着别人傻自己聪明,但还是成天装傻给人看,这是真正有主意的人。
人忌人明而自愚,智无潜藏,其害无止。
不乐意别人机灵自己傻,就玩儿命抖机灵,这就纯属作死。
明不接愚,愚者勿长其明。
聪明的人不接触傻子,接触傻子不会让聪明人变得更聪明。
智不结怨,仇者无惧其智。
有智慧的人不跟人结仇,仇人不会因为你聪明就怕你。
君子仁交,惟忧仁不尽善。小人阴结,惟患阴不制的。
君子人用仁义与人交往,生怕自己不够仁义;小人满怀阴损与人交往,生怕阴损的不够,达不到目的。
君子弗胜小人,殆于此也。
君子注定吃小人的亏,就是因为这个。
·节仪卷五
外君子而内小人者,真小人也。
外表君子作派,内心小人肚肠的,这是真小人。
外小人而内君子者,真君子也。
行事手段并不比小人差,内心却有君子情怀的,这才配称真君子。
德高者不矜,义重者轻害。
道德高尚的人以道德高自居,就不太知道保重自己,看重仁义的人太重仁义,总是忽视自己面对的危害。
人慕君子,行则小人,君子难为也。
人们满嘴说的都是仰慕君子,办出来的却还都是小人事儿,那谁要真当君子,谁就该倒霉了。
人怨小人,实则忘义,小人无羁也。
人们口口声声憎恶小人,实际上做事情还是不仁不义,也就是说,做小人其实是没有任何阻碍的——憎恶全是空喊。
难为获寡,无羁利丰,是以人皆小人也。
可谁也不愿意当倒霉蛋,因为倒霉蛋比别人倒霉,谁都愿意没有任何阻碍,因为没有任何阻碍就比别人牛B,所以,所有人就都成了小人了。
位高节低,人贱义薄。
社会阶层越高,气节越低——因为要装孙子;人越贫贱,义气越是狗屁——因为先要活着。
君子不堪辱其志,小人不堪坏其身。
君子最受不了的是精神屈辱,小人最受不了的是身体上的困苦。
君子避于乱也,小人达于朝堂。
所以,一有乱子,君子就躲起来自保高洁去了,小人却常能当上国家领导。
节不抵金,人困难为君子。
气节比不上金钱实在,所以,人一穷了,就做不成君子——因为讲不起气节。
义不抵命,势危难拒小人。
义气也不如生命重要,所以,身处危困就没办法不当小人,因为活命更重要。
不畏人言,惟计利害,此非节义之道,然生之道焉。
不管别人如何夸奖或批评,只管自己的利益存亡,这不是追求气节仁义的道理,却是能让你安身立命、生存下来的道理。
·明鉴卷六
福不察非福,祸不预必祸。
有福气,可你自己不知道,这就白费。有祸事,你自己不能预先避免,那就准得倒霉。
福祸先知,事尽济耳。
要是福祸都能提前预知,那就牛B了。
施小信而大诈逞,窥小处而大谋定。
偶尔装装实在,大阴谋就能得逞;多偷偷看看细节,大的决策就能搞定。
事不可绝,言不能尽,至亲亦戒也。
做事情要留后路,说话要留半句,纵然是亲爹也得防着点儿。
佯惧实忍,外恭内忌,奸人亦惑也。
心里头有火儿也要憋着,假装畏惧,心里头满是仇恨也得藏着,假装恭敬,您要能这么办,坏心眼儿再多的人也得上当。
知戒近福,惑人远祸,俟变则存矣。
知道克制自己的表现、学会迷惑人,就可以少惹事儿、多成事儿了——反正就是得会变化、会掩饰,您就放心大胆的活着吧。
私人惟用,其利致远。
只跟用得上的人套近乎,讲交情,这是可以长远地对你有利的。
天恩难测,惟财可恃。
别指望领导的保护,包括领导在内的一切都是钱的孙子。
以奸治奸,奸灭自安。
用坏人治坏人,坏人被整死了,整人的那位自己也知道了厉害,也就老实了。
伏恶勿善,其患不生。
别干什么坏事儿,但也别傻了吧唧干什么好事儿,这样就能永远不惹事儿。
计非金者莫施,人非智者弗谋,愚者当戒哉。
计策不牛逼,就别一条道走到黑;要是知道谁傻B,就别跟丫商量事儿,珍惜生命,远离傻B——如果您自己脑子就不怎么好使,那就更得注意以上这两条儿。
·谤言卷七
人微不诤,才庸不荐。
职位低贱的人,没必要老给他提意见帮助他成长,真没什么本事的那些货,也别到处瞎推荐,推了也没用——该整的要整,该毁的要毁。
攻其人忌,人难容也。
攻击一个人,就找他最遭人羡慕嫉妒恨的那一点,大伙儿就都帮你毁他了。
陷其窘地人自污,谤之易也。
或者把他推到一个进退两难怎么办都只有难办的境地,别人就会替你开始戳他脊梁骨了,这是简易诽谤法。
善其仇者人莫识,谤之奇也。
好好对待那些跟他有仇的人,他们早晚会跳出来毁他,而别人又不知道跟你有关系,这就又上一个层次,算是奇招了。
究其末事人未察,谤之实也。
把别人不知道的他干过的各种小事儿揪出来,挑出毛病公诸于世,这是实在有效的毁谤法。
设其恶言人弗辩,谤之成也。
把关于他的种种坏话全给散播出去,真真假假的谁也分辨不了,这算直接毁谤成功了。
谤而不辩,其事自明,人恶稍减也。
被人毁谤,要是不争辩,只等事实慢慢儿冒出来,旁人的厌恶或许还能稍微减少一些。
谤而强辩,其事反浊,人怨益增也。
可要是他非争辩不可,只能是越描越黑,纯属找死。
失之上者,下必毁之;失之下者,上必疑之。
上头对谁印象不好了,你看着吧,很快下边的人也该开始毁他了。同样,要是下头的人对谁不满意了,上头很快也将对这人产生怀疑。
假天责人掩私,假民言事见信,人者尽惑焉。
借助上头的意见打压别人实现自己的目的,或者借助下头的舆论来说事儿讨取上头的信任,任谁都得上当。
·示伪卷八
无伪则无真也。
没有虚假,也就没有真实。
真不忌伪,伪不代真,忌其莫辩。
真的不怕假的,因为假的无论如何也真不了,怕的是真真假假掺合一块儿分不清楚。
伪不足自祸,真无忌人恶。
虚伪却又虚伪的不够,那就会给自己惹祸;真诚要是真诚到毫无忌讳,那就是缺心眼儿。
顺其上者,伪非过焉。
顺应上头的意思,哪怕是装的,也不是什么过错。
逆其上者,真亦罪焉。
悖逆上头的意思,哪怕是真诚,也是找倒霉。
求忌直也,曲之乃得。
要求什么东西,别说的太直了,用点技巧含蓄点,才容易成功。
拒忌明也,婉之无失。
拒绝什么东西,也别有什么说什么,委婉点儿,照样能达到目的。
忠主仁也,君子仁不弃旧。
“忠”往往会指向“仁”,仁厚的人不忍弃旧,自然也就忠了;
仁主行也,小人行弗怀恩。
“仁”往往就要起而行之,君子知道通过行动来感恩,这就是仁,而小人就不会这样。
君子困不惑人,小人达则背主,伪之故,非困达也。
君子落魄了也不会骗人,小人一发达了就开始在主子背后耍花活,这是因为君子不虚伪、小人虚伪,跟发达不发达毫无关系。
俗礼,不拘者非伪;事恶,守诺者非信。物异而情易矣。
如果是虚礼俗套,一副浑不吝偏不服就跟别人不一样的姿态,也不算什么虚伪,明明是坏事儿,还说一不二一条道走到黑的,也算不上什么实诚,得具体问题具体分析。
·降心卷九
以智治人,智穷人背也。
用智慧来管理下属,智慧什么时候枯竭了,那些人也就都背弃你了。
伏人慑心,其志无改矣。
改用攻心,让他们从心里、从灵魂深处被慑服住,那么无论什么时候他们也不会改变方向了。
上宠者弗明贵,上怨者休暗结。
被上头宠信的人,别太拿自己当回事儿;被上头瞧不上的,也别私下解党与上头作对。
术不显则功成,谋暗用则致胜。
权谋手段,隐蔽的好才能成功,要悄悄地来,打枪地不要。
君子制于亲,亲为质自从也。
君子的弱点在于他们珍爱亲属,拿他们亲属做人质相要挟,也就从了。
小人畏于烈,奸恒施自败也。
小人的弱点在于害怕比他们更狠的,你就比他们更恶毒,他们就服了。
理不直言,谏非善辩,无嫌乃及焉。
说理、劝谏不一定是有什么说什么或者跟人争辩,别遭人恨才能达到目的。
情非彰示,事不昭显,顺变乃就焉。
有什么态度、做什么事情,也没必要先弄得天下人都知道,顺应形势该藏着就藏着才能成功。
仁堪诛君子,义不灭小人,仁义戒滥也。
仁义二字,能够拖累死君子,却灭不掉小人,还是不要滥施仁义,适度才好。
恩莫弃贤者,威亦施奸恶,恩威戒偏也。
施好处别忘了那些你以为不喜欢好处的好人,惩罚措施也别忘了给那些谄媚的坏人来点儿,好事儿坏事儿得掺合者来,得让吃肉的也挨挨板子,挨板子的也来点儿肉尝尝。
·揣知卷十
善察者知人,善思者知心。
善于观察,就能了解人,善于思考,你才能理解人。
知人不惧,知心堪御。
了解了他们,你就不会怕他们了;理解了他们,你就已经能搞定他们了。
知不示人,示人者祸也。
智慧别让别人看见,让别人看见就会惹祸。
密而测之,人忌处解矣。
偷偷地猜度、揣测,不让他们知道,人们对你的嫉恨就会消除掉。
君子惑于微,不惑于大。
君子总是在大事儿上明白,小事儿上糊涂。
小人虑于近,不虑于远。
小人总是在眼前事儿上考虑多,未来的事儿上考虑少。
设疑而惑,真伪可鉴焉。
掌握了这个规律,制造些问题给他们,看看他们的反应,你就知道谁真诚谁虚伪,谁是君子谁是小人了。
附贵而缘,殃祸可避焉。
然后认准你觉得有前途的、能富贵的,攀附一下,以后的祸事就可以避免了。
结左右以观情,无不知也。
结交各路朋友,留神各路的消息和反映,那就什么事儿也瞒不过你了。
置险难以绝念,无不破哉。
常如身处险境般心存忧患,抛掉无关的累赘想法——恭喜您,您已经神光护体,攻无不克了。
<完>
项目失败的经验
建议准备做项目和正在做项目的都来看看,吸取一下教训。
1.过多的工作压到了同一个人身上
在很多小公司[或小项目组]里,总有那么一个核心程序员,负担了几乎所有的编程任务。这个人即使是个天才,也会被繁重的工作压的没有学习和自我提高的时间。
GPU程序在GameByro中的使用
引言:
GameBryo拥有一套复杂的材质系统,这套材质系统可以根据渲染对象的状态和属性生成不同的shader代码,提高了渲染流程的适应性,可以使你定义一套材质能适应多种渲染对象。同时,GameByro将shader的初始化和使用插件化,方便与美术工具集成,并且实现了平台无关性。为了实现这些目的,GameByro使用了一套复杂的机制,本文主要解析GameByro如何生成、编译并使用shader代码。
Shader
GameBryo的shader的接口封装在NiShader中,顶点数据流声明,常量表的访问,渲染状态的设置都是通过这个类(有点类似于D3Deffect)。在程序运行NiShader是由NiShaderFactory负责管理的,NiShaderFactory通过NiShaderLibrary从文件中创建shader,用全局性的map管理起来。NiShaderLibrary通过解析shader文本创建NiShader对象,并调用3D图形接口编译shader代码,将这个类以dll的形式封装,就可以作为插件来使用。NiShader类的创建可以通过解析文件来进行,也可以通过C++的类来定制,只需从NiShader上继承即可。GameByro为PC平台提供了一个NiD3DXEffectShaderLib库,这个库提供了解析shader文件和初始化shader对象的功能。用户只需按GameByro定义的格式编写shader代码的语意和注释,NiD3DXEffectShaderLibrary就会根据文本来创建NiD3Dshader对象,在应用程序中就可以通过Techinqe的名称来访问这个对象。通过这种机制,我们将shader文本文件放在相关美术工具指定的目录下,在工具中就可以使用这些shader,并且能够通过shader的语意和注释为相关参数和变量生成UI,方便美术调试。
WIN平台上的整个流程如下:
1. 应用程序在启动时会先初始化整个shader系统,接下来导入Shader解析库和加载库(dll的形式)。
2. 接下来应用程序将NiD3DShader的初始化工作委托给NiShaderLibrary来处理,NiShaderLibrary首先通过NiD3DXEffectLoader载入所有的shader文本文件,并通过NiD3DXEffectParser解析文本生成NiD3DXEffectFile对象,同时NiD3DXEffectLoader还负责将shader代码编译成二进制形式的GPU程序。
3. 最后由NiD3DXEffectTechnique负责通过NiD3DXEffectFile上的信息生成NiD3Dshader对象。
4. 所有的shader对象创建后,NiShaderLibrary的初始化就结束了,最后由NiShaderFactory负责统一管理。
材质:
NiMaterial为渲染对象生成和定义Shader,NiMaterialInstance为渲染对象分配 和Cach Shader。NiFragmentMaterial提供了一个Shader Tree框架,在它的继承类中可以使用这个框架搭建shader tree。这个机制允许NiFragmentMaterial根据对象不同的渲染状态生成不同的shader代码,Cach在内存中,并保存到磁盘文件。GameByro描述符的概念大量使用,包括前面提到的Shader解析过程也是通过描述符来传递信息。在材质系统中主要使用了NiMaterialDescriptor和NiGPUProgramDescriptor这个两个类做描述符,这两个类中保存的信息是兼容的,都是为了描述某种材质在渲染对象的某一特定渲染状态下所对应的GPU程序的特征。NiFragmentMaterial通过渲染目标的状态和属性生成NiMaterialDescriptor,并通过NiMaterialDescriptor查找匹配的shader,如果找不到,则通过shader tree生成相应的shader程序,并保存到磁盘文件中。当下一次应用程序启动时就可以通过这个文件直接创建NiShader对象。可以说通过NiFragmentMaterial生成的shader代码是为特定的渲染对象在特定的情况下量身打造的。
整个过程的详细流程如下:
1. 在每次渲染一个物体之前,NiMaterialInstance会先判断这个物体的shader程序是否需要更新,如果不需要更新,就直接返回当前Cach的NiShader;如果需要更新, NiMaterialInstance首先会根据物体的渲染状态为其生成一个NiMaterialDescriptor,然后将这个NiMaterialDescriptor和当前Cach住的NiShader进行比较,如果匹配仍然返回当前Cach的NiShader,如果不匹配,将获得shader的工作转交给NiMaterial进行。
2. NiMaterial首先通过这个NiShaderFactory 查询匹配这个NiMaterialDescriptor的NiShader,如果找不到,就通过NiMaterialDescriptor生成NiShader,同时生成一段Shader代码,并保存到以shader描述符中的特征码来命名对应的shader文件。
3. 当获得相应的NiShader对象后,NiMaterialInstance会调用NiShader的SetupGeometry接口,在这个接口中会进行顶点声明。
以下是NiMaterialInstance为Geometry选择shader的代码:
NiShader* NiMaterialInstance::GetCurrentShader(NiRenderObject* pkGeometry,
const NiPropertyState* pkState,
const NiDynamicEffectState* pkEffects)
{
if (m_spMaterial)
{
bool bGetNewShader = m_eNeedsUpdate == DIRTY;
if (m_eNeedsUpdate == UNKNOWN)
bGetNewShader = pkGeometry->GetMaterialNeedsUpdateDefault();
// Check if shader is still current
if (bGetNewShader && m_spCachedShader)
{
bGetNewShader = !m_spMaterial->IsShaderCurrent(m_spCachedShader,
pkGeometry, pkState, pkEffects, m_uiMaterialExtraData);
}
// Get a new shader
if (bGetNewShader)
{
NiShader* pkNewShader = m_spMaterial->GetCurrentShader(
pkGeometry, pkState, pkEffects, m_uiMaterialExtraData);
if (pkNewShader)
{
NIASSERT(m_spCachedShader != pkNewShader);
ClearCachedShader();
m_spCachedShader = pkNewShader;
if (!pkNewShader->SetupGeometry(pkGeometry, this))
ClearCachedShader();
}
else
{
ClearCachedShader();
}
}
m_eNeedsUpdate = UNKNOWN;
}
return m_spCachedShader;
}
如果想通过NiFragmentMaterial实现自己的shader tree就需要在NiFragmentMaterial提供的接口中实现自己拼装代码的逻辑,代码块由NiMaterialLibraryNode封装,NiMaterialLibraryNode既可以直接写C++代码来定义,也可以先写成XML脚本,再由专门的解析工具转换成C++代码。
由NiStandardMaterial生成的shader代码文件如下图所示:
文件名就是NiMaterialDescriptor的掩码,用来标识的shader代码的行为。
Shader代码的行为描述如下:
Shader description:
APPLYMODE = 1
WORLDPOSITION = 0
WORLDNORMAL = 0
WORLDNBT = 0
WORLDVIEW = 0
NORMALMAPTYPE = 0
PARALLAXMAPCOUNT = 0
BASEMAPCOUNT = 1
NORMALMAPCOUNT = 0
DARKMAPCOUNT = 0
DETAILMAPCOUNT = 0
BUMPMAPCOUNT = 0
GLOSSMAPCOUNT = 0
GLOWMAPCOUNT = 0
CUSTOMMAP00COUNT = 0
CUSTOMMAP01COUNT = 0
CUSTOMMAP02COUNT = 0
CUSTOMMAP03COUNT = 0
CUSTOMMAP04COUNT = 0
DECALMAPCOUNT = 0
FOGENABLED = 0
ENVMAPTYPE = 0
PROJLIGHTMAPCOUNT = 0
PROJLIGHTMAPTYPES = 0
PROJLIGHTMAPCLIPPED = 0
PROJSHADOWMAPCOUNT = 0
PROJSHADOWMAPTYPES = 0
PROJSHADOWMAPCLIPPED = 0
PERVERTEXLIGHTING = 1
UVSETFORMAP00 = 0
UVSETFORMAP01 = 0
UVSETFORMAP02 = 0
UVSETFORMAP03 = 0
UVSETFORMAP04 = 0
UVSETFORMAP05 = 0
UVSETFORMAP06 = 0
UVSETFORMAP07 = 0
UVSETFORMAP08 = 0
UVSETFORMAP09 = 0
UVSETFORMAP10 = 0
UVSETFORMAP11 = 0
POINTLIGHTCOUNT = 0
SPOTLIGHTCOUNT = 0
DIRLIGHTCOUNT = 0
SHADOWMAPFORLIGHT = 0
SPECULAR = 1
AMBDIFFEMISSIVE = 0
LIGHTINGMODE = 1
APPLYAMBIENT = 0
BASEMAPALPHAONLY = 0
APPLYEMISSIVE = 0
SHADOWTECHNIQUE = 0
ALPHATEST = 0
NiStanderMaterial就是根据这些掩码的数据来生成shader代码,用户可以通过重载GenerateVertexShadeTree、GeneratePixelShadeTree、CreateShader这些接口来定义自己的shader生成规则。
增加自己的渲染效果:
通过前几节我们可以了解到,想定义自己的材质,一是通过编写shader代码完成。在应用程序初始化的时候,这些shader代码会被初始化成NiShader对象,进一步的通过NiShader对象来初始化NiSingleShaderMaterial对象,并分配给渲染对象。在GameByro默认的渲染流程中,这些步骤都是自动进行的,美术只需在3DMAX插件中为几何体的材质指定Shader程序,导出到nif文件,应用程序就能正确加载并渲染;二是定义自己的NiMaterialFragment类,在类中定义如何生成shader,在应用程序运行时只要将这个类的实例指派给几何体,这个类就会自动为几何体生成shader。这两种方式对于美术人员来说,主要区别在于,采用第一种方法定义的材质,其渲染数据的设置必须严格符合shader代码中所需的数据,否则就会报错。(比如说,顶点数据流必须严格符合shader程序的定义,必须为shader中每个采样器提供格式正确的纹理);而采用第二种方法定义的材质,就有很高的容错和适应性,但是这种容错性和适应性需要自己写代码来完成,GameByro提供的NiStanderMaterial就提供了这套完整的机制。每个贴图槽内的贴图如果你设置就会生成相应的贴图处理流程,如果不设置,就没有这张贴图的处理流程。
为了验证这个过程,笔者尝试增加了一个自己的shader特效——SubSurfaceScattering,简称3s,其原理是模拟光在半透明物体中散射的效果。由于该效果无须预处理过程,所有的贴图均来自磁盘文件,所以比较容易融合到GameByro工作流中。
笔者将在FX COMPOSER中调试通过的fx文件放入SDK中的SDK\Win32\Shaders\Data目录下,在3DMAX的材质面板选择GameByroShader,然后就可在显示shader的组合框中看到文件中定义的Techinqe,选择点击apply按钮,就会出现自定义的参数调整界面。
通过调整参数,最终得到皮肤和玉器的渲染效果如下:
皮肤
玉器
总结
GameByro的这套开发流程非常方便直观,但是美术仅能为shader程序分配静态的数据源,比如说光照图等,CubeMap等;而一些在程序中实时生成的纹理数据则无法整合到美术工具中,比如说阴影图、折射图、反射图等,这些都需要程序写代码来实现。调试起来就不大方便了。大部分情况下,我们只需要使用GameByro提供的NiStanderMaterial就可以完成大部分材质的需求,特殊的效果可以自己写shader或者通过引擎提供的shader库来完成,只有当我们需要即根据复杂的情况做很多不同的处理时,我们才需要重载NiFragmentMaterial搭建自己的shader tree。不过搭建shader tree的程序一般比较复杂,编写难度大,虽然引擎允许通过XML文件来编写材质节点,但是使用起来仍然不方便。GameByro并没有提供相关的后期处理的开发工具,后期处理的特效并不能所见即所得,这方面还需完善。
GameByro为几何体在特定的环境下生成专用的shader代码,具有一定的灵活性,但是也付出了以下代价:
l 分析几何体的属性和当前状态,为其生成shader代码的过程有性能损耗。
l Shader代码生成后会保存到磁盘文件中,这个过程如果不使用异步,可能会引起阻塞。
l 生成的NiShader对象会有内存消耗。由于GameByro默认的实现是将所有的shader文件初始化成NiShader对象,所以当游戏运行的时间久了以后会生成大量的shader文件,这时候内存的消耗可能会很可观,同时加载的时间也会增加。不过可以自己控制加载的流程,在这里进行性能优化。
GameByro渲染系统剖析
引言:
GameByro作为一款次世代引擎,使用了复杂的材质系统,用来满足各种各样的需求。材质代表了物体受到光照后所呈现出的质感,而这种质感在计算机图形学中需要着色代码来完成,所以当前流行的图形引擎设计是使用被渲染对象的材质与shader相关联,GameByro也不例外。GameByro的材质系统可以通过shade tree生成shader程序,增强了应用程序层对可编程渲染管线的控制能力。
渲染架构概览:
在GameByro中,对象表面的色彩、纹理、光滑度、透明度、反射率、折射率、发光度等可视属性与传统的材质系统分离,独立的成为了对象的渲染属性(NiProperty),而材质(NiMaterial)仅用来对着色程序的封装,这样就实现了渲染数据和渲染方法的分离,降低了耦合性。如上所说的这些可视属性在Gamebyro中会封装成一个属性对象,在应用程序中如果对对象挂载这个属性对象,在GPU程序中就可以访问这个属性对象的值。渲染属性对象可以在创建时指定其类型,如纹理、浮点、矩阵、向量或数组,此外一些全局性的对象也可以通过在Shader中用语意声明为全局object对象,如灯光和摄影机等,这样就可以以同样的方式来访问这些对象上的属性。
GameByro每一帧的渲染(NiRenderFrame)划分为多个步骤(NiRenderStep),每个步骤又包含很多个批(NiRenderClick),NiRenderFrame封装了上层对渲染系统调用的接口,而NiRenderClick则代表了图形硬件的一次绘制操作(对渲染队列中所有的对象的顶点缓存调用DrawPrimitive),当应用程序调用NiRenderFrame的Display接口时, NiRenderFrame会依次调用每一个NiRenderStep的Render()接口,NiRenderStep就会执行所有的NiRenderClick操作。
对于每个NiRenderClick来说,首先要设置视口和渲染目标,也就是渲染数据流的入口和出口。视口建立以后就可以通过关联的摄影机对场景图中的对象进行裁剪(默认的有视口裁剪和遮挡裁剪,此外还可以通过回调函数加入自己的裁剪方式),将未被裁剪的对象放入渲染队列。然后Gambyro会根据材质来对渲染队列中的对象进行排序,让材质相同的对象处于相邻位置,这样可以减少切换shader的开销。
如图所示为帧渲染系统的结构图(简化版)
材质系统:
GameByro中的材质代表渲染对象所采用的方法。前面说过。纹理属性包含了着色所需的原料,那么材质就指定了对这些原料的加工方法。基于当前可编程渲染管线设计,材质就成为连接对象与GPU程序的中间层,应用程序可以通过材质将shader应用于几何体。
NiMaterial类是所有材质的基类,这个类通过一个Map来保存当前应用程序中所有NiMaterial的指针,当然这个Map是静态也就是说相当于全局变量,通过static NiMaterial* NiMaterial:: GetMaterial(const NiFixedString& kName)接口对这个全局的Map进行访问。也就是说,当前环境中所有的NiMaterial对象是通过NiMaterial类来管理的。此外NiMaterial类还通过静态成员变量保存了一个工作路径(即shader文件路径),通过这个路径加载shader文件。NiMaterial就像是一个中介,全权代理对对象的渲染工作。用户可以通过重载NiMaterial来实现自己的渲染机制。每个NiMaterial都是全局性的,可以作用于多个甚至是所有的渲染对象,但一个渲染对象也可以拥有多个NiMaterial,但只能有一个处于激活状态的NiMaterial。
NiMaterial的派生类NiFragmentMaterial提供了对可编程渲染管线完整的控制机制,内部保存了NiShader的哈希表、一个NiGPUProgramCache数组。并且NiFragmentMaterial会生成一个用来编译GPU程序的shade tree(后面会有解释)。这样的话,每个NiFragmentMaterial可以对应多个shader程序,这样就提供了一种机制,在运行时根据不同的运行环境和渲染对象不同的状态,来选择合适的shader程序。在NiRenderClick依次渲染可见集中的每个对象时,首先会判断其是否需要被渲染的标记(flag),如果需要被渲染,则使用NiMaterial::IsShaderCurrent接口判断当前shader(上一次渲染所使用的shader)是否有效,所谓有效就是仍然存在并且可以应用于本次的渲染对象,如果无效,则会调用NiMaterial::GetCurrentShader获得shader,用于本次渲染。NiMaterial::GetCurrentShader会根据渲染对象的属性和当前环境硬件条件来选择合适的shader程序。当然,可以通过重载IsShaderCurrent和GetCurrentShader接口来指定自己的有效性判断规则和如何选择shader程序的方案。 NiFragmentMaterial提供了一套搭建shade tree的框架,用户可以通过重载来搭建自己的shade tree,当然,如果不想通过shade tree的形式生成shader程序也可以,使用NiSingleShaderMaterial可以从文件生成shader程序。
Shade Tree:
什么是shade tree呢?我们通常编写的shader代码是线性执行的,即每个pass流程执行的是文本上定义好的shader流程,每一段shader功能模块是按一定顺序依次执行的。如果需要修改流程中的某一部分就需要更改相关的shader代码并重新编译。而shade tree将shader代码以树形结构组织起来,每一个shader代码块(一般是一个函数)都会被编译成一个节点,通过定义输入变量和输出变量来提供数据流的入口和出口,这些节点的插入和删除可以通过应用程序来控制,从而灵活的控制整个渲染过程。这样shader程序中的一些核心模块可以由美术通过工具生成,然后插入到shade tree中,只要输入和输出的接口不变,就无须修改其他代码,从而降低了美术开发shader的门槛。
GameByro通过以下几个类搭建shade tree:
l NiMaterialConfigurator:shade tree被封装在这个对象中,Uniform constants被封装在NiMaterialResource中,而NiMaterialNode封装了相关的shader代码,所有的资源和节点通过NiMaterialResourceBinding连接起来。当所有的连接都确立以后,NiMaterialConfigurator会调用Evaluate接口生成GPU程序和一个输入Uniform资源的集合。
l NiMaterialFragmentNodes:这个类包含了一个shader代码片段的集合,这些代码片段为不同的平台和编程语言所编写。这就为shader程序员提供了更大的灵活性,用以控制他们的代码在不同平台和图形硬件上的表现。例如:在高端平台可以采用高级的shader model提供更好的效果,而在低端平台上可以关闭一些特效来加快速度。
l NiMaterialNodeLibraries:这个类是一个NiMaterialNode的集合,其实也就是一个shader库。它允许shade tree节点完全基于数据驱动。shader库的生成可以通过两种方式,解析XML文件或用XML文件生成C++代码。GameByro提供了相关的解析器和代码生成器。
l NiMaterialResources:shade tree中的Uniform constants,支持多种数据类型,包括Constant、Predefined、Attribute、Global、Object。
固定管线的渲染:
GameByro支持固定管线的渲染,其纹理混合过程如下。
固定管线的着色处理流程
上图很清楚的显示出了每个stage的操作,平行的表示两张纹理的采样是同时进行的,特定情况下右边的纹理可能被忽略。
大部分情况下,应用程序不会使用上面所有的stage,开启或者关闭那个stage可以由应用程序来指定。
以下为多重采样的原理图:
固定管线的纹理多重采样
缺省的着色处理流程:
GameByro提供了一个默认的着色处理流程,封装在NiMaterial的派生类NiStandardMaterial中,这个类执行类似于固定管线的流程,在不同阶段将纹理采样、并将采样到的数据混合到最终的结果中去。
GameByro默认的材质系统的特性如下:
- Skinned and unskinned transformations. Skinned transformations can support up to 30 bones per draw call.
- Vertex colors
- Base maps
- Normal maps
- Parallax maps
- Dark maps
- Detail maps
- Bump environment maps
- Gloss maps
- Glow maps
- Decal maps (up to 3)
- Cubic and spherical environment maps
- Point/Spot/Directional/Ambient lights contributing to the diffuse, specular, and ambient color. Up to 8 total lights. Per-pixel or per-vertex.
- Projected light maps. Clipped or unclipped. (Up to 3)
- Projected shadow maps. Clipped or unclipped. (Up to 3)
- Texture transforms per map.
- Per-vertex fog
下图显示为不同的纹理、灯光、材质属性的组合过程,不过需要注意的是,视差贴图和凹凸贴图属于特殊的情况,它们仅仅影响到纹理采样的UV坐标,而并非直接对最后的颜色值产生贡献。视差贴图会改变所有贴图采样的UV坐标,而凹凸贴图仅对环境贴图的UV产生影响。
以上流程完全由shade tree构建,NiStandardMaterial提供了大量的函数接口用于对每个流程的控制,用户可以通过重载相关的接口,插入自己的shade tree节点,修改每一步的操作或处理过程。例如:
virtual bool HandleBaseMap(Context& kContext, NiMaterialResource* pkUVSet,
NiMaterialResource*& pkDiffuseColorAccum,
NiMaterialResource*& pkOpacity, bool bOpacityOnly);
当然,整个流程的顺序和结构修改起来比较困难,如果有需要可以定制自己的材质系统,搭建自己的shade tree。
NiStandardMaterial提供了若干回调函数,这些函数可以动态的修改流程,分割PASS,对shader运行失败进行容错。
l SplitPerPixelLights/SplitPerVertexLights:这两个函数分别作用于逐顶点光照和逐像素光 照,当物体所受的光源数量太多,超过了顶点或像素着色器的能力时,通过这些函数可以将失败的pass分割成两个,如果分割出的pass仍然不能执行,那么函数会被递归调用,直到每个pass只有一个光源为止。
l SplitTextureMaps:这个函数会把对纹理采样的pass进行分割,当纹理查询过多时,顶点或像素着色器就会过于复杂,这时就可能导致shader运行失败。此函数只能迭代一次,生成一个额外的pass。
l DropParallaxMap:这个函数用来从几何体上移除视差贴图,且不产生额外的pass。
l DropParallaxMapThenSplitLights:这个函数首先调用DropParallaxMap移除视差贴图,然后一直调用SplitPerPixelLights直到失败为止。
NiMaterialInstance:
NiMaterial并不是直接与NiRenderObject相关联,而是经过了NiMaterialIstance这个中间层,由它来代理将NiMaterial关联到几何体,它负责调用NiMaterial为NiRenderObject生成NiShader,通过改变NiMaterialIstance上的接口SetMaterialNeedsUpdate,可以决定每一帧NiRenderObject所使用的材质是否需要被更换,通过接口SetDefaultMaterialNeedsUpdateFlag,可以决定当前材质所需的数据(即当前渲染流程所需的数据)是否要被更新。这样每个NiMaterialIstance只能被一个NiRenderObject所拥有,而多个NiMaterialIstance可以共享一个NiMaterial,这样就减少了重复创建NiMaterial的时间和空间上的开销,同时降低了渲染对象个材质之间的耦合度。
如下为材质系统的类结构简化图:
渲染属性:
前面提到过,GameByro将渲染所需要加工的数据全部封装在了NiProperty中,只要在shader中用指定的语法进行声明,就可以访问这些属性的值。
目前引擎中已经定义了12种属性,均派生自NiProperty,分别代表渲染数据的12种不同类型:
用户也可以自定义属性类型,但所对应的数据类型要被shader语言所支持。
光照与阴影:
光照与阴影密不可分,因为阴影就是由光照产生的,前面在材质系统中已经提到过光照对着色的影响,这里重点阐述,GameByro是怎样根据光源产生阴影的。由GameByro提供的阴影均基于ShadowMap技术,
但也提供了ShaowVolume的示例代码。
Shadowing System是完全建立在帧渲染系统上的, 通过一个RenderClick生成ShadowMap,然后在正常渲染流程开始之前将ShadowMap更新到可见集内每一个渲染对象上。这样当渲染对象使用NiStandardMaterial时就会根据光源的阴影技术来对ShadowMap进行采样,并将结果与最终的输出颜色按一定比例混合。
阴影系统由以下几个类构成:
- Shadow Write Materials:从NiFragmentMaterial派生,封装了生成 ShadowMap的算法和着色程序。
GameByro提供了三种类型的Shadow Write Materials,分别为NiPointShadowWriteMaterial、
NiDirectionalShadowWriteMaterial、NiSpotShadowWriteMaterial,适用于三种不同的光源类型。
- Shadow Technique:这个类封装了阴影算法的细节,包括生成ShadowMap和使用ShadowMap投射阴影。
- Shadow Render Click: 这个类是一个生成ShadowMap的批,这个类的对象是由shadow click generator负责生成。
- Shadow Click Validator: Shadow Render Click:通过此类对象判断接受阴影的几何体对于shadow generator来说是否可见。
- Shadow Map: 每个ShadowMap对象包含一个作为阴影图的纹理,shadowmap对象由shadowManager直接管理,每个shadowmap对象都被一个shadow generator引用。
- Shadow Cube Map: 同shadowmap作用相同,只是阴影图的纹理类型为CubeMap。主要用于点光源生成的全方向阴影。
- Shadow Generator: 阴影生成器,每个ShadowGenerator都对应一个NiDyamicEffect(NiLight的基类),也就是为这个NiDyamicEffect代表的光源生成阴影, 生成阴影所采用的技术由对象引用的ShadowTechnique来决定。
- Shadow Click Generator: 生成ShadowMap的Shadow Render Click都由此类负责创建。这个类为每个ShadowGenerator指定ShadowMap,并负责每帧更新ShadowMap和ShadowMap所对应的变换矩阵。
- Shadow Manager:所有的ShadowMap、ShadowTechnique、ShadowGenerator、shadow render click对象都由ShadowManager统一管理,并负责使用一个shadow click generator 在每一帧生成一个shadow render click的列表。
阴影系统静态结构如下:
1. 在应用程序初始化阶段,通过调用NiShadowManager的Initialize()接口实现对整个阴影系统的初始化,此时应用程序会注册所有的ShadowTechnique,并初始化NiShadowClickGenerator。
2. 当我们创建一个NiLight以后,我们可以通过NiShadowManager为这个NiLight新建一个NiShadowGenerator,NiShadowGenerator会通过NiLight的类型来选择合适的NiShadowTechnique,此时NiShadowManager会为新的NiShadowGenerator创建一个NiShadowRenderClick。
3. 当帧渲染系统启动后,NiShadowRenderClick的PerformRendering()接口会被调用,此时NiShadowRenderClick会通过引用的NiGenerator获得阴影生成的着色程序和所需的数据(例如深度偏移),同时通过NiGenerator引用的Camera获得场景图中的可见集。下一步就是对可见集中的渲染对象添加ShadowWriteMaterial并设为激活状态,而ShadowWriteMaterial的类型是根据NiDyamicEffect的类型指定的。最后NiRenderClick就会启动渲染流水线,将可见集中的对象的深度全部渲染到ShadowMap中。
4. 在第一个RenderClick中生成了ShadowMap,下面就要使用这些ShadowMap投射阴影。在每一帧开始之前,用户还可以自己指定不接受阴影的节点,手动将其插入NiShadowGenerator::m_kUnaffectedReceiverList中。在渲染BackBuffer的RenderClick中,首先会对场景图中的节点进行一次遍历,将不受阴影的节点放入NiShadowGenerator:: m_kUnaffectedCasterList的列表。在对每个节点进行渲染时,会遍历NiShadowManager中所有的NiShadowGenerator,判断这些NiShadowGenerator是否对这个节点有影响,判断的规则是此节点是否存在于UnaffectedReceiverList和UnaffectedCasterList这两个链表中,如果存在于任何一个链表,则节点不受此NiShadowGenerator影响,如果受此NiShadowGenerator影响,那么就将该NiShadowGenerator上的ShadowMap和数据更新到节点上的渲染属性中,NiStandardMaterial会根据这些数据选择对ShadowMap采样的方式,并将结果混合到最终的输出颜色中。
值得注意的是,点光源的shadowMap默认的是采用CubeMap实现,用户可以通过接口选择不使用CubeMap实现,当采用CubeMap实现时,光源无法产生软阴影。
渲染系统的扩展:
为了验证GameByro渲染系统的扩展性,笔者尝试着加入了一个后期处理特效Screen Space Ambient Occlusion(SSAO),即屏幕空间的遮蔽,由于仅仅为了熟悉GameByro的渲染流程,所以笔者并未对SSAO算法做深究,仅仅用了自己简化的算法。
在渲染过程中先单独使用一个RanderClick将场景中的深度渲染到一张纹理上,然后在渲染到后台缓冲区的RenderClick中对深度纹理进行采样,执行SSAO算法,将结果混合到最终的结果中。采样点的偏移坐标是通过对一组随机向量进行归一化再乘以0~1之间的随机数而生成的,即长度为0~1之间的随机向量。
PS代码如下:
float VerticalRange:GLOBAL; //控制XY方向采样范围变量,可以在应用程序层对其进行调整
float HorizontalRange:GLOBAL; //控制在Z方向采样范围的变量
float calAO(float2 texCoord,float dw, float dh ) //通过当前像素所标和偏移量计算AO
{
float2 coord = float2(texCoord.x + dw, texCoord.y + dh);
float4 CenterPos = tex2D(DepthSampler,texCoord);
float4 CurPos = tex2D(DepthSampler,coord);
float depthDiff = clamp(CenterPos.z - CurPos.z,0,VerticalRange);
float ao = depthDiff/length(CurPos.xyz - CenterPos.xyz);
return ao;
}
// Pixel shader
float4 PS_SSAO(VS_OUTPUT In) : COLOR
{
float2 texCoord = In.BaseTex;
float depth = tex2D(DepthSampler,texCoord).z;
float ao = 0.0;
float scale = HorizontalRange/depth; //因为采样范围会受深度影响,故除以此系数。
for(int i=0; i<32; ++i)
{
float2 offset = arrRandomPt[i].xy* scale;
ao += calAO(texCoord, offset.x, offset.y);
}
ao/=32;
float4 color = tex2D(BaseSampler,texCoord);
color.xyz *= (1.0 - ao);
return color;
}
最终实现效果如下:
SSAO生成的明暗图 无SSAO材质
以下两图上图为无SSAO效果,下图为开启SSAO后的效果。
总结:
GameByro的帧渲染系统是比较灵活,想加入自己的渲染流程是比较容易的,此外由于RenderTarget和RenderView都可以由用户指定,所以想实现自己的shader效果不是很难。然而,NiStanderMaterial的shade tree比较复杂,总共高达6000行代码以上,过程非常复杂,这就是说,如果想实现自己的材质处理流程也要付出相当大的工作量,阴影系统虽然实现了较低的耦合度,但是实现过于复杂,不够简洁高效。
作者:叶起涟漪