Big Chimpin!
Avatar

Bian Jiang

Vcard Download vCard   what is this?
Rss_icon

Recent Activity


Filter by:
All
  • 99%的人都不知道手机的隐藏功能

    1、当不喜欢别人打搅自己的时候,又不想关机,给大家一个办法,使自己的号码变成空号。
    输入 **21*999999# ,按打电话时候的拨出键 。当别人拨打你的电话时候就你的号码就变成空号了。
    再输入 ##21#,在按拨出键 又正常了。
    PS:这个功能移动公司是不收费的,应该是手机号码的一个漏洞。可以当场实验,如果觉得好用就转给你好友,以后不想接听那个令你伤心的她/或他的电话的时候,就用得到了。

    2、 识别手机质量的好坏
    先按*#06#,然后在你的手机上会出现一个序列号给你,你就数到第七个和第八个数。
    假如你的第七个和第八个数是下面对应的两个数,那么你的手机质量的好坏就确定了。
    0,2 or 2,0,是很差的手机
    0,8 or 8,0,是好一点儿的手机
    0,1 or 1,0,是非常好的手机
    0,0 代表是原产公司生产的,是质量最好的手机(原装手机)
    1,3 代表是阿赛拜疆生产的,是非常非常差的手机

    3、隐形的备用电池
    你的手机电量不足了,为了让它能够继续使用,按*3370#键,手机会重新启动,启动完毕后,你就会发现电量增加了50%。这部分隐藏的备用电量用完了你就必须得充电了,再次充电的时候,隐形的备用电池也同时充电,下次电量低的时候又可以用这个方法。知道这个在紧急情况下如果手机电量不足非常管用。

    4、车用遥控器落在车里了?
    你的车用遥控能打开吧?如果可以,在你有一天将车用遥控器落在车里而且备用的遥控又在家里的话,你会发现有个手机真方便,用手机拨通家里人的手机,将你的手机拿在离车门一英尺的地方,同时家里人拿着遥控器在他的手机旁边按响遥控器上的开锁键,这边你的车门就可以打开了。这个方法不管你把车开得离家有多远都奏效。 (SEVEN亲身试过了,很有效,很神奇哦)

    5、紧急情况
    全世界的手机都可以拨打的共同紧急救援号码是112,加入你发现自己所在的地区无手机信号覆盖,同时你又遇到了紧急状况,用你的手机拨打112准没错,因为这时候你的手机会自动搜索所有可用的网络并建立起紧急呼叫。特别有趣的是,即使你的手机是在键盘锁定的状态,你同样可以拨打112。试试吧!

    6、手机被偷了?
    有个办法让小偷也用不了,嘿嘿!查看手机的序列号,只需键入* # 0 6 #, 15位序列号会出现在手机屏幕上,全世界的每一台手机都有一个独一无二的序列号,把这个序列号记录下来并保存好。有一天如果你的手机不幸被偷了,打电话给手机提供商,并提供你的手机序列号,他们会帮你把手机屏蔽,这样即使小偷换了SIM卡,仍然无法使用,你的手机对小偷来说变得一无是处。如果全世界每个手机持有者都这么做,那么偷手机就没有意义了。在澳洲,警方甚至建立了一个被盗手机数据库,如果你的手机被找到了,就可以归还给你了。

    7、手机电池不要等到没电才充电。
    一般我们都会有一种想法就是手机的电池电力要全部放完再充电比较好基本上是没错的,因为我们在以前使用的充电电池大部分是镍氢(NiH)电池,而镍氢电池有所谓的记忆效应若不放完电再充的话会导致电池寿命急速减少。因此我们才会用到最后一滴电才开始充电。但现在的手机及一般IA产品大部分都用锂(Li)电池,而锂电池的话就没有记忆效应的问题。若大家还是等到全部用完电后再充的话反而会使得锂电池内部的化学物质无法反应而寿命减少。最好的方法就是没事就充电让它随时随地保持最佳满格状态,这样你的电池就可用的又长又久喔。这是从厂商那得到的讯息,并经过本身测试而得。

    8、当手机正在充电时,请勿接电话!!
    原因是手机在充电时,来电接听的话会有潜在的危险。印度有一个31岁在保险公司任职业务经理的年轻人,十几天前在手机还接着充电器的时候接听电话,过了几秒大量的电流经过手机,这个年轻人被摔落到地面,家人发现时,手指烧伤,心跳微弱,并且已经失去意识。经紧急送到医院后,医生宣布到院死亡。行动电话是目前大家最常使用的现代发明。然而,我们也必须要警觉到仪器致死的危险。

    9、手机剩一格时不要使用
    收讯满格与只剩一格时相比,发射强度竟然相差1000倍以上.所以……常讲手机的人……要注意哦……^0^、昨天从一位手机商那儿获得一项很重要的讯息,那就是当你发现手机的收讯强度只剩下一格的时候,宁可挂断不谈或者是改用公用电话.千万不要再滔滔不绝、口沫横飞、浓情蜜意、欲罢不能、没完没了…为什幺呢?大家都知道手机的电磁波一直是让人担心的问题.而手机的设计为了在收讯较差的地区仍能保有相当的通话质量,会加强手机的电磁波发射强度.当收讯满格与只剩一格时相比,发射强度竟然相差1000倍以上.电磁波强度高达0.6W(瓦特).0.6W究竟有多强呢?我无法具体描述它对你的脑袋会有什幺不良影响,但可以换成两个例子来比较:

    1)把喇叭直径约4公分左右的小型收音机音量开到最大然后贴在耳朵上,那样的噪音能量一般为0.25W,不到0.5W。   2)把手指头放在输出强度0.1W的雷射光前面(相当于光纤网络的?D干线能量)几秒钟内你会有灼痛的感觉,你能长时间忍受上述这两种状况吗? 那你又如何确认0.6W的电磁波紧贴在你的耳朵上会没事呢?

    10.12593+电话号码=陷阱
    你是不是把外地朋友的电话用17951+电话号码的格式储存在电话号码本里?而不是单独拨?那么收费就会从0.39元每分钟变成1.3元每分钟.我也向 1860查询过了他们的解释是如果储存在电话号码本里?系统将无法识别。所以无法获得资费优惠,必须每次在键盘上直接按12xxx。神州行用户如此?动感地带用户,全球通也一样。如果你是一个中国移动用户,当你知道中国移动为你设置以下的陷阱的时候,便不再惊讶于你的话费为何会像长了翅膀一样的飞走。用 12593+电话号码可以优惠,但如果你预先将“12593+电话号码“存在手机的电话本,使用的时候调出来然后拔打出去,这时中国移动不承认你使用了 12593这种优惠的拔打方式,而按照直接拔打的方式计费。如果你是在漫游,两种计费方式可以相差7倍之多!当我得知如此计费之后,我真的不知如何表达我的愤怒,后来打10086咨询时,如果不是主动冶询问这个问题,工号为6608的小姐根本就不告诉我这样的计费。

    11、手机费的寄生虫
    手机莫名其妙定置了无用短信,强烈建议大家都看一下自己有没有中招,最简单方法退订每月偷你手机费的寄生虫!中国移动在3.15被迫推出一项新业务,如果您是中国移动的手机用户,键入数字“0000“,发送短信至10086,数秒钟内将自动回复一条短信列表,显示您的手机上究竟订制了哪些短信服务,究竟是哪些短信服务商明着、暗着每月扣除您的手机费;键入数字“00000“,发送短信至186201,即可退订所有短信服务。

    12、不要赶着凑正好1分钟
    我们打电话的时候常常会为了正好赶在1:00前结束而庆幸,但其实并不是这样的,据一位中国移动的工作人员说,其实在你通话到0:55的时候就已经算一分钟了,所以0:55~1:00的通话时间其实是算你2分钟的钱~

    13、手机一进水,
    请切记不要作任何按键动作,尤其是关机(一按任何动作,水马上会跟着电路板流串),正确的方法为马上打开外盖,直接将电池拿下,直接强迫断电,可保主机板不被水侵袭。这个常识非常重要,故转告各位,使大家的手机可用久一点。学一学吧!以后以备不时之需啊!

    14、如何让手机电池起死回生
    当你的行动电话电池使用时间变短(记忆效应或老化)时,你是否会再买一颗电池来更换呢?下次当你碰到这种情况时请省下你的钱,告诉你一个很有效的方法不妨试试看:
    1)把电池用报纸包起来再放进塑料袋裹包好放入冷冻库三天(报纸可吸收多余水份)
    2)三天后取出常温下放二天
    3)二天后将电池充电,充饱后装进行动电话裹测试(预估可救回80%-90%)

    15、给你的手机做个CPR吧!
    手机是否常断电?或是明明充饱了电没多久就又没电了?一定怀疑过是不是手机的寿命终了?别担心,它只是一时“心跳停止”,只要一块小小的橡皮擦就能起死回生了!把电池取出后用橡皮擦把电池上的接点(黄铜片)擦干净,再装回手机上,你会发现真是太神奇了!它竟然活过来了!还像颗新的呢!真的很有用,提供大家做参考!
    16、教你如何消除手机屏幕刮痕
    大家是否常常会遇到手机屏幕有刮痕而不知如何处理的情况呢?告诉大家一个好用的秘方....(前几天在电视上看到的) 把牙膏适量挤在湿抹布上后用力在手机屏幕刮伤处前后左右来回用力涂匀..... 你将发现.....手机的屏幕刮痕会因此而消失....很神奇吧...!! 更神奇的事....在用干净的抹布或卫生纸擦干净后..手机屏幕还会变得更亮哦....
    台大化学教授表示:原理为牙膏它只是刷牙的辅助用品,具有磨擦作用(修补作用) 和去除菌斑,清洁抛光牙面,因此使用在手机屏幕上面会有同样的效果

    如果您的阅读器看不到视频,请移步原文链接: http://ta.md/703/
    人人都是艺术家 Copyright © 2010 - 2012 分享国内外精彩网事。
    更多精彩欢迎您订阅 http://feeds.feedburner.com/tamd,欢迎网友投稿推荐文章。
    广告一则: .me 域名全国最低价,首年仅需40元,续费130元促销中! )

  • New Talk and Tutorials

    Rob Pike recently gave a talk at Stanford's Computer Systems Colloquium (EE380). Titled "Another Go at Language Design", the presentation gives an overview of the itches Go was built to scratch, and how Go addresses those problems. You can view a video stream of the talk, and download the slides.

    Last week's release included a code lab, "Writing Web Applications," that details the construction of a simple wiki program. It is a practical introduction to some fundamental Go concepts, and the first of a series of Go code labs.

    Lastly, we are often asked "How do Go packages work?" It's easier to show than to explain, so I put together a Go Packages screen cast that demonstrates the process of writing, building, installing, and redistributing Go packages. I hope to post more of these covering a variety of Go programming topics to the gocoding YouTube channel in the near future.

  • 人民是靠不住的,军警也是靠不住的

    作者:涂子方 | 评论(6) | 标签:时事, 社会, 政治, 民情

    虽然说“枪杆子里面出政权”,但有时候也有例外,而且这种例外在现代社会似乎越来越多。

    官媒中新社北京4月8日电,吉尔吉斯斯坦共和国反对派领导人、前外长罗萨·奥坦巴耶娃8日宣布,她领导的反对派已经成立临时政府,接管国家政权。

    奥坦巴耶娃要求吉现任总统库尔曼别克·巴基耶夫辞职。她声称已经成立了一个由她本人负责的临时政府接管政权。她表示:“临时政府将接管半年,直到我们起草一份新宪法,并创造能够确保自由公正地选举领导人的环境。”

    昨晚另一官媒CCTV1的新闻联播对此消息的报道也很谨慎,电视中没有播出吉尔吉斯首都比什凯克近日发生的大规模群众骚乱场面,此前在报道伊朗总统选举发生的群众骚乱时也是如此。

    不知出于什么原因,中国官媒对全世界发生的大规模群众骚乱都谨慎报道,大概是有关部门怕这玩意儿像猪流感一样的会传染。

    而网上的视频、图片和港澳电视的报道就要客观得多:

    吉尔吉斯军警开枪射杀示威群众;

    血染街头、倒卧在地的死伤群众;

    奋不顾身的示威群众围抢军警的武器;

    落单的军警被群众按在地上拳打脚踢;

    夺枪了的群众向军警开火;

    政府办公大楼、总统府、国家电视台等要地被示威群众占领;

    建筑物、被推翻的汽车在烈火中熊熊燃烧;

    ……。

    最后的情况是,军警即使奉命开枪打死打伤了近千人,但还是没有将反对派群众的示威镇压下去,反而激起了更大规模的群众反抗,于是总统巴基耶夫逃到不知什么地方躲了起来,看来现政府垮台已成定局。

    巴基耶夫政府为何会垮台?估计咱官媒照例会宣传这是吉尔吉斯国内外敌对势力操纵的结果,也许是,人家国家的事情,谁知道呢。

    但毛主席教导我们说:“内因是变化的根据,外因是变化的条件,外因通过内因而起作用”。苍蝇不叮无缝的蛋,关键还是巴基耶夫集团自己的事情没有做好,不然吉尔吉斯的群众也不会冒着枪林弹雨,前赴后继的用生命和鲜血来被人家操纵。

    网上有报道说,去年年底,总统巴基耶夫的小儿子、32岁的马克西姆·巴基耶夫被任命为发展、投资和革新部部长,直接控制了该国的财政大权。吉政府从今年1月起大幅提高国内电力和供暖价格,其中电力价格几乎提高了2倍。与此同时,总统巴基耶夫开始推行政府结构和宪法改革,人们普遍认为,这些举措将强化总统权力,加剧独裁倾向。

    吉尔吉斯发生的事情告诫全世界所有的当政者:不要明目张胆的安排官二代担任要职;不要让物价飞涨;不要搞逆民意的体制改革。

    人民是靠不住的。

    军警也是靠不住的。

    涂子方的最新更新:
  • 要思考好制度是如何形成的 / 2010-04-08 12:44 / 评论数(1)
  • 总算有人说了真话 / 2010-04-07 11:19 / 评论数(4)
  • 由王家岭矿难所想到的 / 2010-04-06 11:00 / 评论数(6)
  • 慎终追远话清明 / 2010-04-05 12:39 / 评论数(0)
  • 好事不出门,坏事传千里 / 2010-04-04 11:42 / 评论数(8)
  • 为什么优秀开发者进入Google后就不参与开源了

    很多优秀的开发者在进入Google之前都是非常活跃的开源贡献者,但是进入Google之后往往就销声匿迹了,包括嘲笑了此现象的Memcached 作者Brad在进入Google之后也无法逃脱此规律。Brad在最近一篇文章Contributing to Open Source projects谈到相关原因

    • 许多优秀开发者都很喜欢编程,他们喜欢研究有趣有挑战的问题,并不特别在意这些项目是否开源。
    • 大家都太忙,Google似乎用尽了每个人的空余时间。并不是说Google强迫大家一天到晚都在干活,而是由于Google里面太多有趣的东西做了,Brad经常挂在口头一句话就是“现在手头有7个属于20%空余时间的项目”。
    • Google的开发环境太好了,源代码控制,build系统,code review工具,debugger调试工具,profiler调优工具,submit queues, continuous builds, test bots, 文档以及所有相关的自动化工具及流程非常完善。因此很容易hack任何项目,在任何地方,或者给任何人提交patch,并且值得一提的是,很容易找到对应的人或者list去提交patch。通常说来,提交patch是参与特性讨论,表达诚意的最好方式,即使你的patch是有问题的。

    从上面尤其是第3点来看,Google确实是技术人员的理想环境。

    Similar Posts:
  • Google Go:初级读本

    from http://www.infoq.com/cn/articles/google-go-primer

    作者 Samuel Tesla 译者 黄璜 发布于 2010年4月2日 上午12时5分

    Google最近发布新型的编程语言,Go。它被设计为将现代编程语言的先进 性带入到目前仍由C语言占统治地位的系统层面。然而,这一语言仍在试验阶段并在不断演变。

    Go语言的设计者计划设计一门简单、高效、安全和 并发的语言。这门语言简单到甚至不需要有一个符号表来进行词法分析。它可以快速地编译;整个工程的编译时间在秒以下的情况是常事。它具备垃圾回收功能,因 此从内存的角度是安全的。它进行静态类型检查,并且不允许强制类型转换,因而对于类型而言是安全的。同时语言还内建了强大的并发实现机制。

    阅读Go

    Go的语法传承了与C一样的风格。程序由函数组成,而函数体是一系列的语句序列。一段代码块用花括号括起来。这门语言保留有限的关键字。表达式使用 同样的中缀运算符。语法上并无 太多出奇之处。

    Go语言的作者在设计这一语言时坚持一个单一的指导原则:简单明了至上。一些新的语法构件提供了简明地表达一些约定俗成的概 念的方式,相较之下用C表达显得冗长。而其他方面则是针对几十年的使用所呈现出来的一些不合理的语言选择作出了改进。

    变量声明

    变量是如下声明的:

    var sum int // 简单声明
    var total int = 42 // 声明并初始化

    最值得注意的是,这些声明里的类型跟在变量名的后面。乍一看有点怪,但这更清晰明了。比如,以下面这个C片段来说:

    int* a, b;

    它并明了,但这里实际的意思是a是一个指针,但b不是。如果要将两者都声明为指针,必须要重复星号。然后在Go语言里,通过如下方式可以将两者都 声明为指针:

    var a, b *int

    如果一个变量初始化了,编译器通常能推断它的类型,所以程序员不必显式的敲出来:

    var label = "name"

    然而,在这种情况下var几乎显得是多余了。因此,Go的作者引入了一个新的运算符来 声明和初始化一个新的变量:

    name := "Samuel"

    条件语句

    Go语言当中的条件句与C当中所熟知的if-else构造一样,但条件不需要被打包在括号内。这样可以减少阅读代码时的视觉上的混乱。

    括号并不是唯一被移去的视觉干扰。在条件之间可以包括一个简单的语句,所以如下的代码:

    result := someFunc();
    if result > 0 {
    /* Do something */
    } else {
    /* Handle error */
    }

    可以被精简成:

    if result := someFunc(); result > 0 { 
    /* Do something */
    } else {
    /* Handle error */
    }

    然而,在后面这个例子当中,result只在条件块内部有效——而前者 中,它在整个包含它的上下文中都是可存取的。

    分支语句

    分支语句同样是似曾相识,但也有增强。像条件语句一样,它允许一个简单的语句位于分支的表达式之前。然而,他们相对于在C语言中的分支而言走得更远。

    首先,为了让分支跳转更简明,作了两个修改。情况可以是逗号分隔的列表,而fall-throuth也不再是默认的行为。

    因此,如下的C代码:

    int result;
    switch (byte) {
    case 'a':
    case 'b':
    {
    result = 1
    break
    }

    default:
    result = 0
    }

    在Go里就变成了这样:

    var result int
    switch byte {
    case 'a', 'b':
    result = 1
    default:
    result = 0
    }

    第二点,Go的分支跳转可以匹配比整数和字符更多的内容,任何有效的表达式都可以作为跳转语句值。只要它与分支条件的类型是一样的。

    因此如下的C代码:

    int result = calculate();
    if (result < 0) {
    /* negative */
    } else if (result > 0) {
    /* positive */
    } else {
    /* zero */
    }

    在Go里可以这样表达:

    switch result := calculate(); true {
    case result < 0:
    /* negative */
    case result > 0:
    /* positive */
    default:
    /* zero */
    }

    这些都是公共的约定俗成,比如如果分支值省略了,就是默认为真,所以上面的代码可以这样写:

    switch result := calculate(); {
    case result < 0:
    /* negative */
    case result > 0:
    /* positive */
    default:
    /* zero */
    }

    循环

    Go只有一个关键字用于引入循环。但它提供了除do-while外C语言当中所有可用的循环方式。

    条件

    for a > b { /* ... */ }

    初始,条件和步进

    for i := 0; i < 10; i++ { /* ... */ }

    范围

    range语句右边的表达式必须是arrayslicestring或者map, 或是指向array的指针,也可以是channel

    for i := range "hello" { /* ... */ }

    无限循环

    for { /* ever */ }

    函数

    声明函数的语法与C不同。就像变量声明一样,类型是在它们所描述的术语之后声明的。在C语言中:

    int add(int a, b) { return a + b }

    在Go里面是这样描述的:

    func add(a, b int) int { return a + b }

    多返回值

    在C语言当中常见的做法是保留一个返回值来表示错误(比如,read()返回0),或 者保留返回值来通知状态,并将传递存储结果的内存地址的指针。这容易产生了不安全的编程实践,因此在像Go语言这样有良好管理的语言中是不可行的。

    认识到这一问题的影响已超出了函数结果与错误通讯的简单需求的范畴,Go的作者们在语言中内建了函数返回多个值的能力。

    作为例子,这个函数将返回整数除法的两个部分:

    func divide(a, b int) (int, int) {
    quotient := a / b
    remainder := a % b
    return quotient, remainder
    }

    有了多个返回值,有良好的代码文档会更好&mdash;&mdash;而Go允许你给返回值命名,就像参数一样。你可以对这些返回的变量赋值,就像其它的变量一样。所以我们可以重写divide:

    func divide(a, b int) (quotient, remainder int) {
    quotient = a / b
    remainder = a % b
    return
    }

    多返回值的出现促进了"comma-ok"的模式。有可能失败的函数可以返回第二个布尔结果来表示成功。作为替代,也可以返回一个错误对象,因此像下面这样的代码也就不见怪了:

    if result, ok := moreMagic(); ok {
    /* Do something with result */
    }

    匿名函数

    有了垃圾收集器意味着为许多不同的特性敞开了大门&mdash;&mdash;其中就包括匿名函数。Go为声明匿名函数提供了简单的语法。像许多动态语言一样,这些函数在它们被定义的范围内创建了词法闭包。

    考虑如下的程序:

    func makeAdder(x int) (func(int) int) {
    return func(y int) int { return x + y }
    }

    func main() {
    add5 := makeAdder(5)
    add36 := makeAdder(36)
    fmt.Println("The answer:", add5(add36(1))) //=> The answer: 42
    }

    基本类型

    像C语言一样,Go提供了一系列的基本类型,常见的布尔,整数和浮点数类型都具备。它有一个Unicode的字符串类型和数组类型。同时该语言还引入了两 种新的类型:slicemap

    数组和切片

    Go语言当中的数组不是像C语言那样动态的。它们的大小是类型的一部分,在编译时就决定了。数组的索引还是使用的熟悉的C语法(如 a[i]),并且与C一样,索引是由0开始的。编译器提供了内建的功能在编译时求得一个数组的长度 (如 len(a))。如果试图超过数组界限写入,会产生一个运行时错误。

    Go还提供了切片(slices),作为数组的变形。一个切片(slice)表示一个数组内的连续分段,支持程序员指定底层存储的明确部分。构建一个切片 的语法与访问一个数组元素类似:

    /* Construct a slice on ary that starts at s and is len elements long */
    s1 := ary[s:len]

    /* Omit the length to create a slice to the end of ary */
    s2 := ary[s:]

    /* Slices behave just like arrays */
    s[0] == ary[s] //=> true

    // Changing the value in a slice changes it in the array
    ary[s] = 1
    s[0] = 42
    ary[s] == 42 //=> true

    该切片所引用的数组分段可以通过将新的切片赋值给同一变量来更改:

    /* Move the start of the slice forward by one, but do not move the end */
    s2 = s2[1:]

    /* Slices can only move forward */
    s2 = s2[-1:] // this is a compile error

    切片的长度可以更改,只要不超出切片的容量。切片s的容量是数组从s[0]到数组尾端的大小,并由内建的cap()函数返回。一个切片的长度永远不能超出它的容量。

    这里有一个展示长度和容量交互的例子:

    a := [...]int{1,2,3,4,5} // The ... means "whatever length the initializer has"
    len(a) //=> 5

    /* Slice from the middle */
    s := a[2:4] //=> [3 4]
    len(s), cap(s) //=> 2, 3

    /* Grow the slice */
    s = s[0:3] //=> [3 4 5]
    len(s), cap(s) //=> 3, 3

    /* Cannot grow it past its capacity */
    s = s[0:4] // this is a compile error

    通常,一个切片就是一个程序所需要的全部了,在这种情况下,程序员根本用不着一个数组,Go有两种方式直接创建切片而不用引用底层存储:

    /* literal */
    s1 := []int{1,2,3,4,5}

    /* empty (all zero values) */
    s2 := make([]int, 10) // cap(s2) == len(s2) == 10

    Map类型

    几乎每个现在流行的动态语言都有的数据类型,但在C中不具备的,就是dictionary。Go提供了一个基本的dictionary类型叫做map。下 面的例子展示了如何创建和使用Go map:

    m := make(map[string] int) // A mapping of strings to ints

    /* Store some values */
    m["foo"] = 42
    m["bar"] = 30

    /* Read, and exit program with a runtime error if key is not present. */
    x := m["foo"]

    /* Read, with comma-ok check; ok will be false if key was not present. */
    x, ok := m["bar"]

    /* Check for presence of key, _ means "I don't care about this value." */
    _, ok := m["baz"] // ok == false

    /* Assign zero as a valid value */
    m["foo"] = 0;
    _, ok := m["foo"] // ok == true

    /* Delete a key */
    m["bar"] = 0, false
    _, ok := m["bar"] // ok == false

    面向对象

    Go语言支持类似于C语言中使用的面向对象风格。数据被组织成structs,然后定义操作这些structs的函数。类似于Python,Go语言提供 了定义函数并调用它们的方式,因此语法并不会笨拙。

    Struct类型

    定义一个新的struct类型很简单:

    type Point struct {
    x, y float64
    }

    现在这一类型的值可以通过内建的函数new来分配,这将返回一个指针,指向一块内存单元,其所占内存槽初始化为零。

    var p *Point = new(Point)
    p.x = 3
    p.y = 4

    这显得很冗长,而Go语言的一个目标是尽可能的简明扼要。所以提供了一个同时分配和初始化struct的语法:

    var p1 Point = Point{3,4}  // Value
    var p2 *Point = &Point{3,4} // Pointer

    方法

    一旦声明了类型,就可以将该类型显式的作为第一个参数来声明函数:

    func (self Point) Length() float {
    return math.Sqrt(self.x*self.x + self.y*self.y);
    }

    这些函数之后可作为struct的方法而被调用:

    p := Point{3,4}
    d := p.Length() //=> 5

    方法实际上既可以声明为值也可以声明为指针类型。Go将会适当的处理引用或解引用对象,所以既可以对类型T,也可以对类型*T声明方式,并合理地使用它们。

    让我们为Point扩展一个变换器:

    /* Note the receiver is *Point */
    func (self *Point) Scale(factor float64) {
    self.x = self.x * factor
    self.y = self.y * factor
    }

    然后我们可以像这样调用:

    p.Scale(2);
    d = p.Length() //=> 10

    很重要的一点是理解传递给MoveToXY的self和其它的参数一样,并且是传递,而不是引用传递。如果它被声明为Point,那么在方法内修改的struct就不再跟调用方的一样&mdash;&mdash;值在它们传递给方法的时候被 拷贝,并在调用结束后被丢弃。

    接口

    像Ruby这样的动态语言所强调面向对象编程的风格认为对象的行为比哪种对象是动态类型(duck typing)更为重要。Go所 带来的一个最强大的特性之一就是提供了可以在编程时运用动态类型的思想而把行为定义的合法性检查的工作推到编译时。这一行为的名字被称作接口

    定义一个接口很简单:

    type Writer interface {
    Write(p []byte) (n int, err os.Error)
    }

    这里定义了一个接口和一个写字节缓冲的方法。任何实现了这一方法的对象也实现了这一接口。不需要像Java一样进行声明,编译器能推断出来。这既给予了动态类型的表达能力又保留了静态类型检查的安全。

    Go当中接口的运作方式支持开发者在编写程序的时候发现程序的类型。如果几个对象间存在公共行为,而开发者想要抽象这种行为,那么它就可以创建一个接口并使用它。

    考虑如下的代码:

    // Somewhere in some code:
    type Widget struct {}
    func (Widget) Frob() { /* do something */ }

    // Somewhere else in the code:
    type Sprocket struct {}
    func (Sprocket) Frob() { /* do something else */ }

    /* New code, and we want to take both Widgets and Sprockets and Frob them */
    type Frobber interface {
    Frob()
    }

    func frobtastic(f Frobber) { f.Frob() }

    需要特别指出的很重要的一点就是所有的对象都实现了这个空接口:

    interface {}

    继承

    Go语言不支持继承,至少与大多数语言的继承不一样。并不存在类型的层次结构。相较于继承,Go鼓励使用组合和委派,并为此提供了相应的语法甜点使其更容易接受。

    有了这样的定义:

    type Engine interface {
    Start()
    Stop()
    }

    type Car struct {
    Engine
    }

    于是我可以像下面这样编写:

    func GoToWorkIn(c Car) {
    /* get in car */

    c.Start();

    /* drive to work */

    c.Stop();

    /* get out of car */
    }

    当我声明Car这个struct的时候,我定义了一个匿名成员。这是一 个只能被其类型识别的成员。匿名成员与其它的成员一样,并有着和类型一样的名字。因此我还可以写成c.Engine.Start()。 如果Car并没有其自身方法可以满足调用的话,编译器自动的会将在Car上的调用委派给它的Engine上面的方法。

    由匿名成员提供的分离方法的规则是保守的。如果为一个类型定义了一个方法,就使用它。如果不是,就使用为匿名成员定义的方法。如果有两个匿名成员都提供一 个方法,编译器将会报错,但只在该方法被调用的情况下。

    这种组合是通过委派来实现的,而不是继承。一旦匿名成员的方法被调用,控制流整个都被委派给了该方法。所以你无法做到和下面的例子一样来模拟类型层次:

    type Base struct {}
    func (Base) Magic() { fmt.Print("base magic") }
    func (self Base) MoreMagic() {
    self.Magic()
    self.Magic()
    }

    type Foo struct {
    Base
    }
    func (Foo) Magic() { fmt.Print("foo magic") }

    当你创建一个Foo对象时,它将会影响Base的两个方法。然而,当你调用MoreMagic时, 你将得不到期望的结果:

    f := new(Foo)
    f.Magic() //=> foo magic
    f.MoreMagic() //=> base magic base magic

    并发

    Go的作者选择了消息传递模型来作为推荐的并发编程方法。该语言同样支持共享内存,然后作者自有道理:

    不要通过共享内存来通信,相反,通过通信来共享内存。

    该语言提供了两个基本的构件来支持这一范型:goroutineschannels

    Go例程

    Goroutine是轻量级的并行程序执行路径,与线程,coroutine或者进程类似。然而,它们彼此相当不同,因此Go作者决定给它一个新的名字并 放弃其它术语可能隐含的意义。

    创建一个goroutine来运行名为DoThis的函数十分简单:

    go DoThis() // but do not wait for it to complete

    匿名的函数可以这样使用:

    go func() {
    for { /* do something forever */ }
    }() // Note that the function must be invoked

    这些goroutine将会通过Go运行时而映射到适当的操作系统原语(比如,POSIX线程)。

    通道类型

    有了goroutine,代码的并行执行就容易了。然而,它们之间仍然需要通讯机制。Channel提供一个FIFO通信队列刚好能达到这一目的。

    以下是使用channel的语法:

    /* Creating a channel uses make(), not new - it was also used for map creation */
    ch := make(chan int)

    /* Sending a value blocks until the value is read */
    ch <- 4

    /* Reading a value blocks until a value is available */
    i := <-ch

    举例来说,如果我们想要进行长时间运行的数值计算,我们可以这样做:

    ch := make(chan int)

    go func() {
    result := 0
    for i := 0; i < 100000000; i++ {
    result = result + i
    }
    ch <- result
    }()

    /* Do something for a while */

    sum := <-ch // This will block if the calculation is not done yet
    fmt.Println("The sum is:", sum)

    channel的阻塞行为并非永远是最佳的。该语言提供了两种对其进行定制的方式:

    1. 程序员可以指定缓冲大小&mdash;&mdash;想缓冲的channel发送消息不会阻塞,除非缓冲已满,同样从缓冲的channel读取也不会阻塞,除非缓冲是空的。
    2. 该语言同时还提供了不会被阻塞的发送和接收的能力,而操作成功是仍然要报告。
    /* Create a channel with buffer size 5 */
    ch := make(chan int, 5)

    /* Send without blocking, ok will be true if value was buffered */
    ok := ch <- 42

    /* Read without blocking, ok will be true if a value was read */
    val, ok := <-ch

    Go提供了一种简单的机制来组织代码:包。每个文件开头都会声明它属于哪一个包,每个文件也可以引入它所用到的包。任何首字母大写的名字是由包导出的,并可以被其它的包所使用。

    以下是一个完整的源文件:

    package geometry

    import "math"

    /* Point is capitalized, so it is visible outside the package. */

    type Point struct {

    /* the fields are not capitalized, so they are not visible
    outside of the package */

    x, y float64
    }

    /* These functions are visible outside of the package */

    func (self Point) Length() float64 {
    /* This uses a function in the math package */
    return math.Sqrt(self.x*self.x + self.y*self.y)
    }

    func (self *Point) Scale(factor float64) {
    self.setX(self.x * factor)
    self.setY(self.y * factor)
    }

    /* These functions are not visible outside of the package, but can be
    used inside the package */

    func (self *Point) setX(x float64) { self.x = x }
    func (self *Point) setY(y float64) { self.y = y }

    缺失

    Go语言的作者试图将代码的清晰明确作为设计该语言作出所有决定的指导思想。第二个目标是生产一个编译速度很快的语言。有了这两个标准作为方向,来 自其它语言的许多特性就不那么适合了。许多程序员会发现他们最爱的语言特性在Go当中不存在,确实,有很多人也许会觉得Go语言由于缺乏其它语言所共有的 一些特性,还不太可用。

    这当中两个缺失的特性就是异常和泛型,两者在其它语言当中都是非常有用的。而它们目前都不是Go的一分子。但因为该 语言仍处于试验阶段,它们有可能最终会加入到语言里。然而,如果将Go与其它语言作比较的话,我们应当记住Go是打算在系统编程层面作为C语言的替代。明 白这一点的话,那么缺失的这许多特性倒也不是很大的问题了。

    最后,因为这一语言才刚刚发布,因此它没有什么类库或工具可以用,也没有Go语 言的集成编程环境。Go语言标准库有些有用的代码,但这与更为成熟的语言比 起来仍还是很少的。

    查看英文原文Google Go: A Primer


    感谢马国耀对本文的审校。

    给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

  • 用JavaScript玩转计算机图形学(二)基本光源

    阅读: 989 评论: 11 作者: Milo Yip 发表于 2010-04-02 20:44 原文链接

    上一篇介绍了简单的光线追踪,凑合了临时用的光源去渲染效果。这次将讲解三种基本光源,及一些背景理论。过分简化的教材和现成API(OpenGL/Direct3D等)可能会做成一些错误理解。在此,希望文章能简单之余,又不失背后理论。读者明白之后,可把概念简化,或按实际情况调整。

    本文代码可在此下载(10KiB)。

    读者若喜欢本文,可按推荐按钮以示鼓励。如果写得不够清楚,或有错误之处,可留言相告。

    在物理上,光(light)可以视为电磁波(electromagnetic wave)或光子(photon)。在计算机图形学的领域里,通常只会用到光的部份物理性质,例如假设光是直线前进(不受因引力影响),忽略光的速度,通常不考虑衍射(diffraction)、干涉(interference )等等(好吧,也不考虑量子行为☺)。因为,计算机图形学不是理物学,最终目标(笔者认为)只是要渲染视觉上美的事物,只要模拟到某个合适层次的模型,有时候还为了美观而采用非物理/非真实的方式。

    方向光源

    光源(light source)放射(emit)光,而非散射(scatter)或吸收(absorb)光。

    最简单的光源模型,是方向光源(directional light),又称平行光源。这种光源假设光在无限远放射,在任何位置,放射方向都是一致的,可以模拟类似太阳的光线(虽然实际上太阳并非无限远)。

    方向光源的方向,通常用光向量(light vector)去表示。为方便计算,通常是单位向量,并且和光的放射方向相反

    方向光源的另一个属性,是指定其照明的量。量度光的科学叫幅射度量学(radiometry),本文暂且略过其细节。这里只用到光的其中一个量度方式,就是每秒通过每单位面积平面的光子总能量,称为幅照度(irradiance)。

    光的颜色,是由不同频率的光波及其频谱,在人类视觉上形成的。详细内容又涉及光度测定(photometry)、比色法(colorimetry)、视觉感知(visual perception)、甚至哲学等,有机会再谈。这里只使用常见的红绿蓝三个颜色通道(color channel)。光源的幅照度也可以用这三通道来描述,因此,仍可用前文的Color类来描述幅照度。但注意,光的幅照度范围是零到无限大,并不是[0,1]或[0,255]。光的"颜色"和材质的"颜色"并非同一个概念,关于这点,读者可思考以下一个简单命题

    客观上,有接近白色的纸,但没有白色的光

    关于这个命题,和材质的"颜色",将于下回分解。

    阴影

    一个光源的阴影(shadow),是因不透明障碍物,以致其不能到达的地方。我们可使用已有的几何相交功能,去检测某一位置,在方向上有否障碍物。光源追踪方法在阴影处理上很简单,光删化方法就复杂得多。

    实现DirectionalLight类

    在编程时,需要为不同种类的光源设计一个共通接口。渲染器要从光源取得,在某个空间位置,其光向量和幅照度。在此,定义光源有一成员函数sample(scene, position),并传回一个LightSample对象:

    LightSample = function(L, EL) { this.L = L; this.EL = EL; };
    LightSample.zero = new LightSample(Vector3.zero, Color.black);
    

    以下是方向光源的代码,预设使用阴影:

    DirectionalLight = function(irradiance, direction) { this.irradiance = irradiance; this.direction = direction; this.shadow = true; };
    
    DirectionalLight.prototype = {
        initialize: function() { this.L = this.direction.normalize().negate(); },
    
        sample: function(scene, position) {
            // 阴影测试
            if (this.shadow) {
                var shadowRay = new Ray3(position, this.L);
                var shadowResult = scene.intersect(shadowRay);
                if (shadowResult.geometry)
                    return LightSample.zero;
            }
    
            return new LightSample(this.L, this.irradiance);
        }
    };
    

    渲染幅照度

    sample()函数可以传回相对光向量的幅照度,但物体表面并不一定垂直于光向量。光源越接近平面,每面积接受的能量就越少。可以想像太阳在中午是最亮的,日出日落时是最暗的。如下图所示,平面法向量方向的面积,是光向量方向的面积的倍,而幅照度则为其倒数,即倍。

    因此,设光源的光向量方向幅照度为,平面接收到的幅照度为

     

    幅照度是能量,可以累加,所以多个光源下,平面接收到的总幅照度为

    以下的简单代码,测试一个方向光源在场境中的总幅照度:

    function renderLight(canvas, scene, lights, camera) {
        // 从canvas取得imgdata和pixels,跟之前的代码一样
        // ...
    
        scene.initialize();
        for (var k in lights)
            lights[k].initialize();
        camera.initialize();
    
        var i = 0;
        for (var y = 0; y < h; y++) {
            var sy = 1 - y / h;
            for (var x = 0; x < w; x++) {
                var sx = x / w;
                var ray = camera.generateRay(sx, sy);
                var result = scene.intersect(ray);
                if (result.geometry) {
                    var color = Color.black;
                    for (var k in lights) {
                        var lightSample = lights[k].sample(scene, result.position);
    
                        if (lightSample != lightSample.zero) {
                            var NdotL = result.normal.dot(lightSample.L);
    
                            // 夹角小约90度,即光源在平面的前面
                            if (NdotL >= 0)
                                color = color.add(lightSample.EL.multiply(NdotL));
                        }
                    }
                    pixels[i] = color.r * 255;
                    pixels[i + 1] = color.g * 255;
                    pixels[i + 2] = color.b * 255;
                    pixels[i + 3] = 255;
                }
                i += 4;
            }
        }
    
        ctx.putImageData(imgdata, 0, 0);
    }
    
    

    renderLight( document.getElementById('renderCanvas1'), new Union([ new Plane(new Vector3(0, 1, 0), 0), new Plane(new Vector3(0, 0, 1), -50), new Plane(new Vector3(1, 0, 0), -20), new Sphere(new Vector3(0, 10, -10), 10) ]), [new DirectionalLight(Color.white, new Vector3(-1.75, -2, -1.5))], new PerspectiveCamera(new Vector3(0, 10, 10), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90));

    Run

     

    修改代码试试看

    • 改變光源的顏色 (也試試超過1的值)
      改變光源的方向 (在DirectionalLight.initialize()裡自動做了normalize,這輸入不需位單位向量)
      改變光源的幅照度 (也試試超過1的值)
    • 改變光源的方向 (在DirectionalLight.initialize()裡自動做了normalize,這輸入不需位單位向量)

    点光源

    点光源/点光灯(point light),又称全向光源/泛光源/泛光灯(omnidirectional light/omni light),是指一个无限小的点,向所有光向平均地散射光。

    其光向量,就是表面位置往点光源位置的方向:

    学习物理时,经常有这种往所有方向发射的情况(例如引力、声音等)。类比可知,接收到的能量和距离的关系,是成平方反比定律的:

      

     当中I为幅射强度(intensity, radiant intensity),当r=1时,幅射强度和幅照度相等。

    通常称为衰减(attenuation)系数。有时候会为各种需求,写一些非物理正确的衰减系数。

    实现PointLight类

    以下代码中,不直接使用normalize(),令r和其平方可以在之后分别使用,算是简单的优化。

    PointLight = function(intensity, position) { this.intensity = intensity; this.position = position; this.shadow = true; };
    
    PointLight.prototype = {
        initialize: function() { },
        sample: function(scene, position) {
            // 计算L,但保留r和r^2,供之后使用
            var delta = this.position.subtract(position);
            var rr = delta.sqrLength();
            var r = Math.sqrt(rr);
            var L = delta.divide(r);
    
            // 阴影测试
            if (this.shadow) {
                var shadowRay = new Ray3(position, L);
                var shadowResult = scene.intersect(shadowRay);
                // 在r以内的相交点才会遮蔽光源
                if (shadowResult.geometry && shadowResult.distance <= r)
                    return LightSample.zero;
            }
    
            // 平方反比衰减
            var attenuation = 1 / rr;
    
            // 计算幅照度
            return new LightSample(L, this.intensity.multiply(attenuation));
        }
    };
    

    renderLight( document.getElementById('renderCanvas2'), new Union([ new Plane(new Vector3(0, 1, 0), 0), new Plane(new Vector3(0, 0, 1), -50), new Plane(new Vector3(1, 0, 0), -20), new Sphere(new Vector3(0, 10, -10), 10) ]), [new PointLight(Color.white.multiply(2000), new Vector3(30, 40, 20))], new PerspectiveCamera(new Vector3(0, 10, 10), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90));

    Run

     

    修改代码试试看

    • 改变幅射强度
    • 移动光源
    • 加入多一个点光源

    聚光灯

    现实中,并不存在理想的点光源,放射的光在不同方向是有差异的。聚光灯(spot light)是常用的一种模式,它在点光源的基础上,加入圆锥形的范围。聚光灯可以有不同的模型,以下采用Direct3D固定功能管道(fixed-function pipeline)用的模型做示范。

    聚光灯有一个主要方向s,再设置两个圆锥范围,称为内圆锥和外圆锥,两圆锥之间的范围称为半影(penumbra)。内外圆锥的内角分别为。聚光灯可计算一个聚光灯系数,范围为[0,1],代表某方向的放射比率。内圆锥中系数为1(最亮),内圆锥和外圆锥之间系数由1逐渐变成0。另外,可用另一参数p代表衰减(falloff),决定内圆锥和外圆锥之间系数变化。方程式如下:

    实现SpotLight类

    SpotLight类只是多了那几个参数,以计算聚光灯系数,最后结合到幅照度。很多参数可在initialize()里预计算,减少在sample()里重复运算。

    SpotLight = function(intensity, position, direction, theta, phi, falloff) {
        this.intensity = intensity;
        this.position = position;
        this.direction = direction;
        this.theta = theta;
        this.phi = phi;
        this.falloff = falloff;
        this.shadow = true;
    };
    
    SpotLight.prototype = {
        initialize: function() {
            this.S = this.direction.normalize().negate();
            this.cosTheta = Math.cos(this.theta * Math.PI / 180 / 2);
            this.cosPhi = Math.cos(this.phi * Math.PI / 180 / 2);
            this.baseMultiplier = 1 / (this.cosTheta - this.cosPhi);
        },
    
        sample: function(scene, position) {
            // 计算L,但保留r和r^2,供之后使用
            var delta = this.position.subtract(position);
            var rr = delta.sqrLength();
            var r = Math.sqrt(rr);
            var L = delta.divide(r);
    
            // 计算聚光灯因子
            var spot;
            var SdotL = this.S.dot(L);
            if (SdotL >= this.cosTheta)
                spot = 1;
            else if (SdotL <= this.cosPhi)
                spot = 0;
            else
                spot = Math.pow((SdotL - this.cosPhi) * this.baseMultiplier, this.falloff);
    
            // 阴影测试
            if (this.shadow) {
                var shadowRay = new Ray3(position, L);
                var shadowResult = scene.intersect(shadowRay);
                // 在r以内的相交点才会遮蔽光源
                if (shadowResult.geometry && shadowResult.distance <= r)
                    return LightSample.zero;
            }
    
            // 平方反比衰减
            var attenuation = 1 / rr;
    
            // 计算幅照度
            return new LightSample(L, this.intensity.multiply(attenuation * spot));
        }
    };
    

    renderLight( document.getElementById('renderCanvas3'), new Union([ new Plane(new Vector3(0, 1, 0), 0), new Plane(new Vector3(0, 0, 1), -50), new Plane(new Vector3(1, 0, 0), -20), new Sphere(new Vector3(0, 10, -10), 10) ]), [new SpotLight(Color.white.multiply(2000), new Vector3(30, 40, 20), new Vector3(-1, -1, -1), 20, 30, 0.5)], new PerspectiveCamera(new Vector3(0, 10, 10), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90));

    Run

     

    修改代码试试看

    • 改变各个参数

    例子

    三原色

    这个例子把三原色聚光灯重叠射度地板,可以看到它们的颜色混合。

    renderLight( document.getElementById('renderCanvas4'), new Union([ new Plane(new Vector3(0, 1, 0), 0), new Plane(new Vector3(0, 0, 1), -50), new Plane(new Vector3(1, 0, 0), -20) ]), [ new PointLight(Color.white.multiply(1000), new Vector3(30, 40, 20)), new SpotLight(Color.red.multiply(3000), new Vector3(0, 30, 10), new Vector3(0, -1, -1), 20, 30, 1), new SpotLight(Color.green.multiply(3000), new Vector3(6, 30, 20), new Vector3(0, -1, -1), 20, 30, 1), new SpotLight(Color.blue.multiply(3000), new Vector3(-6, 30, 20), new Vector3(0, -1, -1), 20, 30, 1) ], new PerspectiveCamera(new Vector3(0, 40, 15), new Vector3(0, -1.25, -1), new Vector3(0, 1, 0), 60));

    Run

     

    修改代码试试看

    • 如果,幅射强度是负值的话,会怎么样?(虽然未证实反光子(antiphoton)的存在,但读者能想到图形学上的功能么?)

    很多光源

    这个例子在天花加了36个点光源,和一个从后往前的填充用方向光源。有时候灯光师会加入填充光源(fill light),去加强对象的轮廓及立体感(有时候用上冷暖色的对比)。这个渲染比较慢,可能要半分钟啊!

    var lights = []; for (var x = 10; x <= 30; x += 4) for (var z = 20; z <= 40; z += 4) lights.push(new PointLight(Color.white.multiply(80), new Vector3(x, 50, z))); // var fillLight = new DirectionalLight(Color.white.multiply(0.25), new Vector3(1.5, 1, 0.5)); fillLight.shadow = false; lights.push(fillLight); // renderLight( document.getElementById('renderCanvas5'), new Union([ new Plane(new Vector3(0, 1, 0), 0), new Plane(new Vector3(0, 0, 1), -50), new Plane(new Vector3(1, 0, 0), -20), new Sphere(new Vector3(0, 10, -10), 10) ]), lights, new PerspectiveCamera(new Vector3(0, 10, 10), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90));

    Run

     

    修改代码试试看

    • 把光源放在不同位置(例如接近地面)
    • 把每个光源的颜色加入差异

    结语

    本文简单介绍了三种基本的光源,这些光源除了应用在光线追踪渲染器上,也常用在光栅化渲染器中。

    除这三种以外,还有一类比较高阶的光源──面光源(area light)。面光源比这三种光源更真实,也能完美地做到真实的柔和阴影。如果能实现面光源,基本上也不用特定做「光源」这种类,取而代之,可以设定某些材质本身能发光即可。当然,没有免费午餐,随之而来的时间复杂度也增加。

    有了光源,下一篇大概会开始谈材质,讲述光源和材质间的互动。

    参考

    • Tomas Möller, Eric Haines, Naty Hoffman, Real-time Rendering 3rd Edition, AK Peters 2008
    • Matt Pharr, Greg Humphreys, Physically Based Rendering, Morgan Kaufmann, 2004

    评论: 11 查看评论 发表评论

    找优秀程序员,就在博客园


    最新新闻:
    · 雅虎针对iPad推出雅虎娱乐应用(2010-04-02 23:03)
    · 纳斯达克针对iPad推出投资组合管理应用(2010-04-02 23:01)
    · 《时代》杂志iPad应用售价4.99美元(2010-04-02 22:59)
    · 夏普今年将推3D 面板 可裸眼观看3D图片(2010-04-02 22:56)
    · 现有软件编写方式阻碍发挥多核潜力?(2010-04-02 22:14)

    编辑推荐:时代周刊:iPad能否让乔布斯续写传奇

    网站导航:博客园首页  个人主页  新闻  闪存  小组  博问  社区  知识库

  • 各省定居国外官员人数一览表

    源引自人民日报资料库

    自一九九二年以来,外逃省部级(包括副省部级)

    地  区    人 数  地厅级或以上官员  携带资金(美元)

    北京市     225人     58人      25亿

    天津市     122人     19人      14亿

    河北省     340人     26人      31亿

    山西省     236人     41人      17亿

    辽宁省     367人     52人      117亿

    吉林省     117人     14人      26亿

    黑龙江     230人     42人      85亿

    上海市     206人     66人      250亿

    江苏省     313人     40人      140亿

    浙江省     142人     48人      86亿

    安徽省     97人     19人      30亿

    福建省     480人     102人      365亿

    江西省     125人     21人      26亿

    山东省     352人     54人      150亿

    河南省     124人     27人      50亿

    湖北省     365人     33人      60亿

    湖南省     300人     20人      70亿

    广东省    1640人     170人     1550亿

    广 西     217人     25人      55亿

    海南省     140人     22人      27亿

    重庆市     175人     31人      18亿

    四川省     144人     40人      50亿

    云南省     238人     48人      60亿

    陕西省     146人     16人      28亿

    新 疆     260人     24人      30亿

    另附: 行政管理费(或曰公务支出)在国家财政支出中的比重:

    德 国(1998年) 2.7%

    埃 及(1997年) 3.1%

    英 国(1999年) 4.2%

    韩 国(1997年) 5.1%

    泰 国(2000年) 5.2%

    印 度(2000年) 6.3%

    加拿大(2000年) 7.1%

    俄罗斯(2000年) 7.6%

    美 国(2000年) 9.9%

    中 国(2000年)25.7%

    用于教育,医疗的比列:

    中国:3.8%

    印度:19.7%

    美国:21.5%

    日本:23.3%

    相关日志

    • 无相关日志
  • [公务员] 公务员买房内部价惊人!zz 发信人: YYJN (烟雨江南), 信区: Career_Servant
    标 题: 公务员买房内部价惊人!zz
    发信站: 水木社区 (Tue Mar 30 20:33:50 2010), 站内

    我是来问问是不是真的的?
    http://bbs.gz.house.163.com/bbs/junjing/171275586.html

    公务员买房内部价惊人!
    最近发现北京的公务员集资购房和团购房真是便宜的吓人!怪不得人人都争当公务员啊!

    市公务员集资房,位置刘家窑桥西南,均价4000/平,周边30000/平
    铁道部集资房,位置西直门铁科院院内,均价2000/平,周边50000/平
    公安部集资房,位置广渠门外,均价4500/平,周边35000/平
    外交部团购房,位置双井桥东南,项目名称:禧福会国际社区,团购价6000/平,市场价30000/平
    外交部集资建房,位置劲松桥 项目名称:和谐雅园 集资价:5800/平 市场价30000/平
    中石油团购房,位置太阳宫地铁站,项目名称:太阳星城,团购价8800/平,市场价28000/平
    市区政府团购房,位置大红门,项目名称:京投快线·阳光花园,团购价6000/平,市场价25000/平
    市发改委建委集资房,位置六里桥西局,均价4000/平,市场价30000/平
    人民银行集资房,位置宣武门康乐里,均价2000/平,市场价50000/平
    市铁路局集资房,位置广安门手帕口南,均价5000/平,市场价32000/平
    中信银行团购房,位置菜市口,项目:中信城,团购价5000/平,市场价33000/平

    朋友们!这实在太让人无语了!大家一起来818吧!

    --

    ※ 来源:·水木社区 newsmth.net·[FROM: 125.39.143.*]
  • 中国十大令人寒心的冷笑话
    来源:新浪论坛

    1、秦始皇修筑万里长城时死了许多人,孟姜女的丈夫万喜良也在其中。听到这个消息,孟姜女只觉得天昏地暗,一下子昏倒在地,醒来后,她伤心地痛哭起来,只哭得天愁地惨,日月无光。不知哭了多久,忽听得天摇地动般地一声巨响,长城崩塌了几十里,露出了数不清的尸骨。孟姜女咬破手指,把血滴在一具具的尸骨上,她心里暗暗祷告:如果是丈夫的尸骨,血就会渗进骨头,如果不是,血就会流向四方。终于,孟姜女用这种方法找到了万喜良的尸骨。她抱着这堆白骨,哭着说道: “老万,你的死跟你丫本人素质不高有关啊!”

    ----1130日,七煤公司一领导在接受采访时表示,“11·27”矿难的主要原因归咎于井下矿工对规章制度执行不力,劳动者的素质离我们的要求还差很远。


    2、武松醉打蒋门神、替施恩夺了快活林之后,中了张都监、张团练的计,几乎命丧飞云浦。武松杀了张都监的几名爪牙,寻思了半晌,怨恨冲天:“不杀得张都监,如何出得这口恨气!”便去死尸身边解下腰刀,选好的取把将来跨了,拣条好朴刀提着,直奔孟州城张都监的后花园。张都监、张团练、蒋门神正在鸳鸯楼吃酒,冷不防武松闯了进来,噗噗几刀砍死蒋门神、张团练。武松踏着张都监的脑袋喝道:“你们这帮贼子,为何黑道白道勾结、串通一气害我?”张都监颤颤巍巍地答道:“说句实话,官匪勾结的重要原因,是我们的待遇过低了!”

    ----成都火车站派出所副所长付小华接受采访时表示:“出现‘警匪勾结’这种情况的重要原因是 pol.ice待遇过低”。

    3、董存瑞牺牲后到了天堂,上帝问他:“你是怎么死的?”董存瑞说:“为了炸敌人的碉堡,被de- tona-tor包炸死的”;上帝听后勃然大怒,说道:“胡说!你胆敢骗我?”董存瑞说:“我没骗您啊!”上帝说:“你以为我不懂科学吗?谁不知道,爆炸只会产生水和二氧化碳,你不是被水淹死的、就是被二氧化碳薰死的,怎么可能是被炸死的呢?!”

    ----吉林石化的人所说:爆炸产生水和二氧化碳,不会污染水源

    4、孔子路过泰山脚下,有一个妇女在墓前哀伤地哭泣。孔子手扶车沿听她哭诉,并让弟子 问她缘由,妇女说:“以前我的公公被老虎咬死,我的丈夫跟着被老虎咬死,现在我的儿子也被老虎咬死了;”孔子说:“事情都过去了,又何必伤心?那为什么不离开这里呢?”妇女说:“我怕失去低收入者作为纳税人的荣誉!”孔子于是对弟子道:“小子识之,苛政虽猛于虎,然纳税人的荣誉牛b于苛政也!”

    ----全国人大农业与农村委员会委员任正隆则认为,起征点太高剥夺了低收入者作为“纳税人”的荣誉。

    5、汉朝的淮南王刘安派人进山访仙,从仙翁手里得到了一张仙方。他把自己关进暗房里,炼起仙丹来。八卦炉里炼出一些圆滚滚的仙丹,他一口气吞下5颗,飘飘悠悠飞上天去了!门外的鸡犬一看,也跟着大吃起来,不一会,空中一阵鸡鸣狗叫,原来它们也飞上天了!有人问道:“刘安,你家的鸡犬怎么也跟着成仙了?”刘安说: “为了防止拉登发动kb袭击、撞击天庭,我特意实行“一人得道,鸡犬升天”制,在任何紧急情况下,都能及时帮助疏散与救援,这是一个安全上的举措,并不是专门把成仙作为福利”。

    ----广州地铁线网听政会上,地铁员工家属免费坐地铁引起代表争议,地铁总经理解释,是为了“反恐需要”。

    6、三国演义里,诸葛亮造木牛流马,用来运送粮草,以此大败曹军。但后来木牛流马却失传了,即便是诸葛亮的得意弟姜维也不会造。诸葛军师临终前众将问他:“军师,木牛流马这般好用,为何您再也不造了?” 孔明长叹一声曰:“某交通学大学士、大教授的研究结果表明,木牛流马的污染比汽车飞机大,为了子孙后代的幸福,你还是等着坐汽车吧!”

    ----“中国城市环境污染不是由汽车造成的,而是由自行车造成的”。国内一家搞环境研究的权威机构经过一番调查与研后得出的一个“科学”结论。

    7、老栓也向那边看,却只见一堆人的后背;颈项都伸得很长,仿佛许多鸭,被无形的手捏住了的,向上提着。静了一会,似乎有点声音,便又动摇起来,轰的一声,都向后退;一直散到老栓立着的地方,几乎将他挤倒了。 “喂!一手交钱,一手交货!”一个浑身黑色的人,站在老栓面前,眼光正像两把刀,刺得老栓缩小了一半。那人一只大手,向他摊着;一只手却撮着一个鲜红的馒头,那红的还是一点一点的往下滴。 老栓慌忙摸出洋钱,抖抖的想交给他,却又不敢去接他的东西。那人便焦急起来,嚷道,“怎么?嫌贵?舍不得银子?” 老栓还踌躇着,黑的人便抢过灯笼,一把扯下纸罩,裹了馒头,塞与老栓;一手抓过洋钱,捏一捏,转身去了。嘴里哼着说:“这血馒头是药,不能当馒头卖!价格不贵,不同意降价!”

    ----“药品怎么能当馒头卖?”在“看病难,药价贵”呼声高涨时,东盛制药集团总裁陶朝辉却反其道而行之,抛出“馒头论”,坚持“药价不贵,不同意降价”。

    8、宋代穷儒陈世美,进京考中状元,被招为驸马。其发妻秦香莲带二子上京寻亲,陈世美翻脸不认人;秦香莲悲痛欲绝,发誓要讨还情债。陈世美勃然大怒,上表朝廷奏曰:臣以为,开封自古就是神圣之地,岂容外地人随便进入?应该建立人口准入制度!同时,对那些恶意讨情之人,应坚决打击!”

    ----在刚刚结束的北京市“两会”上,政协委员张惟英教授提出“建立人口准入制度”的建议:目前北京市的居住人口已超过各种资源的人口承载极限,严重制约了北京的发展,建议摸清北京市实际需要的人才类别,用准入制度进行合理的引入,规范人口流动。

    9、有一日,窦娥碰到苏三、杨乃武、小白菜等人,就问他们:“你们都平反昭雪了吗?”众人说:“都昭雪了”;窦娥又问:“那少奇兄弟、德怀兄弟、志新妹妹呢?”众人说:“也都平反了”。窦娥便道:“我说什么来着,咱们的司法就是公正!那么多案件从错的纠成正的,这难道不是司法公正的体现吗?”

    ----被无辜关押11年的佘祥林被宣告无罪了,但这一悲剧投石入湖的震荡,远远没有平息。当事人申请国家赔偿、责任人被追究法律责任,尚都在公众的持续关注中。种种怨怒未消之下,另一方面却居然频频出现奇怪的言论:41日湖北高院向该省法院系统发出通知,要求认真总结避免佘祥林被冤杀的经验;最高法副院长万鄂湘日前在就此案答媒体问时又说:“是否司法不公应该从最后纠正的结果看。这个案件从错的又纠成正的,难道不是司法公正的体现吗?”

    10、一天,周扒皮去找刘文彩,“刘大哥,我们村那些穷棒子们发牢骚,说他们活得太苦、活得没意思”; 刘文彩说:“他们是我国巨大的财富,没有他们的辛苦哪有咱们少数人的享乐,他们的存在和维持现在的状态是很有必要的。” 周扒皮说:“有的长工说他想读书!” 刘文彩道:“咱们的教育改革已经成功了,他还嚷嚷个屁!” 周扒皮说:“他们说收租院放高利贷是暴利”; 刘文彩道:“放高利贷就该暴利,谁让他们不幸生在x国了?我们就是要把暴利进行到底!” 周扒皮说:“他们还说现在收入差距过大,存在两极分化”; 刘文彩道:“纯属放屁!大家都在同一个经纬度上,又不是一个在南极、一个在北极,哪来的两极分化?!”

    ----经济学家厉以宁如是说“8亿多农民和下岗工人是中国巨大的财富,没有他们的辛苦哪有少数人的享乐,他们的存在和维持现在的状态是很有必要的。”
    GFW Blog入围德国之声国际博客大赛记者无疆界特别奖,敬请大家帮忙投票。翻墙利器"赛风"(Psiphon)代理新网址:http://no21984.org/。被墙网站收集:http://delicious.com/GFWbookmark,请使用GFWlist为标签,帮助我们收集被墙网站的信息。敬请订阅GFW Blog:http://feeds2.feedburner.com/chinagfwblog,邮件订阅:https://groups.google.com/group/gfw-blog。更多翻墙工具介绍和下载: 推客浏览器(http://twitbrowser.net/blog/,墙内镜像:http://tm005.nl.am/),Sesawe(http://www.sesawwe.net/)。翻墙互助小组邮件列表: http://groups.google.com/group/bypassgfw。
  • 用JavaScript玩转计算机图形学(一)光线追踪入门

    阅读: 8465 评论: 55 作者: Milo Yip 发表于 2010-03-29 00:05 原文链接

    系列简介

    记得小时候读过一本关于计算机图形学(computer graphics, CG)的入门书,从此就爱上了CG。本系列希望,采用很多人认识的JavaScript语言去分享CG,令更多人有机会接触,并爱上CG。

    本系列的特点之一,是读者能在浏览器里直接执行代码,也可重覆修改代码测试。透过这种互动,也许能更深刻体会内容。读者只要懂得JavaScript(因为JavaScript很简单,学过Java/C/C++/C#之类的语言也应没问题)和一点点线性代数(linear algebra)就可以了。

    笔者在大学期间并没有修读CG课程,虽然看过相关书籍,始终未亲手做过全域光照的渲染器,本文也作为个人的学习分享。此外,笔者也差不多十年没接触JavaScript,希望各位不吝赐教。

    本文简介

    多数程序员听到3D CG,就会联想到Direct3D、OpenGL等API。事实上,这些流行的API主要为实时渲染(real-time rendering)而设,一般采用光栅化(rasterization)方式,渲染大量的三角形(或其他几何图元种类(primitive types))。这种基于光栅化的渲染系统,只支持局部光照(local illumination)。换句话说,渲染几何图形的一个像素时,光照计算只能取得该像素的资讯,而不能访问其他几何图形资讯。理论上,阴影(shadow)、反射(reflection)、折射(refraction)等为全局光照(global illumination)效果,实际上,栅格化渲染系统可以使用预处理(如阴影贴图(shadow mapping)、环境贴图(environment mapping))去模拟这些效果。

    全局光照计算量大,一般也没有特殊硬件加速(通常只使用CPU而非GPU),所以只适合离线渲染(offline rendering),例如3D Studio Max、Maya等工具。其中一个支持全局光照的方法,称为光线追踪(ray tracing)。光线追踪能简单直接地支持阴影、反射、折射,实现起来亦非常容易。本文的例子里,只用了数十行JavaScript代码(除canvas外不需要其他特殊插件和库),就能实现一个支持反射的光线追踪渲染器。光线追踪可以用来学习很多计算机图形学的课题,也许比学习Direct3D/OpenGL更容易。现在,先介绍点理论吧。

    光线追踪

    光栅化渲染,简单地说,就是把大量三角形画到屏幕上。当中会采用深度缓冲(depth buffer, z-buffer),来解决多个三角形重叠时的前后问题。三角形数目影响效能,但三角形在屏幕上的总面积才是主要瓶颈。

    光线追踪,简单地说,就是从摄影机的位置,通过影像平面上的像素位置(比较正确的说法是取样(sampling)位置),发射一束光线到场景,求光线和几何图形间最近的交点,再求该交点的著色。如果该交点的材质是反射性的,可以在该交点向反射方向继续追踪。光线追踪除了容易支持一些全局光照效果外,亦不局限于三角形作为几何图形的单位。任何几何图形,能与一束光线计算交点(intersection point),就能支持。

    上图(來源)显示了光线追踪的基本方式。要计算一点是否在阴影之内,也只须发射一束光线到光源,检测中间有没有障碍物而已。不过光源和阴影留待下回分解。

    初试画板

    光线追踪的输出只是一个影像(image),所谓影像,就是二维颜色数组。

    要在浏览器内,用JavaScript生成一个影像,目前可以使用HTML 5的<canvas>。但现时Internet Explorer(直至版本8)还不支持<canvas>,其他浏览器如Chrome、Firefox、Opera等就可以。

    以下是一个简单的实验,把每个象素填入颜色,左至右越来越红,上至下越来越绿。

    var canvas = document.getElementById("testCanvas"); var ctx = canvas.getContext("2d"); var w = canvas.attributes.width.value; var h = canvas.attributes.height.value; ctx.fillStyle = "rgb(0,0,0)"; ctx.fillRect(0, 0, w, h); var imgdata = ctx.getImageData(0, 0, w, h); var pixels = imgdata.data; var i = 0; for (var y = 0; y < h; y++) for (var x = 0; x < w; x++) { pixels[i++] = x / w * 255; pixels[i++] = y / h * 255; pixels[i++] = 0; pixels[i++] = 255; } ctx.putImageData(imgdata, 0, 0);

    Run

     

    左邊的canvas定義如下:

    <canvas width="256" height="256" id="testCanvas"></canvas>
    

    修改代码试试看

    • 把第三个pixels[i++] = 0 改为255 (即蓝色全开)
    • 把第四个pixels[i++] = 255 改为128 (alpha=128)
    • 可以只修改两个for循环里面的代码,画一个国际象棋棋盘么?

    这实验说明,从canvas取得的影像资料canvas.getImageData(...).data是个一维数组,该数组每四个元素代表一个象素(按红, 绿, 蓝, alpha排列),这些象素在影像中从上至下、左至右排列。

    解决实验平台的技术问题后,可开始从基础类别开始实现。

    基础类

    笔者使用基于物件(object-based)的方式编写JavaScript。

    三维向量

    三维向量(3D vector)可谓CG里最常用型别了。这里三维向量用Vector3类实现,用(x, y, z)表示。 Vector3亦用来表示空间中的点(point),而不另建类。先看代码:

    Vector3 = function(x, y, z) { this.x = x; this.y = y; this.z = z; };
    
    Vector3.prototype = {
        copy : function() { return new Vector3(this.x, this.y, this.z); },
        length : function() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); },
        sqrLength : function() { return this.x * this.x + this.y * this.y + this.z * this.z; },
        normalize : function() { var inv = 1/this.length(); return new Vector3(this.x * inv, this.y * inv, this.z * inv); },
        negate : function() { return new Vector3(-this.x, -this.y, -this.z); },
        add : function(v) { return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z); },
        subtract : function(v) { return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z); },
        multiply : function(f) { return new Vector3(this.x * f, this.y * f, this.z * f); },
        divide : function(f) { var invf = 1/f; return new Vector3(this.x * invf, this.y * invf, this.z * invf); },
        dot : function(v) { return this.x * v.x + this.y * v.y + this.z * v.z; },
        cross : function(v) { return new Vector3(-this.z * v.y + this.y * v.z, this.z * v.x - this.x * v.z, -this.y * v.x + this.x * v.y); }
    };
    
    Vector3.zero = new Vector3(0, 0, 0);
    

    这些类方法(如normalize、negate、add等),如果传回Vector3类对象,都会传回一个新建构的Vector3。这些三维向量的功能很简单,不在此详述。注意multiply和divide是与纯量(scalar)相乘和相除。

    Vector3.zero用作常量,避免每次重新构建。值得一提,这些常量必需在prototype设定之后才能定义。

    光线

    所谓光线(ray),从一点向某方向发射也。数学上可用参数函数(parametric function)表示:

    当中,o即发谢起点(origin),d为方向。在本文的例子里,都假设d为单位向量(unit vector),因此t为距离。实现如下:

    Ray3 = function(origin, direction) { this.origin = origin; this.direction = direction; }
    
    Ray3.prototype = {
        getPoint : function(t) { return this.origin.add(this.direction.multiply(t)); }
    };
    

    球体

    球体(sphere)是其中一个最简单的立体几何图形。这里只考虑球体的表面(surface),中心点为c、半径为r的球体表面可用等式(equation)表示:

    如前文所述,需要计算光线和球体的最近交点。只要把光线x = r(t)代入球体等式,把该等式求解就是交点。为简化方程,设v=o - c,则:

    因为d为单位向量,所以二次方的系数可以消去。 t的二次方程式的解为

    若根号内为负数,即相交不发生。另外,由于这里只需要取最近的交点,因此正负号只需取负号。代码实现如下:

    Sphere = function(center, radius) { this.center = center; this.radius = radius; };
    
    Sphere.prototype = {
        copy : function() { return new Sphere(this.center.copy(), this.radius.copy()); },
    
        initialize : function() {
            this.sqrRadius = this.radius * this.radius;
        },
    
        intersect : function(ray) {
            var v = ray.origin.subtract(this.center);
            var a0 = v.sqrLength() - this.sqrRadius;
            var DdotV = ray.direction.dot(v);
    
            if (DdotV <= 0) {
                var discr = DdotV * DdotV - a0;
                if (discr >= 0) {
                    var result = new IntersectResult();
                    result.geometry = this;
                    result.distance = -DdotV - Math.sqrt(discr);
                    result.position = ray.getPoint(result.distance);
                    result.normal = result.position.subtract(this.center).normalize();
                    return result;
                }
            }
    
            return IntersectResult.noHit;
        }
    };
    

    实现代码时,尽快用最少的运算剔除没相交的情况(Math.sqrt是比较慢的函数)。另外,预计算了球体半径r的平方,此为一个优化。

    这里用到一个IntersectResult类,这个类只用来记录交点的几何物件(geometry)、距离(distance)、位置(position)和法向量(normal)。 IntersectResult.noHit的geometry为null,代表光线没有和任何几何物件相交。

    IntersectResult = function() {
        this.geometry = null;
        this.distance = 0;
        this.position = Vector3.zero;
        this.normal = Vector3.zero;
    };
    
    IntersectResult.noHit = new IntersectResult();
    

    摄影机

    摄影机在光线追踪系统里,负责把影像的取样位置,生成一束光线。

    由于影像的大小是可变的(多少像素宽x多少像素高),为方便计算,这里设定一个统一的取样座标(sx, sy),以左下角为(0,0),右上角为(1 ,1)。

    从数学角度来说,摄影机透过投影(projection),把三维空间投射到二维空间上。常见的投影有正投影(orthographic projection)、透视投影(perspective projection)等等。这里首先实现透视投影。 ]]>

    透视摄影机

    透视摄影机比较像肉眼和真实摄影机的原理,能表现远小近大的观察方式。透视投影从视点(view point/eye position),向某个方向观察场景,观察的角度范围称为视野(field of view, FOV)。除了定义观察的向前(forward)是那个方向,还需要定义在影像平面中,何谓上下和左右。为简单起见,暂时不考虑宽高不同的影像,FOV同时代表水平和垂直方向的视野角度。

    上图显示,从摄影机上方显示的几个参数。 forward和right分别是向前和向右的单位向量。

    因为视点是固定的,光线的起点不变。要生成光线,只须用取样座标(sx, sy)计算其方向d。留意FOV和s的关系为:

    把sx从[0, 1]映射到[-1,1],就可以用right向量和s,来计算r向量,代码如下:

    PerspectiveCamera = function(eye, front, up, fov) { this.eye = eye; this.front = front; this.refUp = up; this.fov = fov; };
    
    PerspectiveCamera.prototype = {
        initialize : function() {
            this.right = this.front.cross(this.refUp);
            this.up = this.right.cross(this.front);
            this.fovScale = Math.tan(this.fov * 0.5 * Math.PI / 180) * 2;
        },
    
        generateRay : function(x, y) {
            var r = this.right.multiply((x - 0.5) * this.fovScale);
            var u = this.up.multiply((y - 0.5) * this.fovScale);
            return new Ray3(this.eye, this.front.add(r).add(u).normalize());
        }
    };
    

    代码中fov为度数,转为弧度才能使用Math.tan()。另外,fovScale预先乘了2,因为sx映射到[-1,1]每次都要乘以2。 sy和sx的做法一样,把两个在影像平面的向量,加上forward向量,就成为光线方向d。因之后的计算需要,最后把d变成单位向量。

    渲染测试

    写了Vector3、Ray3、Sphere、IntersectResult、Camera五个类之后,终于可以开始渲染一点东西出来!

    基本的做法是遍历影像的取样座标(sx, sy),用Camera把(sx, sy)转为Ray3,和场景(例如Sphere)计算最近交点,把该交点的属性转为颜色,写入影像的相对位置里。

    把不同的属性渲染出来,是CG编程里经常用的测试和调试手法。笔者也是用此方法,修正了一些错误。

    渲染深度

    深度(depth)就是从IntersectResult取得最近相交点的距离,因深度的范围是从零至无限,为了把它显示出来,可以把它的一个区间映射到灰阶。这里用[0, maxDepth]映射至[255, 0],即深度0的像素为白色,深度达maxDepth的像素为黑色。

    // renderDepth.htm
    function renderDepth(canvas, scene, camera, maxDepth) {
        // 从canvas取得imgdata和pixels,跟之前的代码一样
        // ...
    
        scene.initialize();
        camera.initialize();
    
        var i = 0;
        for (var y = 0; y < h; y++) {
            var sy = 1 - y / h;
            for (var x = 0; x < w; x++) {
                var sx = x / w;            
                var ray = camera.generateRay(sx, sy);
                var result = scene.intersect(ray);
                if (result.geometry) {
                    var depth = 255 - Math.min((result.distance / maxDepth) * 255, 255);
                    pixels[i    ] = depth;
                    pixels[i + 1] = depth;
                    pixels[i + 2] = depth;
                    pixels[i + 3] = 255;
                }
                i += 4;
            }
        }
    
        ctx.putImageData(imgdata, 0, 0);
    }

    renderDepth( document.getElementById('depthCanvas'), new Sphere(new Vector3(0, 10, -10), 10), new PerspectiveCamera(new Vector3(0, 10, 10), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90), 20);

    Run

     

    这里的观看方向是,正X轴向右,正Y轴向上,正Z轴向后。

    修改代码试试看

    • 改变球体的位置
    • 改变球体的半径
    • 改变fov(PerspectiveCamera最后的参数)
    • 改变maxDepth(renderDepth最后的参数)
    • 改变摄影机的方向,例如向左转一点点(记得要是单位向量啊!可以用new Vector(...).normalize())

    渲染法向量

    相交测试也计算了几何物件在相交位置的法向量,这里也可把它视觉化。法向量是一个单位向量,其每个元素的范围是[-1, 1]。把单位向量映射到颜色的常用方法为,把(x, y, z)映射至(r, g, b),范围从[-1, 1]映射至[0, 255]。

    // renderNormal.htm
    function renderNormal(canvas, scene, camera) {
        // ...
                if (result.geometry) {
                    pixels[i    ] = (result.normal.x + 1) * 128;
                    pixels[i + 1] = (result.normal.y + 1) * 128;
                    pixels[i + 2] = (result.normal.z + 1) * 128;
                    pixels[i + 3] = 255;
                }
        // ...
    }
    

    renderNormal( document.getElementById('normalCanvas'), new Sphere(new Vector3(0, 10, -10), 10), new PerspectiveCamera(new Vector3(0, 10, 10), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90), 20);

    Run

     

    球体上方的法向量是接近(0, 1, 0),所以是浅绿色(0.5, 1, 0.5)。

    修改代码试试看

    • 从球体的正上方往下看

    材质

    渲染深度和法向量只为测试和调试,要显示物件的"真实"颜色,需要定义该交点向某方向(如往视点的方向)发出的光的颜色,称之为几个图形的材质(material )。

    材质的接口为function sample(ray, posiiton, normal) ,传回颜色Color的对象。这是个极简陋的接口,临时做一些效果出来,有机会再详谈。

    颜色

    颜色在CG里最简单是用红、绿、蓝三个通道(color channel)。为实现简单的Phong材质,还加入了对颜色的简单操作。

    Color = function(r, g, b) { this.r = r; this.g = g; this.b = b };
    
    Color.prototype = {
        copy : function() { return new Color(this.r, this.g, this.b); },
        add : function(c) { return new Color(this.r + c.r, this.g + c.g, this.b + c.b); },
        multiply : function(s) { return new Color(this.r * s, this.g * s, this.b * s); },
        modulate : function(c) { return new Color(this.r * c.r, this.g * c.g, this.b * c.b); }
    };
    
    Color.black = new Color(0, 0, 0);
    Color.white = new Color(1, 1, 1);
    Color.red = new Color(1, 0, 0);
    Color.green = new Color(0, 1, 0);
    Color.blue = new Color(0, 0, 1);
    

    这Color类很像Vector3类,值得留意的是,颜色有调制(modulate)操作,其意义为两个颜色中每个颜色通道相乘。

    格子材质

    CG世界里,国际象棋棋盘是最常见的测试用纹理(texture)。这里不考虑纹理贴图(texture mapping)的问题,只凭(x, z)坐标计算某位置发出黑色或白色的光(黑色的光不叫光吧,哈哈)。

    CheckerMaterial = function(scale, reflectiveness) { this.scale = scale; this.reflectiveness = reflectiveness; };
    
    CheckerMaterial.prototype = {
        sample : function(ray, position, normal) {
            return Math.abs((Math.floor(position.x * 0.1) + Math.floor(position.z * this.scale)) % 2) < 1 ? Color.black : Color.white;
        }
    };
    

    代码中scale的意义为1坐标单位有多少个格子,例如scale=0.1即一个格子的大小为10x10。

    Phong材质

    这里实现简单的Phong材质,因为未有光源系统,只用全域变量设置一个临时的光源方向,并只计算漫射(diffuse)和镜射(specular)。

    PhongMaterial = function(diffuse, specular, shininess, reflectiveness) {
        this.diffuse = diffuse;
        this.specular = specular;
        this.shininess = shininess;
        this.reflectiveness = reflectiveness;
    };
    
    // global temp
    var lightDir = new Vector3(1, 1, 1).normalize();
    var lightColor = Color.white;
    
    PhongMaterial.prototype = {
        sample: function(ray, position, normal) {
            var NdotL = normal.dot(lightDir);
            var H = (lightDir.subtract(ray.direction)).normalize();
            var NdotH = normal.dot(H);
            var diffuseTerm = this.diffuse.multiply(Math.max(NdotL, 0));
            var specularTerm = this.specular.multiply(Math.pow(Math.max(NdotH, 0), this.shininess));
            return lightColor.modulate(diffuseTerm.add(specularTerm));
        }
    };
    

    Phong的内容不在此述。

    渲染材质

    修改之前的渲染代码,当碰到相交时,就向几何对象取得material属性,并调用sample方法函数取得颜色。

    // rayTrace.htm
    function rayTrace(canvas, scene, camera) {
        // ...
                if (result.geometry) {
                    var color = result.geometry.material.sample(ray, result.position, result.normal);
                    pixels[i] = color.r * 255;
                    pixels[i + 1] = color.g * 255;
                    pixels[i + 2] = color.b * 255;
                    pixels[i + 3] = 255;
                }
        // ...
    }
    

    var plane = new Plane(new Vector3(0, 1, 0), 0); var sphere1 = new Sphere(new Vector3(-10, 10, -10), 10); var sphere2 = new Sphere(new Vector3(10, 10, -10), 10); plane.material = new CheckerMaterial(0.1); sphere1.material = new PhongMaterial(Color.red, Color.white, 16); sphere2.material = new PhongMaterial(Color.blue, Color.white, 16); rayTrace( document.getElementById('rayTraceCanvas'), new Union([plane, sphere1, sphere2]), new PerspectiveCamera(new Vector3(0, 5, 15), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90));

    Run

     

    修改代码试试看

    • 改变fov,有了格子地板效果应该很明显
    • 改变CheckerMaterial的scale
    • 把原来红色的球改为绿色
    • 把原来红色的球改为黄色
    • 改变shininess(PhongMaterial最后一个参数)

    多个几何物件

    只渲染一个几何物件太乏味,这节再加入一个无限平面,和介绍如何组合多个几何物件。

    平面

    一个(无限)平面(Plane)在数学上可用等式定义:

    n为平面的法向量,d为空间原点至平面的最短距离。光线和平面的相交计算很简单,这里不详述了。

    Plane = function(normal, d) { this.normal = normal; this.d = d; };
    
    Plane.prototype = {
        copy : function() { return new plane(this.normal.copy(), this.d); },
    
        initialize : function() {
            this.position = this.normal.multiply(this.d);
        },
        
        intersect : function(ray) {
            var a = ray.direction.dot(this.normal);
            if (a >= 0)
                return IntersectResult.noHit;
    
            var b = this.normal.dot(ray.origin.subtract(this.position));
            var result = new IntersectResult();
            result.geometry = this;
            result.distance = -b / a;
            result.position = ray.getPoint(result.distance);
            result.normal = this.normal;
            return result;
        }
    };
    

    并集

    把多个几何物件结合起来,可以使用集(set)的概念。这里最容易实现的操作,就是并集(union),即光线要找到一组几个图形的最近交点。无需改其他代码,只加入一个Union类就可以:

    Union = function(geometries) { this.geometries = geometries; };
    
    Union.prototype = {
        initialize: function() {
            for (var i in this.geometries)
                this.geometries[i].initialize();
        },
        
        intersect: function(ray) {
            var minDistance = Infinity;
            var minResult = IntersectResult.noHit;
            for (var i in this.geometries) {
                var result = this.geometries[i].intersect(ray);
                if (result.geometry && result.distance < minDistance) {
                    minDistance = result.distance;
                    minResult = result;
                }
            }
            return minResult;
        }
    };
    

    可以看到,这里利用Javascript的多型(polymorphism)的特性,完全不用修改原来的代码,就可以扩展功能。

    如前所述,这里只考虑几何几何图形的表面。如果考虑几何图形是实心的,就可以用构造实体几何(constructive solid geometry, CSG)方法,提供并集、交集、补集等操作。容后再谈。

    反射

    以上实现的,也只是局部照明。只要再加入一点点代码,就可以实现反射。

    下图说明反射向量的计算方法:

    把d投射到n上(因n是单位向量,只需要点乘即可),就可以计算d在n上的长度,把d减去这长度两倍的法向量,就是反射向量r。数学上可写成:

    一般材质并非完全反射(镜子除外),因此这里为材质加上一个反射度(reflectiveness)的属性。反射的功能很简单,只要在碰到反射度非零的材质,就继续向反射方向追踪,并把结果按反射度来混合。例如一个材质的反射度为25%,则它传回的颜色是75%本身颜色,加上25%反射传回来的颜色。

    另外,不断反射会做成大量的运算,甚至乎永远不能停止(考虑摄影机在两个镜子中间)。因此要限制反射的次数。含反射功能的光线追踪代码如下:

    function rayTraceRecursive(scene, ray, maxReflect) {
        var result = scene.intersect(ray);
        
        if (result.geometry) {
            var reflectiveness = result.geometry.material.reflectiveness;
            var color = result.geometry.material.sample(ray, result.position, result.normal);
            color = color.multiply(1 - reflectiveness);
            
            if (reflectiveness > 0 && maxReflect > 0) {
                var r = result.normal.multiply(-2 * result.normal.dot(ray.direction)).add(ray.direction);
                ray = new Ray3(result.position, r);
                var reflectedColor = rayTraceRecursive(scene, ray, maxReflect - 1);
                color = color.add(reflectedColor.multiply(reflectiveness));
            }
            return color;
        }
        else
            return Color.black;
    }
    
    function rayTraceReflection(canvas, scene, camera, maxReflect) {
        // 从canvas取得imgdata和pixels,跟之前的代码一样
        // ...
    
        scene.initialize();
        camera.initialize();
    
        var i = 0;
        for (var y = 0; y < h; y++) {
            var sy = 1 - y / h;
            for (var x = 0; x < w; x++) {
                var sx = x / w;
                var ray = camera.generateRay(sx, sy);
                var color = rayTraceRecursive(scene, ray, maxReflect);
                pixels[i++] = color.r * 255;
                pixels[i++] = color.g * 255;
                pixels[i++] = color.b * 255;
                pixels[i++] = 255;
            }
        }
    
        ctx.putImageData(imgdata, 0, 0);
    }
    

    var plane = new Plane(new Vector3(0, 1, 0), 0); var sphere1 = new Sphere(new Vector3(-10, 10, -10), 10); var sphere2 = new Sphere(new Vector3(10, 10, -10), 10); plane.material = new CheckerMaterial(0.1, 0.5); sphere1.material = new PhongMaterial(Color.red, Color.white, 16, 0.25); sphere2.material = new PhongMaterial(Color.blue, Color.white, 16, 0.25); rayTraceReflection( document.getElementById('rayTraceReflectionCanvas'), new Union([plane, sphere1, sphere2]), new PerspectiveCamera(new Vector3(0, 5, 15), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90), 3);

    Run

     

    修改代码试试看

    • 改变一个球的reflectiveness,试试0、1及之间的数值
    • 改变maxReflect(rayTraceReflection最后一个参数)
    • 加入更多的球体(可用for循环啊……不过小心渲染时间太长)

    结语

    能体会到计算机图形学的有趣之处么?百多行简单的JavaScript代码,就绘画出像真的影像,那种满足感实非笔墨所能形容。

    本文实现了一个简单的光线追踪渲染器,支持球体、平面、Phong材质、格子材质、多重反射等功能。读者可以下载这组代码,加入不同的扩展,也可以尝试翻译做熟悉的编程语言。很多光线追踪用到的计算机图形技术,也可以应用到实时图形编程里,例如光源和材质的计算,基本上可以简易翻译做实时图形的著色器(shader)编程。

    游戏里采用光栅化渲染技术已有二十年以上,这几年的硬件发展,使其他渲染方法也能用于实时应用。光线追踪和其他类似的方法,有个当今重要优点,就是能高度平行化。采样之间并没有依赖性,例如256x256=65536个采样,理论上,可使用65536个机器/核心独立执行追踪,那么完成时间只是最慢的一个取样所需的时间。

    笔者希望继续撰写这系列,例如包括以下内容:

    • 其他几何图形(长方体、柱体、三角形、曲面、高度场、等值面、……)
    • 光源(方向光源、点光源、聚光灯、阴影、ambient occlusion)
    • 材质(Phong-Blinn、Oren-Nayar、Torrance-Sparrow、折射、 Fresnel、BRDF、BSDF……)
    • 纹理(纹理座标、采样、Perlin noise)
    • 摄影机模型(正投射、全景、景深)
    • 成像流程(渐进渲染、反锯齿、后期处理)
    • 优化方法(场景剖分、低阶优化)
    • 其他全局光照渲染方法

    祈望得到大家的意见反馈。

    参考

    更新

    • 2010年3月31日,网友HouSisong把本文代码以C++实现,并完全保留了原设计,代码可於他的博文下载。

    评论: 55 查看评论 发表评论

    找优秀程序员,就在博客园


    最新新闻:
    · 雅虎针对iPad推出雅虎娱乐应用(2010-04-02 23:03)
    · 纳斯达克针对iPad推出投资组合管理应用(2010-04-02 23:01)
    · 《时代》杂志iPad应用售价4.99美元(2010-04-02 22:59)
    · 夏普今年将推3D 面板 可裸眼观看3D图片(2010-04-02 22:56)
    · 现有软件编写方式阻碍发挥多核潜力?(2010-04-02 22:14)

    编辑推荐:时代周刊:iPad能否让乔布斯续写传奇

    网站导航:博客园首页  个人主页  新闻  闪存  小组  博问  社区  知识库

  • Next page