再談紋理

前篇文章《可視化編程中的幾個基礎概念及我的理解》很是簡單的記錄了一下本身對紋理等相關概念的理解。這裏,對其在作進一步的挖掘,從osg的源碼提及。html

:所謂紋理,也即圖片,說到底不過一堆數字的組合。紋理、圖片等從本質上講均可以看作數據的一種存儲格式而已。既然紋理是數據,那麼它就有大小,正負等數據特徵。
因此,見到紋理的時候,並不能單純的認爲其只是存儲圖像特徵(圖像數據特徵是其元素格式通常爲rgb、rg、g等通道模式,顏色通常取值爲0-1或者0-255),而應當把它當作數據看待,也就說紋理採樣值(紋素)能夠有負值!!編程

再談glTexImage2D()

該函數在osg或者opengl編程中有着很是重要的做用--從cpu向gpu傳送數據,函數原型:segmentfault

void glTexImage2D(    GLenum target,
     GLint level,
     GLint internalFormat,
     GLsizei width,
     GLsizei height,
     GLint border,
     GLenum format,
     GLenum type,
     const GLvoid * data);

其中參數:target、internalFormat、format和type對紋理的讀取採樣等操做有着很是重要的影響。
target:指定所綁定的紋理單元。
internalFormat:根據數據自己的特色,應用程序指定讀取該紋理數據的分辨率及格式。
format:也能夠表示爲pixelFormat,指定紋理在內存中的存貯格式。
type:紋素的數據類型。函數

應用程序主要經過這幾個參數決定了如何向GPU傳送數據以及傳送什麼樣的數據。
例如:某紋理數據爲4通道,且每一個紋素的單個通道所佔內存爲32字節,數據類型爲float型,則該紋理數據的internalFormat的值應該爲:GL_RGBA32F,該紋理數據在內存中的基本存儲格式應該爲GL_RGBA,type對應的值應該爲GL_FLOAT.佈局

初學opengl或者osg時,可能並沒意識到該函數的重要做用,一直採樣默認方式,進而致使項目遲滯!spa

OSG紋理

這裏並不打算記錄OSG如何讀取紋理、綁定紋理、設置採樣方式、添加狀態等設置,只是單單從源碼看紋理,以及OSG默認紋理設置給本身帶來的坑,並最後修改OSG設置紋理的源碼,以實現本身的目的。code

利用osgDB::readImageFIle();將紋理數據從硬盤讀入內存以後,可直接將其添加到osg::texture2D對象,就基本完成了將圖片轉換爲紋理的過程。這一過程看似簡單,OSG卻默默的作了不少不少鮮爲人知的事情,其中最重要的就是設置glTexImage2D()函數。orm

osg相似opengl,也是一個大的狀態機,雖然在主程序(每一幀繪製前)中作了各類各樣狀態的設定,可是真正起做用的時候倒是在每一幀的繪製過程當中完成的。xml

讀入紋理以後,直接定位函數glTexIamge2D()的源碼,在osg源碼的Texture.cpp文件中:htm

glTexImage2D( target, 0, _internalFormat,
                inwidth, inheight, _borderWidth,
                (GLenum)image->getPixelFormat(),
                (GLenum)image->getDataType(),
                dataPtr);

能夠看到,該函數的format和type參數是由讀入紋理image決定的。再看該源碼中另外兩個比較重要的參數inwidth,inheight,這兩個參數並無用image->s()image->r(),也就是說,該函數**向GPU傳送數據的時候並不必定是傳送的原始圖像(紋理)的大小!!**,事實也是確實如此!!

例如:讀入一份四通道的tiff數據,數據類型是float類型,該紋理像素爲3600*1720,使用在不修改源碼的狀況下對該數據採樣如圖;

在終端輸出以下提示:Scaling image 11.tif from (3600,1720) to (4096,2048)
當用ps軟件將該tiff圖像的分辨率調節爲4096*2048時,輸出入下:

得出結論:OSG加載紋理,並將紋理傳送到GPU的過程當中,默認對紋理進行了縮放。其實不僅是縮放,還對紋理進行了歸一化處理--將紋素轉換爲 0-1 之間的數據!!
上述的縮放一般並非咱們想要的!

