1、深度
所謂深度,就是在openGL坐標系中,像素點Z坐標距離攝像機的距離。攝像機可能放在坐標系的任何位置,那么,就不能簡單的說Z數值越大或越小,就是越靠近攝像機。
2、深度緩沖區
深度緩沖區原理就是把一個距離觀察平面(近裁剪面)的深度值(或距離)與窗口中的每個像素相關聯。
首先,使用glClear(GL_DEPTH_BUFFER_BIT),把所有像素的深度值設置為最大值(一般是遠裁剪面)。
然后,在場景中以任意次序繪制所有物體。硬件或者軟件所執行的圖形計算把每一個繪制表面轉換為窗口上一些像素的集合,此時並不考慮是否被其他物體遮擋。
其次,OpenGL會計算這些表面和觀察平面的距離。如果啟用了深度緩沖區,在繪制每個像素之前,OpenGL會把它的深度值和已經存儲在這個像素的深度值進行比較。新像素深度值<原先像素深度值,則新像素值會取代原先的;反之,新像素值被遮擋,他顏色值和深度將被丟棄。
為了啟動深度緩沖區,必須先啟動它,即glEnable(GL_DEPTH_TEST)。每次繪制場景之前,需要先清除深度緩沖區,即glClear(GL_DEPTH_BUFFER_BIT),然后以任意次序繪制場景中的物體。
數學基礎:
待渲染的照相機空間中的深度經常定義為近距 near 到遠距 far 之間的 z 值,Z坐標和X、Y坐標一樣。在變換、裁減和透視除法后,Z的范圍為-1.0~1.0。DepthRange映射指定Z坐標的變換,這與用於將X和Y映射到窗口坐標的視口變換類似,在透視變換之后,得到新的 z' 值:

