• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            不會飛的鳥

            2010年12月10日 ... 不鳥他們!!! 我要用自己開發的分布式文件系統、分布式調度系統、分布式檢索系統, 做自己的搜索引擎!!!大魚有大志!!! ---楊書童

            #

            游戲引擎基礎(六)(聲音系統,音頻APIs)

            6部分: 聲音系統,音頻APIs


            聲音系統
              由于人們玩的游戲在種類和技術上的進步,聲音和音樂近幾年來在游戲中正逐漸變得重要起來(聲音是一個實際游戲的可玩特點,比如在Thief和其它同類游戲中的聽覺提示)。現在四聲道環繞系統在游戲玩家的寶庫中是負擔得起的和平常的事。給定空間的聲音,噪音的障礙和閉塞,和動態的音樂,如今許多游戲使用這些提高玩家情緒上的反應,更多的關注投入到這個領域就不足為奇了。

              現在在PC競技場中,游戲玩家實際上只有一種聲音卡可以選擇 -- PC聲卡制造商創新公司(Creative Labs)的Sound Blaster Live 從舊的時間個人計算機聲音卡片制造業者有創造力的中心. 多年來創新公司已經為DirectX提供了他們的EAX聲音擴展,并且他們是發起新的OpenAL(開放音頻庫Open Audio Library)的創立者。就如同OpenGL是一個圖形API一樣,OpenAL,像它起來聽一樣,是一個聲音系統的APIOpenAL 被設計為支持大多數通常聲卡的許多特征,而且在一個特定的硬件特征不可得時提供一個軟件替代。

              為了更好的定義 OpenAL,我向創新公司的Garin Hiebert詢問了其定義:

              "這里借用我們的 " OpenAL 規格和叁考" 的一個定義:

              OpenAL 是對音頻硬件的一個軟件接口,給程序員提供一個產生高質量多通道輸出的能力。OpenAL 是在模擬的三維環境里產生聲音的一種重要方法。它想要跨平臺并容易使用,在風格和規范上與OpenGL相似。任何已經熟悉OpenGL的程序員將發現OpenAL非常熟悉。

              OpenAL API能容易地被擴展適應插件技術.創新公司已經把EAX支持加入到這套API了,程序員可以用來給他們的聲音環境增加復雜的反響,比賽和障礙效果。

              如同Jedi Knight II: Outcast 一樣,連同Eagle 世界/聲音特征編輯器,Soldier of Fortune II 以這個新系統為特征。什么是Eagle 在介紹這個以前,讓我們討論一些其他的系統,并定義一些聲音術語。


              另外的一個系統是Miles聲音系統。Miles是一家公司,它為你的代碼生產插件,在充分利用每塊聲卡時處理所有必須的到特定聲音卡的說話(比如Sound Blaster Live!系列,或者老的A3D聲卡)。它非常像一個API前端,捆綁了一些額外的特征在里面。 在其他事物當中Miles讓你存取一些事物像MP3解壓縮。 它是很好的解決方案,但像任何事一樣,它花費金錢并是你的代碼和硬件之間的額外一層。雖然對於快速的聲音系統制造,它非常有用,而且他們有段時間了,因此他們的確精通自己的業務。


            聲音術語
              讓我們開始障礙和閉塞。它們聽起來一樣,但不是這樣。閉塞基本上意謂著一個聲音在播放時聽者在他們之間有一些閉合的障礙物。

              比如說,在NOLF2的一個屏幕鏡頭上你聽到房子里面壞蛋的聲音。你能聽到他們,但是他們的聲音相當低沉而沙啞。障礙是相似的,但是你和聲音之間的障礙物并不是閉合的。一個好的例子就是在你和聲源之間有一根柱子。由于房間中的回聲你仍然聽得到這個聲音,但是它和聲音直接傳遞到你的耳朵里是不同的。當然這確實依賴于知道在你的耳朵和聲源之間的直線上是什么。而且根據房間的大小,聲源到你的距離等等,需要的處理能變得相當耗時。后面我們將會談到跟蹤--足可以說它時常是比較慢的幀速率的原因。Quake III 里面的A3D 代碼做了這些事情,關閉這些選項通常能夠提高幀速率。Tribe 2 是這種弊病的另外一個受害者。關閉3D聲音選項則你的幀速率立即好轉,這在你考慮Tribes世界有多大和你能看見多遠時有意義。

              接著是聲音物質的特征。大部分聲卡可以讓你能夠用可定義的過濾器作用于聲音從而修正播放的聲音。例如,在水下,或者在一個布料遮蓋的房間中,或者在一個長的走廊中,或者在歌劇院,聽到的聲音有著很大的不同。能夠根據你所處的環境改變你聽到聲音的方式是相當不錯的。

              我們回到Eagle… 這是一個編輯器,允許多數第一人稱射擊游戲地圖設計者將他們的地圖導入到這個工具,然后構造簡化的幾何形體來為實際游戲引擎中的EAX代碼產生一個聲音地圖。其思想是你不需要一個真實的圖形地圖的復雜幾何形體來模擬聲音環境。你也能夠給產生的簡化地圖分配聲音物質,這樣聲音環境就能夠動態地改變。我親眼目睹了這在Soldier of FortuneUnreal Tournament上的示范,確實相當引人注目。 我這在財富和 Unreal 巡回賽和它的軍人上真的對示范是證人相當醒目. 當你跳入水中時,聽到所有的聲音改變,這是一個非常令人沉浸的經歷。

              好,讓我們繼續吧。

              對于游戲機,由于靜態的硬件,你的各種可能性會更受限制盡管在PlayStation 2Xbox上,硬件相當不錯。我說的限制,僅僅是指擴展,而不是它所能夠做的。我一點也不會感到驚訝看到這些游戲機上的游戲很快支持杜比數字5.1Dolby Digital 5.1)輸出。Xbox ,由于它的 MCP 音頻處理器,能夠將任何游戲音頻編碼為5.1,并且游戲不需要特別編碼就能利用這個特征。杜比(Dolby)把ProLogic II 帶到了 PS2 上,并與Factor 5合作為GameCube游戲實現了ProLogic II。在 Xbox 之上,Halo, Madden 2002 Project Gotham Racing等游戲都有5.1杜比數字音頻內容。DTS最近也為 PS2 游戲開發者發布了SDK,為這個平臺上的游戲帶來了降低了比特率的DTS音頻版本。


            位置的聲音--一個復雜的世界
              現在有一些很少有處理的聲音空間化問題。我說的是把聲音放在一個真實的3D世界中。有四個揚聲器在你周圍是一個很棒的開始,但這仍然只是在二維方向。在你的上方和下方沒有揚聲器,你沒有真正獲得3D聲音。有一些聲音調制過濾器試圖解決這個問題,但實際上沒有真實東西的代替物。當然真實地大多數游戲多半只是在二維方向上,因此這仍然不是太大的問題。

              實際上任何聲音系統最重要的特征之一是把聲音混合在一起。根據你所處的位置,空間中聲音的位置,每個聲音的音量大小,一旦你決定了實際上你能夠聽到的聲音,然后你必須混合這些聲音。通常聲音卡自己處理這些,這首先是聲音卡存在的主要原因。然而,外面有一些引擎決定首先用軟件做一次預混合。直到你著眼于一點點歷史以前,這并沒有真正地帶來多大的意義。

              當聲音卡最初問世的時候,有許多不同的混合方法。一些聲卡可以混合8種聲音,一些單位16種,一些32種,等等。 如果你總想聽到16種可能的聲音,但你不知道聲音卡是否能夠處理,那么你回到了嘗試和試驗的道路上 就是你自己用軟件混合。這實際上是Quake III聲音系統的工作方式,但提一個問題:"Quake III是為A3DSound Blaster Live!聲卡世界發布的,這比以前更加標準化,為什么還這樣做?" 這是個好問題。實際上Quake III的聲音系統幾乎每行代碼都和Quake II中的聲音系統一樣。而且Quake I,甚至Doom也是這樣。你想一想,向上直到 A3D 聲卡和 SB Live! 聲卡,許多年來聲音系統的需求沒有真正地改變過。兩個揚聲器,二維方向,音量簡單地隨著距離減小。從Doom一直到Quake III沒有發生太大變化。而且在游戲行業中,如果不是迫不得已,別理會它。

              通常你會僅僅使用DirectSound為你做聲音混合,因為它會可以使用的聲音硬件,或者轉而依靠軟件,很多地方就像DirectX3D顯示卡所做的一樣。在 90% 的聲音情形中,依靠軟件混合對你的幀速率沒有真正發生太多不同。當DirectSound在一些狂熱的編碼者眼中甚至還不是一絲光線時,Doom引擎就已經產生了。它從來沒有得到更新過,因為它從來就沒有真的需要更新。

              當然,你可以使用 SoundBlaster Live!聲卡的一些聰明特征,例如房間的回聲特性: 一塊石窟,或一個禮堂,一個巨穴, 一個足球體育館等。而且你真的應該使用由硬件提供的混合器,畢竟,那是它存在的目的。這種方法的一個不足之處是程序本身時常無法獲得混合結果,因為混合是在聲卡內部完成而不是在主存。如果由于某種原因你需要看到產生的音量,你是運氣不好。


            Music Tracks in Games
            (游戲中的音軌)
              我們沒有過多的談到游戲中的音樂生成。傳統的有兩種方法,一種是簡單的音樂 .wav 文件(或同等物)。它被預先制作做好,準備運行,和最小忙亂。然而,這些在內存和回放時間方面很昂貴。第二種方式用預設的樣本編碼MIDI音軌。這時常比較節省內存,但缺點是必須同時把一些聲音混合在一起,因而會把聲音通道用光。

              動態音樂就是根據在游戲中目睹的行動改變你的音樂的能力,比如探險用慢節奏的音樂,戰斗用快節奏的音樂。預先制作的音樂的一個困難之處是要合拍,因此你可以從一段音樂漸弱到另一段音樂,這對于MIDI音軌比較容易。盡管時常你足夠快速地淡出,或者一段音樂在播放另一段音樂之前已經消失了,你能僥幸不被察覺。

              在我們離開這個主題之前,順便說一下,值得一提的是存在一些公司專門為你的游戲創作特定意義的音樂。FatMan(www.fatman.com) 就是一家這樣的公司。音樂可能比其他別的東西更加容易外包,這是他們存在的方式。

              最后,游戲現在的事情自然是MP3格式,允許巨大的11 1的聲音樣本壓縮,然而在送到聲音卡之前只花費CPU很少的時間解壓縮。當我在Rave Software工作時,在Star Trek Voyager: Elite Force 中,我們設法用MP3在一張CD上面完全支持三種語言,仍然為較多的圖形留有空間。主要地,我們 MP3 只用于非玩家角色(NPC)的語音,由于游戲的全部音頻效果MP3流和動態解壓縮超出了硬件的處理能力,雖然在將來這是肯定可能的。比較新的格式,如來自 Dolby AAC 和來自微軟的WMA,以將近兩倍MP3的壓縮率提供了相等或者更高的音頻質量(實際上一半的比特率),可能應用到將來的游戲中。

              以上是這一章節的內容,下面將是網絡和連線游戲環境的開發。



            夢在天涯 2007-12-04 13:20 發表評論

            posted @ 2009-04-10 10:44 不會飛的鳥 閱讀(299) | 評論 (0)編輯 收藏

            游戲引擎基礎(四)(模型與動畫,細節級別)

            4部份: 模型與動畫,細節級別


            角色建模與動畫
              你的角色模型在屏幕上看起來怎么樣,怎樣容易創建它們,紋理,以及動畫對于現代游戲試圖完成的`消除不可信`因素來說至關重要。角色模型系統逐漸變得復雜起來, 包括較高的多邊形數量模型, 和讓模型在屏幕上移動的更好方式。

              如今你需要一個骨骼模型系統,有骨架和網格細節層次,單個頂點骨架的評估,骨架動畫忽略,以及比賽中停留的角度忽略。而這些甚至還沒有開始涉及一些你能做的很好的事情,像動畫混合,骨架反向運動學(IK),和單個骨架限制,以及相片真實感的紋理。這個清單還能夠繼續列下去。但是真的,在用專業行話說了所有這些以后,我們在這里真正談論的是什么呢?讓我們看看。

              讓我們定義一個基于網格的系統和一個骨骼動畫系統作為開始。在基于網格的系統,對于每一個動畫幀,你要定義模型網格的每個點在世界中的位置。舉例來說,你有一個包含200 個多邊形的手的模型,有 300 個頂點(注意,在頂點和多邊形之間通常并不是3個對1個的關系,因為大量多邊形時常共享頂點使用條形和扇形,你能大幅減少頂點數量)。如果動畫有 10 幀,那么你就需要在內存中有300個頂點位置的數據。 總共有300 x 10 = 3000 頂點,每個頂點由xyz和顏色/alpha信息組成。你能看見這個增長起來是多么的快。Quake III III 都使用了這種系統,這種系統確實有動態變形網格的能力,比如使裙子擺動,或者讓頭發飄動。

              相比之下,在骨骼動畫系統,網格是由骨架組成的骨骼( 骨架是你運動的對象) 網格頂點和骨架本身相關,所以它們在模型中的位置都是相對于骨架,而不是網格代表每個頂點在世界中的位置。因此,如果你移動骨架,組成多邊形的頂點的位置也相應改變。這意謂著你只必須使骨骼運動,典型情況大約有 50 個左右的骨架很明顯極大地節省了內存。


            骨骼動畫附加的好處
              骨骼動畫的另一個優點是能夠根據影響頂點的一些骨架來分別估價每個頂點。例如,雙臂的骨架運動,肩,脖子而且甚至軀干都能在肩中影響網格。當你移動軀干的時候,網格就活像一個角色一樣移動。總的效果是3D角色能夠實現的動畫更加流暢和可信,且需要更少的內存。每個人都贏了。

              當然這里的缺點是,如果你想要使有機的東西運動且很好,比如說頭發,或者披肩,為了讓它看起來自然,你最后不得不在里面放置數量驚人的骨架,這會抬高一些處理時間。

              基于骨骼的系統能帶給你的一些其他事情是忽略特定層次骨架的能力 -- ,"我不關心動畫想要對這塊骨架所做的事情,我想要讓它指向世界中的一個特定點"。這很棒。你能讓模型著眼于世界中的事件,或者使他們的腳在他們站著的地面保持水平。這一切非常微妙,但它可以幫助帶給場景附加的真實感。

              在骨骼系統,你甚至可以指定"我需要把這個特別的動畫用於模型的腿,而一個不同的攜槍或射擊動畫在模型軀干上播放,且那家伙(角色)叫喊的不同動畫效果在模型的頭部播放"。非常妙。Ghoul2 ( Soldier of Fortune II: Double Helix and Jedi Knight I: Outcast中使用了Raven的動畫系統 ) 擁有所有這些好東西,且特別被設計為允許程序員使用所有這些忽略能力。這對動畫的節省像你一樣難以相信。像你一樣的動畫上的這次救援不相信. Raven有一個角色行走的動畫和一個站立開火的動畫,并在它同時行走和開火形下把這兩個動畫合并,而不是需要一個動畫表示角色行走并開火。


            More Skeletons in the Closet
              先前描述的效果可以通過具有層次的骨骼系統來完成。這是什么意思呢?意思是每塊骨架實際上的位置相對于它的父親,而不是每個骨架直接位于空間中的地方。這意謂著如果你移動父親骨架,那么它所有的子孫骨架也跟著移動,在代碼上不需要任何額外的努力。這是讓你能夠在任何骨架層次改變動畫,而且通過骨骼其余部分向下傳遞的東西。

              創建一個沒有層次的骨骼系統是可能的 -- 但那時你不能忽略一個骨架并且預期它工作。你所看到的只是身體上的一個骨架開始了新動畫,除非你實現了某種向下傳遞信息的系統,否則在該骨架下面的其它骨架保持原來的動畫。首先由一個層次系統開始,你就自動地獲得這些效果。

              許多今天的動畫系統中正開始出現一些比較新的特征,如動畫混合,從一個正在播放的動畫轉變到另外一個動畫需要經過一小段時間,而不是立即從一個動畫突然轉變到另外一個。舉例來說,你有個角色在行走,然后他停了下來。你不是僅僅突然地轉變動畫,讓他的腿和腳停在無效位置,而是一秒鐘混合一半,這樣腳似乎自然地移到了新的動畫。不能夠過高的評價這種效果 -- 混合是一個微妙的事情,但如果正確的運用,它真的有些差別。


            反向運動學
              反向運動學 (IK) 是被許多人們丟棄的一個專業術語,對它的真實含義沒有多少概念。IK 是如今游戲里面一個相對比較新的系統。使用 IK ,程序員能夠移動一只手,或一條腿, 模型的其余關節自動重新定位,因此模型被正確定向。而且有模型的關節新位置的其馀者他們自己,因此模型正確的被定向。比如,你將會說,"好,手 , 去拾起桌子上的那個杯子"并指出杯子在世界中的位置。手就會移動到那里,且它后面的身體會調節其自身以便雙臂移動,身體適當彎曲,等等。

              也有和IK相反的事情,叫做前向運動學,本質上與 IK 工作的次序相反。想像一只手,手附著在手臂上,手臂附著在身體上。現在想像你重重地擊中了身體。通常手臂像連迦般抽動,且手臂末梢的手隨之振動。 IK 能夠移動身體,并讓其余的四肢自己以真實的方式移動。基本上它需要動畫師設定每種工作的大量信息 -- 像關節所能通過的運動范圍,如果一塊骨架前面的骨架移動,那么這塊骨架將移動多少百分比,等等。

              和它現在一樣,盡管很好,它是一個很大的處理問題,不用它你可以有不同的動畫組合而脫身。值得注意的是,真正的 IK 解決辦法需要一個層次骨骼系統而不是一個模型空間系統 -- 否則它們都耗時太多以致無法恰當地計算每個骨架。


            LOD
            幾何系統
              最后,我們應當快速討論一下與縮放模型幾何復雜度相關的細節級別(LOD)系統(與討論MIP映射時使用的LOD相對照)。假定如今絕大多數PC游戲支持的處理器速度的巨大范圍,以及你可能渲染的任何給定可視場景的動態性質(在屏幕上有一個角色還是12個?) 你通常需要一些系統來處理這樣的情況,比如,當系統接近極限試圖同時在屏幕上繪制出12個角色,每個角色有3000個多邊形,并維持現實的幀速率。 LOD 被設計來協助這樣的情景中。最基本的情況,它是在任何給定時間動態地改變你在屏幕上繪制的角色的多邊形數量的能力。面對現實吧,當一個角色走遠,也許只有十個屏幕像素高度,你真的不需要3000個多邊形來渲染這個角色 -- 或許300個就夠了,而且你很難分辨出差別。

              一些 LOD 系統將會需要你建立模型的多個版本,而且他們將會依靠模型離觀察者的接近程度來改變屏幕上的LOD級別, 以及多少個多邊形正被同時顯示。更加復雜的系統實際上將會動態地減少屏幕上的多邊形數量,在任何給定時間,任何給定的角色,動態地 -- MessiahSacrifice包括了這種風格的技術,盡管在CPU方面并不便宜。你必須確信,與首先簡單地渲染整個事物相比,你的 LOD 系統沒有花較多的時間計算出要渲染那些多邊形(或不渲染)。 任一方式都將會工作,由于如今我們試圖要在屏幕上繪制的多邊形數量,這是件非常必要的事情。注意, DX9 將會支持硬件執行的自適應幾何縮放(tessellation)

              歸結起來是,得到一個運動流暢,其表現和移動在視覺上可信,屏幕上看起來逼真的模型。流暢的動畫時常是通過手工建造動畫和運動捕捉動畫的組合得到。有時你僅僅手工建立了一個給定的動畫 -- 當你在為一個模型做一些你在現實生活中不能做到的事情的動畫時, 你傾向于這樣做 -- 舉例來說,你確實不能向后彎腰,或像Mortal Kombat 4中的Lui Kang那樣在行進的腳踏車上踢腿,通常運動捕捉這時候就出局了! 通常運動捕捉動畫 -- 實際上視頻捕捉活生生的演員貫穿于你想在屏幕上所看到的動畫 -- 是得到逼真的東西的方式。真實感的東西能使一款普通游戲看起來很棒,而且能掩飾許多事情。比如 NFL Blitz,屏幕上的模型大約有 200 個多邊形。它們在靜止站立時看起來可怕的斑駁,一旦這些模型跑動起來它們就有快速流暢的動畫,模型自身的許多丑陋消失了。眼睛容易看見的是 '逼真的' 動畫而不是模型自身的結構。 一個不錯的模型設計師能夠掩飾大多數模型缺陷。

              我希望這些帶給你對模型和動畫問題的洞察力。在第五部份中,我們將會更加深入3D世界的建造,討論一些物理,運動和效果系統的東西。



            夢在天涯 2007-12-04 13:18 發表評論

            posted @ 2009-04-10 10:44 不會飛的鳥 閱讀(122) | 評論 (0)編輯 收藏

            游戲引擎基礎(三)(內存使用,特效和API)

            3部份: 內存使用,特效和API


            關于內存使用的思考
              讓我們想一想,在今天實際上是如何使用3D 顯卡內存的以及在將來又會如何使用。 如今絕大多數3D顯卡處理32位像素顏色,8位紅色, 8位藍色,8 位綠色,和 8 位透明度。這些組合的紅,藍和綠256個色度,可以組成 167 百萬種顏色-- 那是你我可以在一個監視器上看見的所有顏色。

              那么,游戲設計大師John Carmack 為什么要求 64 位顏色分辨率呢? 如果我們看不出區別,又有什么意義呢? 意義是: 比如說, 有十幾個燈光照射模型上的點,顏色顏色各不相同。 我們取模型的最初顏色,然后計算一個燈光的照射,模型顏色值將改變。 然后我們計算另外的一個燈光, 模型顏色值進一步改變。 這里的問題是,因為顏色值只有8位,在計算了4個燈光之后,8位的顏色值將不足以給我們最后的顏色較好的分辨率和表現。分辨率的不足是由量化誤差導致的,本質原因是由于位數不足引起的舍入誤差。

              你能很快地用盡位數,而且同樣地,所有的顏色被清掉。每顏色16 32 位,你有一個更高分辨率,因此你能夠反復著色以適當地表現最后的顏色。這樣的顏色深度很快就能消耗大量的存儲空間。我們也應提到整個顯卡內存與紋理內存。這里所要說的是,每個3D 顯卡實際只有有限的內存,而這些內存要存儲前端和后端緩沖區,Z 緩沖區,還有所有的令人驚奇的紋理。最初的 Voodoo1 顯卡只有2MB顯存,后來 Riva TNT提高到16MB顯存。然后 GeForce ATI Rage32MB顯存, 現在一些 GeForce 2 4的顯卡和 Radeons 帶有 64MB 128MB 的顯存。 這為什么重要? 好吧,讓我們看一些數字

              比如你想讓你的游戲看起來最好,所以你想要讓它以32位屏幕, 1280x1024分辨率和32 Z- 緩沖跑起來。 好,屏幕上每個像素4個字節,外加每個像素4字節的Z-緩沖,因為都是每像素32位。我們有1280x1024 個像素也就是 1310720個像素。基于前端緩沖區和Z-緩沖區的字節數,這個數字乘以8,是 10485760字節。包括一個后端緩沖區,這樣是 1280x1024x12 也就是 15728640 字節, 15MB 在一個 16MB 顯存的顯卡上,就只給我們剩下1MB 來存儲所有的紋理。 現在如果最初的紋理是真32 位或 4字節寬,那么我們每幀能在顯卡上存儲 1MB/4字節每像素 = 262144個像素。這大約是4 256x256 的紋理頁面。

              很清楚,上述例子表明,舊的16MB 顯卡沒有現代游戲表現其絢麗畫面所需要的足夠內存。很明顯,在它繪制畫面的時候,我們每幀都必須重新把紋理裝載到顯卡。實際上,設計AGP總線的目的就是完成這個任務,不過, AGP 還是要比 3D 掀卡的幀緩沖區慢,所以你會受到性能上的一些損失。很明顯,如果紋理由32位降低到16位,你就能夠通過AGP以較低的分辨率傳送兩倍數量的紋理。如果你的游戲以每個像素比較低的色彩分辨率跑, 那么就可以有更多的顯示內存用來保存常用的紋理 (稱為高速緩存紋理) 但實際上你永遠不可能預知使用者將如何設置他們的系統。如果他們有一個在高分辨率和顏色深度跑的顯卡,那么他們將會更可能那樣設定他們的顯卡。



              我們現在開始講霧,它是某種視覺上的效果。如今絕大多數的引擎都能處理霧, 因為霧非常方便地讓遠處的世界淡出視野,所以當模型和場景地理越過觀察體后平面進入視覺范圍內時,你就不會看見它們突然從遠處跳出來了。 也有一種稱為體霧的技術。這種霧不是隨物體離照相機的距離而定,它實際上是一個你能看見的真實對象,并且可以穿越它,從另外一側出去 -- 當你在穿越對象的時候,視覺上霧的可見程度隨著變化。想象一下穿過云團 -- 這是體霧的一個完美例子。體霧的一些好的實現例子是Quake III一些關卡中的紅色霧,或新的Rogue Squadron II Lucas Arts GameCube 版本。其中有一些是我曾經見過的最好的云--大約與你能看見的一樣真實。

              在我們討論霧化的時候,可能是簡短介紹一下 Alpha 測試和紋理Alpha混合的好時機。當渲染器往屏幕上畫一個特定像素時,假定它已經通過 Z- 緩沖測試 (在下面定義),我們可能最后做一些Alpha測試。我們可能發現為了顯示像素后面的某些東西,像素需要透明繪制。這意味著我們必須取得像素的已有值,和我們新的像素值進行混和,并把混合結果的像素值放回原處。這稱為讀-修改-寫操作,遠比正常的像素寫操作費時。

              你可以用不同類型的混合,這些不同的效果被稱為混合模式。直接Alpha混合只是把背景像素的一些百分比值加到新像素的相反百分比值上面。還有加法混合,將舊像素的一些百分比,和特定數量(而不是百分比)的新像素相加。 這樣效果會更加鮮明。 (Kyle's Lightsaber Jedi Knight II 中的效果)

              每當廠商提供新的顯卡時,我們可以得到硬件支持的更新更復雜的混合模式,從而制作出更多更眩目的效果。GF3+4和最近的Radeon顯卡提供的像素操作,已經到了極限。


            模板陰影與深度測試
              用模板產生陰影效果,事情就變得復雜而昂貴了。這里不討論太多細節(可以寫成一篇單獨的文章了),其思想是,從光源視角繪制模型視圖,然后用這個把多邊形紋理形狀產生或投射到受影響的物體表面。

              實際上你是在視野中投射將會在其他多邊形上面的光體。最后你得到看似真實的光照,甚至帶有視角在里面。因為要動態創建紋理,并對同一場景進行多遍繪制,所以這很昂貴。

              你能用眾多不同方法產生陰影,情形時常是這樣一來,渲染質量與產生效果所需要的渲染工作成比例。有所謂的硬陰影或軟陰影之分,而后者較好,因為它們更加準確地模仿陰影通常在真實世界的行為。 通常有一些被游戲開發者偏愛的足夠好的方法。如要更多的了解陰影,請參考 Dave Salvator 3D 流水線一文。


            深度測試
              現在我們開始討論深度測試, 深度測試丟棄隱藏的像素,過度繪制開始起作用。過度繪制非常簡單在一幀中,你數次繪制一個像素位置。它以3D場景中Z(深度)方向上存在的元素數量為基礎,也被稱為深度復雜度。如果你常常太多的過度繪制, -- 舉例來說, 符咒的眩目視覺特效,就象Heretic II,能讓你的幀速率變得很糟糕。當屏幕上的一些人們彼此施放符咒時,Heretic II設計的一些最初效果造成的情形是,他們在一幀中對屏幕上每個相同的像素畫了40! 不用說,這必須調整,尤其是軟件渲染器,除了將游戲降低到象是滑雪表演外,它根本不能處理這樣的負荷。深度測試是一種用來決定在相同的像素位置上哪些對象在其它對象前面的技術,這樣我們就能夠避免繪制那些隱藏的對象。

              看著場景并想想你所看不見的。 換句話說,是什么在其他場景對象前面,或者隱藏了其他場景對象? 是深度測試作出的這個決定。

              我將進一步解釋深度深度如何幫助提高幀速率。想像一個很瑣細的場景,大量的多邊形 (或像素)位于彼此的后面,在渲染器獲得他們之間沒有一個快速的方法丟棄他們。對非Alpha混合的多邊形分類排序( Z- 方向上),首先渲染離你最近的那些多邊形,優先使用距離最近的像素填充屏幕。所以當你要渲染它們后面的像素(由Z或者深度測試決定)時,這些像素很快被丟棄,從而避免了混合步驟并節省了時間。如果你從后到前繪制,所有隱藏的對象將被完全繪制,然后又被其他對象完全重寫覆蓋。場景越復雜,這種情況就越糟糕,所以深度測試是個好東西。


            抗鋸齒
              讓我們快速的看一下抗鋸齒。當渲染單個多邊形時,3D 顯卡仔細檢查已經渲染的,并對新的多邊形的邊緣進行柔化,這樣你就不會得到明顯可見的鋸齒形的像素邊緣。兩種技術方法之一通常被用來處理。 第一種方法是單個多邊形層次,需要你從視野后面到前面渲染多邊形,這樣每個多邊形都能和它后面的進行適當的混合。如果不按序進行渲染,最后你會看見各種奇怪的效果。在第二種方法中,使用比實際顯示更大的分辯率來渲染整幅幀畫面,然后在你縮小圖像時,尖銳的鋸齒形邊緣就混合消失了。這第二種方法的結果不錯,但因為顯卡需要渲染比實際結果幀更多的像素,所以需要大量的內存資源和很高的內存帶寬。

              多數新的顯卡能很好地處理這些,但仍然有多種抗鋸齒模式可以供你選擇,因此你可以在性能和質量之間作出折衷。對於當今流行的各種不同抗鋸齒技術的更詳細討論請參見Dave Salvator 3D 流水線一文。


            頂點與像素著色
              在結束討論渲染技術之前,我們快速的說一下頂點和像素著色,最近它們正引起很多關注。頂點著色是一種直接使用顯卡硬件特征的方式,不使用API。舉例來說,如果顯卡支持硬件 T & L ,你可以用DirectXOpenGL編程,并希望你的頂點通過 T & L 單元 (因為這完全由驅動程序處理,所以沒有辦法確信),或者你直接利用顯卡硬件使用頂點著色。它們允許你根據顯卡自身特征進行特別編碼,你自己特殊的編碼使用T & L 引擎,以及為了發揮你的最大優勢,顯卡必須提供的其他別的特征。 事實上,現在nVidia ATI 在他們大量的顯卡上都提供了這個特征。

              不幸的是,顯卡之間表示頂點著色的方法并不一致。你不能象使用DirectX或者OpenGL 那樣,為頂點著色編寫一次代碼就可以在任何顯卡上運行,這可是個壞消息。然而,因為你直接和顯卡硬件交流,它為快速渲染頂點著色可能生成的效果提供最大的承諾。( 如同創造很不錯的特效 -- 你能夠使用頂點著色以API沒有提供的方式影響事物)。事實上,頂點著色正在真的將3D 圖形顯示卡帶回到游戲機的編碼方式,直接存取硬件,最大限度利用系統的必須知識,而不是依靠API來為你做一切。對一些程序員來說,會對這種編碼方式感到吃驚,但這是進步代價。

              進一步闡述,頂點著色是一些在頂點被送到顯卡渲染之前計算和運行頂點效果程序或者例程。你可以在主CPU上面用軟件來做這些事情,或者使用顯卡上的頂點著色。 為動畫模型變換網格是頂點程序的主選。

              像素著色是那些你寫的例程,當繪制紋理時,這些例程就逐個像素被執行。你有效地用這些新的例程推翻了顯卡硬件正常情況做的混合模式運算。這允許你做一些很不錯的像素效果, 比如,使遠處的紋理模糊,添加炮火煙霧, 產生水中的反射效果等。一旦 ATI nVidia 能實際上就像素著色版本達成一致( DX9's 新的高級陰影語言將會幫助促進這一目標), 我一點不驚訝DirectX OpenGL采用Glide的方式-- 有幫助開始, 但最終不是把任何顯卡發揮到極限的最好方法。我認為我會有興趣觀望將來。


            最后(In Closing...
              最終,渲染器是游戲程序員最受評判的地方。在這個行業,視覺上的華麗非常重要,因此它為知道你正在做的買單。對于渲染器程序員,最壞的因素之一就是3D 顯卡工業界變化的速度。一天,你正在嘗試使透明圖像正確地工作;第二天 nVidia 正在做頂點著色編程的展示。而且發展非常快,大致上,四年以前為那個時代的 3D 顯卡寫的代碼現在已經過時了,需要全部重寫。 甚至John Carmack 這樣描述過,他知道四年以前為充分發揮那個時期顯卡的性能所寫的不錯的代碼,如今很平凡 -- 因此他產生了為每個新的id項目完全重寫渲染器的欲望。Epic Tim Sweeney贊同 -- 這里是去年他給我的評論:

              我們已經足足花費了9個月時間來更換所有的渲染代碼。最初的 Unreal 被設計為軟件渲染和后來擴展為硬件渲染。下一代引擎被設計為 GeForce 及更好的圖形顯示卡,且多邊形吞吐量是Unreal Tournament100倍。

              這需要全部替換渲染器。很幸運,該引擎模塊化程度足夠好,我們可以保持引擎的其余部分編輯器,物理學,人工智能,網絡--不改動,盡管我們一直在以許多方式改進這些部分。

              搭配長篇文章的短篇報導(Sidebar):API -- 祝福和詛咒
              那么什么是API? 它是應用程序編程接口,將不一致的后端用一致的前端呈現出來。舉例來說,很大程度上每種3D顯示卡的3D實現方式都有所差別。然而,他們全部都呈現一個一致的前端給最終使用者或者程序員,所以他們知道他們為X 3D顯示卡寫的代碼將會在Y 3D顯示卡上面有相同的結果。好吧,不管怎樣理論上是那樣。 大約在三年以前這可能是相當真實的陳述,但自那以后,在nVidia 公司的引領下,3D顯卡行業的事情發生了變化。

              如今在PC領域,除非你正計劃建造自己的軟件光柵引擎,使用CPU來繪制你所有的精靈,多邊形和粒子 -- 而且人們仍然在這樣做。跟Unreal一樣,Age of Empires II: Age of Kings有一個優秀的軟件渲染器否則你將使用兩種可能的圖形APIOpenGL或者 DirectX 之一。OpenGL是一種真正的跨平臺API (使用這種API寫的軟件可以在LinuxWindowsMacOS上運行。) 而且有多年的歷史了,為人所熟知,但也開始慢慢地顯示出它的古老。 大約在四年以前,定義OpenGL驅動特征集一直是所有顯示卡廠商工作的方向。

              然而,一旦在目標達成以后,沒有預先制定特征工作方向的路線圖,這時候,所有的顯卡開發商開始在特征集上分道揚鑣,使用OpenGL擴展。

              3dfx 創造了T- 緩沖。 nVidia 努力尋求硬件變換和光照計算。Matrox努力獲取凹凸貼圖。等等。 我以前說過的一句話,"過去幾年以來,3D顯示卡領域的事情發生了變化。"委婉地說明了這一切。

              無論如何,另一個可以選擇的API DirectX。這受Microsoft公司控制,且在PC Xbox 上被完美地支持。由于明顯的原因,DirectX 沒有Apple或者 Linux 版本。因為Microsoft控制著 DirectX,大體上它容易更好地集成在Windows里面。

              OpenGLDirectX之間的基本差別是前者由社區擁有,而后者由Microsoft擁有。如果你想要 DirectX 為你的 3D 顯示卡支持一個新的特征,那么你需要游說微軟,希望采納你的愿望,并等待新的 DirectX發行版本。對于OpenGL,由于顯示卡制造商為3D顯示卡提供驅動程序,你能夠通過OpenGL擴展立即獲得顯示卡的新特征。這是好,但作為游戲開發者,當你為游戲編碼的時候,你不能指望它們很普遍。它們可能讓你的游戲速度提升50%,但你不能要求別人有一塊GeForce 3 來跑你的游戲。好吧,你可以這么做,但如果你想來年還在這個行業的話,這是個相當愚蠢的主意。

              這是對這個問題極大的簡單化,對我所有描述的也有各種例外情況,但這里一般的思想是很確實的。對于DirectX ,在任何既定時間你容易確切地知道你能從顯示卡獲得的特征,如果一個特征不能獲得,DirectX 將會用軟件模擬它(也不總是一件好事情,因為這樣有時侯非常的慢,但那是另外一回事)。對于OpenGL,你可以更加貼近顯示卡的特征,但代價是不能確定將會獲得的準確特征。



            夢在天涯 2007-12-04 13:17 發表評論

            posted @ 2009-04-10 10:44 不會飛的鳥 閱讀(90) | 評論 (0)編輯 收藏

            游戲引擎基礎(二)(3D環境的光照和紋理)

            2部份: 3D環境的光照和紋理


            世界的燈光
              在變換過程中, 通常是在稱為觀察空間的坐標空間中, 我們遇到了最重要的運算之一: 光照計算。 它是一種這樣的事情, 當它工作時,你不關注它,但當它不工作時, 你就非常關注它了。有很多不同的光照方法,從簡單的計算多邊形對于燈光的朝向,并根據燈光到多邊形的方向和距離加上燈光顏色的百分比值,一直到產生邊緣平滑的燈光貼圖疊加基本紋理。而且一些 API 實際上提供預先建造的光照方法。舉例來說,OpenGL 提供了每多邊形,每頂點,和每像素的光照計算。

              在頂點光照中,你要決定一個頂點被多少個多邊形共享,并計算出共享該頂點的所有多邊形法向量的均值(稱為法向量),并將該法向量賦頂點。一個給定多邊形的每個頂點會有不同的法向量,所以你需要漸變或插值多邊形頂點的光照顏色以便得到平滑的光照效果。 你沒有必要用這種光照方式查看每個單獨的多邊形。 這種方式的優點是時常可以使用硬件轉換與光照(T & L)來幫助快速完成。 不足之處是它不能產生陰影。 舉例來說,即使燈光是在模型的右側,左手臂應該在被身體投影的陰影中,而實際上模型的雙臂卻以同樣的方式被照明了。

              這些簡單的方法使用著色來達到它們的目標。 當用平面光照繪制一個多邊形時, 你讓渲染(繪制)引擎把整個多邊形都著上一種指定的顏色。這叫做平面著色光照。 (該方法中,多邊形均對應一個光強度,表面上所有點都用相同的強度值顯示,渲染繪制時得到一種平面效果,多邊形的邊緣不能精確的顯示出來)

              對于頂點著色 ( Gouraud 著色) ,你讓渲染引擎給每個頂點賦予特定的顏色。 在繪制多邊形上各點投影所對應的像素時,根據它們與各頂點的距離,對這些頂點的顏色進行插值計算。 (實際上Quake III 模型使用的就是這種方法, 效果好的令人驚奇)

              還有就是 Phong 著色。如同 Gouraud 著色,通過紋理工作,但不對每個頂點顏色進行插值決定像素顏色值, 它對每個頂點的法向量進行插值,會為每個頂點投影的像素做相同的工作。對于 Gouraud 著色,你需要知道哪些光投射在每個頂點上。對于 Phong 著色,你對每個像素也要知道這么多。

              一點也不令人驚訝, Phong 著色可以得到更加平滑的效果,因為每個像素都需要進行光照計算,其繪制非常耗費時間。平面光照處理方法很快速, 但比較粗糙。Phong 著色比 Gouraud 著色計算更昂貴,但效果最好,可以達到鏡面高光效果("高亮") 這些都需要你在游戲開發中折衷權衡。


            不同的燈光
              接著是生成照明映射,你用第二個紋理映射(照明映射)與已有的紋理混合來產生照明效果。這樣工作得很好, 但這本質上是在渲染之前預先生成的一種罐裝效果。如果你使用動態照明 (即,燈光移動, 或者沒有程序的干預而打開和關閉),你得必須在每一幀重新生成照明映射,按照動態燈光的運動方式修改這些照明映射。燈光映射能夠快速的渲染,但對存儲這些燈光紋理所需的內存消耗非常昂貴。你可以使用一些壓縮技巧使它們占用較少的的內存空間,或減少其尺寸大小, 甚至使它們是單色的 (這樣做就不會有彩色燈光了),等等。 如果你確實在場景中有多個動態燈光, 重新生成照明映射將以昂貴的CPU周期而告終。

              許多游戲通常使用某種混合照明方式。 Quake III為例,場景使用照明映射, 動畫模型使用頂點照明。 預先處理的燈光不會對動畫模型產生正確的效果 -- 整個多邊形模型得到燈光的全部光照值 -- 而動態照明將被用來產生正確的效果。 使用混合照明方式是多數的人們沒有注意到的一個折衷,它通常讓效果看起來"正確" 這就是游戲的全部做一切必要的工作讓效果看起來"正確",但不必真的是正確的。

              當然,所有這些在新的Doom引擎里面都不復存在了,但要看到所有的效果,至少需要 1GHZ CPU GeForce 2 顯卡。是進步了,但一切都是有代價的。

              一旦場景經過轉換和照明, 我們就進行裁剪運算。 不進入血淋淋的細節而,剪斷運算決定哪些三角形完全在場景 (被稱為觀察平截頭體) 之內或部份地在場景之內。完全在場景之內的三角形被稱為細節接受,它們被處理。對于只是部分在場景之內的三角形, 位于平截頭體外面的部分將被裁剪掉,余下位于平截頭體內部的多邊形部分將需要重新閉合,以便其完全位于可見場景之內。 (更多的細節請參考我們的 3D 流水線指導一文)

              場景經過裁剪以后,流水線中的下一個階段就是三角形生成階段(也叫做掃描 線轉換),場景被映射到2D 屏幕坐標。到這里,就是渲染(繪制)運算了。


            紋理與MIP映射
              紋理在使3D場景看起來真實方面異常重要,它們是你應用到場景區域或對象的一些分解成多邊形的小圖片。多重紋理耗費大量的內存,有不同的技術來幫助管理它們的尺寸大小。紋理壓縮是在保持圖片信息的情況下,讓紋理數據更小的一種方法。紋理壓縮占用較少的游戲CD空間,更重要的是,占用較少內存和3D 顯卡存儲空間。另外,在你第一次要求顯卡顯示紋理的時候,壓縮的(較小的) 版本經過 AGP 接口從 PC 主存送到3D 顯卡, 會更快一些。紋理壓縮是件好事情。 在下面我們將會更多的討論紋理壓縮。


            MIP
            映射(多紋理映射)
              游戲引擎用來減少紋理內存和帶寬需求的另外一個技術就是 MIP 映射。 MIP 映射技術通過預先處理紋理,產生它的多個拷貝紋理,每個相繼的拷貝是上一個拷貝的一半大小。為什么要這樣做?要回答這個問題,你需要了解 3D 顯卡是如何顯示紋理的。最壞情況,你選擇一個紋理,貼到一個多邊形上,然后輸出到屏幕。我們說這是一對一的關系,最初紋理映射圖的一個紋素 (紋理元素) 對應到紋理映射對象多邊形的一個像素。如果你顯示的多邊形被縮小一半,紋理的紋素就每間隔一個被顯示。這樣通常沒有什么問題 -- 但在某些情況下會導致一些視覺上的怪異現象。讓我們看看磚塊墻壁。 假設最初的紋理是一面磚墻,有許多磚塊,磚塊之間的泥漿寬度只有一個像素。如果你把多邊形縮小一半, 紋素只是每間隔一個被應用,這時候,所有的泥漿會突然消失,因為它們被縮掉了。你只會看到一些奇怪的圖像。

              使用 MIP 映射,你可以在顯示卡應用紋理之前,自己縮放圖像,因為可以預先處理紋理,你做得更好一些,讓泥漿不被縮掉。當 3D 顯卡用紋理繪制多邊形時,它檢測到縮放因子,說,"你知道,我要使用小一些的紋理,而不是縮小最大的紋理,這樣看起來會更好一些。" 在這里, MIP 映射為了一切,一切也為了 MIP 映射。


            多重紋理與凹凸映射
              單一紋理映射給整個3D 真實感圖形帶來很大的不同, 但使用多重紋理甚至可以達到一些更加令人難忘的效果。過去這一直需要多遍渲染(繪制),嚴重影響了像素填充率。 但許多具有多流水線的3D 加速卡,如ATI's Radeon nVidia's GeForce 2及更高級的顯卡,多重紋理可以在一遍渲染(繪制)過程中完成。 產生多重紋理效果時, 你先用一個紋理繪制多邊形,然后再用另外一個紋理透明地繪制在多邊形上面。這讓你可以使紋理看上去在移動,或脈動, 甚至產生陰影效果 (我們在照明一節中描述過)。繪制第一個紋理映射,然后在上面繪制帶透明的全黑紋理,引起一種是所有的織法黑色的但是有一個透明分層堆積過它的頂端 這就是 -- 即時陰影。 該技術被稱為照明映射 ( 有時也稱為 暗映射),直至新的Doom ,一直是Id引擎里關卡照明的傳統方法。

              凹凸貼圖是最近涌現出來的一種古老技術。幾年以前 Matrox 第一個在流行的 3D 游戲中發起使用各種不同形式的凹凸貼圖。就是生成紋理來表現燈光在表面的投射,表現表面的凹凸或表面的裂縫。 凹凸貼圖并不隨著燈光一起移動 -- 它被設計用來表現一個表面上的細小瑕疵,而不是大的凹凸。 比如說,在飛行模擬器中,你可以使用凹凸貼圖來產生像是隨機的地表細節,而不是重復地使用相同的紋理,看上去一點趣味也沒有。

              凹凸貼圖產生相當明顯的表面細節,盡管是很高明的戲法,但嚴格意義上講,凹凸貼圖并不隨著你的觀察角度而變化。比較新的 ATI nVidia 顯卡片能執行每像素運算,這種缺省觀察角度的不足就真的不再是有力而快速的法則了。 無論是哪一種方法, 到目前為止,沒有游戲開發者太多的使用; 更多的游戲能夠且應該使用凹凸貼圖。


            高速緩存抖動 = 糟糕的事物
              紋理高速緩存的管理游戲引擎的速度至關重要。 和任何高速緩存一樣,緩存命中很好,而不命中將很糟糕。如果遇到紋理在圖形顯示卡內存被頻繁地換入換出的情況,這就是紋理高速緩存抖動。發生這種情況時,通常API將會廢棄每個紋理,結果是所有的紋理在下一幀將被重新加載,這非常耗時和浪費。對游戲玩家來說,當API重新加載紋理高速緩存時,會導致幀速率遲鈍。

              在紋理高速緩存管理中,有各種不同的技術將紋理高速緩存抖動減到最少這是確保任何 3D 游戲引擎速度的一個決定性因素。 紋理管理是件好事情這意味著只要求顯卡使用紋理一次,而不是重復使用。這聽起來有點自相矛盾,但效果是它意謂著對顯卡說,"看, 所有這些多邊形全部使用這一個紋理,我們能夠僅僅加載這個紋理一次而不是許多次嗎?" 這阻止API ( 或圖形驅動軟件) 上傳多次向顯卡加載紋理。象OpenGL這樣的API實際上通常處理紋理高速緩存管理,意謂著,根據一些規則,比如紋理存取的頻率,API決定哪些紋理儲存在顯卡上,哪些紋理存儲在主存。 真正的問題來了:a) 你時常無法知道API正在使用的準確規則。 b)你時常要求在一幀中繪制更多的紋理,以致超出了顯卡內存空間所能容納的紋理。

              另外一種紋理高速緩存管理技術是我們早先討論的紋理壓縮。很象聲音波形文件被壓縮成 MP3 文件,盡管無法達到那樣的壓縮比率,但紋理可以被壓縮。 從聲音波形文件到MP3的壓縮可以達到 11:1的壓縮比率,而絕大多數硬件支持的紋理壓縮運算法則只有 4:1 的壓縮比率,盡管如此,這樣能產生很大的差別。 除此之外,在渲染(繪制)過程中,只有在需要時,硬件才動態地對紋理進行解壓縮。這一點非常棒,我們僅僅擦除即將可能用到的表面。

              如上所述,另外一種技術確保渲染器要求顯卡對每個紋理只繪制一次。確定你想要渲染(繪制)的使用相同紋理的所有多邊形同時送到顯卡,而不是一個模型在這里,另一個模型在那里,然后又回到最初的紋理論。僅僅繪制一次,你也就通過AGP接口傳送一次。Quake III 在其陰影系統就是這么做的。處理多邊形時,把它們加入到一個內部的陰影列表,一旦所有的多邊形處理完畢,渲染器遍歷紋理列表,就將紋理及所有使用這些紋理的多邊形同時傳送出去。

              上述過程在使用顯卡的硬件 T & L(如果支持的話)時,并不怎么有效。你面臨的結局是,滿屏幕都是使用相同紋理的大量的多邊形小群組,所有多邊形都使用不同的變換矩陣。這意謂著更多的時間花在建立顯卡的硬件 T & L 引擎 ,更多的時間被浪費了。 無論如何,因為他們有助于對整個模型使用統一的紋理,所以它對實際屏幕上的模型可以有效地工作。但是因為許多多邊形傾向使用相同的墻壁紋理,所以對于世界場景的渲染,它常常就是地獄。通常它沒有這么嚴重,因為大體而言,世界的紋理不會有那么大,這樣一來API的紋理緩存系統將會替你處理這些,并把紋理保留在顯卡以備再次使用。

              在游戲機上,通常沒有紋理高速緩存系統(除非你寫一個)。在 PS2 上面,你最好是遠離"一次紋理" 的方法。在 Xbox 上面, 這是不重要的,因為它本身沒有圖形內存(它是 UMA 體系結構),且所有的紋理無論如何始終保留在主存之中。

              事實上,在今天的現代PC FPS 游戲中,試圖通過AGP接口傳送大量紋理是第二個最通常的瓶頸。最大的瓶頸是實際幾何處理,它要使東西出現在它應該出現的地方。在如今的3D FPS 游戲中,最耗費時間的工作,顯然是那些計算模型中每個頂點正確的世界位置的數學運算。如果你不把場景的紋理保持在預算之內,僅居其次的就是通過AGP接口傳送大量的紋理了。然而,你確實有能力影響這些。 通過降低頂層的 MIP 級別(還記得系統在哪里不斷地為你細分紋理嗎?) 你就能夠把系統正在嘗試送到顯卡的紋理大小減少一半。你的視覺質量會有所下降-- 尤其是在引人注目的電影片斷中--但是你的幀速率上升了。這種方式對網絡游戲尤其有幫助。實際上,Soldier of Fortune IIJedi Knight II: Outcast這兩款游戲在設計時針對的顯卡還不是市場上的大眾主流顯卡。為了以最大大小觀看他們的紋理,你的3D 顯卡至少需要有128MB的內存。這兩種產品在思想上都是給未來設計的。

              上面就是第 2 部份。在下面章節中,我們將介紹許多主題,包括內存管理,霧效果,深度測試, 抗鋸齒,頂點著色,API等。



            夢在天涯 2007-12-04 13:16 發表評論

            posted @ 2009-04-10 10:44 不會飛的鳥 閱讀(213) | 評論 (0)編輯 收藏

            游戲引擎基礎(一)(渲染和構造3D世界)

                 摘要:  本系列轉自:http://m.shnenglu.com/orlando/archive/2007/12/03/37734.html 謝謝作者有這么好的文章與大家共享!代表所有有幸閱讀到次書的讀者謝謝先!第1部分: 游戲引擎介紹, 渲染和構造3D世界介紹  自Doom游戲時代以來我們已經走了很遠。 DOOM不只是一款偉大的游戲,它同時也開創了一種新的游戲編程模式: 游戲 "引...  閱讀全文

            夢在天涯 2007-12-04 13:14 發表評論

            posted @ 2009-04-10 10:44 不會飛的鳥 閱讀(125) | 評論 (0)編輯 收藏

            [轉載]堆棧,堆棧,堆和棧的區別

            堆和棧的區別
            一、預備知識—程序的內存分配
            一個由c/C++編譯的程序占用的內存分為以下幾個部分
            1、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。
            2、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似于鏈表,呵呵。
            3、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。 - 程序結束后有系統釋放 
            4、文字常量區—常量字符串就是放在這里的。 程序結束后由系統釋放
            5、程序代碼區—存放函數體的二進制代碼。
            二、例子程序 
            這是一個前輩寫的,非常詳細 
            //main.cpp 
            int a = 0; 全局初始化區 
            char *p1; 全局未初始化區 
            main() 

            int b; 棧 
            char s[] = "abc"; 棧 
            char *p2; 棧 
            char *p3 = "123456"; 123456\0在常量區,p3在棧上。 
            static int c =0; 全局(靜態)初始化區 
            p1 = (char *)malloc(10); 
            p2 = (char *)malloc(20); 
            分配得來得10和20字節的區域就在堆區。 
            strcpy(p1, "123456"); 123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。 

             


            二、堆和棧的理論知識 
            2.1申請方式 
            stack: 
            由系統自動分配。 例如,聲明在函數中一個局部變量 int b; 系統自動在棧中為b開辟空間 
            heap: 
            需要程序員自己申請,并指明大小,在c中malloc函數 
            如p1 = (char *)malloc(10); 
            在C++中用new運算符 
            如p2 = (char *)malloc(10); 
            但是注意p1、p2本身是在棧中的。 


            2.2 
            申請后系統的響應 
            棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。 
            堆:首先應該知道操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時, 
            會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序,另外,對于大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。 

            2.3申請大小的限制 
            棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。 
            堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。 


            2.4申請效率的比較: 
            棧由系統自動分配,速度較快。但程序員是無法控制的。 
            堆是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便. 
            另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活。 

            2.5堆和棧中的存儲內容 
            棧: 在函數調用時,第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的地址,然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然后是函數中的局部變量。注意靜態變量是不入棧的。 
            當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。 
            堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。 

            2.6存取效率的比較 

            char s1[] = "aaaaaaaaaaaaaaa"; 
            char *s2 = "bbbbbbbbbbbbbbbbb"; 
            aaaaaaaaaaa是在運行時刻賦值的; 
            而bbbbbbbbbbb是在編譯時就確定的; 
            但是,在以后的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。 
            比如: 
            #include 
            void main() 

            char a = 1; 
            char c[] = "1234567890"; 
            char *p ="1234567890"; 
            a = c[1]; 
            a = p[1]; 
            return; 

            對應的匯編代碼 
            10: a = c[1]; 
            00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 
            0040106A 88 4D FC mov byte ptr [ebp-4],cl 
            11: a = p[1]; 
            0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 
            00401070 8A 42 01 mov al,byte ptr [edx+1] 
            00401073 88 45 FC mov byte ptr [ebp-4],al 
            第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,在根據edx讀取字符,顯然慢了。 


            2.7小結: 
            堆和棧的區別可以用如下的比喻來看出: 
            使用棧就象我們去飯館里吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。 
            使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。 



            windows進程中的內存結構


            在閱讀本文之前,如果你連堆棧是什么多不知道的話,請先閱讀文章后面的基礎知識。 

            接觸過編程的人都知道,高級語言都能通過變量名來訪問內存中的數據。那么這些變量在內存中是如何存放的呢?程序又是如何使用這些變量的呢?下面就會對此進行深入的討論。下文中的C語言代碼如沒有特別聲明,默認都使用VC編譯的release版。 

            首先,來了解一下 C 語言的變量是如何在內存分部的。C 語言有全局變量(Global)、本地變量(Local),靜態變量(Static)、寄存器變量(Regeister)。每種變量都有不同的分配方式。先來看下面這段代碼: 

            #include <stdio.h> 

            int g1=0, g2=0, g3=0; 

            int main() 

            static int s1=0, s2=0, s3=0; 
            int v1=0, v2=0, v3=0; 

            //打印出各個變量的內存地址 

            printf("0x%08x\n",&v1); //打印各本地變量的內存地址 
            printf("0x%08x\n",&v2); 
            printf("0x%08x\n\n",&v3); 
            printf("0x%08x\n",&g1); //打印各全局變量的內存地址 
            printf("0x%08x\n",&g2); 
            printf("0x%08x\n\n",&g3); 
            printf("0x%08x\n",&s1); //打印各靜態變量的內存地址 
            printf("0x%08x\n",&s2); 
            printf("0x%08x\n\n",&s3); 
            return 0; 

            編譯后的執行結果是: 

            0x0012ff78 
            0x0012ff7c 
            0x0012ff80 

            0x004068d0 
            0x004068d4 
            0x004068d8 

            0x004068dc 
            0x004068e0 
            0x004068e4 

            輸出的結果就是變量的內存地址。其中v1,v2,v3是本地變量,g1,g2,g3是全局變量,s1,s2,s3是靜態變量。你可以看到這些變量在內存是連續分布的,但是本地變量和全局變量分配的內存地址差了十萬八千里,而全局變量和靜態變量分配的內存是連續的。這是因為本地變量和全局/靜態變量是分配在不同類型的內存區域中的結果。對于一個進程的內存空間而言,可以在邏輯上分成3個部份:代碼區,靜態數據區和動態數據區。動態數據區一般就是“堆棧”。“棧(stack)”和“堆(heap)”是兩種不同的動態數據區,棧是一種線性結構,堆是一種鏈式結構。進程的每個線程都有私有的“棧”,所以每個線程雖然代碼一樣,但本地變量的數據都是互不干擾。一個堆棧可以通過“基地址”和“棧頂”地址來描述。全局變量和靜態變量分配在靜態數據區,本地變量分配在動態數據區,即堆棧中。程序通過堆棧的基地址和偏移量來訪問本地變量。 


            ├———————┤低端內存區域 
            │ …… │ 
            ├———————┤ 
            │ 動態數據區 │ 
            ├———————┤ 
            │ …… │ 
            ├———————┤ 
            │ 代碼區 │ 
            ├———————┤ 
            │ 靜態數據區 │ 
            ├———————┤ 
            │ …… │ 
            ├———————┤高端內存區域 


            堆棧是一個先進后出的數據結構,棧頂地址總是小于等于棧的基地址。我們可以先了解一下函數調用的過程,以便對堆棧在程序中的作用有更深入的了解。不同的語言有不同的函數調用規定,這些因素有參數的壓入規則和堆棧的平衡。windows API的調用規則和ANSI C的函數調用規則是不一樣的,前者由被調函數調整堆棧,后者由調用者調整堆棧。兩者通過“__stdcall”和“__cdecl”前綴區分。先看下面這段代碼: 

            #include <stdio.h> 

            void __stdcall func(int param1,int param2,int param3) 

            int var1=param1; 
            int var2=param2; 
            int var3=param3; 
            printf("0x%08x\n",¶m1); //打印出各個變量的內存地址 
            printf("0x%08x\n",¶m2); 
            printf("0x%08x\n\n",¶m3); 
            printf("0x%08x\n",&var1); 
            printf("0x%08x\n",&var2); 
            printf("0x%08x\n\n",&var3); 
            return; 

            int main() 

            func(1,2,3); 
            return 0; 

            編譯后的執行結果是: 

            0x0012ff78 
            0x0012ff7c 
            0x0012ff80 

            0x0012ff68 
            0x0012ff6c 
            0x0012ff70 


            ├———————┤<—函數執行時的棧頂(ESP)、低端內存區域 
            │ …… │ 
            ├———————┤ 
            │ var 1 │ 
            ├———————┤ 
            │ var 2 │ 
            ├———————┤ 
            │ var 3 │ 
            ├———————┤ 
            │ RET │ 
            ├———————┤<—“__cdecl”函數返回后的棧頂(ESP) 
            │ parameter 1 │ 
            ├———————┤ 
            │ parameter 2 │ 
            ├———————┤ 
            │ parameter 3 │ 
            ├———————┤<—“__stdcall”函數返回后的棧頂(ESP) 
            │ …… │ 
            ├———————┤<—棧底(基地址 EBP)、高端內存區域 


            上圖就是函數調用過程中堆棧的樣子了。首先,三個參數以從又到左的次序壓入堆棧,先壓“param3”,再壓“param2”,最后壓入“param1”;然后壓入函數的返回地址(RET),接著跳轉到函數地址接著執行(這里要補充一點,介紹UNIX下的緩沖溢出原理的文章中都提到在壓入RET后,繼續壓入當前EBP,然后用當前ESP代替EBP。然而,有一篇介紹windows下函數調用的文章中說,在windows下的函數調用也有這一步驟,但根據我的實際調試,并未發現這一步,這還可以從param3和var1之間只有4字節的間隙這點看出來);第三步,將棧頂(ESP)減去一個數,為本地變量分配內存空間,上例中是減去12字節(ESP=ESP-3*4,每個int變量占用4個字節);接著就初始化本地變量的內存空間。由于“__stdcall”調用由被調函數調整堆棧,所以在函數返回前要恢復堆棧,先回收本地變量占用的內存(ESP=ESP+3*4),然后取出返回地址,填入EIP寄存器,回收先前壓入參數占用的內存(ESP=ESP+3*4),繼續執行調用者的代碼。參見下列匯編代碼: 

            ;--------------func 函數的匯編代碼------------------- 

            :00401000 83EC0C sub esp, 0000000C //創建本地變量的內存空間 
            :00401003 8B442410 mov eax, dword ptr [esp+10] 
            :00401007 8B4C2414 mov ecx, dword ptr [esp+14] 
            :0040100B 8B542418 mov edx, dword ptr [esp+18] 
            :0040100F 89442400 mov dword ptr [esp], eax 
            :00401013 8D442410 lea eax, dword ptr [esp+10] 
            :00401017 894C2404 mov dword ptr [esp+04], ecx 

            ……………………(省略若干代碼) 

            :00401075 83C43C add esp, 0000003C ;恢復堆棧,回收本地變量的內存空間 
            :00401078 C3 ret 000C ;函數返回,恢復參數占用的內存空間 
            ;如果是“__cdecl”的話,這里是“ret”,堆棧將由調用者恢復 

            ;-------------------函數結束------------------------- 


            ;--------------主程序調用func函數的代碼-------------- 

            :00401080 6A03 push 00000003 //壓入參數param3 
            :00401082 6A02 push 00000002 //壓入參數param2 
            :00401084 6A01 push 00000001 //壓入參數param1 
            :00401086 E875FFFFFF call 00401000 //調用func函數 
            ;如果是“__cdecl”的話,將在這里恢復堆棧,“add esp, 0000000C” 

            聰明的讀者看到這里,差不多就明白緩沖溢出的原理了。先來看下面的代碼: 

            #include <stdio.h> 
            #include <string.h> 

            void __stdcall func() 

            char lpBuff[8]="\0"; 
            strcat(lpBuff,"AAAAAAAAAAA"); 
            return; 

            int main() 

            func(); 
            return 0; 

            編譯后執行一下回怎么樣?哈,“"0x00414141"指令引用的"0x00000000"內存。該內存不能為"read"。”,“非法操作”嘍!"41"就是"A"的16進制的ASCII碼了,那明顯就是strcat這句出的問題了。"lpBuff"的大小只有8字節,算進結尾的\0,那strcat最多只能寫入7個"A",但程序實際寫入了11個"A"外加1個\0。再來看看上面那幅圖,多出來的4個字節正好覆蓋了RET的所在的內存空間,導致函數返回到一個錯誤的內存地址,執行了錯誤的指令。如果能精心構造這個字符串,使它分成三部分,前一部份僅僅是填充的無意義數據以達到溢出的目的,接著是一個覆蓋RET的數據,緊接著是一段shellcode,那只要著個RET地址能指向這段shellcode的第一個指令,那函數返回時就能執行shellcode了。但是軟件的不同版本和不同的運行環境都可能影響這段shellcode在內存中的位置,那么要構造這個RET是十分困難的。一般都在RET和shellcode之間填充大量的NOP指令,使得exploit有更強的通用性。 


            ├———————┤<—低端內存區域 
            │ …… │ 
            ├———————┤<—由exploit填入數據的開始 
            │ │ 
            │ buffer │<—填入無用的數據 
            │ │ 
            ├———————┤ 
            │ RET │<—指向shellcode,或NOP指令的范圍 
            ├———————┤ 
            │ NOP │ 
            │ …… │<—填入的NOP指令,是RET可指向的范圍 
            │ NOP │ 
            ├———————┤ 
            │ │ 
            │ shellcode │ 
            │ │ 
            ├———————┤<—由exploit填入數據的結束 
            │ …… │ 
            ├———————┤<—高端內存區域 


            windows下的動態數據除了可存放在棧中,還可以存放在堆中。了解C++的朋友都知道,C++可以使用new關鍵字來動態分配內存。來看下面的C++代碼: 

            #include <stdio.h> 
            #include <iostream.h> 
            #include <windows.h> 

            void func() 

            char *buffer=new char[128]; 
            char bufflocal[128]; 
            static char buffstatic[128]; 
            printf("0x%08x\n",buffer); //打印堆中變量的內存地址 
            printf("0x%08x\n",bufflocal); //打印本地變量的內存地址 
            printf("0x%08x\n",buffstatic); //打印靜態變量的內存地址 

            void main() 

            func(); 
            return; 

            程序執行結果為: 

            0x004107d0 
            0x0012ff04 
            0x004068c0 

            可以發現用new關鍵字分配的內存即不在棧中,也不在靜態數據區。VC編譯器是通過windows下的“堆(heap)”來實現new關鍵字的內存動態分配。在講“堆”之前,先來了解一下和“堆”有關的幾個API函數: 

            HeapAlloc 在堆中申請內存空間 
            HeapCreate 創建一個新的堆對象 
            HeapDestroy 銷毀一個堆對象 
            HeapFree 釋放申請的內存 
            HeapWalk 枚舉堆對象的所有內存塊 
            GetProcessHeap 取得進程的默認堆對象 
            GetProcessHeaps 取得進程所有的堆對象 
            LocalAlloc 
            GlobalAlloc 

            當進程初始化時,系統會自動為進程創建一個默認堆,這個堆默認所占內存的大小為1M。堆對象由系統進行管理,它在內存中以鏈式結構存在。通過下面的代碼可以通過堆動態申請內存空間: 

            HANDLE hHeap=GetProcessHeap(); 
            char *buff=HeapAlloc(hHeap,0,8); 

            其中hHeap是堆對象的句柄,buff是指向申請的內存空間的地址。那這個hHeap究竟是什么呢?它的值有什么意義嗎?看看下面這段代碼吧: 

            #pragma comment(linker,"/entry:main") //定義程序的入口 
            #include <windows.h> 

            _CRTIMP int (__cdecl *printf)(const char *, ...); //定義STL函數printf 
            /*--------------------------------------------------------------------------- 
            寫到這里,我們順便來復習一下前面所講的知識: 
            (*注)printf函數是C語言的標準函數庫中函數,VC的標準函數庫由msvcrt.dll模塊實現。 
            由函數定義可見,printf的參數個數是可變的,函數內部無法預先知道調用者壓入的參數個數,函數只能通過分析第一個參數字符串的格式來獲得壓入參數的信息,由于這里參數的個數是動態的,所以必須由調用者來平衡堆棧,這里便使用了__cdecl調用規則。BTW,Windows系統的API函數基本上是__stdcall調用形式,只有一個API例外,那就是wsprintf,它使用__cdecl調用規則,同printf函數一樣,這是由于它的參數個數是可變的緣故。 
            ---------------------------------------------------------------------------*/ 
            void main() 

            HANDLE hHeap=GetProcessHeap(); 
            char *buff=HeapAlloc(hHeap,0,0x10); 
            char *buff2=HeapAlloc(hHeap,0,0x10); 
            HMODULE hMsvcrt=LoadLibrary("msvcrt.dll"); 
            printf=(void *)GetProcAddress(hMsvcrt,"printf"); 
            printf("0x%08x\n",hHeap); 
            printf("0x%08x\n",buff); 
            printf("0x%08x\n\n",buff2); 

            執行結果為: 

            0x00130000 
            0x00133100 
            0x00133118 

            hHeap的值怎么和那個buff的值那么接近呢?其實hHeap這個句柄就是指向HEAP首部的地址。在進程的用戶區存著一個叫PEB(進程環境塊)的結構,這個結構中存放著一些有關進程的重要信息,其中在PEB首地址偏移0x18處存放的ProcessHeap就是進程默認堆的地址,而偏移0x90處存放了指向進程所有堆的地址列表的指針。windows有很多API都使用進程的默認堆來存放動態數據,如windows 2000下的所有ANSI版本的函數都是在默認堆中申請內存來轉換ANSI字符串到Unicode字符串的。對一個堆的訪問是順序進行的,同一時刻只能有一個線程訪問堆中的數據,當多個線程同時有訪問要求時,只能排隊等待,這樣便造成程序執行效率下降。 

            最后來說說內存中的數據對齊。所位數據對齊,是指數據所在的內存地址必須是該數據長度的整數倍,DWORD數據的內存起始地址能被4除盡,WORD數據的內存起始地址能被2除盡,x86 CPU能直接訪問對齊的數據,當他試圖訪問一個未對齊的數據時,會在內部進行一系列的調整,這些調整對于程序來說是透明的,但是會降低運行速度,所以編譯器在編譯程序時會盡量保證數據對齊。同樣一段代碼,我們來看看用VC、Dev-C++和lcc三個不同編譯器編譯出來的程序的執行結果: 

            #include <stdio.h> 

            int main() 

            int a; 
            char b; 
            int c; 
            printf("0x%08x\n",&a); 
            printf("0x%08x\n",&b); 
            printf("0x%08x\n",&c); 
            return 0; 

            這是用VC編譯后的執行結果: 
            0x0012ff7c 
            0x0012ff7b 
            0x0012ff80 
            變量在內存中的順序:b(1字節)-a(4字節)-c(4字節)。 

            這是用Dev-C++編譯后的執行結果: 
            0x0022ff7c 
            0x0022ff7b 
            0x0022ff74 
            變量在內存中的順序:c(4字節)-中間相隔3字節-b(占1字節)-a(4字節)。 

            這是用lcc編譯后的執行結果: 
            0x0012ff6c 
            0x0012ff6b 
            0x0012ff64 
            變量在內存中的順序:同上。 

            三個編譯器都做到了數據對齊,但是后兩個編譯器顯然沒VC“聰明”,讓一個char占了4字節,浪費內存哦。 


            基礎知識: 
            堆棧是一種簡單的數據結構,是一種只允許在其一端進行插入或刪除的線性表。允許插入或刪除操作的一端稱為棧頂,另一端稱為棧底,對堆棧的插入和刪除操作被稱為入棧和出棧。有一組CPU指令可以實現對進程的內存實現堆棧訪問。其中,POP指令實現出棧操作,PUSH指令實現入棧操作。CPU的ESP寄存器存放當前線程的棧頂指針,EBP寄存器中保存當前線程的棧底指針。CPU的EIP寄存器存放下一個CPU指令存放的內存地址,當CPU執行完當前的指令后,從EIP寄存器中讀取下一條指令的內存地址,然后繼續執行。 


            參考:《Windows下的HEAP溢出及其利用》by: isno 
            《windows核心編程》by: Jeffrey Richter 



            摘要: 討論常見的堆性能問題以及如何防范它們。(共 9 頁)

            前言
            您是否是動態分配的 C/C++ 對象忠實且幸運的用戶?您是否在模塊間的往返通信中頻繁地使用了“自動化”?您的程序是否因堆分配而運行起來很慢?不僅僅您遇到這樣的問題。幾乎所有項目遲早都會遇到堆問題。大家都想說,“我的代碼真正好,只是堆太慢”。那只是部分正確。更深入理解堆及其用法、以及會發生什么問題,是很有用的。

            什么是堆?
            (如果您已經知道什么是堆,可以跳到“什么是常見的堆性能問題?”部分)

            在程序中,使用堆來動態分配和釋放對象。在下列情況下,調用堆操作: 

            事先不知道程序所需對象的數量和大小。


            對象太大而不適合堆棧分配程序。
            堆使用了在運行時分配給代碼和堆棧的內存之外的部分內存。下圖給出了堆分配程序的不同層。


            GlobalAlloc/GlobalFree:Microsoft Win32 堆調用,這些調用直接與每個進程的默認堆進行對話。

            LocalAlloc/LocalFree:Win32 堆調用(為了與 Microsoft Windows NT 兼容),這些調用直接與每個進程的默認堆進行對話。

            COM 的 IMalloc 分配程序(或 CoTaskMemAlloc / CoTaskMemFree):函數使用每個進程的默認堆。自動化程序使用“組件對象模型 (COM)”的分配程序,而申請的程序使用每個進程堆。

            C/C++ 運行時 (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操作符。如 Microsoft Visual Basic 和 Java 等語言也提供了新的操作符并使用垃圾收集來代替堆。CRT 創建自己的私有堆,駐留在 Win32 堆的頂部。

            Windows NT 中,Win32 堆是 Windows NT 運行時分配程序周圍的薄層。所有 API 轉發它們的請求給 NTDLL。

            Windows NT 運行時分配程序提供 Windows NT 內的核心堆分配程序。它由具有 128 個大小從 8 到 1,024 字節的空閑列表的前端分配程序組成。后端分配程序使用虛擬內存來保留和提交頁。

            在圖表的底部是“虛擬內存分配程序”,操作系統使用它來保留和提交頁。所有分配程序使用虛擬內存進行數據的存取。

            分配和釋放塊不就那么簡單嗎?為何花費這么長時間?

            堆實現的注意事項
            傳統上,操作系統和運行時庫是與堆的實現共存的。在一個進程的開始,操作系統創建一個默認堆,叫做“進程堆”。如果沒有其他堆可使用,則塊的分配使用“進程堆”。語言運行時也能在進程內創建單獨的堆。(例如,C 運行時創建它自己的堆。)除這些專用的堆外,應用程序或許多已載入的動態鏈接庫 (DLL) 之一可以創建和使用單獨的堆。Win32 提供一整套 API 來創建和使用私有堆。有關堆函數(英文)的詳盡指導,請參見 MSDN。

            當應用程序或 DLL 創建私有堆時,這些堆存在于進程空間,并且在進程內是可訪問的。從給定堆分配的數據將在同一個堆上釋放。(不能從一個堆分配而在另一個堆釋放。)

            在所有虛擬內存系統中,堆駐留在操作系統的“虛擬內存管理器”的頂部。語言運行時堆也駐留在虛擬內存頂部。某些情況下,這些堆是操作系統堆中的層,而語言運行時堆則通過大塊的分配來執行自己的內存管理。不使用操作系統堆,而使用虛擬內存函數更利于堆的分配和塊的使用。

            典型的堆實現由前、后端分配程序組成。前端分配程序維持固定大小塊的空閑列表。對于一次分配調用,堆嘗試從前端列表找到一個自由塊。如果失敗,堆被迫從后端(保留和提交虛擬內存)分配一個大塊來滿足請求。通用的實現有每塊分配的開銷,這將耗費執行周期,也減少了可使用的存儲空間。

            Knowledge Base 文章 Q10758,“用 calloc() 和 malloc() 管理內存” (搜索文章編號), 包含了有關這些主題的更多背景知識。另外,有關堆實現和設計的詳細討論也可在下列著作中找到:“Dynamic Storage Allocation: A Survey and Critical Review”,作者 Paul R. Wilson、Mark S. Johnstone、Michael Neely 和 David Boles;“International Workshop on Memory Management”, 作者 Kinross, Scotland, UK, 1995 年 9 月(http://www.cs.utexas.edu/users/oops/papers.html)(英文)。

            Windows NT 的實現(Windows NT 版本 4.0 和更新版本) 使用了 127 個大小從 8 到 1,024 字節的 8 字節對齊塊空閑列表和一個“大塊”列表。“大塊”列表(空閑列表[0]) 保存大于 1,024 字節的塊。空閑列表容納了用雙向鏈表鏈接在一起的對象。默認情況下,“進程堆”執行收集操作。(收集是將相鄰空閑塊合并成一個大塊的操作。)收集耗費了額外的周期,但減少了堆塊的內部碎片。

            單一全局鎖保護堆,防止多線程式的使用。(請參見“Server Performance and Scalability Killers”中的第一個注意事項, George Reilly 所著,在 “MSDN Online Web Workshop”上(站點:http://msdn.microsoft.com/workshop/server/iis/tencom.asp(英文)。)單一全局鎖本質上是用來保護堆數據結構,防止跨多線程的隨機存取。若堆操作太頻繁,單一全局鎖會對性能有不利的影響。

            什么是常見的堆性能問題?
            以下是您使用堆時會遇到的最常見問題: 

            分配操作造成的速度減慢。光分配就耗費很長時間。最可能導致運行速度減慢原因是空閑列表沒有塊,所以運行時分配程序代碼會耗費周期尋找較大的空閑塊,或從后端分配程序分配新塊。


            釋放操作造成的速度減慢。釋放操作耗費較多周期,主要是啟用了收集操作。收集期間,每個釋放操作“查找”它的相鄰塊,取出它們并構造成較大塊,然后再把此較大塊插入空閑列表。在查找期間,內存可能會隨機碰到,從而導致高速緩存不能命中,性能降低。


            堆競爭造成的速度減慢。當兩個或多個線程同時訪問數據,而且一個線程繼續進行之前必須等待另一個線程完成時就發生競爭。競爭總是導致麻煩;這也是目前多處理器系統遇到的最大問題。當大量使用內存塊的應用程序或 DLL 以多線程方式運行(或運行于多處理器系統上)時將導致速度減慢。單一鎖定的使用—常用的解決方案—意味著使用堆的所有操作是序列化的。當等待鎖定時序列化會引起線程切換上下文。可以想象交叉路口閃爍的紅燈處走走停停導致的速度減慢。 
            競爭通常會導致線程和進程的上下文切換。上下文切換的開銷是很大的,但開銷更大的是數據從處理器高速緩存中丟失,以及后來線程復活時的數據重建。

            堆破壞造成的速度減慢。造成堆破壞的原因是應用程序對堆塊的不正確使用。通常情形包括釋放已釋放的堆塊或使用已釋放的堆塊,以及塊的越界重寫等明顯問題。(破壞不在本文討論范圍之內。有關內存重寫和泄漏等其他細節,請參見 Microsoft Visual C++(R) 調試文檔 。)


            頻繁的分配和重分配造成的速度減慢。這是使用腳本語言時非常普遍的現象。如字符串被反復分配,隨重分配增長和釋放。不要這樣做,如果可能,盡量分配大字符串和使用緩沖區。另一種方法就是盡量少用連接操作。
            競爭是在分配和釋放操作中導致速度減慢的問題。理想情況下,希望使用沒有競爭和快速分配/釋放的堆。可惜,現在還沒有這樣的通用堆,也許將來會有。

            在所有的服務器系統中(如 IIS、MSProxy、DatabaseStacks、網絡服務器、 Exchange 和其他), 堆鎖定實在是個大瓶頸。處理器數越多,競爭就越會惡化。

            盡量減少堆的使用
            現在您明白使用堆時存在的問題了,難道您不想擁有能解決這些問題的超級魔棒嗎?我可希望有。但沒有魔法能使堆運行加快—因此不要期望在產品出貨之前的最后一星期能夠大為改觀。如果提前規劃堆策略,情況將會大大好轉。調整使用堆的方法,減少對堆的操作是提高性能的良方。

            如何減少使用堆操作?通過利用數據結構內的位置可減少堆操作的次數。請考慮下列實例:

            struct ObjectA {
               // objectA 的數據 
            }

            struct ObjectB {
               // objectB 的數據 
            }

            // 同時使用 objectA 和 objectB

            //
            // 使用指針 
            //
            struct ObjectB {
               struct ObjectA * pObjA;
               // objectB 的數據 
            }

            //
            // 使用嵌入
            //
            struct ObjectB {
               struct ObjectA pObjA;
               // objectB 的數據 
            }

            //
            // 集合 – 在另一對象內使用 objectA 和 objectB
            //

            struct ObjectX {
               struct ObjectA  objA;
               struct ObjectB  objB;
            }

            避免使用指針關聯兩個數據結構。如果使用指針關聯兩個數據結構,前面實例中的對象 A 和 B 將被分別分配和釋放。這會增加額外開銷—我們要避免這種做法。


            把帶指針的子對象嵌入父對象。當對象中有指針時,則意味著對象中有動態元素(百分之八十)和沒有引用的新位置。嵌入增加了位置從而減少了進一步分配/釋放的需求。這將提高應用程序的性能。


            合并小對象形成大對象(聚合)。聚合減少分配和釋放的塊的數量。如果有幾個開發者,各自開發設計的不同部分,則最終會有許多小對象需要合并。集成的挑戰就是要找到正確的聚合邊界。


            內聯緩沖區能夠滿足百分之八十的需要(aka 80-20 規則)。個別情況下,需要內存緩沖區來保存字符串/二進制數據,但事先不知道總字節數。估計并內聯一個大小能滿足百分之八十需要的緩沖區。對剩余的百分之二十,可以分配一個新的緩沖區和指向這個緩沖區的指針。這樣,就減少分配和釋放調用并增加數據的位置空間,從根本上提高代碼的性能。


            在塊中分配對象(塊化)。塊化是以組的方式一次分配多個對象的方法。如果對列表的項連續跟蹤,例如對一個 {名稱,值} 對的列表,有兩種選擇:選擇一是為每一個“名稱-值”對分配一個節點;選擇二是分配一個能容納(如五個)“名稱-值”對的結構。例如,一般情況下,如果存儲四對,就可減少節點的數量,如果需要額外的空間數量,則使用附加的鏈表指針。 
            塊化是友好的處理器高速緩存,特別是對于 L1-高速緩存,因為它提供了增加的位置 —不用說對于塊分配,很多數據塊會在同一個虛擬頁中。

            正確使用 _amblksiz。C 運行時 (CRT) 有它的自定義前端分配程序,該分配程序從后端(Win32 堆)分配大小為 _amblksiz 的塊。將 _amblksiz 設置為較高的值能潛在地減少對后端的調用次數。這只對廣泛使用 CRT 的程序適用。
            使用上述技術將獲得的好處會因對象類型、大小及工作量而有所不同。但總能在性能和可升縮性方面有所收獲。另一方面,代碼會有點特殊,但如果經過深思熟慮,代碼還是很容易管理的。

            其他提高性能的技術
            下面是一些提高速度的技術: 

            使用 Windows NT5 堆 
            由于幾個同事的努力和辛勤工作,1998 年初 Microsoft Windows(R) 2000 中有了幾個重大改進:

            改進了堆代碼內的鎖定。堆代碼對每堆一個鎖。全局鎖保護堆數據結構,防止多線程式的使用。但不幸的是,在高通信量的情況下,堆仍受困于全局鎖,導致高競爭和低性能。Windows 2000 中,鎖內代碼的臨界區將競爭的可能性減到最小,從而提高了可伸縮性。


            使用 “Lookaside”列表。堆數據結構對塊的所有空閑項使用了大小在 8 到 1,024 字節(以 8-字節遞增)的快速高速緩存。快速高速緩存最初保護在全局鎖內。現在,使用 lookaside 列表來訪問這些快速高速緩存空閑列表。這些列表不要求鎖定,而是使用 64 位的互鎖操作,因此提高了性能。


            內部數據結構算法也得到改進。
            這些改進避免了對分配高速緩存的需求,但不排除其他的優化。使用 Windows NT5 堆評估您的代碼;它對小于 1,024 字節 (1 KB) 的塊(來自前端分配程序的塊)是最佳的。GlobalAlloc() 和 LocalAlloc() 建立在同一堆上,是存取每個進程堆的通用機制。如果希望獲得高的局部性能,則使用 Heap(R) API 來存取每個進程堆,或為分配操作創建自己的堆。如果需要對大塊操作,也可以直接使用 VirtualAlloc() / VirtualFree() 操作。

            上述改進已在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改進后,堆鎖的競爭率顯著降低。這使所有 Win32 堆的直接用戶受益。CRT 堆建立于 Win32 堆的頂部,但它使用自己的小塊堆,因而不能從 Windows NT 改進中受益。(Visual C++ 版本 6.0 也有改進的堆分配程序。)

            使用分配高速緩存 
            分配高速緩存允許高速緩存分配的塊,以便將來重用。這能夠減少對進程堆(或全局堆)的分配/釋放調用的次數,也允許最大限度的重用曾經分配的塊。另外,分配高速緩存允許收集統計信息,以便較好地理解對象在較高層次上的使用。

            典型地,自定義堆分配程序在進程堆的頂部實現。自定義堆分配程序與系統堆的行為很相似。主要的差別是它在進程堆的頂部為分配的對象提供高速緩存。高速緩存設計成一套固定大小(如 32 字節、64 字節、128 字節等)。這一個很好的策略,但這種自定義堆分配程序丟失與分配和釋放的對象相關的“語義信息”。 

            與自定義堆分配程序相反,“分配高速緩存”作為每類分配高速緩存來實現。除能夠提供自定義堆分配程序的所有好處之外,它們還能夠保留大量語義信息。每個分配高速緩存處理程序與一個目標二進制對象關聯。它能夠使用一套參數進行初始化,這些參數表示并發級別、對象大小和保持在空閑列表中的元素的數量等。分配高速緩存處理程序對象維持自己的私有空閑實體池(不超過指定的閥值)并使用私有保護鎖。合在一起,分配高速緩存和私有鎖減少了與主系統堆的通信量,因而提供了增加的并發、最大限度的重用和較高的可伸縮性。

            需要使用清理程序來定期檢查所有分配高速緩存處理程序的活動情況并回收未用的資源。如果發現沒有活動,將釋放分配對象的池,從而提高性能。

            可以審核每個分配/釋放活動。第一級信息包括對象、分配和釋放調用的總數。通過查看它們的統計信息可以得出各個對象之間的語義關系。利用以上介紹的許多技術之一,這種關系可以用來減少內存分配。

            分配高速緩存也起到了調試助手的作用,幫助您跟蹤沒有完全清除的對象數量。通過查看動態堆棧返回蹤跡和除沒有清除的對象之外的簽名,甚至能夠找到確切的失敗的調用者。

            MP 堆 
            MP 堆是對多處理器友好的分布式分配的程序包,在 Win32 SDK(Windows NT 4.0 和更新版本)中可以得到。最初由 JVert 實現,此處堆抽象建立在 Win32 堆程序包的頂部。MP 堆創建多個 Win32 堆,并試圖將分配調用分布到不同堆,以減少在所有單一鎖上的競爭。

            本程序包是好的步驟 —一種改進的 MP-友好的自定義堆分配程序。但是,它不提供語義信息和缺乏統計功能。通常將 MP 堆作為 SDK 庫來使用。如果使用這個 SDK 創建可重用組件,您將大大受益。但是,如果在每個 DLL 中建立這個 SDK 庫,將增加工作設置。

            重新思考算法和數據結構 
            要在多處理器機器上伸縮,則算法、實現、數據結構和硬件必須動態伸縮。請看最經常分配和釋放的數據結構。試問,“我能用不同的數據結構完成此工作嗎?”例如,如果在應用程序初始化時加載了只讀項的列表,這個列表不必是線性鏈接的列表。如果是動態分配的數組就非常好。動態分配的數組將減少內存中的堆塊和碎片,從而增強性能。

            減少需要的小對象的數量減少堆分配程序的負載。例如,我們在服務器的關鍵處理路徑上使用五個不同的對象,每個對象單獨分配和釋放。一起高速緩存這些對象,把堆調用從五個減少到一個,顯著減少了堆的負載,特別當每秒鐘處理 1,000 個以上的請求時。

            如果大量使用“Automation”結構,請考慮從主線代碼中刪除“Automation BSTR”,或至少避免重復的 BSTR 操作。(BSTR 連接導致過多的重分配和分配/釋放操作。)

            摘要
            對所有平臺往往都存在堆實現,因此有巨大的開銷。每個單獨代碼都有特定的要求,但設計能采用本文討論的基本理論來減少堆之間的相互作用。 

            評價您的代碼中堆的使用。


            改進您的代碼,以使用較少的堆調用:分析關鍵路徑和固定數據結構。


            在實現自定義的包裝程序之前使用量化堆調用成本的方法。


            如果對性能不滿意,請要求 OS 組改進堆。更多這類請求意味著對改進堆的更多關注。


            要求 C 運行時組針對 OS 所提供的堆制作小巧的分配包裝程序。隨著 OS 堆的改進,C 運行時堆調用的成本將減小。


            操作系統(Windows NT 家族)正在不斷改進堆。請隨時關注和利用這些改進。
            Murali Krishnan 是 Internet Information Server (IIS) 組的首席軟件設計工程師。從 1.0 版本開始他就設計 IIS,并成功發行了 1.0 版本到 4.0 版本。Murali 組織并領導 IIS 性能組三年 (1995-1998), 從一開始就影響 IIS 性能。他擁有威斯康星州 Madison 大學的 M.S.和印度 Anna 大學的 B.S.。工作之外,他喜歡閱讀、打排球和家庭烹飪。



            http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835
            我在學習對象的生存方式的時候見到一種是在堆棧(stack)之中,如下  
            CObject  object;  
            還有一種是在堆(heap)中  如下  
            CObject*  pobject=new  CObject();  
             
            請問  
            (1)這兩種方式有什么區別?  
            (2)堆棧與堆有什么區別??  
             
             
            ---------------------------------------------------------------  
             
            1)  about  stack,  system  will  allocate  memory  to  the  instance  of  object  automatically,  and  to  the
             heap,  you  must  allocate  memory  to  the  instance  of  object  with  new  or  malloc  manually.  
            2)  when  function  ends,  system  will  automatically  free  the  memory  area  of  stack,  but  to  the 
            heap,  you  must  free  the  memory  area  manually  with  free  or  delete,  else  it  will  result  in  memory
            leak.  
            3)棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。  
            4)堆上分配的內存可以有我們自己決定,使用非常靈活。  
            ---------------------------------------------------------------  

            posted @ 2009-04-10 09:11 不會飛的鳥 閱讀(133) | 評論 (0)編輯 收藏

            [交互設計工具/原型創作工具]Axure RP Pro 5.0新特性 破解版 注冊碼

            先感謝木的{moond},要不是一次提供了那么多畫原型圖的工具,我現在還在Word和Visio里面打轉呢。

            這些軟件能試用的我差不多都用了一下,其中覺得Axure的RP Pro 4挺好的,功能包括站點地圖、流程設計、頁面框架設計、簡單交互設計等,可以生成HTML、Word等格式。

            RP操作比用Dreamweaver簡單多了,圖片、文字、輸入框等等所有組件全是可視化操作,可以很方便的實現網站交互內容的原型設計,可將以前死板的頁面版式全部替換為有點擊、鏈接效果的網頁,nice~

            同時可以為網站設計提供AJAX式的交互處理,RP提供一種動態層的組件,在同一頁設定不同狀態的效果,然后用鏈接、按鈕等觸發即可產生動態效果,這樣網站設計就變得更加生動,意圖也就更加直觀。

            可惜,這么好的軟件只能試用30天,哪位兄臺要是有解決方法一定要通知我,這里先謝了~

            以下對RP5的新特性做了下簡單翻譯:

            Axure RP Pro 5.0增加了眾多新特性,并且著重增強了設計師間的協作、交互功能和更多的定制功能,生成也變的更快。

            新功能包括:

            1. 共享工程
            1.1 可以在網絡驅動器上創建共享工程,而不必非安裝在服務器上
            1.2 管理和便捷頁面只需要通過簡單的 check in / check out 即可
            1.3 維護和瀏覽歷史版本
            1.4 更多介紹

            2. 交互功能的增強
            2.1 OnFocus 和 OnLostFocus 事件 (更多)
            2.2 使用和禁止Widgets行為
            2.3 模仿在圖片上的錨點/熱區行為 (更多)
            2.4 移動動態層(Dynamic Panels)行為 (更多)
            2.5 在其他行為運行之前暫停當前行為
            2.6 頁面間多達25個變量的儲存

            3. 規范的增強
            3.1 可以更快的把演示生成為Microsoft Word 2007格式(使用Microsoft Office兼容包可與Microsoft Word 2000+兼容)
            3.2 有1欄和2欄布局的選項
            3.3 可以更簡單的定制Word模板
            3.4 有更多新選項去配置規范內容

            4. 更多增強
            4.1 優化Tab交互
            4.2 可以選擇與主窗體分離(比如Sitemap和Widgets)
            4.3 有更好的保存機制來處理關閉和重開一個文件
            4.4 帶有梯度和透明設置的顏色工具
            4.5 打印設置是被保存的

            查看原文

            同時提示一下:如果之前裝過RP4,并且有license的話,RP5可以直接使用,
            是作為免費升級的,當然,你也可以只當下來看看,有30天試用

            =======================================================================

            互聯網行業產品經理的一項重要工作,就是進行產品原型設計(Prototype Design)。而產品原型設計最基礎的工作,就是結合批注、大量的說明以及流程圖畫框架圖wireframe,將自己的產品原型完整而準確的表述給UI、UE、程序工程師,市場人員,并通過溝通會議,反復修改prototype 直至最終確認,開始投入執行。

            進行產品原型設計的軟件工具也有很多種,我寫的這個教程所介紹的Axure RP,是taobao、dangdang等國內大型網絡公司的團隊在推廣使用的原型設計軟件。同時,此軟件也在產品經理圈子中廣為流傳。之所以Axure RP得到了大家的認同和推廣,正是因為其簡便的操作和使用,符合了產品經理、交互設計師們的需求。

            在正式談Axure RP之前,我們先來看看做產品原型設計,現在大致有哪些工具可以使用,而他們的利弊何在。
            紙筆:簡單易得,上手難度為零。有力于瞬間創意的產生與記錄,有力于對文檔即時的討論與修改。但是保真度不高,難以表述頁面流程,更難以表述交互信息與程序需求細節。
            Word:上手難度普通。可以畫wireframe,能夠畫頁面流程,能夠使用批注與文字說明。但是對交互表達不好,也不利于演示。
            PPT:上手難度普通。易于畫框架圖,易于做批注,也可以表達交互流程,也擅長演示。但是不利于大篇幅的文檔表達。
            Visio:功能相對比較復雜。善于畫流程圖,框架圖。不利于批注與大篇幅的文字說明。同樣不利于交互的表達與演示。
            Photshop/fireworks:操作難度相對較大,易于畫框架圖、流程圖。不利于表達交互設計,不擅長文字說明與批注。
            Dreamweave:操作難度大,需要基礎的html知識。易于畫框架圖、流程圖、表達交互設計。不擅長文字說明與批注。

            以上這些工具,都是產品經理經常會使用到的,但是從根本上來說,這些工具都不是做prototype design的專門利器,需要根據產品開發不同的目的,不同的開發階段,選擇不同的工具搭配使用,才能達到表達、溝通的目的。

            比如使用紙筆,更適合在產品創意階段使用,可以快速記錄閃電般的思路和靈感;也可以在即時討論溝通時使用,通過圖形快速表達自己的產品思路,及時的畫出來,是再好不過的方法。而word則適合在用文字詳細表達產品,對產品進行細節說明時使用,圖片結合文字的排版,是word最擅長的工作。而ppt自然是演示時更好。visio則可以適用于各種流程圖、關系圖的表達,更可通過畫use case 獲取用戶需求。PS/FW是圖片處理的工具,DW則是所見即所得的網頁開發軟件,這些是設計師的看家本領,對于普通的產品經理來說,需要耗費太多的精力去掌握。

            其實每件工具,每個軟件,在創造它的初期,軟件設計師們都給它賦予了性格、氣質。因為每個工具的產生,都是為了滿足人類的某一方面需求。比如鋤頭是鋤土的,起子是起螺絲的,電熨斗是燙衣服的。但是不同的工具都有自己的工作領域,在其他領域它并不擅長。而以上的軟件在創造的初期,并非為了幫助產品經理、ue完成產品原型設計,因此他們都不能在prototype design 這件工作上得心應手。而Axure RP正是在互聯網產品大張其道的前提下,為滿足prototype design創建的需求,應運而生。

            Axure RP 能幫助網站需求設計者,快捷而簡便的創建 基于目錄組織的原型文檔、功能說明、交互界面以及帶注釋的wireframe網頁,并可自動生成用于演示的網頁文件和word文檔,以提供演示與開發。

            沒錯!Axure RP 的特點是:

            • 快速創建帶注釋的wireframe文件,并可根據所設置的時間周期,軟件自動保存文檔,確保文件安全。
              ?
            • 在不寫任何一條html與javascript語句的情況下,通過創建的文檔以及相關條件和注釋,一鍵生成html prototype演示。
              ?
            • 根據設計稿,一鍵生成一致而專業的word版本的原型設計文檔。

            說到這里相信很多人已經激起了興趣,但是在開始學習之前,我認為我們還是有必要先了解一下軟件短短的歷史,創造這一軟件的公司-Axure Software Solutions, Inc.該公司創建于2002年五月,Axure RP是這一軟件公司的旗艦產品,2003年一月Axure RP第一版本上線發表,至今已經正式發行到了第四個版本,而我提筆寫到這里的時候,Axure 5.0版本beta版本已經正式提供下載試用,雖然我已經下載使用,但是我想,寫教程還是應該先從穩定的4.6版說起,至于5.0版,我們可以伴隨著軟件一起成長。

            接下來我會結合圖片,分幾個步驟分享我對Axure的認識,
            一、 界面與功能
            二、 工具欄
            三、 站點地圖
            四、 組件與使用方法
            五、 復用模板與使用
            六、 交互功能與注釋
            七、 實例

            當然,在書寫的過程中我會根據具體的情況再進行調整,盡量做到圖文并茂,易于理解。寫這個教程的目的,一方面為自己熟悉與更加理解Axure,另一方面也鞭策自己完善自己的blog網站www.2tan.net,同時也希望以自己的綿薄之力,為希望學習Axure的朋友分享一點經驗。由于這是我第一次嘗試寫教程,難免會出現偏頗,也希望朋友們能夠不吝賜教,共同進步。

            另,為e文好的朋友附上自學Axure RP的幾個途徑:
            1、 打開軟件,按F1調取幫助文檔,對照文檔學習。
            2、 登錄http://www.axure.com/au-home.aspx 查看flash視頻學習。
            3、 登錄 Axure 博客 http://axure.com/cs/blogs/Default.aspx,了解軟件最新信息。
            4、 登錄討論組http://axure.com/cs/forums/Default.aspx,參與討論。

            并提供Axure RP pro 4.6版本的下載
            http://www.2tan.net/LoadMod.asp?plugins=downloadForPJBlog
            由于放存軟件的網絡硬盤在下載數量不多的情況下可能會刪除文件,如遇到死鏈接,可留言給我,我將重新上傳:)
            (軟件僅供學習使用,反對商業用途 -_-!!!)


            Name:3ddown
            Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq+7w1RH97k5MWctqVHA

            posted @ 2009-03-20 13:23 不會飛的鳥 閱讀(749) | 評論 (1)編輯 收藏

            PowerDesigner下建索引、自增列、檢查設計模型

            From: http://www.qqread.com/java/2006/07/u996183002.html

            這段時間,使用PD做數據庫模型,感覺很不錯,將自已的經驗總給一下.還有許多功能我沒時間總結,以后有時間,繼續補吧
              
            . 如何在PowerDesigner下建索引
              
             1. 雙擊表設計圖,出來Table Properties,在Tab 頁中選擇 Indexes
              
               數據庫建模工具PowerDesigner總結(組圖)



             2. 單擊新建索引的屬性,出現Indexex Properties
              
             數據庫建模工具PowerDesigner總結(組圖)



             3. 增加一個索引包含的字段
              
             數據庫建模工具PowerDesigner總結(組圖)



            點擊"下圖中的增加列圖標出現圖如下"



            選中相關字段后,點OK
            最后返回屬性頁,點preview出現圖如下



            好了,索引建立成功

            ======================================

            .如何在PowerDesigner 下建自增列
              
             1. 使用SqlServer 數據庫中的下列語句來完成
              
              建表語句中,在要做為自增列的字段中,加上如下
              IDENTITY(1,1)
              
              還有可以使用下面語句,重置自增種子
              dbcc checkident(ConfigSys,reseed,0);
              
            .如何在PowerDesigner 下檢查設計模型
              
              1. 在菜單欄中選擇 Tools -? Check Model, 如下圖
              
             數據庫建模工具PowerDesigner總結(組圖)



              2. 選擇要檢查的每項設置
              
             數據庫建模工具PowerDesigner總結(組圖)



              3. 確定后,將出來檢查結果匯總信息
              
             數據庫建模工具PowerDesigner總結(組圖)

            posted @ 2009-03-18 09:03 不會飛的鳥 閱讀(2026) | 評論 (0)編輯 收藏

            c語言中rand()函數怎么用?

            rand(產生隨機數)
            相關函數
            srand

            表頭文件
            #include<stdlib.h>

            定義函數
            int rand(void)

            函數說明
            rand()會返回一隨機數值,范圍在0至RAND_MAX 間。在調用此函數產生隨機數前,必須先利用srand()設好隨機數種子,如果未設隨機數種子,rand()在調用時會自動設隨機數種子為1。關于隨機數種子請參考srand()。

            返回值
            返回0至RAND_MAX之間的隨機數值,RAND_MAX定義在stdlib.h,其值為2147483647。

            范例
            /* 產生介于1 到10 間的隨機數值,此范例未設隨機數種子,完整的隨機數產生請參考
            srand()*/
            #include<stdlib.h>
            main()
            {
            int i,j;
            for(i=0;i<10;i++)
            {
            j=1+(int)(10.0*rand()/(RAND_MAX+1.0));
            printf("%d ",j);
            }
            }

            執行
            9 4 8 8 10 2 4 8 3 6
            9 4 8 8 10 2 4 8 3 6





            srand(設置隨機數種子)
            相關函數
            rand

            表頭文件
            #include<stdlib.h>

            定義函數
            void srand (unsigned int seed);

            函數說明
            srand()用來設置rand()產生隨機數時的隨機數種子。參數seed必須是個整數,通常可以利用geypid()或time(0)的返回值來當做seed。如果每次seed都設相同值,rand()所產生的隨機數值每次就會一樣。

            返回值

            范例
            /* 產生介于1 到10 間的隨機數值,此范例與執行結果可與rand()參照*/
            #include<time.h>
            #include<stdlib.h>
            main()
            {
            int i,j;
            srand((int)time(0));
            for(i=0;i<10;i++)
            {
            j=1+(int)(10.0*rand()/(RAND_MAX+1.0));
            printf(" %d ",j);
            }
            }

            執行
            5 8 8 8 10 2 10 8 9 9
            2 9 7 4 10 3 2 10 8 7

            posted @ 2009-03-11 11:04 不會飛的鳥 閱讀(6791) | 評論 (0)編輯 收藏

            如何使用 類進行文件的 I/O 處理

            原文出處:How to Use <fstream> Classes for File I/O
            摘要:傳統的文件 I/O 庫如 Unix 的 <io.h> 和 <stdio.h> ,由于其程序接口的原因,在很大程度上強制程序員進行某些處理,缺乏類型安全和國際化支持。C++ 的 <fstream> 庫則在文件的 I/O 方面提供了一個增強的、面向對象的、具有國際化意識的庫。本文將介紹如何使用這個庫進行文件的 I/O 處理并利用它來編寫易于跨平臺的代碼。


              大多數 C++ 程序員都熟悉不止一個文件 I/O 庫。首先是傳統的 Unix 風格的庫,它由一些低級函數如 read() 和 open()組成。其次是 ANSI C 的 <stdio.h> 庫,它包含 fopen() 和 fread()等函數。其它的還有一些具備所有權的庫或框架,比如 MFC,它有很多自己的文件處理類。
              這些庫一般都很難跨平臺使用。更糟的是,上述提到的 C 庫由于其程序接口的原因,在很大程度上強制程序員進行某些處理,而且缺乏類型安全支持。
              標準 C++ 提供提供了一個增強的、面向對象的、具有國際化意識的  <fstream> 庫。這個庫包含一系列派生于標準 ios_base 和 ios 類的類模板。因此, <fstream> 提供了高級的自動控制機制和健壯性。本文下面將示范如何使用  <fstream> 類實現文件的輸入/輸出處理:

            第一步:創建文件流
              輸入文件流(ifstream)支持重載的 >> 操作符,同樣,輸出文件流(ofstream)支持重載的 << 操作符。結合了輸入和輸出的文件流被稱為 fstream。下面的程序創建了一個 ifstream 對象:dict,并將該對象中的每一個單字顯示到屏幕上:

            #include <iostream>
            #include <string>
            #include <fstream>
            #include <cstdlib>
            using namespace std;
            int main()
            {
            string s;
            cout<<"enter dictionary file: ";
            cin>>s;
            ifstream dict (s.c_str());
            if (!dictionary) // were there any errors on opening?
            exit(-1);
            while (dictionary >> s) cout << s <<''\n'';
            }      
              我們必須調用 string::c_str() 成員函數,因為 fstream 對象只接受常量字符串作為文件名。當你將文件名作為參數傳遞時,構造函數試圖打開指定的文件。接著,我們用重載的 !操作符來檢查文件的狀態。如果出錯,該操作符估值為 true。最后一行是個循環,每次反復都從文件讀取一個單字,將它拷貝到 s,然后顯示出來。注意我們不必顯式地檢查 EOF,因為重載操作符 >> 會自動處理。此外,我們不用顯式地關閉此文件,因為析構函數會為我們做這件事情。
              過時和荒廢的 <fstream.h> 庫支持 ios::nocreate 和 ios::noreplace 標志。但新的 <fstream> 庫已經取代了 <fstream.h> 并不再支持這兩個標志。
             
            文件的打開模式
              如果你不顯式指定打開模式,fstream 類將使用默認值。例如,ifstream 默認以讀方式打開某個文件并將文件指針置為文件的開始處。為了向某個文件寫入數據,你需要創建一個 ofstream 對象。<fstream> 定義了下列打開模式和文件屬性:
            ios::app // 從后面添加
            ios::ate // 打開并找到文件尾
            ios::binary // 二進制模式 I/O (與文本模式相對)
            ios::in // 只讀打開
            ios::out // 寫打開
            ios::trunc // 將文件截為 0 長度

            你可以用位域操作符 OR 組合這些標志:

            ofstream logfile("login.dat", ios::binary | ios::app);

            fstream 類型對象同時支持讀和寫操作:

            fstream logfile("database.dat", ios::in | ios::out);

            第二步:設置文件的位置
              文件具備一個邏輯指針,它指向該文件中的某個偏移位置。你可以通過調用seekp()成員函數,以字節為單位將這個指針定位到文件的任意位置。為了獲取從文件開始處到當前偏移的字節數,調用seekp()即可。在下面的例子中,程序將文件位置前移10個字節,然后調用 tellp()報告新位置:

            ofstream fout("parts.txt");
            fout.seekp(10); // 從0偏移開始前進 10 個字節
            cout<<"new position: "<<fout.tellp(); // 顯示 10

            你可以用下面的常量重新定位文ian指針:

            ios::beg // 文件開始位置
            ios::cur // 當前位置,例如: ios::cur+5
            ios::end // 文件尾

            第三步:讀寫數據
              fstream 類為所有內建數據類型以及 std::string 和 std::complex 類型重載 << 和 >> 操作符。下面的例子示范了這些操作符的使用方法:

            fstream logfile("log.dat");
            logfile<<time(0)<<"danny"<<''\n''; // 寫一條新記錄
            logfile.seekp(ios::beg); // 位置重置
            logfile>>login>>user; // 讀取以前寫入的值

            posted @ 2009-03-05 11:52 不會飛的鳥 閱讀(185) | 評論 (0)編輯 收藏

            僅列出標題
            共9頁: 1 2 3 4 5 6 7 8 9 
            老司机午夜网站国内精品久久久久久久久| 亚洲av伊人久久综合密臀性色| 亚洲精品乱码久久久久久蜜桃图片 | 亚洲AV无码久久精品成人| 久久久久人妻精品一区| 久久99国产精品久久99| 亚洲国产精品一区二区三区久久| 一本色道久久HEZYO无码| 国产精品美女久久久久久2018| 精品久久久久一区二区三区| 97精品依人久久久大香线蕉97| 久久91精品国产91久久户| 亚洲精品无码久久毛片| 久久99国产精品99久久| 99精品国产99久久久久久97| 国产福利电影一区二区三区久久久久成人精品综合 | 久久久久国产一级毛片高清版| 香港aa三级久久三级老师2021国产三级精品三级在 | 狠狠色丁香婷婷久久综合不卡| 亚洲伊人久久综合影院| 色综合久久中文色婷婷| 亚洲狠狠婷婷综合久久久久| 久久99国产精品成人欧美| 国内精品久久久久久久97牛牛| 一本久久a久久精品综合香蕉| 日本精品久久久久中文字幕8| 伊人久久大香线蕉AV色婷婷色| 99久久夜色精品国产网站| 国产精品久久国产精品99盘 | 日韩久久无码免费毛片软件| 久久精品中文字幕久久| 韩国免费A级毛片久久| 人妻无码αv中文字幕久久琪琪布| 久久无码一区二区三区少妇| 久久久久久一区国产精品| 91精品无码久久久久久五月天| 欧美久久精品一级c片片| 国产精品禁18久久久夂久| 久久水蜜桃亚洲av无码精品麻豆| 久久久久亚洲AV无码观看| 中文字幕久久波多野结衣av|