探究OpenGL光照模型的著色器實現

來源:互聯網
上載者:User

 

         OpenGL的著色器是新一代顯卡提供給開發人員一個小程式,為的是讓開發人員對光照、座標轉換以及像素進行一些個人化的處理。OpenGL的著色器有一種專門的語言:GLSL,現在的GLSL應該全面轉向Shader Model5,像我這樣的初學者還需要花費更長的時間來學習才能基本瞭解OpenGL的著色器方面的知識。

         下面兩圖展示了OpenGL從固定渲染管線到可程式化渲染管線的變化



         我們可以很容易地看出,OpenGL的頂點著色器取代了固定渲染管線的轉換、光照、紋理座標產生和轉換;片斷著色器取代了紋理、顏色求和和霧的操作。在OpenGL3.2版本中加入了幾何著色器(GeometryShader)這個概念,在OpenGL4.0中又添加了分格化控制(Tessellation Control)和分格化評估(Tessellation Evaluation)著色器,最新的OpenGL版本4.3則添加了計算著色器(Compute Shader)。看來OpenGL的著色器真是越來越複雜,越來越重要了。

         下面介紹一下光照模型在頂點著色器的實現,所有的內容都可以在《OpenGL超級寶典(第四版)》中找到。

         漫反射光照是一種簡單的光照模型,它只考慮漫反射。它的公式是:


         N是頂點的單位法線,L是表示從頂點到光源的單位向量方向。Cmat是表面材料的顏色,Cli是光線的顏色,Cdiff是最終的散射顏色。注意,N和L在傳入之前一定要單位化。如果用頂點著色器來實現的話,則是:


uniform vec3 lightPos[1];void main( void ){    // 法線的MVP變換    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;    vec3 N = normalize( gl_NormalMatrix * gl_Normal );    vec4 V = gl_ModelViewMatrix * gl_Vertex;    vec3 L = normalize( lightPos[0] - V.xyz );    // 輸出散射顏色    float NdotL = dot( N, L );    gl_FrontColor = gl_Color * vec4( max( 0.0, NdotL ) );}

         注意,這裡lightPos是一個標記為uniform的變數,這意味著可以在運行期傳入的一個值到lightPos中來改變著色器的行為。

         下面是漫反射光照模型的運行結果:


         只有漫反射,再漂亮的模型也會失去光澤,我們必須找出一個方法來顯示模型的高光,這時應採用鏡面反射光照模型。鏡面反射光照模型的公式是:


H表示光線向量和視圖向量(可通過視圖矩陣轉換)之間的夾角正中的方向。稱為半形向量。Sexp是最終產生的鏡面顏色。N、L、Cmat和Cli的值與散射光方程式相同。下面是用著色器實現的代碼:

uniform vec3 lightPos[1];void main( void ){    // 法線變換    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;    vec3 N = normalize( gl_NormalMatrix * gl_Normal );    vec4 V = gl_ModelViewMatrix * gl_Vertex;    vec3 L = normalize( lightPos[0] - V.xyz );    vec3 H = normalize( L + vec3( 0.0, 0.0,1.0 ) );    const float specularExp = 128.0;    // 計算散射光照    float NdotL = max( 0.0, dot( N, L ) );    vec4 diffuse = gl_Color * vec4( NdotL );    // 計算鏡面光照    float NdotH = max( 0.0, dot( N, H ) );    vec4 specular = vec4( 0.0 );    if ( NdotL > 0.0 )        specular = vec4( pow( NdotH, specularExp ) );    // 求散射和鏡面成分之和    gl_FrontColor = diffuse + specular;}

         下面是渲染的:


         這種渲染模式並不是理想的,因為僅僅是簡單的插值,在面數不多的幾何體上光照非常難看,為此有兩種解決方案。其一是使用輔助顏色(二級顏色,Secondary Color)將鏡面反射顏色與漫反射顏色進行分離,第二種方法則是使用一維紋理來為幾何體貼上高光的“紋理”。下面是採用分離顏色的方法的頂點著色器:


uniform vec3 lightPos[1];void main( void ){    // 法線變換    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;    vec3 N = normalize( gl_NormalMatrix * gl_Normal );    vec4 V = gl_ModelViewMatrix * gl_Vertex;    vec3 L = normalize( lightPos[0] - V.xyz );    vec3 H = normalize( L + vec3( 0.0, 0.0,1.0 ) );    const float specularExp = 128.0;    // 把散射顏色放在主顏色中    float NdotL = max( 0.0, dot( N, L ) );    gl_FrontColor = gl_Color * vec4( NdotL );    // 把鏡面顏色放到輔助顏色中    float NdotH = max( 0.0, dot( N, H ) );    gl_FrontSecondaryColor = ( NdotL > 0.0 )?        vec4( pow( NdotH, specularExp ) ):        vec4( 0.0 );}

         下面是:

         採用一維紋理的方法較麻煩。首先在初始化的時候建立一維紋理,代碼如下:

bool OpenGLView::SetupTextureSpecular( void ){    ……    // 使用0號紋理進行紋理設定,並且開啟一維紋理    glGetIntegerv( GL_MAX_TEXTURE_SIZE, &maxTexSize );    glActiveTexture( GL_TEXTURE0 );    glGenTextures( 1, &texID );    glBindTexture( GL_TEXTURE_1D, texID );    // 建立一維的紋理,並且設定它    GLfloat texture[512 * 4];    GLint texSize = ( maxTexSize > 512 ) ? 512 : maxTexSize;    float r = 1.0f, g = 1.0f, b = 1.0f;    for ( int x = 0; x < 512; ++x )    {        // Incoming N.H has been scaled by 8 and biased by -7 to take better        // advantage of the texture space.  Otherwise, the texture will be        // entirely zeros until ~7/8 of the way into it.  This way, we expand        // the useful 1/8 of the range and get better precision.        texture[x*4+0] = r * (float)pow(((double)x / (double)(texSize-1)) * 0.125f + 0.875f, 128.0);        texture[x*4+1] = g * (float)pow(((double)x / (double)(texSize-1)) * 0.125f + 0.875f, 128.0);        texture[x*4+2] = b * (float)pow(((double)x / (double)(texSize-1)) * 0.125f + 0.875f, 128.0);        texture[x*4+3] = 1.0f;    }    // Make sure the first texel is exactly zero.  Most    // incoming texcoords will clamp to this texel.    //texture[0] = texture[1] = texture[2] = 0.0f;    glTexImage1D( GL_TEXTURE_1D, 0, GL_RGBA16, texSize, 0, GL_RGBA, GL_FLOAT, texture );    return true;}

    隨後設定一些狀態:

glEnable( GL_TEXTURE_1D );glActiveTexture( GL_TEXTURE0 );glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD );glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );glBindTexture( GL_TEXTURE_1D, texID );

         最後看看頂點著色器是如何寫的:

uniform vec3 lightPos[1];void main( void ){    // 法線變換    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;    vec3 N = normalize( gl_NormalMatrix * gl_Normal );    vec4 V = gl_ModelViewMatrix * gl_Vertex;    vec3 L = normalize( lightPos[0] - V.xyz );    vec3 H = normalize( L + vec3( 0.0, 0.0,1.0 ) );    const float specularExp = 128.0;    // 把散射顏色放在主顏色中    float NdotL = max( 0.0, dot( N, L ) );    gl_FrontColor = gl_Color * vec4( NdotL );    // 把鏡面顏色放到輔助顏色中    float NdotH = 0.0;if ( NdotL > 0.0 )NdotH = max( 0.0, dot( N, H ) * 8.0 - 7.0 );gl_TexCoord[0] = vec4( NdotH, 0.0, 0.0, 1.0 );}

    執行效果如,發現高光範圍明顯比上面兩個要小,說明光照的精確度很高。最後說明一下,我似乎在Intel的集顯上看不全這些效果,所以大家還是盡量選用高效能獨顯對其進行渲染。


         參考:《OpenGL超級寶典(第四版)》

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.