其中
是照相機空間的值,它有時候也表示為 w 或者 w'。
結果 z' 是在 -1 到 1 之間歸一化之后的值,其中近距 near 平面位於 -1 處,遠距 far 平面位於 1 處。在這個范圍之外的相應點在視圖體之外,不需要進行渲染。
為了實現深度緩沖,在整個屏幕空間上的對當前多邊形頂點之間進行插值來計算 z' 的值,通常這些中間數值在深度緩沖區中用定點數格式保存。距離近距 near 平面越近,z' 值越密;距離越遠,z' 值越稀。這樣距離照相機越近精度越高。near 平面距離照相機越近,則遠距離位置的精度越低。near 平面距離照相機太近是在遠距離物體產生人為誤差的一個常見因素。
3、深度測試
OpenGL中的深度測試是采用深度緩存器算法,消除場景中的不可見面。在默認情況下,深度緩存中深度值的范圍在0.0到1.0之間,這個范圍值可以通過函數:
glDepthRange (nearNormDepth, farNormalDepth);
將深度值的范圍變為nearNormDepth到farNormalDepth之間。這里nearNormDepth和farNormalDepth可以取0.0到1.0范圍內的任意值,甚至可以讓nearNormDepth > farNormalDepth。這樣,通過glDepthRange函數可以在透視投影有限觀察空間中的任意區域進行深度測試。
另一個非常有用的函數是:
glClearDepth (maxDepth);
參數maxDepth可以是0.0到1.0范圍內的任意值。glClearDepth用maxDepth對深度緩存進行初始化,而默認情況下,深度緩存用1.0進行初始化。由於在進行深度測試中,大於深度緩存初始值的多邊形都不會被繪制,因此glClearDepth函數可以用來加速深度測試處理。這里需要注意的是指定了深度緩存的初始化值之后,應調用:
glClear(GL_DEPTH_BUFFER_BIT); 完成深度緩存的初始化。
在深度測試中,默認情況是將需要繪制的新像素的z值與深度緩沖區中對應位置的z值進行比較,如果比深度緩存中的值小,那么用新像素的顏色值更新幀緩存中對應像素的顏色值。這種比較測試的方式可以通過函數:
glDepthFunc(func);
進行修改。其中參數func的值可以為GL_NEVER(沒有處理)、GL_ALWAYS(處理所有)、GL_LESS(小於)、GL_LEQUAL(小於等於)、GL_EQUAL(等於)、GL_GEQUAL(大於等於)、GL_GREATER(大於)或GL_NOTEQUAL(不等於),其中默認值是GL_LESS。這些測試可以在各種應用中減少深度緩存處理的的計算。
opengl中有一個非常有用的函數:glReadPixels(),可以讀取各種緩沖區(深度、顏色,etc)的數值。要將opengl的繪制場景保存成圖片,也需要使用這個函數。
一個簡單的例子見如下的c程序。按鍵盤上的“C”鍵,可以將讀取的圖像緩沖區數據存儲成tmpcolor.txt。
#include "windows.h"
#include <GL/glut.h>
#include <GL/GLAUX.H>
#include <iostream>
using namespace std;
//
typedef GLbyte* bytePt;
int winWidth = 400;
int winHeight = 400;
int arrLen = winWidth * winHeight * 3;
GLbyte* colorArr = new GLbyte[ arrLen ];
void saveColorData(bytePt& _pt, string& _str) {
FILE* pFile = NULL;
pFile = fopen(_str.c_str(), "wt");
if(!pFile) { fprintf(stderr, "error \n"); exit(-1); }
for(int i=0; i<winWidth * winHeight * 3; i ++) {
if(colorArr[i] == -1) { colorArr[i] = 255; }
}
for(int i=0; i<winWidth * winHeight * 3; i ++) {
fprintf(pFile, "%d\n", colorArr[i]);
}
fclose(pFile);
printf("color data saved! \n");
}
void init() {
glClearColor(0.5, 0.5, 0.5, 0.0);
glShadeModel(GL_SMOOTH);
}
void display() {
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, 1.0, 0.1, 500.0);
glMatrixMode(GL_MODELVIEW);
glColor3f(1.0, 0.0, 0.0);
glBegin(GL_TRIANGLES);
glVertex3f(0.0, 25.0, 0.0);
glVertex3f(-25.0, -25.0, 0.0);
glVertex3f(25.0, -25.0, 0.0);
glEnd();
glFlush();
}
void keyboard(unsigned char key, int x , int y) {
GLint viewPort[4] = {0};
switch(key) {
case 'c':
case 'C':
glGetIntegerv(GL_VIEWPORT, viewPort);
glReadPixels(viewPort[0], viewPort[1], viewPort[2], viewPort[3], GL_RGB, GL_UNSIGNED_BYTE, colorArr);
printf("color data read !\n");
saveColorData(colorArr, (string)"tmpcolor.txt");
default:
break;
}
}
int main(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(200, 200);
glutInitWindowSize(400, 400);
glutCreateWindow(argv[0]);
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutMainLoop();
delete [] colorArr;
return 0;
}
tmpcolor.txt 中將每個像素的顏色按R、G、B順序存放成了一個向量,即(R,G,B,R,G,B,...)。而且,讀取緩沖區時的坐標原點是窗口坐標系的坐標原點(圖片左下角);因此,可以在matlab中通過調整得到原來的圖片:
function test
a = load('tmpcolor.txt');
pos = find(a == -1);
a(pos) = 255;
r = a(1:3:end);
g = a(2:3:end);
b = a(3:3:end);
img = zeros(400,400,3);
img(:,:,1) = reshape(r,[400,400]);
img(:,:,2) = reshape(g,[400,400]);
img(:,:,3) = reshape(b,[400,400]);
img = uint8(img);
tmpR = zeros(400,400); tmpR = img(:,:,1);
tmpG = zeros(400,400); tmpG = img(:,:,2);
tmpB = zeros(400,400); tmpB = img(:,:,3);
for i=1:1:400
img(i,:,1) = tmpR(:,400-i+1);
img(i,:,2) = tmpG(:,400-i+1);
img(i,:,3) = tmpB(:,400-i+1);
end
figure;
imshow(img);
end
當然,讀到數據后,可以直接使用openCV等工具方便地存儲圖片。一段對應的opencv代碼為:
void saveColorData2img(bytePt& _pt, string& _str) {
cv::Mat img;
vector<cv::Mat> imgPlanes;
img.create(winHeight, winWidth, CV_8UC3);
cv::split(img, imgPlanes);
for(int i = 0; i < winHeight; i ++) {
UCHAR* plane0Ptr = imgPlanes[0].ptr<UCHAR>(i);
UCHAR* plane1Ptr = imgPlanes[1].ptr<UCHAR>(i);
UCHAR* plane2Ptr = imgPlanes[2].ptr<UCHAR>(i);
for(int j = 0; j < winWidth; j ++) {
int k = 3 * (i * winWidth + j);
plane2Ptr[j] = _pt[k];
plane1Ptr[j] = _pt[k+1];
plane0Ptr[j] = _pt[k+2];
}
}
cv::merge(imgPlanes, img);
cv::flip(img, img ,0); // !!!
cv::imwrite(_str.c_str(), img);
printf("opencv save opengl img done! \n");
}
需要注意的是,如果想要把集成在MFC中的openGL場景轉存成圖片,因為MFC中的像素格式只支持RGBA和顏色索引,所以 glReadPixels 中需要使用 GL_RGBA 作為參數。對應寫了一個C++類,可供參考:
class glGrabber {
public:
glGrabber();
~glGrabber();
void glGrab();
void saveColorData2Img(string& _str);
private:
GLbyte* colorArr;
GLint viewPort[4];
int winWidth;
int winHeight;
};
//
glGrabber::glGrabber() {
colorArr = NULL;
}
//
glGrabber::~glGrabber() {
if(colorArr!=NULL) { delete [] colorArr; colorArr = NULL; }
}
//
void glGrabber::glGrab() {
glGetIntegerv(GL_VIEWPORT, viewPort);
if(colorArr != NULL) { delete [] colorArr; colorArr = NULL; }
winWidth = viewPort[2];
winHeight = viewPort[3];
colorArr = new GLbyte[ winWidth * winHeight * 4 ]; // MFC的像素格式只支持RGBA
glReadPixels(viewPort[0], viewPort[1], viewPort[2], viewPort[3], GL_RGBA, GL_UNSIGNED_BYTE, colorArr); // RGBA
printf("x: %d, y: %d, window width: %d, window height: %d \n", viewPort[0], viewPort[1], viewPort[2], viewPort[3]);
printf("color data read! \n");
}
//
void glGrabber::saveColorData2Img(string& _str) {
cv::Mat img;
vector<cv::Mat> imgPlanes;
img.create(winHeight, winWidth, CV_8UC3);
cv::split(img, imgPlanes);
for(int i = 0; i < winHeight; i ++) {
UCHAR* plane0Ptr = imgPlanes[0].ptr<UCHAR>(i);
UCHAR* plane1Ptr = imgPlanes[1].ptr<UCHAR>(i);
UCHAR* plane2Ptr = imgPlanes[2].ptr<UCHAR>(i);
for(int j = 0; j < winWidth; j ++) {
int k = 4 * (i * winWidth + j); // RGBA
plane2Ptr[j] = colorArr[k];
plane1Ptr[j] = colorArr[k+1];
plane0Ptr[j] = colorArr[k+2];
}
}
cv::merge(imgPlanes, img);
cv::flip(img, img ,0); // !!!
cv::namedWindow("openglGrab");
cv::imshow("openglGrab", img);
cv::waitKey();
//cv::imwrite(_str.c_str(), img);
printf("opencv save opengl img done! \n");
}