OpenGL紋理映射總結
大概步驟:
1.創建紋理對象,併爲他指定一個紋理.
2.確定紋理如何應用到每個像素上.
3.啓用紋理貼圖
4.繪製場景,提供紋理和幾何座標
過濾:由於我們提供的紋理圖像很少能和最終的屏幕座標形成對應,大小不同,所以需要設置過濾項目.允許我們進行插值或者勻和,指定放大縮小的函數.glTexParameter*(),使用過濾模式GL_NEAREST那麼紋理單位最鄰近的將被使用,GL_LINEAR那麼就用2*2的包含紋理數據的數組加權組作爲紋理;
命名紋理對象:glGenTexures(GLSize n,Gluint *textureNames); n爲產生n個未使用的對象值,textureNames爲紋理名字數組,你可能有幾個紋理需要使用,這個數組來區分.
1.你需要載入圖片時候的紋理定義
void glTexImage2D( GLenum target, GLint level, GLint components,GLsizei width, GLsizei height, GLint border,GLenum format, GLenum type, const GLvoid *pixels );
定義一個二維紋理映射。target是常數 GL_TEXTURE_2D, level表示多級分辨率的紋理圖象的級數。若只有一種分辨率,level爲0。components是從1到4的整數,1:選擇R;2:選擇R A;3:選擇R G B;
源文檔 <http://www.pinxue.net/OpenGL/credbook/chapter9_textuer.htm>
綁定紋理對象:glBindTexture(Glenum target,Gluint,glTexImage*),將把數據存儲到這個紋理對象中,如果需要紋理圖像的顏色和物體表面的顏色進行組合,不是直接貼圖,那麼就需要glTexEvn*()函數.
確定紋理座標:glTexCoord2f(1.of,1.Of);glVertex3f(1.Of,1.Of,0.Of);比如這一句話.對於設置紋理貼圖的座標和繪圖座標的確定問題.一般的情況假設紋理和圖片都是正方形的,那麼我們希望紋理映射到整個物體上面,這個時候紋理座標按照逆時針放心依次(0,0),(1,0),(1,1),(0,1),其中的四個座標只是代表的向量,並不是真實的座標,如果是要一半貼到物體上就應該是0.5的值了,假如你給的紋理座標大於1,那麼將會貼多個紋理,比如值爲2的時候,會有4個紋理貼圖.
1 概述
概括的說, 紋理映射機制允許你將一個圖像關聯到一個多邊形上,從而呈現出真實視覺效果。例如, 你可以將書的封面圖像應用到一個方形上, 這樣這個方形看起來就像是一本書了。 你可以將地球的地圖通過紋理映射應用到一個球體上, 那麼這個球體就是一個3D的具真實感的地球了。紋理映射在當今的3D圖形應用上處處皆是。遊戲都是通過紋理映射來作爲虛擬真實的第一個步驟。
紋理映射是一個二維的數組。數組中的每一項稱之爲紋理點( texel )。 雖然這個數組是二維的, 但是可以映射到非二維的對象上, 如球體或者其他的 3D 對象模型上。
比較常見的是, 開發者在他們的圖形應用中運用二維紋理, 當然一維或者三維的紋理也並非未聞。二維紋理有寬度和高度決定二維。一維紋理也有寬度和高度, 只是高度被設爲值 1(單位:像素 pixel). 而三維紋理不僅具有寬度和高度, 還有深度, 所以三維爲紋理又稱爲立體紋理。我們討論的主要是二維紋理。
2 預備知識: 紋理座標
在 OpenGl 中是通過指定紋理座標來將紋理映射到多邊形上去的. 在紋理座標系中, 左下角是 (0,0), 右上角是 (1,1). 2D 紋理的座標中通過指定 (s,t) (s爲x軸上,t爲y軸上, 取值0~1). 1D, 3D, 4D紋理座標系中對應的需要指定 (s), (s,t,r), (s,t, r,q).
紋理座標需要通過函數 glTexCoord() 來設置, 此函數:
void glTexCoord{1234}{sifd}(TYPE coords); |
void glTexCoord{1234}{sifd}v(TYPE coords); |
如將 2D 紋理座標設爲 (0.2, 0.4):
1 |
glTexCoord2f(0.2f, 0.4f); |
每次通過 glVertex() 指定一個頂點時, 當前的紋理座標會被應用到這個點上. 所以每指定一個新的頂點, 需要同時修改紋理座標:
1 2 3 4 5 6 |
glBegin(GL_POLYGON); glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.5f, 0.5f, 0.5f);//左下角 glTexCoord2f(1.0f, 0.0f); glVertex3f(0.5f, 0.5f, 0.5f); // 右下角 glTexCoord2f(1.0f, 1.0f); glVertex3f(0.5f, 0.5f, -0.5f);// 右上角 glTexCoord2f(0.0f, 1.0f); glVertex3f(-0.5f, 0.5f, -0.5f);// 左上角 glEnd(); |
至此, 我們知道了紋理座標如何賦值.且看如何創建紋理:
3 使用紋理映射
紋理就是應用到多邊形上的圖像. 這些圖像可以從文件中加載, 或是在內存中生成. 一旦你將圖像數據加載到了內存中, 你需要指定其爲紋理映射來使用它. 指定其爲紋理映射, 首先需要生成一個紋理對象, 其中存儲着紋理的諸如圖像數據, 如何應用等信息.
紋理是一個OpenGL狀態, 因而通過 glEnable() 和 glDisable() 來開閉, 參數是 GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP.
3.1 紋理對象
紋理對象是內部數據類型, 存儲着紋理數據和選項等. 你不能直接訪問它, 但是可以通過一個整數的 ID 來作爲其句柄 (handler) 來跟蹤之. 爲了分配到一個唯一的 ID, OpenGL 提供了glGenTextures() 函數來獲取一個有效的 ID 標識值:
void glGenTexture(Glsizei n, GLuint *texture);
texture 是一個數組, 用於存儲分配到的n個ID值. 在調用一次 glGenTextures() 後, 會將分配到的 ID 標識爲'已用', 雖然直到綁定後才真正爲'已用'.
分配3個紋理對象 ID:
1 2 |
unsigned int textureObjects[3]; glGenTexture(3, textureObjects); |
3.2 紋理綁定
在第一次綁定一個紋理對象時, 會將一系列初始值來適應你的應用. 函數 glBindTexture() 用於綁定操作:
void glBindTexture(GLenum target, GLuint texture);
target 指定了紋理類型: GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP. texure 是你希望綁定的紋理對象的 ID.
一個被綁定的紋理對象直到被刪除,或被另外的紋理對象綁定到 target 上才被解除綁定. 當一個紋理對象綁定到 target 上後, OpenGL 的後續的紋理操作都是基於這個紋理對象的。
1 2 3 4 5 6 7 8 9 10 |
glBindTexture (GL_TEXTURE_2D, textureObject[0]); // 後面的對 GL_TEXTURE_2D 的紋理操作影響 textureObject[0]
glBindTexture (GL_TEXTURE_3D, textureObject[1]); // 後面的對 GL_TEXTURE_3D 的紋理操作影響 textureObject[1] // 對 GL_TEXTURE_2D 的紋理操作依然影響 textureObject[0]
glBindTexture (GL_TEXTURE_2D, textureObject[2]); // 後面的對 GL_TEXTURE_2D 的紋理操作影響 textureObject[2] // 對 GL_TEXTURE_3D 的紋理操作依然影響 textureObject[1] |
3.3 刪除紋理對象
創建一個紋理對象後, OpenGL爲其分配內存, 所以當不再使用一個紋理對象時, 爲防止內存泄露, 必須刪除. 刪除紋理對象的函數: glDeleteTexture() :
void glDeleteTexure(Glsizei n, Gluint *texture);
texture 指定了要刪除的紋理對象的 ID (n個). 在刪除後, texture 中的各個對象 ID 會置爲0.
3.4 駐留紋理
顯卡有一塊固定大小的內存區域專門用於存儲紋理數據。當數據超量時,會將一部分紋理數據移除到系統內存中(通常是最近最少使用的紋理數據). 當這些移除的紋理被再次使用時,會影響擊中率, 因爲它們會被再次移入顯卡的內存中。你可以查看一個紋理對象是否駐留在顯卡內存中未被移出, 通過函數 glAreTexturesResident() :
GLboolean glAreTexturesResident (GLsizei n, GLuint *textures, GLboolean *residents);
texture 中每一項紋理對象的駐留情況會存儲在 resident 參數中返回。 若 textures 中有一項紋理對象不在內存駐留內存, 函數會返回 GL_FALSE.
3.5 紋理優先級
紋理的優先級是針對駐留顯卡內存而言。優先級設置函數 glPrioritizeTextures() :
void glPrioritizeTextures (GLsizei n, GLuint *textures, GLclampf *priorities)
前兩個參數 textures 和 n 指定了要設置優先級的紋理對象數組。 priorities 是 textures 中每一項紋理對象對應的優先級, priorities 中每一項的優先級取值區間是 [0,1], 0爲優先級最低, 1 爲最高。 glPrioritizeTextures() 函數會忽略掉那些未使用的和優先級要設爲 0 的紋理對象。
4 指定紋理
OpenGL 提供了三個函數來指定紋理: glTexImage1D(), glTexImage2D(), glTexImage3D(). 這三個版本用於相應維數的紋理, 例如如果紋理是3D紋理,則需要有 glTexImage3D() 來指定。
4.1 2D 紋理
void glTexImage2D (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* texels);
參數 target 是 GL_TEXTURE_2D (二維紋理) 或 GL_PROXY_TEXTURE_2D (二維代理紋理), 代理紋理暫且不提。
參數 level 指定了紋理映射細節的級別,用在mipmap中。 基本的紋理圖像級別爲0, 在後面的mipmap部分講解。
參數 internalFormat 指定了紋理存儲在顯存中的內部格式, 取值在下表, 爲兼容 OpenGL1.0 internalFormat 可以取值 1,2,3,4 分別對應常量 LUMINANCE, LUMINANCE_ALPHA, RGB, RGBA.
格式 |
註解 |
GL_ALPHA |
Alpha 值 |
GL_DEPTH_COMPONENT |
深度值 |
GL_LUMINCE |
灰度值 |
GL_LUMINANCE_ALPHA |
灰度值和 Alpha 值 |
GL_INTENSITY |
亮度值 |
GL_RGB |
Red, Green, Blue三原色值 |
GL_RGBA |
Red, Green, Blue 和 Alpha 值 |
紋理內部格式
參數 width 和 height 定義了紋理映射的大小,前面已經說過紋理映射就是一個二維數組。 和 glDrawPixels() 一樣, 紋理映射的寬度和高度必須是 2 的整數次冪。
參數 border 註明了紋理是否有邊框。無邊框取值爲 0, 有邊框取值爲 1, 邊框的顏色由 GL_TEXTURE_BORDER_COLOR 選項設置。
接下來的三個參數主要定義了圖像數據的格式。
參數 format 定義了圖像數據數組 texels 中的格式。可以取值如下:
格式 |
註解 |
GL_COLOR_INDEX |
顏色索引值 |
GL_DEPTH_COMPONENT |
深度值 |
GL_RED |
紅色像素值 |
GL_GREEN |
綠色像素值 |
GL_BLUE |
藍色像素值 |
GL_ALPHA |
Alpha 值 |
GL_RGB |
Red, Green, Blue 三原色值 |
GL_RGBA |
Red, Green, Blue 和 Alpha 值 |
GL_BGR |
Blue, Green, Red 值 |
GL_BGRA |
Blue, Green, Red 和 Alpha 值 |
GL_LUMINANCE |
灰度值 |
GL_LUMINANCE_ALPHA |
灰度值和 Alpha 值 |
圖像數據數組 texels 格式
參數 type 定義了圖像數據數組 texels 中的數據類型。可取值如下
數據類型 |
註解 |
GL_BITMAP |
一位(0或1) |
GL_BYTE |
帶符號8位整形值(一個字節) |
GL_UNSIGNED_BYTE |
不帶符號8位整形值(一個字節) |
GL_SHORT |
帶符號16位整形值(2個字節) |
GL_UNSIGNED_SHORT |
不帶符號16未整形值(2個字節) |
GL_INT |
帶符號32位整形值(4個字節) |
GL_UNSIGNED_INT |
不帶符號32位整形值(4個字節) |
GL_FLOAT |
單精度浮點型(4個字節) |
GL_UNSIGNED_BYTE_3_3_2 |
壓縮到不帶符號8位整形:R3,G3,B2 |
GL_UNSIGNED_BYTE_2__3_REV |
壓縮到不帶符號8位整形:B2,G3,R3 |
GL_UNSIGNED_SHORT_5_6_5 |
壓縮到不帶符號16位整形:R5,G6,B5 |
GL_UNSIGNED_SHORT_5_6_5_REV |
壓縮到不帶符號16位整形:B5,G6,R5 |
GL_UNSIGNED_SHORT_4_4_4_4 |
壓縮到不帶符號16位整形:R4,G4,B4,A4 |
GL_UNSIGNED_SHORT_4_4_4_4_REV |
壓縮到不帶符號16位整形:A4,B4,G4,R4 |
GL_UNSIGNED_SHORT_5_5_5_1 |
壓縮到不帶符號16位整形:R5,G5,B5,A1 |
GL_UNSIGNED_SHORT_1_5_5_5_REV |
壓縮到不帶符號16位整形:A1,B5,G5,R5 |
GL_UNSIGNED_INT_8_8_8_8 |
壓縮到不帶符號32位整形:R8,G8,B8,A8 |
GL_UNSIGNED_INT_8_8_8_8_REV |
壓縮到不帶符號32位整形:A8,B8,G8,R8 |
GL_UNSIGNED_INT_10_10_10_2 |
壓縮到32位整形:R10,G10,B10,A2 |
GL_UNSIGNED_INT_2_10_10_10_REV |
壓縮到32位整形:A2,B10,G10,R10 |
圖像數據數組 texels 中數據類型
你可能會注意到有壓縮類型, 先看看 GL_UNSIGNED_BYTE_3_3_2, 所有的 red, green 和 blue 被組合成一個不帶符號的8位整形中,在 GL_UNSIGNED_SHORT_4_4_4_4 中是把 red, green , blue 和 alpha 值打包成一個不帶符號的 short 類型。
最後一個參數是 texels, 這個指針指向實際的圖像數據(你自己生成的或是從文件中加載的)。OpenGL 會按照 type 參數指定的格式來讀取這些數據,
例如, 假設你加載了一個 RGBA 圖像到 textureData 中( 寬高爲 textureWidth, textureHeight).你想要用它來指定一個紋理, 可以這樣做:
1 2 |
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData); |
執行完這個函數後, 紋理會加載,等待被使用。
4.2 1D 紋理
1D 紋理其實就是 2D 紋理的特殊形式(高度等於1)。這類紋理常常用來描繪顏色邊界從而創造出陰影效果。創建 1D 紋理的函數如下:
void glTExImage1D (GLenum target, GLint level, GLint internalFormat, GLsizei width, |
GLint border, GLenum format, GLenum type, const GLvoid *texels); |
函數中的參數同 glTexImage2D(), 不同的是 height 參數沒有被給出(因爲定值1), 參數 target 也需要指定爲 *GL_TEXTURE_1D*。
下面是簡單的代碼, 用於創建32個紋理點寬度的 RGBA 紋理:
1 2 3 |
unsigned char imageData[128]; ... glTexImage1D (GL_TEXTURE_1D, 0, GL_RGBA, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); |
4.3 3D 紋理
創建 3D 紋理的函數:
glTexImage3D(GLenum target, GLint level, GLint internalFormat, GLsizei width GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *texels);
函數參數同 glTexImage1D() 和 glTexImage2D() 大部分相同,不同的是多了一個深度參數 depth, 指定了紋理的第三維。
下面的代碼片段, 用於創建一個 16*16*16 個紋理點的 RGB 紋理:
1 2 3 |
... glTexImage3D (GL_TEXTURE_3D, 0, GL_RGB, 16, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData); |
4.4 Cube Map 紋理
一個 Cube Map 紋理是由6個2D紋理組成。對應的, 需要通過 glTexImage2D() 來指定6個 target 參數: GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z.
5 紋理過濾
將紋理映射到多邊形上, 實際上是將紋理的圖像數據空間映射到幀緩衝圖像空間上。所以, 你必須保證紋理圖像加載完成。 紋理圖像被映射到多邊形上可能會造成失真。紋理圖像映射到多邊形上去,屏幕上的一個點可能是紋理點的一個部分(如果視口設置的離紋理很近), 也有可能屏幕上的一個像素點是多個紋理的集合(如果視口設置的足夠遠). 紋理過濾就是告訴 OpenGL 在紋理到屏幕像素點的映射中如何計算最終顯示的圖像數據。
在紋理過濾中, 放大器處理一個屏幕像素點代表一個紋理點的一部分的情況;縮小器處理一個像素點代表多個紋理點的情況. 你可以通過下面函數來告訴 OpenGL 怎樣處理這兩種情況:
void glTexParameter{if}(GLenum target, GLenum pname, T param); |
void glTexParameter{if}v(GLenum target, GLenum pname, T params); |
glTexParameter 不僅僅設置放大器和縮小器, 在本章中,由於只涉及紋理,所以只討論紋理相關的參數取值.
參數 target 指的是紋理目標, 可以是 GL_TEXTURE_1D, GL_TEXTURE_2D*, GL_TEXTURE_3D 或 GL_TEXTURE_CUBE_MAP 。 指定紋理放大過濾器需要指定參數 pname 爲 GL_TEXTURE_MAG_FILTER, 指定紋理縮小過濾器需要指定參數 pname 爲 GL_TEXTURE_MIN_FILTER.
當指定爲 GL_TEXTURE_MAG_FILTER, 參數 param 取值 GL_NEAREST 或 GL_LINEAR. 對放大過濾器而言, 使用 GL_NEAREST 將告訴 OpenGL 使用離像素點中心最近的紋理來渲染, 這被稱作 點樣( point sampling); 使用 GL_LINEAR 告訴 OpenGL 會使用離像素點中心最近的四個紋理的平均值來渲染. 這被稱作 雙線性過濾( bilinear filtering)。
縮小過濾器比放大過濾器的取值更廣, 下表是指定縮小過濾器時, 參數 param 的取值, 下面表中的值是爲了增強渲染質量。
過濾參數 |
註解 |
GL_NEAREST |
使用像素點中心最近的點渲染 |
GL_LINEAR |
使用雙線性過濾 |
GL_NEAREST_MIPMAP_NEAREST |
|
GL_NEAREST_MIPMAP_LINEAR |
|
GL_LINEAR_MIPMAP_NEAREST |
|
GL_LINEAR_MIPMAP_LINEAR |
|
縮小過濾器的參數
在縮小過濾器中, 有4個參數處理mipmap, 這將會在後面的mipmap部分講解。
默認情況下, 放大過濾器的參數爲 GL_LINEAR, 縮小過濾器爲 GL_NEAREST_MIPMAP_LINEAR.
在渲染紋理時, OpenGL 會先檢查當前的紋理是否加載完成,同時也會處理其他事情,如在選用縮小過濾器的mipmap處理時會驗證mipmap的所有級別是否被定義。 如果紋理未完成, 紋理會被禁用。因爲縮小過濾器的缺省值使用mipmap,所以你必須指定所有的mipmap級別或是將縮小過濾器的參數設爲 *GL\_LINEAR* 或*GL\_NEAREST*.
6 簡單例程
在初始化函數 init() 中創建了紋理對象, 設定了過濾模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
bool CGfxOpenGL::init () { glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
// 啓用 2D 紋理 glEnable (GL_TEXTURE_2D);
m_textureOne = new CTargaImage;
// 加載紋理圖像 if (!m_textureOne->Load ("rock.tga")) return false;
// 創建紋理對象, glGenTextures (1, &m_textureObjectOne);
// 綁定紋理對象 glBindTexture (GL_TEXTURE_2D, m_textureObjectOne);
// 設定縮放器的過濾參數 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// 爲紋理對象指定紋理圖像數據 glTExImage2D (GL_TEXTURE_2D, 0, GL_RGB, m_textureOne->GetWidth(), m_textureOne->GetHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, m_textureOne->GetImage());
// 創建第二個紋理對象 glGenTexture (1, &m_textureObjectTown); glBindTexture (GL_TEXTURE_2D, m_textureObjectTwo);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, m_textureOne->GetWidth(), m_textureOne->GetHeight(), 0, GL_TGB, GL_UNSIGNED_BYTE, m_textureOne->GetImage());
// 初始化運動變量 m_zPos = -0.5f; m_zMoveNegative = true;
return true; } |
在 init() 函數中, 我們先啓用 2D 紋理( glEnable() ), 然後加載圖像到 CTargaImage 類中(詳見第6章), 然後通過 glGenTextures() 獲得一個未被使用的紋理對象, 繼而綁定, 指定縮放器的過濾模式, 最後爲紋理指定圖像數據(通過 glTexImage2D() ). 然後同樣的流程創建了第二個紋理對象, 使用了同樣的紋理圖像數據。只是縮放器的過濾參數做了下更改。
主要的渲染函數有兩個 DrawPlane(), Render() :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
void CGfxOpenGL::DrawPlane () { glBegin (GL_TRIANGLE_STRIP); glTexCoord2f (1.0, 0.0); glVertex3f (2.0, -2.0, -2.0); glTexCoord2f (0.0, 0.0); glVertex3f (-2.0, -2.0, -2.0); glTexCoord2f (1.0, 1.0); glVertex3f (2.0, -2.0, 2.0); glTexCoord2f (0.0, 1.0); glVertex3f (-2.0, -2.0, 2.0); glEnd(); }
void CGfxOpenGL::Render () { // 清除屏幕和深度緩存 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 重置當前矩陣 glLoadIdentity ();
// 繪製左邊的多邊形 glPushMatrix (); // glTranslatef (-3.0, 0.0, m_zPos); glRotatef (90.0, 1.0, 0.0, 0.0);
// 綁定紋理 glBindTexture (GL_TEXTURE_2D, m_textureObjectOne);
// 繪製 Plane DrawPlane (); glPopMatrix();
// 同樣地, 繪製右邊多邊形 glPushMatrix (); glTranslatef (3.0, 0.0, m_zPos); glRotatef (90.0, 1.0, 0.0, 0.0); glBindTexture (GL_TEXTURE_2D, m_textureObjectTwo); DrawPlane (); glPopMatrix();
} |
在 DrawPlane() 中, 我們指定了紋理座標然後繪製多邊形的頂點。在 Render() 中,我們先綁定好紋理對象, 然後繪製多邊形。
源文檔 <http://caobeixingqiu.is-programmer.com/posts/18999.html>
/////////////////////////////////////////////////////////////////////////////
// 函數名: T3DDEMTextureLoad(HDC hDC, char *bmpfile)
// 參數列表:hDC -- HDC句柄
// bmpfile -- 紋理圖象文件名
// 函數功能:裝入紋理圖象
/////////////////////////////////////////////////////////////////////////////
int T3D::T3DDEMTextureLoad(HDC hDC, char *bmpfile)
{
//設置紋理參數必須在當前HDC中進行,m_hRC爲PUBLIC變量
wglMakeCurrent(hDC,m_hRC);
//利用輔助庫直接裝入BMP圖象
image=auxDIBImageLoad(bmpfile);
//處理圖象錯誤的情況,注意作爲紋理的圖象尺寸必須是2的n次冪
if(image == NULL) return 0;
if((image->sizeX % 64) == 0 && (image->sizeY % 64) == 0)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D,0,3,image->sizeX,image->sizeY,0,GL_RGB,
GL_UNSIGNED_BYTE,image->data);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR);// GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR);// GL_NEAREST);
//設置紋理環境,確定紋理貼圖方式,見有關幫助
//glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_DECAL);
}
else return 0;
//打開紋理貼圖方式
glEnable(GL_TEXTURE_2D);
wglMakeCurrent(hDC,NULL);
//爲應用程序設置紋理標誌
m_TextureFlag = TRUE;
return 1;
}
物體表面通常並不是具有簡單顏色的平滑面,而是有着花紋圖案等豐富細節的。
計算機三維圖形通過給面貼紋理來表現表面細節。OpenGL默認設置是關閉貼紋理的,所以必須先用命令打開紋理計算。
前面提到過,可以給面指定材質來影響面上各點最終的顏色。能不能同時使用材質和紋理呢?當然是可以的,OpenGL允許你用glTexEnv(GL_TEXTUREN_ENV,GL_TEXTURE_ENV_MODE,mode);命令來設置兩者如何結合以決定最終的顏色。有三種模式GL_DECAL,GL_MODULATE,GL_BLEND。
OpenGL紋理的使用分三步:將紋理裝入內存,將紋理髮送給OpenGL管道,給頂點指定紋理座標。
OpenGL中紋理圖像尺寸必須是2n+2m個像素,m爲圖片包含的拼接邊界的像素數。實際使用中很少使用超過512像素的紋理圖,製作紋理文件時要注意適當縮放。通常我們使用Photoshop之類的工具製作紋理圖片,並保存成文件,然後在程序中對圖片文件進行解碼以讀入內存。常用的圖片格式有.bmp/.jpg/.tif/.gif/.rgb等。在BCB中TPicture直接支持.bmp/.jpg,所以可以用一個LoadFromFile調用完成圖片解碼讀入的工作。
OpenGL體系內有一塊紋理內存,在有硬件加速的情況下,可能是位於顯卡的VRAM裏,否則會是OpenGL庫管理的一塊內存。在這個紋理內存裏圖片是以特定的內部格式保存的,有的顯卡還支持壓縮紋理技術,所以將紋理像素從應用程序內存傳到紋理內存需要進行格式轉換。這在OpenGL中是通過分別描述像素在應用程序內存和紋理內存的格式來完成的,真正轉換工作OpenGL會在內部完成。
定義紋理的命令是glTexImage2/1D(GL_TEX_IMAGE_2/1D,level,components,width,height, border,format,type,*pixels );
OpenGL術語稱應用程序內存讀出像素的過程爲解碼(UNPACK),而向紋理內存寫像素的過程爲編碼(PACK)。用glPixelStore*(GL_[UN]PACK_*,參數值);命令設定編碼[解碼]格式 。對於貼紋理過程我們只需關心解碼過程。
如今的顯卡通常都有比較大的顯存,其中有一部份是專門的紋理存儲區,有的卡還可以將最多64M系統內存映射爲紋理內存,所以我們有可能把經常要用的紋理就保留在紋理內存裏以提高程序性能。OpenGL從1.2開始提供了紋理對象技術,可以把在管道內放多個紋理,每個紋理對應一個數字(名字),需要用到是把這個名字的紋理Bind爲當前紋理就可以了。用glGenTextures (n,*textures);命令取得可用的紋理名字的。
當前紋理已經存在之後,就可以給頂點指定紋理座標,以說明這一頂點的紋理像素在圖上的位置。OpenGL會對根據頂點座標對平面內部進行分割,以確定每一點對應紋理圖上的哪個像素。
指定當前紋理座標的命令是glTexCoord*(s,t,r,q); 此後定義的所有頂點的紋理座標都會是(s,t,r,q)。目前我們只需知道(s,t)是頂點紋理相對圖片左下角原點的位置,座標值應在0~1.0之間。也可以給頂點指定比1大比0小的紋理座標,對於這種超出紋理圖範圍的情況,使你可以用glTexParameter*(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S/T,GL_REPEAT/GL_CLAMP);選擇:
- GL_REPEAT:當紋理比表面小時重複使用紋理以填滿每個點。
-
GL_CLAMP:比1大的當作1,比0小的當作0。
紋理座標之所以設爲0~1之間的實數,是因爲物體表面投影得到的二維平面可能比紋理圖大(像素多)或小(像素少),紋理長寬乘上紋理座標就可以得到對應像素,並且必定在紋理圖內。但計算出的像素座標可能介於像素之間,OpenGL提供了濾波機制來決定最終使用哪個像素,命令是glTexParameter*(GL_TEXTURE_2D,GL_TEXTURE_MAG/MIN_FILTER,GL_NEAREST/GL_LINEAR);。放大和縮小的處理方法可以不同,用參數MAG/MIN分別設定。
- GL_NEAREST:取比較接近的那個像素。
- GL_LINEAR:以周圍四個像素的平均值做爲紋理。
-
bilinear:二次插值,精度更高,但需要自己動手計算。
對於複雜的物體表面來說逐一指定其紋理座標是相當煩瑣的事,所以OpenGL支持紋理座標自動生成。可用glTexGen命令開關。詳情見手冊或聯機幫助。
注意:OpenGL1.2還支持GL_TEXTURE_3D,在低版本OpenGL中三維紋理則是一個展擴。
以下代碼是展示了完整的貼紋理過程:
//----紋理尺寸----------#define TEXW 64#define TEXH 64byte tex[TEXW][TEXH][3];//----生成紋理數據------int i,j;//----定義紋理--------- glPixelStorei(GL_UNPACK_ALIGNMENT,1); glTexImage2D(GL_TEXTURE_2D,0,3,TEXW,TEXH,0,GL_RGB,GL_UNSIGNED_BYTE,tex); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);//----打開紋理計算----- glEnable(GL_TEXTURE_2D);//----使用紋理---------- glBegin(GL_QUADS); glTexCoord2f(-1,-1); glVertex3f(-1,-1,0); glTexCoord2f(-1,1); glVertex3f(-1,1,0); glTexCoord2f(1,1); glVertex3f(1,1,0); glTexCoord2f(1,-1); glVertex3f(1,-1,0); glEnd();
源文檔 <http://www.cnblogs.com/yxnchinahlj/archive/2010/11/23/1885233.html>
OpenGL紋理貼圖
紋理映射是將指定圖像的一部分映射到允許進行紋理映射的每個圖段上。這種映射伴隨着使用一幅圖像的顏色到某一圖段的(s,t,r)座標所指示的位置上並修改該圖段的RGBA顏色。但要特別注意的是,在OpenGL中,紋理映射僅在RGBA模式下說明,在顏色索引模式下,它的使用未作定義。概括地說,使用紋理繪製的一般步驟爲:定義紋理貼圖、控制紋理、說明紋理貼圖方式,定義紋理座標等。
2.1 定義紋理
紋理的定義有兩種:連續法和離散法。連續法把紋理定義爲一個二元函數,函數的定義域就是紋理空間。而離散法則是把紋理定義在一個二維數組中,該數組表示紋理空間中行間隔和列間隔固定的一組網格點上的紋理值。網格點之間的其它點的紋理值可以通過對相鄰網格點上紋理值進行插值來獲得。通過紋理空間與物體空間之間的座標變換,可以把紋理映射到物體表面。一般來說,離散法是較爲常用的紋理定義方法。其實現函數爲
glTexlmage2D()。該函數的原型如下:void glTexImage2D(Gl_enum target,GLint level,Gl_enum compo—nents, GLsizei width, GLsizei height, Glint border,Gl_enumformat。Gl_enumtype,const GLvoid pixels);其中:target指定紋理映射,此處必須是GL—TEXT—URE 2D;level指定紋理圖像分辨率的級數,當只
有一種分辨率時,level=0;Components是選擇用於調整和混合的成分;width和height分別指定紋理圖像的寬和高,必須是2 ,凡爲正數;Border爲邊界的寬度,必須是0和1;format和type分別指定紋理映射的格式和數據類型;Pixels指定一個指針,指向紋理數據在內存中的位置。
2.2 控制紋理
紋理圖像在映射到物體時會產生許多問題。這些問題主要有紋理圖像的紋理怎樣對應到屏幕上的像素、怎樣通過紋理貼圖實現紋理縮放和紋理重複等。其實現函數爲glTexParmneter(),該函數的原型(以glTexParmneterf形式爲例)爲:void glTexPa—rmneterf(GLeRuin target,GLeRuin pname,GLfloat pa—ram),其中target參數爲目標紋理,pname參數的取值有以下幾個:GL TEXTURE MIN FILTER、GL,ⅡⅨ TURE—MAG一兀I肛R、GL—TEXrrI yRE— WRAP一GL— TEXTU RE —WRAP— T,而parmn參數的取值要根據pname而定。
2.3 說明紋理貼圖方式
OpenGL用於紋理貼圖方式的函數爲glTex~v(),該函數的原型(以glTexEnvf形式爲例)爲:voidglTexEnv(Gl_enum target,Gl_enum pname,GLfloat pa—ram)其中target參數必須爲GL—TEXTURE —ENV,pname參數必須爲GL—TEXTURE —ENV—MODE,而參數parmn爲GL—MODULATE 、GL—DECAL或GL—BLEND。
2.4 定義紋理座標
紋理座標控制紋理圖像中的像素怎樣映射到物體。紋理座標可以是1、2、3、4維的,通常用齊次座標來表示,即(5,t,r,q)。OpenGL定義紋理座標的函數爲 xCoord()。該函數共有32種不同的形式。例如:glTexCoord4f(O.Of,0.Of,0.Of,0.Of)。
3 基於MFC的OpenGL中的紋理貼圖利用VC++的MFC AppWizard(exe)建立一個新項目OpenglTexture,選擇基於單文檔界面的應用,其它選項都使用缺省值。在OpenglTextureView.h頭文件中,添加各成員變量和成員函數。
for(i=0;i<128;i++)
{
for(j=0;j<64;j++)
{
c=(((i&Ox08)==0) ((j ))==0)*255;
g=(4*i)%255;
b:(j*i)%255;
imag~Ei兒j][0]=(GIaxbyte)b;
image[i兒J兒1]=(GIaxbyte)g;
image~i][j][2]=(GIaibyte)c;
}
}
glPixelStorei(GL—UNPACK— ALIGNMENT,2);
glTexImage2D(GL—TEXTURE一2D,0,3,64,64 ,0,GL— RGB,GL—UNSIGNED— BYTE,image);
//定義紋理
glTexParameteri(GL— TEXTURE一2D,GL— TEXTURE — W RAP— S,
GL— CLAMP);
//控制紋理
glTexParameteri(GL—
TEXTURE一2D,GL— TE XTURE — WRAP—T,GL— CLAMP);
glTexParameteri(GL—TEXTURE一2D,GL—TEXTURE—MAG—FIL—E R,GL— NEAREST);
glTexParameteri(GL— TEXTURE 一2D,GL— TEXTURE — MIN— FIL—TE R,GL— NE AREST);
rSTexEnvf(GL—TEXTURE —ENV,GL—TEXTURE —ENV—MODE,GL— DECAL);//說明紋理貼圖方式
glEnable(GL— TEXTURE 一2D);//啓動紋理貼圖
glShadeModel(GL—SMOOTH);
glBegin(GL— QUADS);//定義紋理座標和物體幾何座標
glTexCoord2f(1.of,1.Of);glVertex3f(1.Of,1.Of,0.Of);
glTexCoord2f(1.of,0.Of);glVertex3f(1.Of,一1.Of,0.Of);
glTexCoord2f(0.of,0.Of);glVertex3f(一1.Of,一1.Of,0.Of);
glTexCoord2f(0.Of.I.Of);glVertex3f(一I.Of,I.Of,0.Of);
glEnd();
glDisabh(GL—TEXTURE 一2D);//關閉紋理貼圖
結束語
利用OpenGL強大的圖形功能,可以輕鬆地實現逼真的貼圖模型。在此基礎上,運用VC++的MFC應用程序模式,可對OpenGL產生的模型進行更進一步的控制和變化。同時提供給用戶一個友好的操作環境,這在當今的時尚編程中是不可或缺的。
源文檔 <http://hi.baidu.com/imfei/blog/item/c55aa0c4cff5dec239db4909.html>
int CMubanView::LoadGLTextures()
{
int Status=FALSE; //狀態參數跟蹤是否能夠載入位圖以及能否創建紋理
AUX_RGBImageRec* TextureImage[1]; //設置紋理數組
memset(TextureImage,0,sizeof(void*)*1); //清除圖像記錄,將指針設置爲NULL
if (TextureImage[0]=LoadBMP("it.bmp")) //載入位圖
{
Status=TRUE;
glGenTextures(1,&m_texture[0]); //創建紋理
glBindTexture(GL_TEXTURE_2D,m_texture[0]); //根據來自位圖的數據創建NEAREST紋理
glTexImage2D(GL_TEXTURE_2D, //產生的是2D紋理
0, //圖像的詳細程度,一般爲0
3, //圖像的成分,爲RGB
TextureImage[0]->sizeX/2, //圖像寬
TextureImage[0]->sizeY/2, //圖像高
0, //圖像邊框
GL_RGB, //圖像是RGB三色組成
GL_UNSIGNED_BYTE, //圖像數據是無符號字節類型
TextureImage[0]->data); //圖像數據來源
//採用GL_LINEAR使得紋理從很遠處到離屏幕很近時都平滑顯示
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // 線形濾波
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); // 線形濾波
return Status;
}
AUX_RGBImageRec* CMubanView::LoadBMP(char *Filename)
{
FILE *File=NULL;
if (!Filename) //文件名是否存在(判斷結果---文件名是存在的)
{
return NULL;
}
File=fopen(Filename,"r"); //讀取文件
if (File) //文件讀取成功
{
fclose(File); //關閉文件流
return auxDIBImageLoad(Filename); //載入位圖並返回指針
}
return NULL;
}
源文檔 <http://bbs.gameres.com/showthread.asp?threadid=100160>
品寒絕頂雪舞人間
OPENGL的紋理
在3D圖形中,紋理映射是廣泛使用的。紋理映射也是相當複雜的過程:
一 定義紋理
二 控制濾波
三 說明映射方式
四 繪製場景給出頂點的紋理座標和幾何座標
注意!!紋理映射只能在RGBA模式下使用,不適用於顏色索引模式
1.紋理定義
void glTexImage2D( GLenum target, GLint level, GLint components,
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type, const GLvoid *pixels );
定義一個二維紋理映射。
target是常數 GL_TEXTURE_2D
level表示多級分辨率的紋理圖象的級數。若只有一種分辨率,level爲0。
components是從1到4的整數,1:選擇R;2:選擇R A;3:選擇R G B;
4:選擇R G B A;
width height是紋理的尺寸。
format和type描述映射格式和數據類型。它們與前面講的glDrawPixels()中
OPENGL的紋理
在3D圖形中,紋理映射是廣泛使用的。紋理映射也是相當複雜的過程:
一 定義紋理
二 控制濾波
三 說明映射方式
四 繪製場景給出頂點的紋理座標和幾何座標
注意!!紋理映射只能在RGBA模式下使用,不適用於顏色索引模式
1.紋理定義
void glTexImage2D( GLenum target, GLint level, GLint components,
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type, const GLvoid *pixels );
定義一個二維紋理映射。
target是常數 GL_TEXTURE_2D
level表示多級分辨率的紋理圖象的級數。若只有一種分辨率,level爲0。
components是從1到4的整數,1:選擇R;2:選擇R A;3:選擇R G B;
4:選擇R G B A;
width height是紋理的尺寸。
format和type描述映射格式和數據類型。它們與前面講的glDrawPixels()中
GL_NEAREST_MIPMAP_NEAREST
GL_NEAREST_MIPMAP_LINEAR
GL_LINEAR_MIPMAP_NEAREST
GL_LINEAR_MIPMAP_LINEAR
2.1 濾波
原始紋理圖象是個方形圖象,把它映射到奇形怪狀的物體上,一般不可能圖象
上的一個象素對應屏幕的一個象素。因此局部放大縮小時,就要定義合適的濾
波方式(以2D爲例):
void glTexParameter(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
void glTexParameter(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
前者是放大濾波(GL_TEXTURE_MAG_FILTER),
後者是縮小濾波(GL_TEXTURE_MIN_FILTER);
另外,GL_NEAREST是利用最座標最靠近象素中心的紋理元素,這有可能使圖樣
走型,但計算速度快;GL_LINEAR利用線形插值,效果好但計算量大。
2.2重複與縮限
紋理映射可以重複映射或者縮限映射,重複映射時紋理可以在自己的座標S T方
向重複。
對於重複映射:
void glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
void glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
參數GL_REPEAT改爲GL_CLAMP,則縮限,所有大於1的紋理元素值置爲1。所有小於
0的紋理元素值置爲0。
0的紋理元素值置爲0。
3. 映射方式
處理紋理本身圖案顏色和物體本身顏色的關係:
void glTexEnv{if}[v](GLenum target,GLenum pname,TYPE param);
target必須是GL_TEXTURE_ENV;
pname是GL_TEXTURE_ENV_MODE,則param可以是 GL_DECAL GL_MODULATE或
GL_BLEND,說明紋理值與原來顏色不同的處理方式。
pname是GL_TEXTURE_ENV_COLOR,則參數param是包含4個浮點數(R、G、B、A)
的數組。這些值只在採用GL_BLEND紋理函數時才採用。
4. 紋理座標
座標的定義:紋理圖象是方形的,紋理座標可定義成s,t,r,q座標,仿照齊次
座標系的x,y,z,w座標。
void glTexCoord{1234}{sifd}[v](TYPE coords);
設置當前紋理座標,此後調用glVertex*()所產生的頂點都賦予當前的紋理座標。
5. 座標自動產生
有時不需要爲每個物體頂點賦予紋理座標,可以使用
void glTexGen{if}(GLenum coord,GLenum pname,TYPE param);
coord爲:GL_S GL_T GL_R或GL_Q,指明哪個座標自動產生
pname爲GL_TEXTURE_GEN_MODE時
param爲常數:GL_OBJECT_LINEAR GL_EYE_LINEAR或GL_SPHERE_MAP,它們決定用
哪個函數來產生紋理座標
pname爲GL_OBJECT_PLANE或GL_EYE_PLANE,param時一個指向參數數組的指針。
先請看一個簡單的例子:
////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
//創建紋理圖象的子程序
#define TEXTUREWIDTH 64
#define TEXTUREHEIGHT 64
GLubyte Texture[TEXTUREWIDTH][TEXTUREHEIGHT][3];
void makeTexture(void)
void makeTexture(void)
{
int i,j,r,g,b;
for(i=0;i<TEXTUREWIDTH;i++)
{
for(j=0;j<TEXTUREHEIGHT;j++)
{
r=(i*j)%255;
g=(4*i)%255;
b=(4*j)%255;
Texture[i][j][0 =(GLubyte)r;
Texture[i][j][1 =(GLubyte)g;
Texture[i][j][2 =(GLubyte)b;
}
}
}
void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
//創建紋理圖象的原始數據保存在Texture[][][]中
makeTexture();
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
//定義二維紋理
glTexImage2D(GL_TEXTURE_2D,0,3,TEXTUREWIDTH,
TEXTUREHEIGHT,0,GL_RGB,GL_UNSIGNED_BYTE,
&Texture[0][0][0]);
//控制濾波
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
//說明映射方式
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
//這個應該很熟了,啓用紋理模式
glEnable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_2D);
// glShadeModel(GL_FLAT);
}
void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//定義立體視景體
gluPerspective(60.0,1.0*(GLfloat)w/(GLfloat)h,1.0,30.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0,0.0,-3.6);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glBegin(GL_QUADS);//繪製四邊形
//先繪製正方形,用來顯示實際未變形的紋理圖樣
//先繪製正方形,用來顯示實際未變形的紋理圖樣
glTexCoord2f(0.0,0.0);glVertex3f(-2.0,-1.0,0.0);
glTexCoord2f(0.0,1.0);glVertex3f(-2.0,1.0,0.0);
glTexCoord2f(1.0,1.0);glVertex3f(0.0,1.0,0.0);
glTexCoord2f(1.0,0.0);glVertex3f(0.0,-1.0,0.0);
//繪製一個不規則四邊形,用來顯示紋理是如何隨物體形狀而變形的。
glTexCoord2f(0.0,0.0);glVertex3f(0.0,-1.0,0.0);
glTexCoord2f(0.0,1.0);glVertex3f(0.0,1.0,0.0);
glTexCoord2f(1.0,1.0);glVertex3f(1.41421,1.0,-1.41421);
glTexCoord2f(1.0,0.0);glVertex3f(1.41421,-1.0,-1.41421);
glEnd();
glFlush();
}
void main(void)
{
myinit();
auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
從例子來看,除了紋理的定義和控制比較麻煩和不容易理解外,其應用是十分
方便的。只須從紋理的座標系選出合適點附在實際物體頂點上即可。對於複雜
的紋理定義和控制,你也可以自行改變一些參數,看看效果如何。例如把
GL_LINEAR改成GL_NEAREST,則紋理的明顯變的粗糙,但計算結果卻快的多。
你也可以改動glTexCoord2f()的參數,看看如何選定紋理的一部分(例子中是
選定全部紋理)來貼圖。例如1.0改成0.5則選擇實際紋理的左上1/4部分來貼圖。
下次將給出一個更復雜的紋理應用的例子和說明。18:03 98-1-21
---------------------------------------------
這次將結束紋理的內容。緊接上次,在上次平面紋理貼圖中,我們先
定義了一個數組(一維或二維...)來定義紋理的數據,所以紋理本身
是一個N維空間,有自己的座標和頂點。在上次的例子中,我們學會了
如何把紋理數據中的座標和屏幕物體座標相結合,就象把一塊布料扯成
合適的形狀貼在物體表面。而上次唯一沒有使用的函數是紋理座標的自
動產生(最後一個給出的函數),它的意義是產生一個環境紋理,所有
環境內的物體都賦予此紋理,很象一個特殊光源。
/////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
//定義一個一維紋理的數據,從生成來看,保持紅色、蘭色分量255(MAX),
//所以是漸變的紫色紋理,飽和度不斷變化。
//所以是漸變的紫色紋理,飽和度不斷變化。
#define TEXTUREWIDTH 64
GLubyte Texture[3*TEXTUREWIDTH];
void makeTexture(void)
{
int i;
for(i=0;i<TEXTUREWIDTH;i++)
{
Texture[3*i =255;
Texture[3*i+1 =255-2*i;
Texture[3*i+2 =255;
}
}
GLfloat sgenparams[]={1.0,1.0,1.0,0.0};
void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
//創建紋理
makeTexture();
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
//控制紋理
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
glTexParameterf(GL_TEXTURE_1D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameterf(GL_TEXTURE_1D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameterf(GL_TEXTURE_1D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage1D(GL_TEXTURE_1D,0,3,TEXTUREWIDTH,0,
GL_RGB,GL_UNSIGNED_BYTE,Texture);
//唯一與前面例子不同的地方:啓用紋理座標自動產生,生成環境紋理
//紋理的方向S
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
glTexGenfv(GL_S,GL_OBJECT_PLANE,sgenparams);
//啓用紋理
glEnable(GL_TEXTURE_1D);
glEnable(GL_TEXTURE_GEN_S);
//啓用消隱
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glDepthFunc(GL_LESS);
//一些繪圖控制,詳細可參閱VC5聯機幫助
glEnable(GL_CULL_FACE);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glFrontFace(GL_CW);
glCullFace(GL_BACK);
glMaterialf(GL_FRONT,GL_SHININESS,64.0);
// glShadeModel(GL_FLAT);
}
void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0);
else
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0,-4.0,4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(30.0,1.0,0.0,0.0);
//功能強大的輔助庫函數:呵呵畫出一個大茶壺。
auxSolidTeapot(1.5);
glPopMatrix();
glFlush();
}
void main(void)
{
myinit();
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(30.0,1.0,0.0,0.0);
//功能強大的輔助庫函數:呵呵畫出一個大茶壺。
auxSolidTeapot(1.5);
glPopMatrix();
glFlush();
}
void main(void)
{
myinit();
auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////////////
至此紋理的全部內容已經完畢。從運行結果來看,一個物體全部進行
了表面的紋理映射。
-12---------------------------------------------
此次講解OPENGL複雜建模方式,將分幾個部分完成,這篇先介紹圖原擴展:
如何利用專用函數精確繪製平面圖形。下次會講解如何利用法向量生成曲面。
1.點和線
void glPointSize(GLfloat size);
設置點的寬度,size必須>0,缺省1
void glLineWidth(GLfoat width);
設置線寬,width>0,缺省爲1
void glLineStipple(GLint factor,GLushort pattern);
設置線的模式,factor用於對模式進行拉伸的比例因子,pattern是線的模式
例如11001100是虛線(1繪製,0不繪製)
必須要啓用glEnable(GL_LINE_STIPPLE)才能使用以上函數,不再使用時調用
glDisable(GL_LINE_STIPPLE)關閉,這與以前的glEnable();glDisable();的
用法都是類似的。請看下面例子:
///////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,600,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glShadeModel(GL_FLAT);
}
/*
void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0);
else
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0,-4.0,4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
*/
//自定義的繪製直線的函數,參數爲起始點和終止點座標
void line2i(GLint x1,GLint y1,GLint x2,GLint y2)
{
glBegin(GL_LINES);
glVertex2f(x1,y1);
glVertex2f(x2,y2);
glEnd();
glEnd();
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//首先繪製一系列點,點的大小不斷增加
int i;
glColor3f(0.8,0.6,0.4);
for(i=1;i<=10;i++)
{
glPointSize(i*2);
glBegin(GL_POINTS);
glVertex2f(30.0+((GLfloat)i*50.0),330.0);
glEnd();
}
//再繪製兩條虛線,第二條比第一條鬆散一些,由pattern參數即可看出
glEnable(GL_LINE_STIPPLE);
glLineStipple(1,0x0101);//間隔1位
glColor3f(1.0,0.0,0.0);
line2i(20,250,250,250);
glLineStipple(1,0x00ff);//間隔2位
glLineStipple(1,0x00ff);//間隔2位
glColor3f(0.0,0.0,1.0);
line2i(300,250,550,250);
//改變線的繪製寬度的效果--加寬
//重新畫出上面兩條虛線
glLineWidth(5.0);
glEnable(GL_LINE_STIPPLE);
glLineStipple(1,0x0101);
glColor3f(1.0,0.0,0.0);
line2i(50,150,250,150);
glLineStipple(1,0x00ff);
glColor3f(0.0,0.0,1.0);
line2i(300,150,550,150);
glFlush();
}
void main(void)
{
myinit();
// auxReshapeFunc(reshape);
auxMainLoop(display);
auxMainLoop(display);
}
//end of sample
//////////////////////////////////////////////
2.多邊形
void glPolygonMode(GLenum face,GLenum mode);
控制多邊形指定面的繪圖模式,
face爲:GL_FRONT GL_BACK或GL_FRONT_AND BACK
mode爲:GL_POINT GL_LINE或GL_FILL表示多邊型的輪廓點、輪廓線和填充模式
的繪製方式。缺省是填充方式。
void glPolygonStipple(const GLubyte *mask);
其中mask必須是指向32*32的位圖指針,1是繪製、0不繪製
使用上述函數也要調用:
glEnable(GL_POLYGON-STIPPLE);
glDisable(GL_POLYGON_STIPPLE);
請看下面例子:
/////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include <GL/gl.h>
#include <GL/gl.h>
#include <GL/glaux.h>
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
//定義填充模式32*32點陣
GLubyte pattern[]={
0x00,0x01,0x80,0x00,
0x00,0x03,0xc0,0x00,
0x00,0x07,0xe0,0x00,
0x00,0x0f,0xf0,0x00,
0x00,0x1f,0xf8,0x00,
0x00,0x3f,0xfc,0x00,
0x00,0x7f,0xfe,0x00,
0x00,0xff,0xff,0x00,
0x01,0xff,0xff,0x80,
0x03,0xff,0xff,0xc0,
0x07,0xff,0xff,0xe0,
0x0f,0xff,0xff,0xf0,
0x1f,0xff,0xff,0xf8,
0x3f,0xff,0xff,0xfc,
0x3f,0xff,0xff,0xfc,
0x7f,0xff,0xff,0xfe,
0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,
0x7f,0xff,0xff,0xfe,
0x3f,0xff,0xff,0xfc,
0x1f,0xff,0xff,0xf8,
0x0f,0xff,0xff,0xf0,
0x07,0xff,0xff,0xe0,
0x03,0xff,0xff,0xc0,
0x01,0xff,0xff,0x80,
0x00,0xff,0xff,0x00,
0x00,0x7f,0xfe,0x00,
0x00,0x3f,0xfc,0x00,
0x00,0x1f,0xf8,0x00,
0x00,0x0f,0xf0,0x00,
0x00,0x07,0xe0,0x00,
0x00,0x03,0xc0,0x00,
0x00,0x01,0x80,0x00
};
void myinit(void)
void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,400,400);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glShadeModel(GL_FLAT);
}
/*
void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0);
else
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0,-4.0,4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
*/
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//選用蘭色作爲填充色
glColor3f(0.0,0.0,1.0);
//啓用多邊形繪製模式
glEnable(GL_POLYGON_STIPPLE);
//利用定義好的填充模式繪製多邊形
glPolygonStipple(pattern);
//繪製長方形
glRectf(48.0,80.0,210.0,305.0);
glFlush();
}
void main(void)
{
//選用蘭色作爲填充色
glColor3f(0.0,0.0,1.0);
//啓用多邊形繪製模式
glEnable(GL_POLYGON_STIPPLE);
//利用定義好的填充模式繪製多邊形
glPolygonStipple(pattern);
//繪製長方形
glRectf(48.0,80.0,210.0,305.0);
glFlush();
}
void main(void)
{
myinit();
// auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
例子中的運行結果是給出一個表面有定義圖樣的長方形
-13---------------------------------------------
這裏講解OPENGL的曲線生成
1.曲線定義
void glMap1{fd}(GLenum target,TYPE u1,TYPE u2,GLint stride,
GLint order,const TYPE *points);
target指出控制點的意義以及在points參數中需要多少值。具體如下:
target 意義
GL_MAP1_VERTEX_3 X Y Z頂點座標
GL_MAP1_VERTEX_4 X Y Z W頂點座標
GL_MAP1_INDEX 顏色索引
GL_MAP1_COLOR_4 R G B A
GL_MAP1_NORMAL 法向量
GL_MAP1_TEXTURE_COORD_1 S 紋理座標
GL_MAP1_TEXTURE_COORD_2 S T紋理座標
GL_MAP1_TEXTURE_COORD_3 S T R紋理座標
u1,u2是曲線變量U的範圍(具體可以參閱圖形學書籍)一般是0到1
stride是跨度,表示points中控制點偏移量(或說是控制點的維數)
源文檔 <http://www.pinxue.net/OpenGL/credbook/chapter9_textuer.htm>
三維隨機分形地形生成
原文: http://www.gameprogrammer.com/fractal.html
作者: Paul Martz [ [email protected] ]
翻譯:品雪 [[email protected]] 1999/8/4
目 錄
說明:本頁所有圖像均經過優化以減小尺寸,所以與實際圖像會有細微差別。
第一部分:生成隨機分形地形
介紹
十年前,我參加 1986 年 SIGGRAPH 活動, Gavin S. P. Miller 那篇題爲 Definition and Rendering of Terrain Maps 的論文讓我充滿敬畏。該文描述了少數生成分形地形的算法,作者還介紹了一個他們認爲更先進的新方法。
開始我被這些算法能夠生成難以置信的風景圖所震驚!(儘管這些算法被作者認爲"漏洞百出")後來,讀過論文,這些算法之簡單將我完全打敗了。
我從此成爲一個分形地形迷。
算法背後的數學可能相當複雜。然而,完全理解這些數學並不是掌握這些算法的必要條件。很好,否則我得在解釋算法之前講解所有的數,也許永遠也講不到算法。此外,關於分形數學的文字材料數以噸計,參見本文本的參考部分會有所幫助。
同樣的原因,我不會深入到數學細節,也不包括對分形的廣泛總覽及它們可被用來做的每樣東西。相反,我將描述分形地形生成背後的概念,並集中仔細講解 我個人最喜歡的 "diamond-square" 算法。我將演示如何使用這個算法靜態拼嵌高度數據數組,這些數據可用於幾何地形數據、地形紋理數據及雲紋理映射。
分形有什麼用呢?假定你已經知道,那正是你讀本文的原因。隨機地形圖對飛行模擬或製作背景紋理圖(如顯示一帶遠山)十分有用。生成地形的算法也可用於生成部分雲天的紋理圖。
在繼續之前,申明一下:我不是遊戲程序員。如果你爲找到一個快速繪製地形的算法而讀此文,那你來錯了地方。我只描述生成地形模型的過程。着色繪製是你自己的事。
自相似
任何分形最關鍵的概念是自相似。當一個物體的一部分放大後看起來仍與整個物體一樣,那這個物體就是自相似。
考慮一下人體的循環系統。這是自然界中自相似的好例子。從最大的動脈和靜脈分支直到最小的微血管,整個過程都顯現相同的分支模式。如果你不知道正在使用顯微鏡,將無法分辨微血管和大動脈。
現在再考慮一個簡單的球。它是自相似的嗎?不!大幅度放大後,它看起來不再象一個球,而象塊平板。如果你不相信,看看戶外。除非恰好你在太空軌道上看本文,否則將完全沒法看出球是個球體。球體不是自相似的。它最用傳統的歐幾里德幾何描述而不是分開。
地形屬於自相似範疇。手掌上的碎巖鋸齒狀邊緣與遠處地平線邊的山脊有相同的不規則形狀。這使我們可以用分形來生成地形,不管顯示時怎麼放大,它看起來仍然象地面。
關自相似請注意:嚴格意義下,它意味着自分辨 (self-identical) ,即,自身精確的縮略拷貝在逐漸放大縮小時可見。我並不知道自然界存在任何自分辨分形。但 mandelbrot 集是自分辨的。我不會進一步討論 Mandelbrot 集。到參考裏找進一步的信息。
一維中點變換
後邊要講的 diamond-square 算法,在兩維上使用一種中點變換算法。爲幫助你瞭解個大概,我們先看一維情況。
當山脈出現在遠處地平線處時,一維中點變換是繪製山脊的好算法。看看它是怎麼工作的:
以一條水平地平線段開始
重複足夠多次{
對場景中的每條線段做{
找到線段的中點
在 Y 方向上隨機移動中點一段距離
減小隨機數取值範圍
}
}
將隨機數值域減速小多泊呢?那取決於你想要分形的陡峭程度。每次循環減少的越多,所得山脊線就越平滑。但如果減得太多,則會有明顯的鋸齒感。可以粗糙度存在一個常量裏。後面會解釋如何做。
來看個例子。我們以一條 x 從 -1.0 到 1.0 , y 均爲 0 的線段開始。開始,我們將隨機值範圍設爲 -1.0 到 1.0 (可任意取)。這樣我們在此範圍裏生成一個數字,並將中點移動這麼多。這之後,我們就得到了:
現在第二次經過外圈循環,我們有兩段,長度均原來的一半。我們的隨機值也減半,即 -0.5 到 0.5 。我們爲兩個中點都生成一個這個範圍內的隨機點,結果爲:
再次縮減範圍,現在是 -0.25 到 0.25 。再以該範圍內的數變換四個中點後,我們得到了:
有兩件事你可能已經注意到了。
首先,它是遞歸的。實際上,它可以用一個迭代過程相當自然的實現。對於這種情況,遞歸或迭代都成。對於表面生成代碼,使用迭代實現比遞歸會有一些好處。所以爲保持一致,線和麪相應的代碼都使用迭代實現。
其次,它是個非常簡單的算法,然而你能創建非常複雜的結果。這正是分形算法的美妙之處。一些簡單的指令可以建立一個具有豐富細節的圖像。
再跑一下題:少量簡單的指令集能夠生成複雜圖像的事實已經成爲一個新的研究領域稱爲分形圖像壓縮。其思想是保存建立圖像的遞歸指令而不是保存圖像本身。這對於自然界的分形圖像是極有用的,因爲指令相對圖像佔用的空間要少得多。 Choas (混沌)與 Fractals (分形) new Frontiers of Science 有一章及一個附錄涉及本主題,是一般學習分形的好讀物。
回到現實。
不用太費勁,你可以讀取本函數的輸出到一個繪製程序而得到類似如下的東西:
這可作爲窗口景色使用。相關的好東西是它是約束的,所以你可以保留一個相當的小圖像並用它拼出整個場景。如果你不介意在每個方向都看見相同的山,那就這麼幹。
好的,在進入 2D 分形表面之前,你得了解粗糙度常量。這個值決定每次循環隨機數值域的減少量,也就是說,決定分形結果的粗糙程度。例子代碼使用一個 0.0 到 1.0 之間的浮點數並稱之爲 H 。因此 2(-h) 是 1.0( 對於小 H) 到 0.5 (對大 H )範圍內的數。隨機數範圍在每次循環時乘上這個值。如果 H 設爲 1.0 ,則隨機數範圍將每次循環減半,從而得到一個非常平滑的分形。將 H 設爲 0.0 ,則範圍根本不減小,結果有明顯的鋸齒感。
下邊是三個山脊,每個用不同的 H 的值繪製:
高度圖
上邊所說的中點變換算法可以用一個一維數組實現,數組成員是表明線段端點垂直位置的高度值。這數組是就是一個一維高度圖。它將索引( x 值)映射爲高度值( y 值)。
爲模擬隨機地形,我們想將該算法推廣到 3D 空間。爲做到這一點,我們需要一個兩維高度值數組,它將索引 (x,z) 映射爲高度 (y) 。數組只需保存高度值 (y) 。水平面值 (x 和 z) 可以在分析數組時即時生成。
通過對每個高度指定一個顏色,可以將一幅高度圖顯示爲一幅圖像。如下,高點爲白色,低處爲黑色。
繪製高度圖的方法對於生成雲彩紋理圖是很有用的,後邊還會討論。這種表達也可以用於播種一個高度圖。
現在我要講講如何拼嵌我們的二維高度圖數組。
Diamond-Square 算法
正如本文開頭提到過的,我先介紹 Gavin S.P.Miller 的論文中隨機地形生成的概念。具有諷刺意義的是, Miller 在論文中說 diamond-square 算法是有缺陷的,然後描述了一種完全不同的基於重量平均和控制點的算法。
Miller 對 diamond-square 算法的抱怨阻止他嘗試迫使該算法建立一座山,也就是,帶有一個山峯,人爲增加網格中心點的高度。他讓數組中所有的點都隨機生成。如果 Miller 簡單的只隨機生成中心點,那麼即使是他也會同意該算法是個經典的地形生成器。 Diamond-Square 算法可以通過給數組播種值來用一個山峯推出一坐山。比數組中心點更多的點得先播種以構造可接受的結果。他也抱怨一些固有皺摺問題。但你得自己判斷。算法最 初是由 Fourniew , Fussell 和 Carpenter 提出的。
思想如下:你從一個很大的空 2D 數組開始。多大呢?爲簡化起見,他應該是方的,維數應該是 2 的 n 次方加 1 (如 33X33,65X65,129X129 等)。將四個角設爲相同高度。如果你察看所得到東西,它是一個正方形。
取個簡單的例子,用一個 5X5 的數組。(本文後面還要參考這圖,別忘記了)。圖中,圖 a 的四個角種上了初始高度值,表示爲黑點。
這是遞歸細分過程的起點,該過程分兩步:
diamond 步:取四個點的正方形,在正方形中點生成一個隨機值,中點爲兩對角線交點。中點值是平均四個角值再加上一個隨機量計算得到的。這樣就得到了一個棱錐。當網格上分佈着多個正方形時有點象鑽石。
square 步:取每個四點形成的棱錐,在棱錐的中心生成一個隨機值。平均角值再加上與 diamond 步相同的隨機量,計算出每條邊中點值。這又給你一個正方形。
這樣,如果已經生成了一個種子正方形並經過單獨一次細分過程將得到四個方形。第二次經過該過程得到 16 個方形,第三次得到 64 個方形。增長得很快。方形數目等於 2( 2 + I ) ,其中 I 爲遞歸經過細分過程的次數。
參考前五幅插圖,下圖示意了使用我們的 diamond-square 算法兩次經過數組時發生的情況。
對於第一遍經過 diamond 步時,我們依據四個角的值在數組中心生成一個值。我們平均四個角的值(如果種子值相等則完全沒必要),並加上一個 -1.0 到 1.0 之間的隨機值。在插圖 b 中,新值顯示成黑色,已經存在的點顯示爲灰色。
對於 square 步,我們在相同的範圍內生成隨機值。這一步時有四個棱錐;他們在數組中心相交,這樣我們計算四個 diamond 中心。 diamonds 的角被平均以找出新值的基數。插圖 C 用黑色顯示新值,現存值爲灰色。
以上是第一遍,如果用線將這 9 個點邊起來,就可以得到一個線框的表面,看起來就象:
現在進行第二遍。再次從 diamond 步開始。第二遍與第一遍有兩點不同。首先,我們現在有四人四邊形面不是一個,因此我們得計算四個方面的中心。其次,這是關鍵,生成隨機數的範圍已經被減小 了。因爲例子的緣故,讓我們認爲正在使用一個 H=1.0 的值。這將把我們的隨機數取值範圍將從 (-1.0,1.0) 到 (-0.5,0.5) 。在插圖 D 中,我們這一步計算得到的四個正方形中心值顯示爲黑色。
最後,我們進行第二遍的 square 步。有 12 個棱錐中心,我們現在需要計算 12 個新值,如圖 e 中黑色所示。
現在數組中全部 25 個元素都已經生成。我們可以得到如下的線框曲面。
如果分配更大的數組,我們可以進行更多遍,每一遍加入更多細節。例如, 5 遍之後表面看起來如下所示:
前面提到過,數組維數需要爲 2 的整數次方加 1 。這是因爲 2D 數組中的浮點數必須等於 (2n+1)2 。 8 次迭代將需要一個 257X257 的浮點數組,對於標準的 32 位 IEEE 浮點數來說超過 256K 內存。
好了,它就是這麼大。用 char 取代 floats 會有所幫助。例子程序使用 floats ,但你要真的關注內存使用那麼使用 char 。修改例子使用之使用 -128 到 128 的範圍是很容易的,但別忘了將你生成的值限定在 -128 到 128 範圍裏,子序列通過時會生成該範圍以外的值,這會導致溢出。這在使用較小的 H 時尤其可能。
例子程序演示了處理尺寸的另外一種方法。用一個大數組依據 diamond-square 算法進行定位及拼嵌。然後從平行投影體頂視圖進行繪製。這個圖像被讀回來並用作已經拼嵌成較小範圍的第二個數組上的紋理圖。然而,例子程序並沒有這樣做, 一但圖像從幀緩衝讀回,第一個數組就被釋放了。
這有個紋理圖的例子:
該圖經過人工着色,山峯爲白色,山谷爲綠色,兩者之間爲灰色。儘管利用例子程序源碼試試自己的配色方案。
早先還到過用迭代實現這個例程比遞歸好。原因是:一個遞歸實現可能翻採用如下形式:
執行 diamond 步;
執行 square 步;
減小隨機數範圍
調用自己四次。
這是個很簡潔的實現,而且毫無疑問它能工作。但它要求用不充足的數據生成某些點。爲什麼呢?經過第一遍之後,你將再次調用以執行 square 步,但這時並沒有一個棱錐四個角的全部數據。
與之相反,我用個迭代實現,僞碼如下:
當 square 邊長度大於 0 時{
遍歷數組,對每個正方形表達執行 diamond 步
遍歷數組,對每個棱錐表達執行 diamond 步
減小隨機數範圍
}
這樣就消除了遞歸實現中出現的棱錐角丟失問題。但在生成數組邊界的點時還是會碰到這個問題。下圖中,數組中構成棱錐角的位置用亮灰色表示。它們應該被平均以找出新的基本值,即圖中黑色的點。
注意用黑色標記的兩個值。它們實際上是相同的值。每次你在 square 步計算一個邊界上的值時,記得同時把它保存在數組對面邊上。
這意味着前面插圖 e 中,我們實際上不必計算 12 個單獨的值,因爲其中的四個在數組相對的兩條邊上重複。實際上只有 8 個值需要計算。
感興趣的讀都可以練習一下:取出源代碼並使用它在邊界的值不重複時也能工作。這對算法正常工作是沒有必要的,按我寫的方式去做就成。
如果你還沒運行過例子程序,現在或許是時候打開看看了。它從兩次迭代生成的曲面開始。曲面是用線框繪製的,只是簡單的將數組中的值用線段邊接起來。 數組中的值被當作 Y 值,而 X 和 Z 座標是在數組分析時即時生成的。通過將一個方形分成兩個三角形可以輕易的使用三角形繪製出這個曲面。三角形通常都是很好的圖元,因爲它總是凸性的平面。
用 View Opeions 對話框調節 RAndom seed 值。這會導致生成不同的曲面。調高 iterations 值給曲面增加細節。代碼限制這個值到 10 ,這對於我 32M 內存的 PentiumPro 系統有點多,看起來是黑的(或許五年以後,人們會在新的處理器和更高分辨率的顯示器上運行這個程序,會對我爲什麼將它限制到 10 十分奇怪的……)。
第一個 H 值控制表面粗糙度,默認值爲 0.7 。試着設得更高或更低看看結果如何。
是的這個算法偶爾會產生局部尖刺或皺摺。但我偏愛尖或皺摺不明顯依賴於觀察角度或飛過它的速度這種超自然的效果
藍天白雲
現在我們知道如何生成表面。我們可以生成並着色大量的三角形,也可以生成一個高分辨率的紋理圖並將它應用到低分辯率的表面。不管怎樣,效果相當好。那麼,我們怎麼生成頭上的天空呢?它比你想象得要簡單。
diamond-square 算法拼嵌完成的數組非常適於表示雲天的紋理圖。與把數組看作一套高度圖上的 y 值相反,把它看成雲的不透明度數據。最小數組值代表最藍��天空中最晰的部分,最大的值代表最白,天空中雲最重的部分。
分析數組並生成如下的紋理圖是相當瑣碎的:
這與前面的高度圖很象,但我已經限定了高、低值以建立清晰有云的天空。
也可以用例子程序生成一幅類似的圖像。設置 Select rendering type 下拉菜單爲 2D mesh/clouds 。(默認時看起來有像素感,試試把 Cloud iterations 值設爲 8 以上修正之)。試試給這 H 賦不同的值(就是前面剛說過的 Cloud iterations 值),以取得不同的雲的效果。
如果回到本文開頭,第一幅圖結合了許多我在這做的討論。天空是用一個如上的紋理圖作的,沿一個八邊金字塔重複排放多次曲面幾何體用一個高分辯率紋理圖繪製。這個紋理圖是通過從一個平行頂視圖着色一個高度拼嵌有光照曲面而生成的。然後,這個圖被讀出用作紋理圖。
跟隨本文的例子程序被用於生成本文中出現的幾乎所有圖像。
其它算法
可能會想對曲面生成有比樣本代碼更多的控制。例如,可能想用自己的值給數組的前幾遍初始化種子值,這樣,山、谷……可以基本位於你設計的位置。然後用 diamon-square 算法填寫其它細節。
修改代碼,使之在賦值時略過已有值的數組成員是易於完成的。初始化數組爲,例如, -10.0 ,給前幾遍指定自己的值作爲種子,再增強分形生成代碼只給當前值爲 -10.0 的賦值。簡幾遍將不會生成任何值,因爲你的種子值已經在那兒了。後續幾遍將在種子值在基礎上生成新值。
如何取得種子值呢?如果想要的形狀遵循某個已知的數學公式,如正弦曲線,就用這個函數生成值。否則,你得找出創造性的方法完成。我見過的一種算法是用灰度值繪製自己的高度圖。將灰度映射成高度值並存入數組。再用 diamond-square 算法增加更多細節。
除了 diamond-square 算法,還有許多拼嵌表面的方法。
用連續隨機增加, 2D 數組的一個隨機部分增高一個很少的量。反覆多次,對所選中的數組區域加上一個很小的隨機值。這可以生成相當不錯的結果,但不是線性計算的。如果計算時間無所謂,那麼建議試試這個算法。
一另一個相似的方法,製造一個穿過數組的"折斷",並增加其中的一邊,就象地震出現一樣。再重複多次。這也不是一個線性算法,要多遍才能取得可以接受的結果。
參考參考文獻以瞭解更多其它途徑。
第二部分 關於例子源碼
安裝
這個例子源碼放在一個 zip 文件中,用你慣用的解壓縮軟件打開。如果你沒有 zip 工具,試試 www.pkware.com 。
源碼使用 OpenGL API 繪製。如果你的機器上沒有則請下載一個。注意本程序認 SGI 實現的 OpenGL ,注意文件名。
快速起步
啓動 Fractal 例子程序
打開 View Options 對話框
從 Select render type 下拉菜單選 2D mesh/renderd
將 Interations 值改爲 4
將 Tile 值改爲 3 。
使用程序
自己試試就知道了。
代碼結構
fractmod.c 和 fractmod.h 是本例子程序的核心 C 代碼。它構成分形生成模塊。
CFractalExampleView 類是從 1996.12 期 M$ Journal: http://www.microsoft.com/msj 發現的 COpenGLView 類衍生的。 COpenGLView 類由 Ron Fosner 寫成,他說這是他 fully-blown COpenGLView 類的精簡版。要真正看懂,去買他那本由 Addison-Welsley 出版書: OpenGL prgramming for windows95 and windows nt 。
COpenGLView 有一個 RenderScene 虛成員,我們在 CFractalExampleView 裏重載之。這裏將完成主要的繪製工作。函數先檢查 Rendering type 的設定。當設爲 2D mesh/lines or 1D midpoint displacement 時,工作由 RenderScene 完成。否則,別的函數被調用。
CFractalExampleViewe::OnViewDialog 生成 View Options 對話框,並處理設置及在 CFractalExampleView 與對話框類間提取數據。
CFractalExampleView::OnInitialUpdate 管理設置所有 CFractalExamleView 成員變量爲其默認值(含對話框值)。
實際上關於代碼是如何工作的並沒有太多可解釋的地方。我假定你是一個能幹的程序員,且我已經盡力給代碼加上詳細的註釋。如果你不熟悉 OpenGL ,注意 gl 開頭的函數都是 OpenGL API 調用,詳見 VC++ 的幫助文檔或 Blue Book 。
有一個特性是剛開始加進代碼中的。在 FractalExampleView.cpp 文件中,有一個名爲 DEF_HIGHT_VALUE 的預處理常量。它傳給 fractmod.c 文件中的分形生成函數,用來縮放高度值。其實它應該是由 View Options 對話框控制的變量。儘管加上這個特性好了。
關鍵代碼:
1、Square步的端點值平均算法:見核心代碼文件
2、Diamond步的端點值平均算法:見核心代碼文件
3、使用Square-Diamond算法進行分形數組填充:見核心代碼文件
4、地形排列的邊界拼接算法
z = -1.f * (float) (tile-1); for (i=0; i<tile; i++) { x = -1.f * (float) (tile-1); for (j=0; j<tile; j++) { glPushMatrix (); glTranslatef (x, 0.f, z); draw2DFractArrayAsLines (surfFA, size); glPopMatrix (); x += 2.f; } z += 2.f; }
5、地形繪製
bool RenderScene() { int size = 1 << iterations; GLfloat black[4] = {0.f, 0.f, 0.f, 1.f}; GLfloat white[4] = {1.f, 1.f, 1.f, 1.f}; // Handle inverted colors. if (colorInvert) glClearColor (black[0], black[1], black[2], black[3]); else glClearColor (white[0], white[1], white[2], white[3]); // Handle non line mode renderMode settings. if (renderMode == twoDR_clouds) return (renderCloudMap ()); else if (renderMode == twoDR_teximage) return (renderTeximageMap ()); else if (renderMode == twoDR_rendered) return (renderFullImage ()); // Shouldn't take too long, unless drawing huge // AA scenes, but what the heck... CWaitCursor hourglass; glMatrixMode (GL_PROJECTION); glLoadIdentity (); if (arrayIs2D) // Needs to be identical to the same call in renderFullScene. gluPerspective (60., aspectRatio, .1, 15.0); else glOrtho (-aspectRatio, aspectRatio, -1., 1., 1., -1.); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); if (arrayIs2D) { glTranslatef (0.f, yTrans, zTrans); glRotatef (yRot, 0.f, 1.f, 0.f); } glPushAttrib (0xffffffff); glDisable (GL_DEPTH_TEST); glDisable (GL_LIGHTING); if (colorInvert) glColor4fv (white); else glColor4fv (black); if (aaLines) { glEnable (GL_LINE_SMOOTH); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable (GL_LINE_SMOOTH); glDisable (GL_BLEND); } glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (arrayIs2D) { float x, z; UINT i, j; // validate surface if (surfFA && surfFAdirty) { freeFractArray (surfFA); surfFA = NULL; } if (!surfFA) { surfFA = alloc2DFractArray (size); if (surfFA==NULL) return (FALSE); fill2DFractArray (surfFA, size, randomSeed, DEF_HEIGHT_SCALE, surfaceH); surfFAdirty = FALSE; } z = -1.f * (float) (tile-1); for (i=0; i<tile; i++) { x = -1.f * (float) (tile-1); for (j=0; j<tile; j++) { glPushMatrix (); glTranslatef (x, 0.f, z); draw2DFractArrayAsLines (surfFA, size); glPopMatrix (); x += 2.f; } z += 2.f; } } else { float x; UINT i; float *fa; // Always validate and free 1D array. // We won't be doing any realtime transformations on it. fa = alloc1DFractArray (size); if (fa==NULL) return (FALSE); fill1DFractArray (fa, size, randomSeed, DEF_HEIGHT_SCALE, surfaceH); x = -1.f * (float) (tile-1); for (i=0; i<tile; i++) { glPushMatrix (); glTranslatef (x, 0.f, 0.f); draw1DFractArrayAsLines (fa, size); glPopMatrix (); x += 2.f; } freeFractArray (fa); } glFlush(); glPopAttrib (); return (TRUE); }
6、紋理
bool renderTeximageMap () { CWaitCursor hourglass; int size = 1 << teximageIter; UINT pmapDim, smallDim; if (teximageFA && teximageFAdirty) { freeFractArray (teximageFA); teximageFA = NULL; } if (!teximageFA) { teximageFA = alloc2DFractArray (size); if (teximageFA==NULL) return (FALSE); fill2DFractArray (teximageFA, size, randomSeed, DEF_HEIGHT_SCALE, teximageH); } // Find the biggest power of two that will fit // in the current window. This is where we will // draw the image. smallDim = (currentWidth > currentHeight) ? currentHeight : currentWidth; pmapDim = 1 << (UINT) (log ((float) smallDim) / log (2.)); glPushAttrib (0xfffffff); // Only draw into biggest power of 2 square. glViewport (0, 0, pmapDim, pmapDim); glMatrixMode (GL_PROJECTION); glLoadIdentity (); glOrtho (-1.f, 1.f, -1.f, 1.f, 1., -1.); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); // The array is rendered into the XZ plane. We need to // rotate by 90 degrees so we will be looking at it // "top down". glRotatef (90.f, 1.f, 0.f, 0.f); // Init lights now. Light position must be specified // after the rotate, or else it won't be rotated. initLights (); glDisable (GL_DEPTH_TEST); glDisable (GL_FOG); glEnable (GL_LIGHTING); glDisable (GL_BLEND); glClear (GL_COLOR_BUFFER_BIT); renderAsTriangles (teximageFA, size, 1, 0); glFlush (); glPopAttrib (); if (teximageFAdirty) { GLubyte *pmap; pmap = (GLubyte *) malloc (pmapDim*pmapDim*3); if (pmap==NULL) return (FALSE); glReadPixels (0, 0, pmapDim, pmapDim, GL_RGB, GL_UNSIGNED_BYTE, pmap); glNewList (TEXIMAGEMAP, GL_COMPILE); glTexImage2D (GL_TEXTURE_2D, 0, /* lod */ 3, /* num components */ pmapDim, pmapDim, /* width, height */ 0, /* border */ GL_RGB, GL_UNSIGNED_BYTE, /* format, type */ pmap); glEndList (); free (pmap); } teximageFAdirty = FALSE; return (TRUE); }
7、雲
bool renderCloudMap () { CWaitCursor hourglass; int size = 1 << cloudIter; if (cloudFA && cloudFAdirty) { freeFractArray (cloudFA); cloudFA = NULL; } if (!cloudFA) { cloudFA = alloc2DFractArray (size); if (cloudFA==NULL) return (FALSE); fill2DFractArray (cloudFA, size, randomSeed, DEF_HEIGHT_SCALE, cloudH); } glPushAttrib (0xfffffff); if (cloudFAdirty) { GLubyte *tmap; tmap = (GLubyte *) malloc (size*size*3); if (tmap==NULL) return (FALSE); surfToSky (cloudFA, size, tmap); glNewList (CLOUDMAP, GL_COMPILE); glTexImage2D (GL_TEXTURE_2D, 0, /* lod */ 3, /* num components */ size, size, /* width, height */ 0, /* border */ GL_RGB, GL_UNSIGNED_BYTE, /* format, type */ tmap); glEndList (); free (tmap); } glCallList (CLOUDMAP); glMatrixMode (GL_PROJECTION); glLoadIdentity (); if (currentWidth > currentHeight) glOrtho (-1.f, aspectRatio*2.f-1.f, -1.f, 1.f, 1., -1.); else glOrtho (-1.f, 1.f, -1.f, 2.f/aspectRatio-1.f, 1., -1.); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); glDisable (GL_DEPTH_TEST); glDisable (GL_FOG); glDisable (GL_LIGHTING); glDisable (GL_BLEND); glEnable (GL_TEXTURE_2D); glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, (GLfloat) GL_REPLACE); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat) GL_NEAREST); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat) GL_NEAREST); glClear (GL_COLOR_BUFFER_BIT); glBegin (GL_QUADS); { glTexCoord2f (0.f, 0.f); glVertex2f (-1.f, -1.f); glTexCoord2f (1.f, 0.f); glVertex2f (1.f, -1.f); glTexCoord2f (1.f, 1.f); glVertex2f (1.f, 1.f); glTexCoord2f (0.f, 1.f); glVertex2f (-1.f, 1.f); } glEnd (); glFlush (); glPopAttrib (); cloudFAdirty = FALSE; return (TRUE); }
分形計算核心代碼:(fractmod.c)
/* Written by: Paul E. Martz Copyright 1997 by Paul E. Martz, all right reserved Non-commercial use by individuals is permitted. 中文註釋:品雪 [email protected] 1999.8. */ #include <stdio.h> #include <stdlib.h> #include "math.h" #include "fractmod.h" #ifdef __cplusplus extern "C" { #endif #ifdef _WINDOWS /* 爲HPUX而寫,控制Win32編譯*/ #define random() rand() #define srandom(x) srand(x) #endif /* 隨機返回一個(min,max)之間的值。 (任意一個範圍被分爲32767個值。) */ static float randnum (float min, float max) { int r; float x; r = random (); x = (float)(r & 0x7fff) / (float)0x7fff; return (x * (max - min) + min); } /* 定義randnum的簡化界面 */ #define fractRand(v) randnum (-v, v) /* 計算數組中中點i所在線段端點值的平均值。 * * Called by fill1DFractArray. */ static float avgEndpoints (int i, int stride, float *fa) { return ((float) (fa[i-stride] + fa[i+stride]) * .5f); } /* 計算棱錐四角的平均值,(i,j)爲棱錐中心點, stride爲中心到角的縱橫間隔, size是縱橫橫向點個數 由於是正方形,縱橫間隔及兩方向點均相等。 注意,size爲縱橫向頂點數,而subSize爲線段數,均代表數組維數。 size=subSize+1; Called by fill2DFractArray. */ static float avgDiamondVals (int i, int j, int stride, int size, int subSize, float *fa) { /* 爲支持排列表面無縫拼接,當(i,j)處於數組的某條邊時要特別注意。 下面頭四條if語句處理這種情。最後的else處理一般情況(i,j不在邊界上)。*/ if (i == 0) return ((float) (fa[(i*size) + j-stride] + //j-strid fa[(i*size) + j+stride] + //j+strid fa[((subSize-stride)*size) + j] + //i-strid,不存在,用對邊上的頂點 fa[((i+stride)*size) + j]) * .25f);//i+strid else if (i == size-1) return ((float) (fa[(i*size) + j-stride] + //j-strid fa[(i*size) + j+stride] + //j+strid fa[((i-stride)*size) + j] + //i-strid fa[((0+stride)*size) + j]) * .25f);//i+strid,不存在,用對邊上的頂點 else if (j == 0) return ((float) (fa[((i-stride)*size) + j] + fa[((i+stride)*size) + j] + fa[(i*size) + j+stride] + fa[(i*size) + subSize-stride]) * .25f); else if (j == size-1) return ((float) (fa[((i-stride)*size) + j] + fa[((i+stride)*size) + j] + fa[(i*size) + j-stride] + fa[(i*size) + 0+stride]) * .25f); else return ((float) (fa[((i-stride)*size) + j] + //i-strid fa[((i+stride)*size) + j] + //i+strid fa[(i*size) + j-stride] + //j-strid fa[(i*size) + j+stride]) * .25f); //j+strid } /* 平均 中心位於(i,j)的正方形四角的數據值 stride爲正方形邊長度。 X.X .*. X.X 與棱錐步不同,這次不會有邊界的問題。 Called by fill2DFractArray. */ static float avgSquareVals (int i, int j, int stride, int size, float *fa) { return ((float) (fa[((i-stride)*size) + j-stride] + fa[((i-stride)*size) + j+stride] + fa[((i+stride)*size) + j-stride] + fa[((i+stride)*size) + j+stride]) * .25f); } /*判斷size是否爲2的次方,是則返回1,不是或爲0則返回0。*/ static int powerOf2 (int size) { int i, bitcount = 0; /* 下面的代碼假定sizeof(int)*8可以算出一個整型數的位數。 適用於多數平臺 */ for (i=0; i<sizeof(int)*8; i++) if (size & (1<<i)) bitcount++; if (bitcount == 1) /* 注意2的整次方數只會有一個位爲1 */ return (1); else return (0); } /*拼嵌一數組的值爲近似分形布朗運動 */ void fill1DFractArray (float *fa, int size, int seedValue, float heightScale, float h) { int i; int stride; int subSize; float ratio, scale; if (!powerOf2(size) || (size==1)) { return; } subSize = size; size++; srandom (seedValue); /* 設定我們的粗糙度常量 隨機數始終在0.0到1.0範圍內生成。 隨機數將乘上scale,每次迭代後scale將乘上ratio以有效的減少隨機數範圍。*/ ratio = (float) pow (2.,-h); scale = heightScale * ratio; /* 在數組中播種端點。要實現無縫拼接,這些端點相同*/ stride = subSize / 2; fa[0] = fa[subSize] = 0.f; while (stride) { for (i=stride; i<subSize; i+=stride) { fa[i] = scale * fractRand (.5f) + avgEndpoints (i, stride, fa); /* reduce random number range */ scale *= ratio; i+=stride; } stride >>= 1; } } /*用diamond-square算法拼嵌一個浮點數網格填充二維分形高度圖*/ void fill2DFractArray (float *fa, int size, int seedValue, float heightScale, float h) { int i, j; int stride; int oddline; int subSize; float ratio, scale; //只處理維數爲2的整數次方的數組 if (!powerOf2(size) || (size==1)) { return; } /* subSize 以縱橫向線段數計的數組維數 segments 以頂點數計的數組維數 */ subSize = size; size++; /* initialize random number generator */ srandom (seedValue); /* 設定我們的粗糙度常量 隨機數始終在0.0到1.0範圍內生成。 隨機數將乘上scale,每次迭代後scale將乘上ratio以有效的減少隨機數範圍。*/ ratio = (float) pow (2.,-h); scale = heightScale * ratio; /* 設置前四個種子值。如一個4X4數組,我們將初始化下邊*代表的點: * . . . * . . . . . . . . . . . . . . . * . . . * 按diamond-square算法的術語,這將給我們正方形。 數組四角賦相同的值,這使我們排列數組時能無縫的拼接起來*/ stride = subSize / 2; fa[(0*size)+0] = fa[(subSize*size)+0] = fa[(subSize*size)+subSize] = fa[(0*size)+subSize] = 0.f; /*現在依據棱錐種子值遞增加入細節。 根據stride循環,每次循環末尾都會將stride減半, 直到stride爲0時結束 */ while (stride) { /* 先用square數據生成diamond數據。 第一遍經過4X4矩陣時,已有數據爲下邊的X, 我們要生成*處的數據: X . . . X . . . . . . . * . . . . . . . X . . . X */ for (i=stride; i<subSize; i+=stride) { for (j=stride; j<subSize; j+=stride) { fa[(i * size) + j] = scale * fractRand (.5f) + avgSquareVals (i, j, stride, size, fa); j += stride; } i += stride; } /* 先用diamond數據生成square數據。 第一遍經過這段代碼時,已有數據爲下邊的X, 我們要生成*處的數據: X . * . X . . . . . * . X . * . . . . . X . * . X i,j代表我們在數組中的(x,y)位置。我們想生成的第一個值位於(i=2,j=0), 用"oddline"及"stride"來增加j到所需值。 */ oddline = 0; for (i=0; i<subSize; i+=stride) { oddline = (oddline == 0); for (j=0; j<subSize; j+=stride) { if ((oddline) && !j) j+=stride; /* i and j are setup. Call avgDiamondVals with the current position. It will return the average of the surrounding diamond data points. */ fa[(i * size) + j] = scale * fractRand (.5f) + avgDiamondVals (i, j, stride, size, subSize, fa); /* To wrap edges seamlessly, copy edge values around to other side of array */ if (i==0) fa[(subSize*size) + j] = fa[(i * size) + j]; if (j==0) fa[(i*size) + subSize] = fa[(i * size) + j]; j+=stride; } } /* reduce random number range. */ scale *= ratio; stride >>= 1; } } /* * alloc1DFractArray - Allocate float-sized data points for a 1D strip * containing size line segments. */ float *alloc1DFractArray (int size) { /* Increment size (see comment in alloc2DFractArray, below, for an explanation). */ size++; return ((float *) malloc (sizeof(float) * size)); } /* * alloc2DFractArray - Allocate float-sized data points for a sizeXsize * mesh. */ float *alloc2DFractArray (int size) { /* For a sizeXsize array, we need (size+1)X(size+1) space. For example, a 2x2 mesh needs 3x3=9 data points: * * * * * * * * * To account for this, increment 'size'. */ size++; return ((float *) malloc (sizeof(float) * size * size)); } /* * freeFractArray - Takes a pointer to float and frees it. Can be used * to free data that was allocated by either alloc1DFractArray or * alloc2DFractArray. */ void freeFractArray (float *fa) { free (fa); } extern void draw2DLine (float, float, float, float); /* 注意,要使用draw1DFractArrayAsLines將數組繪製成線段要先自己定義 四個參數的draw2DLine,參數爲兩個端點的x,y座標。*/ void draw1DFractArrayAsLines (float *fa, int size) { int i; float x, inc; inc = 2.f / size; x = -1.f; for (i=0; i<size; i++) { draw2DLine (x, fa[i], x+inc, fa[i+1]); x += inc; } } /* 注意,要使用draw2DFractArrayAsLines將數組繪製成網格要先自己定義 六個參數的draw3DLine,參數爲兩個端點的x,y,z座標。*/ void draw2DFractArrayAsLines (float *fa, int size) { extern void draw3DLine (float, float, float, float, float, float); int i, j; float x, z, inc; const int subSize = size; size++; inc = 2.f / subSize; z = -1.f; for (i=0; i<size; i++) { x = -1.f; for (j=0; j<subSize; j++) { draw3DLine (x, fa[(i*size)+j], z, x+inc, fa[(i*size+j+1)], z); if (i<subSize) draw3DLine (x, fa[(i*size)+j], z, x, fa[((i+1)*size+j)], z+inc); x += inc; } if (i<subSize) draw3DLine (x, fa[(i*size)+j], z, x, fa[((i+1)*size+j)], z+inc); z += inc; } } /*法向計算,參數爲三個頂點矢量,其叉乘積就是平面法向*/ static void genNormal (float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, float *normal) { float len; float v1x, v1y, v1z; float v2x, v2y, v2z; v1x = x2 - x1; v1y = y2 - y1; v1z = z2 - z1; v2x = x3 - x1; v2y = y3 - y1; v2z = z3 - z1; normal[0] = v1y*v2z - v1z*v2y; normal[1] = v1z*v2x - v1x*v2z; normal[2] = v1x*v2y - v1y*v2x; len = (float) sqrt (normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]); normal[0] /= len; normal[1] /= len; normal[2] /= len; } /* 用一系列帶有相應法向的三角形繪製高度圖 這是個簡化的例程以便達到目的並快速運行,並演示瞭如何遍歷一個數組。 要使用這個例程,你必需自己定義"draw3DTriangle"函數。 它有12個參數:頭9個是三個頂點的x,y,z世界座標, 最後3個參數是單位長度面法向的x,y,z笛卡爾值。 x,z座標會沿兩軸覆蓋-1.0到1.0的網格。相應的Y座標將從分形數組中取出。 法向量將參考"上"或+y方向生成,頂點順序應該使用右手法則與法向保持一致。 (右手法則:從法向量所指方向上看,頂點按逆時針排列) */ void draw2DFractArrayAsTriangles (float *fa, int size) { extern void draw3DTriangle (float, float, float, float, float, float, float, float, float, float, float, float); int i, j; float x, z, inc; const int subSize = size; size++; inc = 2.f / subSize; z = -1.f; for (i=0; i<subSize; i++) { x = -1.f; for (j=0; j<subSize; j++) { float normal[3]; genNormal (x, fa[(i*size)+j], z, x, fa[((i+1)*size)+j], z+inc, x+inc, fa[(i*size+j+1)], z, normal); draw3DTriangle (x, fa[(i*size)+j], z, x, fa[((i+1)*size)+j], z+inc, x+inc, fa[(i*size+j+1)], z, normal[0], normal[1], normal[2]); genNormal (x, fa[((i+1)*size)+j], z+inc, x+inc, fa[((i+1)*size)+j+1], z+inc, x+inc, fa[(i*size+j+1)], z, normal); draw3DTriangle (x, fa[((i+1)*size)+j], z+inc, x+inc, fa[((i+1)*size)+j+1], z+inc, x+inc, fa[(i*size+j+1)], z, normal[0], normal[1], normal[2]); x += inc; } z += inc; } } #ifdef __cplusplus } #endif
參 考
- Miller, Gavin S. P., The Definition and Rendering of Terrain Maps. SIGGRAPH 1986 Conference Proceedings (Computer Graphics, Volume 20, Number 4, August 1986).
- Voss, Richard D., FRACTALS in NATURE: characterization, measurement, and simulation. SIGGRAPH 1987 course notes #15.
- Peitgen, Jurgens, and Saupe, Chaos and Fractals, New Frontiers of Science. Springer-Verlag, 1992.
- Fournier, A., Fussell, D., Carpenter, L., Computer Rendering of Stochastic Models, Communications of the ACM, June 1982
-
轉自http://www.cnblogs.com/sunliming/archive/2011/07/17/2108917.html