實際追蹤源碼並添加監視可知,參數inwidth, inheight,的值並非原始圖像數據的分辨率,而是與該分辨率數據( 3600 * 1720)最接近的2的冪次方數據(4096 * 2048)。

那麼到底爲何會轉化爲(4096*2048)呢,緣由是:使用osg接口讀入數據並加載紋理的時候,osg自動默認對紋理的分辨率進行了計算,並將非標準紋理(分辨率不是2的冪次方)線性變換爲標準紋理格式,以提升紋理採樣效率。

究竟是那個函數如此"自做聰明"的執行了「縮放」處理呢?!追溯OSG源碼發現,若是讀入的紋理數據分辨率不是標準格式,那麼osg會自動調用函數:

gluScaleImage(&psm, image->getPixelFormat(),
                           image->s(),image->t(),image->getDataType(),image->data(),
                           inwidth,inheight,image->getDataType(),
                           dataPtr)

這「該死」的gluScaleImage函數不只對紋理進行了標準化的縮放,還進行了歸一化處理---將紋素數據轉換爲0-1之間!!

至此,對OSG如何處理紋理有了一個最基本認識。

再談 texture()

首先,texture()是glsl內置函數,主要用來採樣紋理。

上篇文章說道:從綁定到sampler2D當前紋理中做紋理查找(不考慮陰影及比較操做,bias參數暫時不考慮),texture函數的返回值是一個顏色值,也就說返回的vec4對象的各個元素值應該在0~1之間,其實並不算準確。它只是適用於單純的顏色圖片採樣,若採樣的紋理是數據時(如tiff保存的地形數據),用osg紋理貼圖就會出現上圖1所示的狀況。
如上圖1所示,用osg採樣時程序沒有任何錯誤,固然也不會報錯,但採樣結果確定不是正確的,因此也極難排查出可視化效果不理想的緣由。

如今來看texture()函數,能夠確認的是,該函數的返回值不必定是0-1之間,它能夠返回紋理數據自己最原始的數據值(包括負數)。在OSG或者opengl中,glsl的texture()函數返回值可由函數glTexImage()指定;在OSG中,可經過osg::Texture2D的對象和osg::Image的對象共同指定。

在osg中:

image->setPixelFormat(GL_RGBA);  //設置像素的格式(內存中圖像數據的物理佈局)
image->setInternalTextureFormat(GL_RGBA32F_ARB);
image->setDataType(GL_FLOAT);

便可。
緣由:osg中,glTexImage2D()函數調用了image的內部參數,參考上文osg中對glTexImage2D()函數的調用。

上文知道,若是利用osg讀取非標準化的紋理數據,osg會自動調用函數gluScaleImage函數對其進行標準歸一化處理,當texture採樣時其實採樣的已經不是最原始的紋理數據!

到此爲止,一個嚴重問題:若是想對 非標準紋理非歸一化的紋理數據採樣,那應該如何操做?
要解決此問題,目前好像只有修改源碼(若是有其餘方法,請必定留言聯繫,謝謝)。
在osg源碼中,追蹤並搜索gluScaleImage函數,發現這兩個函數都是在一個if(needRescalImage):
圖片描述

將該函數整個註釋掉,而後修改glTexImage函數的參數爲image的寬和高便可,最後從新生成dll庫。這樣,osg便不會自做聰明的對讀入的紋理數據執行gluScaleImage操做。

總結:
一、紋理也是數據,具備數據的通常屬性,如值的大小、正負、存儲方式等,一副普通圖片能夠當作紋理進行採樣,但紋理並不單純的指代圖片。
二、在osg中,若是讀入的是非標準紋理,則osg會默認調用gluScaleImage函數對紋理進行歸一化處理,所謂歸一化是指將數據大小限制在0-1之間,將分辨率調節爲最接近原始圖像分辨率的2的冪次方項。進行歸一化操做時,osg雖然不會報錯,可是會給出提示,相似:Scaling image 紋理路徑 from (3600,1720) to (4096,2048)。此時glsl語言中texture函數採樣的結果已經不是最原始紋理的紋素數據了。三、要想利用osg讀取非標準格式的紋理數據,目前來看好像必須修改源碼,從新生成dll文件。四、修改某開源項目數據源碼時,必定確認本身的程序所調用的dll庫,並最好明確依賴關係,若是庫調用不對,即便修改源碼也不會起做用。

相關文章
相關標籤/搜索