這里不介紹算法原理,只說說在實現過程中遇到的問題,以及背后的原因。開發環境:opengl 2.0 glsl 1.0。
第一個問題:產生深度紋理。
在opengl中每一次離屏渲染需要向opengl提供一個renderframe,一個renderframe包含一個texture和一個renderbuffer.texture是一個存儲特定數據的內存區,可以存儲顏色,深度以及模版。renderbuffer目前不太清楚。
具體代碼如下:
glGenFramebuffers(1, &frameBuff) ; glBindFramebuffer(GL_FRAMEBUFFER, frameBuff) ; glGenTextures(1, &depthTxe) ; glBindTexture(GL_TEXTURE_2D, depthTxe) ; glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16 , mapWidth, mapHight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTxe, 0); glDrawBuffer(GL_NONE) ; glReadBuffer(GL_NONE) ; GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); if( result == GL_FRAMEBUFFER_COMPLETE) { cout << "Framebuffer is complete.\n" << endl ; } else { cout <<"Framebuffer is not complete.\n" << endl ; }
這里需要注意的是,一定要加 glDrawBuffer(GL_NONE) ; glReadBuffer(GL_NONE) 。分別告訴opengl沒有緩沖區接受或者讀取顏色數據。因為我們需要的是深度數據。如果沒有這兩句,那么返回的result是not complete。
曾經嘗試過使用 glTexImage2D 中使用 GL_RGB 而不是 GL_DEPTH_COMPONENT16 相應的下面的 glFramebufferTexture2D 也要使用 GL_COLOR_ATTACHMENT。在fragment shader中將片元的深度值寫入color中。但是這樣有個問題,就是精度不夠,雖然數據是對的,精度在Shadow Map中有着極其重要的位置,稍后介紹。
問題二:各種坐標系的轉化
這里涉及到兩個變化過程。第一個是從物體坐標系-》世界坐標系-》燈光坐標系-》裁剪坐標系(齊次坐標系)-》cvv坐標系(透視除法后得到)。
另一個是物體坐標系-》世界坐標系-》攝像機坐標系-》裁剪坐標系(齊次坐標系)-》cvv坐標系(透視除法后得到)。
具體vertex shader 如下
varying vec3 normal ; varying vec4 lightVertex ; varying vec4 color ; varying vec4 worldCoord ; uniform mat4 lightProj; uniform mat4 lightView; const mat4 biasMatrix = mat4(0.5 , 0.0 , 0.0 , 0.0 , 0.0 , 0.5 , 0.0 , 0.0 , 0.0 , 0.0 , 0.5 , 0.0 , 0.5 , 0.5 , 0.5 , 1.0 ) ; void main() { worldCoord = gl_ModelViewMatrix * gl_Vertex ; normal = normalize(gl_NormalMatrix * gl_Normal); lightVertex = lightProj * lightView * worldCoord ; lightVertex = lightVertex / lightVertex.w ; lightVertex = biasMatrix * lightVertex ; //lightVertex = lightVertex / lightVertex.w ; gl_TexCoord[0] = gl_MultiTexCoord0 ; color = gl_Color ; gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex ; }
這里需要注意的是glsl中沒有提供從物體坐標系到世界坐標系的轉化,gl_ModelViewMatrix實現的是從物體到相機坐標系的轉化,在本次shadow map是實現過程中將一直保證攝像機在世界坐標系的原點位置,方向指向(0,0,-1),向上方向為(0,1,0)。也就是說攝像機和世界坐標系重合。
注意到 biasMatrix,這里可以推知兩點:
(1)glsl中是采用列主序的方式存放矩陣的,也就是數據先放完第一列再放第二列。
(2)glsl紋理的坐標被映射到[0,1]之間。用本次shadow map舉例,從燈光坐標系到齊次坐標系,經過裁剪以及透視除法后,得到的x,y,z的范圍是在[-1,1]之間的,構成一個規范立方體(cvv),那么x , y的值就是紋理在紋理內存中的位置,z就是偽深度,用於z-buffer。但是,opengl在渲染深度紋理時,將x,y,z映射到[0,1]之間,所以要用一個biasMatrix進行平移。
第三個問題:Shadow acne。
這個問題的原因如下圖:
其中,斜線EF是物體表面,當一光柱照射到物體表面時,C點的深度值被寫入shadow map中(這里有一個概念:texle , texture element,可以理解為一個像素)。但是,當從攝像機坐標系中看到D點時,通過坐標系轉化到shadow map中 ,D點比C點的值遠離光源。這樣就造成了shadow acne。效果如下:
這時要加上一個偏移兩bias,具體計算在fragment shader中,其中的m時具體情況具體調整的,時經驗值。
問題四:陰影有鋸齒。
如下圖:
改進方法時使用多次采樣,可以使用采樣次sampler2Dshadow,內部會采樣一個像素周圍的幾個像素。也可以使用本次代碼中的poissonDisk進行優化。效果如下
這個poissonDisk(柏松盤采樣)內容目前不太了解,貌似效果還不錯。
學習心得:在碰到一個新技術時,最好把其最簡單的部分實現,然后看看有什么問題,然后慢慢優化。如果不動手實現,很難體會資料上描述的問題的現象,更不用說問題產生的原因。
一下時全部代碼:
#include <GLUT/GLUT.h> #include <iostream> #include "LoardShaderProgram.h" #include "BW_READBMP.h" using namespace std ; struct COLOR { float r , g , b, w ; } ; float rot = 0.1 ; float transX = 0 ; float transY = 0 ; float transZ = 0 ; GLint program ; GLint lightPosLoc ; GLint lightAmbientLoc ; GLint lightLambertLoc ; GLint lightProjLoc ; GLint lightViewLoc ; GLint mLoc ; GLint ksLoc ; GLint kdLoc ; GLint kaLoc ; GLint texLoc ; GLint tex ; GLint mapWidth = 1024; GLint mapHight = 1024; GLuint depthTxe ; GLuint frameBuff ; GLfloat lightProj[16] ; GLfloat lightView[16] ; float alf = 0 ; float ks = 1; float kd = 0.5 ; float ka = 1.0 ; float m = 0.0035410088 ; COLOR lightPos ; void DrawSence() { glTranslated(0, 0, -8) ; glBegin(GL_QUADS); glColor3f(1, 0, 0) ; // ground //glNormal3f( 0.0f, 0.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glNormal3f(0, 1, 0) ; glVertex3f(4.0f, -2.0f, -4.0f); glNormal3f(0, 1, 0) ; glTexCoord2f(0.0f, 1.0f); glVertex3f( -4.0f, -2.0f, -4.0f); glNormal3f(0, 1, 0) ; glTexCoord2f(1.0f, 1.0f); glVertex3f( -4.0f, -2.0f, 4.0f); glNormal3f(0, 1, 0) ; glTexCoord2f(1.0f, 0.0f); glVertex3f(4.0f, -2.0f, 4.0f); glEnd() ; glTranslated(transX, transY , transZ) ; glRotatef(rot,1.0,0.0,1.0); glutSolidTeapot(1); } void ProcessKeyboard(unsigned char key,int x,int y) { if (key == 'x' || key == 'X') { rot += 0.8; } if (key == 'c' || key == 'C') { m += 0.00001 ; } if (key == 'd' || key == 'D') { m -= 0.00001 ; } if (key == 'u' || key == 'U') { transX += 0.3 ; } if (key == 'i' || key == 'I') { transX -= 0.3 ; } if (key == 'j' || key == 'J') { transY += 0.3 ; } if (key == 'k' || key == 'K') { transY -= 0.3 ; } if (key == 'o' || key == 'O') { transZ += 0.3 ; } if (key == 'p' || key == 'P') { transZ -= 0.3 ; } glutPostRedisplay() ; } void ReSizeScene(int width , int height) { if( height==0 ) { height=1; }//if //設置視口 glViewport(0,0,width,height); //設置透視矩陣 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); //開始對模型矩陣進行操作 glMatrixMode(GL_MODELVIEW); //復位 glLoadIdentity(); } void GenDepthMap() { lightPos.r = 5; lightPos.g = 5; lightPos.b = -5; lightPos.w = 1; glBindFramebuffer(GL_FRAMEBUFFER, frameBuff) ; glDrawBuffer(GL_NONE) ; glViewport(0, 0, mapWidth, mapHight) ; glMatrixMode(GL_PROJECTION) ; glLoadIdentity() ; gluPerspective(90, 4.0/3, 0.1, 100) ; glGetFloatv(GL_PROJECTION_MATRIX, lightProj) ; glMatrixMode(GL_MODELVIEW) ; glLoadIdentity() ; gluLookAt(lightPos.r, lightPos.g, lightPos.b, 0, 0, -10, 0, 1, 0) ; glGetFloatv(GL_MODELVIEW_MATRIX,lightView) ; glDisable(GL_TEXTURE_2D) ; // glUseProgram(shadowPro) ; glClearColor(1.0, 1.0, 1.0, 1.0 ); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) ; DrawSence() ; glUseProgram(0) ; glBindFramebuffer(GL_FRAMEBUFFER, 0) ; glDrawBuffer(GL_FRONT) ; } void ShowSence() { ReSizeScene(400, 300) ; COLOR lightAmbient , lightLambert ; COLOR CameralDir , CameralPos; lightAmbient.r = 0.1;lightAmbient.g = lightAmbient.b = 0.1; lightAmbient.w = 1 ; lightLambert.r = 1 ; lightLambert.g = lightLambert.b = 1 ;lightLambert.w = 1 ; CameralDir.r = 0 ; CameralDir.g = 0 ; CameralDir.b = -1 ; CameralDir.w = 0 ; CameralPos.r = 0 ; CameralPos.g = 0 ; CameralPos.b = 0 ; CameralPos.w = 1 ; ka = 1 ; kd = 0.7 ; ks = 0.4 ; tex = 0 ; glActiveTexture(GL_TEXTURE0) ; glBindTexture(GL_TEXTURE_2D, depthTxe) ; glUseProgram(program) ; glUniform4f(lightPosLoc, lightPos.r, lightPos.g, lightPos.b, lightPos.w) ; glUniform4f(lightAmbientLoc, lightAmbient.r, lightAmbient.g, lightAmbient.b, lightAmbient.w) ; glUniform4f(lightLambertLoc, lightLambert.r, lightLambert.g, lightLambert.b, lightLambert.w) ; glUniformMatrix4fv(lightProjLoc, 1, GL_FALSE, lightProj) ; glUniformMatrix4fv(lightViewLoc, 1, GL_FALSE, lightView) ; glUniform1f(mLoc, m) ; glUniform1f(kdLoc, kd) ; glUniform1f(ksLoc, ks) ; glUniform1f(kaLoc, ka) ; glUniform1i(texLoc , tex) ; gluLookAt(CameralPos.r, CameralPos.g , CameralPos.b, CameralDir.r, CameralDir.g , CameralDir.b, 0 ,1 , 0 ) ; glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) ; DrawSence() ; glUseProgram(0) ; glFlush(); } void display() { GenDepthMap() ; ShowSence() ; } void InitScene() { glClearColor(1.0, 1.0, 0.0, 1.0) ; glGenFramebuffers(1, &frameBuff) ; glBindFramebuffer(GL_FRAMEBUFFER, frameBuff) ; glGenTextures(1, &depthTxe) ; glBindTexture(GL_TEXTURE_2D, depthTxe) ; glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16 , mapWidth, mapHight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTxe, 0); glDrawBuffer(GL_NONE) ; glReadBuffer(GL_NONE) ; GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (result == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER) { cout << "frambuffer GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER " << endl ; } if( result == GL_FRAMEBUFFER_COMPLETE) { cout << "Framebuffer is complete.\n" << endl ; } else { cout <<"Framebuffer is not complete.\n" << endl ; } glBindFramebuffer(GL_FRAMEBUFFER, 0) ; glBindTexture(GL_TEXTURE_2D, 0) ; SHADERINFO shaderInfo[2] ; shaderInfo[0].shader_type = GL_VERTEX_SHADER ; shaderInfo[0].name = "ShadowMap.vertex" ; shaderInfo[1].shader_type = GL_FRAGMENT_SHADER ; shaderInfo[1].name = "ShadowMap.frag" ; program = BWLoadShaders(shaderInfo, 2) ; lightPosLoc = glGetUniformLocation(program, "lightPos") ; lightAmbientLoc = glGetUniformLocation(program, "lightAmbient") ; lightLambertLoc = glGetUniformLocation(program, "lightLambert") ; lightProjLoc = glGetUniformLocation(program, "lightProj") ; lightViewLoc = glGetUniformLocation(program, "lightView") ; mLoc = glGetUniformLocation(program, "m") ; kdLoc = glGetUniformLocation(program, "kd") ; ksLoc = glGetUniformLocation(program, "ks") ; kaLoc = glGetUniformLocation(program, "ka") ; texLoc = glGetUniformLocation(program, "tex") ; glEnable(GL_DEPTH_TEST) ; glDepthFunc(GL_LEQUAL); glShadeModel(GL_FLAT) ; } int main(int argc, char ** argv) { glutInit(&argc, argv); glutInitWindowSize(800, 800); glutInitDisplayMode(GLUT_RGB|GLUT_DEPTH) ; glutCreateWindow("SHADERTEST"); glutKeyboardFunc(&ProcessKeyboard) ; InitScene() ; glutDisplayFunc(display); glutMainLoop(); }
ShadowMap.vertex
varying vec3 normal ; varying vec4 lightVertex ; varying vec4 color ; varying vec4 worldCoord ; uniform mat4 lightProj; uniform mat4 lightView; uniform vec4 lightPos ; uniform vec4 lightLambert ; uniform vec4 lightAmbient ; const mat4 biasMatrix = mat4(0.5 , 0.0 , 0.0 , 0.0 , 0.0 , 0.5 , 0.0 , 0.0 , 0.0 , 0.0 , 0.5 , 0.0 , 0.5 , 0.5 , 0.5 , 1.0 ) ; void main() { worldCoord = gl_ModelViewMatrix * gl_Vertex ; normal = normalize(gl_NormalMatrix * gl_Normal); lightVertex = lightProj * lightView * worldCoord ; lightVertex = lightVertex / lightVertex.w ; lightVertex = biasMatrix * lightVertex ; //lightVertex = lightVertex / lightVertex.w ; gl_TexCoord[0] = gl_MultiTexCoord0 ; color = gl_Color ; gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex ; }
ShadowMap.frag
uniform sampler2D tex ; uniform vec4 lightPos ; uniform vec4 lightLambert ; uniform vec4 lightAmbient ; uniform float m ; uniform float ka ; uniform float kd ; uniform float ks ; varying vec3 normal ; varying vec4 lightVertex ; varying vec4 color ; varying vec4 worldCoord ; void main() { vec2 poissonDisk[4] ; poissonDisk[0] = vec2( -0.94201624, -0.39906216 ) ; poissonDisk[1] = vec2( 0.94558609, -0.76890725 ) ; poissonDisk[2] = vec2( -0.094184101, -0.92938870 ); poissonDisk[3] = vec2( 0.34495938, 0.29387760 ) ; vec3 l = normalize(lightPos - worldCoord).xyz ; vec3 n = normalize(normal) ; vec3 v = normalize(-worldCoord).xyz ; float d = max(dot(n , l) , 0.0) ; float s = max(dot(normalize(v + l) , n) , 0.0) ; float cosTheta = dot(n , l) ; float bias = m*tan(acos(cosTheta)); float shadow = 1.0 ; //使用poissonDisk采樣優化鋸齒 for (int i = 0 ; i < 4; ++i) { if ( texture2D( tex, lightVertex.xy + poissonDisk[i]/700.0 ).z < lightVertex.z-bias ){ shadow -=0.2; } } //不使用優化 /*float depth = texture2D(tex , lightVertex.xy).z ; if (depth + bias < lightVertex.z ) { shadow = 0.2 ; }*/ // shadow = 0.5 ; vec4 finalColor = vec4(0.0 , 0.0 , 0.0 , 1.0) ; //finalColor *= shadow ; finalColor = (finalColor + ks * s * lightLambert + ka*lightAmbient + kd * d * lightLambert)*shadow; gl_FragColor = finalColor ; }