Obj模型功能完善(物體材質,光照,法線貼圖).Cg着色語言+OpenTK+F#實現.


  這篇文章給大家講Obj模型里一些基本功能的完善,包含Cg着色語言,矩陣轉換,光照,多重紋理,法線貼圖的運用.

  在上篇中,我們用GLSL實現了基本的phong光照,這里用Cg着色語言來實現另一鍾Blinn-phong光照模型,平常我們說語言只是手段,關鍵是怎么運用,這個用在如一些高級編程語言上,我們或多或少有不同想法,但是在着色語言上,我認為太對了.因語法都是基於C,C++來的,並且去除很多高級特性,可以說語法都是簡單到了差不多了,關鍵在於他內置的一些傳遞參數的區別上,下來讓我們用Cg着色器語言來完善Obj模型里的基本功能.

  我們想在.Net環境中使用Cg着色器語言,首先我們需要安裝Cg Toolkit,然后使用封裝了Cg Toolkit的cgnet,上面還有Cgnet.OpenTK,針對的是Cgnet在OpenTK環境里的簡單封裝.然后我們在.net環境引用相關DLL,就可以引用到Cg着色器語言了.主要用法和Cg中差不多,在這先說最簡單的頂點差色器與片斷着色器,首先是生成一個Cg着色器語言環境,然后在這環境里就可以獲取最新可用的着色器配置,然后和執行代碼生成對應的着色器語言執行對象,在Cg Toolkit安裝中,可以看到里有很多的學習例子,上述過程每個例子基本都存在這過程,雖然是用C++寫的,看幾次就有印象了,額外說一句,本來我看那些例子還分DXD9,DXD10等,OpenGL就一個,還在想,這是不是太偏向DX了,那想打開一看,Opengl里的初級,高級例子比DXD9,DXD10加起來都多,不知是DX本身自帶還是昨的,反正用OpenGL的足夠學習如何使用Cg語法了.下面針對Cg里做一個簡單的封裝.

type CgContext() =
    let cgContext = CgNet.Context.Create()  
    let vertexParameters = new ParameterDict()
    let fragmentParameters = new ParameterDict()
    do
        CgGL.SetDebugMode(false)
        cgContext.ParameterSettingMode <- ParameterSettingMode.Deferred
    member val VertexProgram = Option<Program>.None with get,set
    member val FragmentProgram = Option<Program>.None with get,set
    member val VectexProfile = ProfileType.Unknown with get,set
    member val FragmentProfile = ProfileType.Unknown with get,set
    member val ErrorMessage = "" with get,set
    member this.CreateVectexProgram(fileName,programName) =
        this.VectexProfile <- ProfileClass.Vertex.GetLatestProfile()
        this.VectexProfile.SetOptimalOptions()
        let vertexProgram = 
            cgContext.CreateProgramFromFile(
                ProgramType.Source,
                fileName,
                this.VectexProfile,
                programName,
                null)
        this.ErrorMessage <- cgContext.LastListing
        vertexProgram.Load()
        this.VertexProgram <- Some vertexProgram
    member this.CreateFragmentProgram(fileName,programName) =
        this.FragmentProfile <- ProfileClass.Fragment.GetLatestProfile()
        this.FragmentProfile.SetOptimalOptions()
        let fragmentProfile = 
            cgContext.CreateProgramFromFile(
                ProgramType.Source,
                fileName,
                this.FragmentProfile,
                programName,
                null)
        this.ErrorMessage <- cgContext.LastListing
        fragmentProfile.Load()
        this.FragmentProgram <- Some fragmentProfile
    member this.VertexParameter name=
        if not (vertexParameters.ContainsKey(name)) then vertexParameters.[name] <- this.VertexProgram.Value.GetNamedParameter(name)
        vertexParameters.[name]    
    member this.FragmentParameter name=
        if not (fragmentParameters.ContainsKey(name)) then fragmentParameters.[name] <- this.FragmentProgram.Value.GetNamedParameter(name)
        fragmentParameters.[name]   
    member this.EnableProfile() =
        if this.VertexProgram.IsSome then 
            this.VertexProgram.Value.Bind()
            this.VectexProfile.EnableProfile()
        if this.FragmentProgram.IsSome then 
            this.FragmentProgram.Value.Bind()
            this.FragmentProfile.EnableProfile()
    member this.UpdateParameter() =
        if this.VertexProgram.IsSome then  this.VertexProgram.Value.UpdateParameters()
        if this.FragmentProgram.IsSome then this.FragmentProgram.Value.UpdateParameters()
    member this.DisableProfile() =
        if this.VertexProgram.IsSome then  this.VertexProgram.Value.DisableProgramProfiles()
        if this.FragmentProgram.IsSome then this.FragmentProgram.Value.DisableProgramProfiles()       
    member this.Unload() =
        if this.VertexProgram.IsSome then  this.VertexProgram.Value.Dispose()
        if this.FragmentProgram.IsSome then this.FragmentProgram.Value.Dispose()
        cgContext.Dispose()
Cg基本用法封裝

  因為是針對上篇中Obj模型的完善,如這里很多代碼是直接在原文的基礎之上添加,在上文中,我們法線是讀的文本,如果沒有,則沒有,如果要運用光照,則一定需要法線,我們可以自己來計算.原理很簡單,在三角形面中,以二條方向不一樣的矢量的叉積就是這個面的法向量,但是通常我們要求的是頂點的法向量,因為在Obj模型中,一個頂點會被多個面使用,故我們用簡單的方式來處理,取這點所有面的法線平均.下面是主要代碼.  

 1  //和頂點數組同樣長的數組,指定,如果這個數組的下標和頂點數組的下標一樣,
 2         //則這數組里存放的數據就是頂點數組里的頂點的關聯面數,所有法線長度.
 3         let pIndN = Array.create this.Positions.Count (0.f,Vector3.Zero)
 4         //根據索引信息來給對應的頂點,法線,紋理坐標賦值
 5         groups |>List.iter (fun p -> 
 6             p.Faces.ForEach(fun face -> 
 7                 face.Vectexs |> Array.iter(fun vect ->
 8                     if vect.PositionIndex > 0 then vect.Position <-this.Positions.[vect.PositionIndex-1]
 9                     if vect.TexcoordIndex > 0 then  vect.Texcoord <- this.Texcoords.[vect.TexcoordIndex-1]
10                     if vect.NormalIndex > 0 then  vect.Normal <- this.Normals.[vect.NormalIndex-1]                      
11                     )
12                 if this.IsAutoNormal && this.Normals.Count < 1 then   
13                     let faceNormal = 
14                         let p1 =Vector3.Subtract(face.Vectexs.[1].Position, face.Vectexs.[0].Position)
15                         let p2 =Vector3.Subtract(face.Vectexs.[2].Position, face.Vectexs.[1].Position)
16                         Vector3.Cross(p1,p2)
17                     face.Vectexs |> Array.iter(fun v ->
18                         let mutable ind,n = pIndN.[v.PositionIndex - 1]
19                         n <- n + faceNormal
20                         pIndN.[v.PositionIndex - 1] <- (ind+1.f,n)
21                     )
22                 )       
23             let mater = this.Materials.Find(fun m -> m.Name = p.Mtllib)
24             if box(mater) <> null then
25                 let mitem = mater.Items.Find(fun i -> i.Name = p.Usemtl)
26                 if box(mitem) <> null then 
27                     p.Material <- mitem
28                     p.Path <- this.Path
29                     p.IsHaveMaterial <- true
30             )
31         if this.IsAutoNormal && this.Normals.Count < 1 then 
32             groups |>List.iter (fun p -> 
33                 p.Faces.ForEach(fun face -> 
34                     face.Vectexs |> Array.iter(fun v ->
35                         let ind,n = pIndN.[v.PositionIndex - 1]
36                         v.Normal <- Vector3.Normalize(n / ind)
37                         v.LinkFace <- int ind
38                     )
39                 )
40             )  
頂點的法線.

 針對原來的處理,增加了十幾行的代碼,先聲明一個和頂點一樣長的數組,在這里,我們這樣定義,這個數組里存放的數據的下標是和頂點數組中對應頂點的下標一樣,這樣我們就能直接對應頂點與頂點的共面數,共有法線的信息.相當於天然的HashMap.可以去掉平常算法中的比對過程,如let ind,n = pIndN.[v.PositionIndex - 1]可以直接用自己的下標定位到求得的共面信息與法線總和.

  有個法向量后,我們來完善另一個地方,我們原來是物體一直是放在原點下的,也就是模型坐標系和世界坐標系是重和的,我們如果移動,翻轉物體后,他就需要自己的模型坐標系了,用來表示他自己與世界坐標系的對應關系.如下代碼.

 1     let mutable m = Matrix4.Identity
 2     let mutable inv = Matrix4.Identity
 3     let getLazyModelMatrix() = 
 4         let tr = Matrix4.CreateTranslation(translation)
 5         let ro = if rotate = Vector3.Zero then Matrix4.Identity else Matrix4.CreateFromAxisAngle(rotate,rotateAngle)
 6         if bFirstRotate then 
 7             m <- Matrix4.Mult(tr,ro) 
 8         else 
 9             m <- Matrix4.Mult(ro,tr) 
10         inv <- Matrix4.Invert(m)
11         m,inv 
12     member this.IsFirstRotate with get() = bFirstRotate and set(value) = bFirstRotate <- value
13     member this.Translation
14         with get() = translation
15         and set(value) = 
16             translation <- value
17             getLazyModelMatrix() |> ignore
18     member this.Rotate 
19         with get() = rotate
20         and set(value) = 
21             rotate <- value
22             getLazyModelMatrix() |> ignore
23     member this.RotateAngle
24         with get() = rotateAngle
25         and set(value) = 
26             rotateAngle <- value
27             getLazyModelMatrix() |> ignore
28     member this.ModelMatrix with get() = m
29     member this.InvertMatrix with get() = inv   
模型坐標系

  增加一個表示旋轉與移動的向量,以及旋轉的角度與是否先旋轉,先旋轉還是先移動生成的模型坐標系是不一樣的,注意矩陣相乘的順序,矩陣不滿足交換律的,先后順序的不同一般會得到不同的矩陣,這里因為在Cg中,最好先經過轉置,轉置后再乘,就變成如下了,如先R后T,則Matrix4.Mult(T,R),這個順序非常重要,后面的坐標系變換都要用到.這樣我們就生成了模型坐標系,這個坐標系的作用就是把以模型坐標系里的坐標變成世界坐標系的坐標,如果我們需要把世界坐標系的坐標變為模型坐標系的,可以直接用上面的模型坐標系的逆矩陣,具體運行過程大家可以查找相關資料,這些只說下,矩陣與逆矩陣相乘等於單位矩陣,就是對角線都是1,別的位置都是0的矩陣,我們一般定義一個矩陣,默認應該都用單元矩陣.

  這里先說下,3D的變換過程大致如下,物體的坐標(經模型坐標系變換成)世界坐標(經過視圖坐標系變換成)視圖模型坐標系(經透視矩陣變換成)屏幕上的坐標(這里說下,Z值並有沒消失,被非線性插值到-1,1之間).這個具體過程,大家想了解可以查找相關資料.下面看一段具體代碼.  

 1         GL.Clear (ClearBufferMask.ColorBufferBit ||| ClearBufferMask.DepthBufferBit)
 2         //生成一個視圖矩陣
 3         let mutable v = Matrix4.LookAt(caram.Eye,caram.Target,Vector3.UnitY)
 4         //定位物體在世界坐標系的位置
 5         model.Translation <- Vector3(5.f,0.f,0.f)
 6         model.Rotate <- Vector3(1.0f,0.f,0.f)
 7         model.RotateAngle <- float32 (-Math.PI/2.0)
 8         //模型矩陣
 9         let m = model.ModelMatrix
10         //Cg與HLSL一樣,使用是行矩陣,與OpenGL的列矩陣需要轉置才能對應.
11         //如果相應矩陣是傳給Cg着色器的,則他在進行相關運行前一定要轉置,如果在OpenGL本身運算,則不需要.
12         m.Transpose()
13         v.Transpose()
14         //生成模型視圖矩陣
15         let mv = Matrix4.Mult(v,m)
16         //啟用相關配置
17         cgContext.EnableProfile()
18         //得到我們設置的視圖矩陣.
19         let mutable p = Matrix4.Identity
20         GL.GetFloat(GetPName.ProjectionMatrix,&p)
21         p.Transpose()
22         //生成模型視圖透視矩陣
23         let mvp = Matrix4.Mult(p,mv)
24         //傳遞值.
25         cgContext.VertexParameter("mvp").SetMatrix(MatrixToArray1 mvp) 
26         //眼睛的位置由世界坐標轉換成模型坐標系.
27         let modelEye = Vector3.Transform(caram.Eye,model.InvertMatrix)
28         cgContext.FragmentParameter("eyePosition").Set(modelEye)
29         //燈光的位置由世界坐標轉換成模型坐標系
30         let modelLight = Vector3.Transform(lightPosition,model.InvertMatrix)
31         cgContext.FragmentParameter("lightPosition").Set(modelLight)
32         //針對模型的各參數設置值.
33         model.Groups |> List.iteri (fun i p -> 
34             if i < 10 then 
35                 cgContext.FragmentParameter("Ke").Set(p.Material.Emissive)
36                 cgContext.FragmentParameter("Ka").Set(p.Material.Ambient)
37                 cgContext.FragmentParameter("Kd").Set(p.Material.Diffuse)
38                 cgContext.FragmentParameter("Ks").Set(p.Material.Specular)
39                 cgContext.FragmentParameter("shininess").Set(p.Material.Shiness)
40                 cgContext.FragmentParameter("dtext").SetTexture(p.Material.DiffuseID)
41                 cgContext.FragmentParameter("dtext").EnableTexture()
42                 cgContext.FragmentParameter("maptext").SetTexture(p.Material.BumpID)
43                 cgContext.FragmentParameter("maptext").EnableTexture()
44                 cgContext.UpdateParameter()
45                 //p.DrawVBO(cgContext,cgContext.FragmentParameter("tt"))
46                 p.DrawVBO()
47                 cgContext.FragmentParameter("dtext").DisableTexture()
48                 cgContext.FragmentParameter("maptext").DisableTexture()
49         )
模型在Cg各參數設置

  這段代碼里相關的操作我做了比較詳細的注釋,在這段代碼里,我們沒看到相關如加載視圖矩陣的操作了,以及針對模型操作調用如GL.Translation,GL.Rotate等操作,這些算法全是我們自己來處理並放入我們寫的着色器里來操作,操作的順序就如上面寫的物體的坐標變換的順序一樣,注意矩陣相乘的順序.過程如果反着來,如世界坐標變成模型坐標,則乘以對應矩陣的逆.

  需要說明的幾點是,在Cg操作中,矩陣的順序與DX是一樣的,都是行矩陣,而Opengl用的列矩陣,那么如果我們相應的矩陣以及操作過后的順序給Cg,那么需要在取出來時就先做轉置的操作,把列矩陣順序變成行矩陣的排列順序.在着色器語言操作中,各個頂點用的坐標系一定要是同一個坐標系,要么都是模型坐標系,要么都是世界坐標系,要么都是視角坐標系,如果不同,顯示的效果可能會與你要得到的效果天差之別,如在上面,我們設置燈的位置在世界坐標里的(0,5,8)處,可以看到代碼位置,我們用模型的逆矩陣求把對應的世界坐標變成了模型坐標,如果不調用這句,后面會有啥結果了,大家可以先想一下.

  寫到這個,大家一定好奇相應的Cg的頂點着色器與片斷着色器的處理了吧.頂點差色器的處理如下:

 1 void v_main(float4 position : POSITION,
 2             float3 normal : NORMAL,
 3             float2 texCoord :  TEXCOORD0,
 4             out float4 oPosition : POSITION,
 5             out float4 objectPos : TEXCOORD0,
 6             out float3 oNormal : TEXCOORD1,
 7             out float2 oTexCoord : TEXCOORD2,
 8             uniform float4x4 modelView,
 9             uniform float4x4 mvp)
10 {    
11     oPosition = mul(mvp,position);
12     objectPos = position;
13     oNormal = normal;
14     oTexCoord = texCoord;
15 }
頂點着色器

   頂點差色器我們可以看到后面有一些out,uniform,POSITION,TEXCOORD0的關鍵詞,讓我們來解析一下相關參數的功能,前三個float4 position : POSITION,float3 normal : NORMAL,float2 texCoord : TEXCOORD0在類型前面沒有關鍵詞,那表示相應數據是Opengl傳遞給我們的,這個時候后綴很重要,第一個POSITION就表示傳遞的是當前的頂點,NORMAL與TEXCOORD0同理.那么后面的如out float4 oPosition : POSITION,out float4 objectPos : TEXCOORD0,out float3 oNormal : TEXCOORD1,out float2 oTexCoord : TEXCOORD2. 前面才說,后面的后綴如POSITION這些很重要,指定是傳入的數據,那么在這里,后綴就與他單詞的意義沒有關系了,可以看到這些前面都帶一個out,這表示這些數據都是傳遞給片斷着色器的,這些后綴與片斷着色器的對應,表示對應的傳值關系.最后的uniform float4x4 modelView,uniform float4x4 mvp表示的是我們從應用程序傳遞過來的數據,在這里我們分別傳來一個模型視圖矩陣,一個模型視圖透視矩陣,如果我們要把所有值都變成在模型視圖下的坐標,我們可以用到這值,但是在這,我們都用模型坐標系,所以沒用到,和GLSL一樣,我們要得到當前頂點的在模型視圖透視的位置,也就是我們看到的屏幕位置.前面說了,如果世界坐標沒有變成模型坐標,在這里,大家還可以處理一下,得到正確的位置,增加一個傳入的模型矩陣,把當前世界坐標系的位置用這矩陣的逆變成模型坐標系.如果這步你還沒進行,那么相關數據就到片斷着色器中了.

  在這里說下,如果大家都用模型視圖坐標系,請注意,如果我們設置這個坐標系下,朝向是向着Z方向前看的,就是越遠Z值越大.那么視角下的的位置和我們OpenGL的位置是不一樣的,這個坐標系和OpenGL的Z值與X軸方向是反的,這樣想吧,我們在屋內看門的右邊就是我們在屋外看門的左邊.我開始全用的是模型視圖坐標系,偏偏和DX一樣,是向着Z軸向前看(沒辦法,模型加載很多都是這種方向)就是因為這個地方,一些位置老不對,搞的我好怨念啊,你為毛不和DX一樣,用符合人體視角的坐標系.

  上在的頂點着色器處理后,就到我們的片斷着色器,代碼主要過程如下:

 1 float3 expand(float3 v)
 2 {
 3     return (v-0.5) * 2.0;
 4 }
 5 
 6 void f_main(float4 position  : TEXCOORD0,                        
 7             float3 normal    : TEXCOORD1,
 8             float2 texCoord  : TEXCOORD2,
 9             out float4 color     : COLOR,
10             uniform float3 globalAmbient,
11             uniform float3 lightColor,
12             uniform float3 lightPosition,
13             uniform float3 eyePosition,
14             uniform float3 Ke,
15             uniform float3 Ka,
16             uniform float3 Kd,
17             uniform float3 Ks,
18             uniform sampler2D dtext,
19             uniform sampler2D maptext,
20             uniform float3 tt,
21             uniform float  shininess
22             )
23 {    
24     float3 N = normal;
25     // Compute emissive term
26     float3 emissive = Ke;
27     // Compute ambient term
28     float3 ambient = Ka * globalAmbient;
29     // Compute the diffuse term
30     float3 L = normalize(lightPosition - P);
31     float diffuseLight = max(dot(L, N), 0);
32     float3 diffuse = Kd * lightColor * diffuseLight;
33     // Compute the specular term
34     float3 V = normalize(eyePosition - P);
35     float3 H = normalize(L + V);
36     float specularLight = pow(max(dot(H, N), 0), shininess);
37     if (diffuseLight <= 0) specularLight = 0;
38     float3 specular = Ks * lightColor * specularLight;
39     //float3 tex =lerp(tex2D(maptext, texCoord).xyz,tex2D(dtext, texCoord).xyz,1.0);
40     float3 tex = tex2D(dtext, texCoord).xyz;
41     float3 light = emissive + ambient + diffuse + specular;
42     color.xyz = light * tex;
43     //    color.xyz = lerp(light,tex,0.5);
44 //    color.xyz = light;
45     color.w = 1;
46 }
片斷着色器.

  在這里,前面的參數也有很多關鍵詞,和前面大部分是一樣的,就是在類型沒有前綴,后面又帶着后綴的,如float4 position : TEXCOORD0, float3 normal : TEXCOORD1,float2 texCoord : TEXCOORD2,這些就是前面頂點着色器傳過來的值.別的就out float4 color : COLOR和前面的out float4 oPosition : POSITION一樣,都是應用的處理,傳遞回給OpenGL用,一個對應的頂點位置,一個對應片斷處理的顏色.后面的uniform一樣,是表示從OpenGL應用程序傳遞進來的值.這個光照模型的算法稱作Blinn-phong,對於上一種光照主要改進在於鏡面光照的計算,他計算頂點到光照與頂點到人眼的矢量二者相加的,因為頂點到光照與頂點到人眼的矢量都取的是單元向量,所以他們相加的矢量,就在他們的半角上,所以這種計算方式也叫求半角,然后求與法線的叉積就是我們要求的鏡面反射量。

  下面我們來說關於法線貼圖相關操作,在Cg中,啟用多個紋理相對來說比較簡單,調用對應有API就能啟用,分別是關聯紋理,啟用紋理,關閉紋理,不需要GLSL那樣還需要調用GL.ActiveTexture這種API來指定當前紋理。首先,我直接把法線貼圖里的RGB轉成法線,然后原來的法線替換法線帖圖里的法線,結果嘛,在某個方向,我們發現能得到正常的光照,但是更多的位置查看是錯誤的結果,如有黑塊等等現象。那時因為在紋理里的光照存取的都是模型在某個位置時的值,如果模型經過一些旋轉等操作,此時在這個光照已經對應不上了,我們想想一面牆,面對我們時法向量是Z軸,如果把牆轉個90度,那時我們來看法向量就是Y軸,好吧,我感覺這個還復雜了說,你直接想,畫一個立方體,他的六面法向量各不一樣,現在引入一個矩陣,讓你六面法向量只需要設置一次,效果如模型矩陣一樣,他能讓你只設置一種情況下的法向量,外界的改變會反映在這矩陣上,我們要做的只是和這矩陣的操作,而不需要去關注他本身的變換。這個矩陣所對應的坐標系是切線坐標系。下面我給出別人對切線空間比較深刻的說明,希望對大家的理解有幫助。

http://www.opengpu.org/forum.php?mod=viewthread&tid=5169這個里面三樓的回復:

簡單地說就是:
1、樓上講的參數曲面上任一點都有切空間,並且有無數個切空間,其中法線是固定的,它與切平面上任意兩條相互垂直的線(副法線與切線)就構成了一個切空間。
2、法線貼圖的用的那個切空間,就是指副法線與切線剛好與uv軸重合的那個。
3、用切空間的好處之一是,對某些對稱的模型,只用做一半貼圖,就可以貼兩面,因為幾何體在鏡像后,對象空間的法線變了,但是切空間里的沒變。

  根據如http://blog.csdn.net/bonchoix/article/details/8619624里下的這張圖,能很好說明切線空間中的U,V如何與模型坐標系的頂點對應上的,注意大部分情況都是模型坐標系,意思是相應的模型坐標的結果通過切線矩陣TNB得到在對應切空間的位置,反過來也可以把要空間里的坐標通過TNB得到模型坐標的結果。與別的坐標系的交互要先通過TNB來操作。

  知道算法后,我們就可以求得切線了,和求法線一樣,需要先求得頂點的各個切線,然后取平均。下面給出主要代碼。

 1                 p.Faces.ForEach(fun face -> 
 2                     let p10 = face.Vectexs.[1].Position - face.Vectexs.[0].Position
 3                     let p20 = face.Vectexs.[2].Position - face.Vectexs.[0].Position
 4                     let t10 = face.Vectexs.[1].Texcoord - face.Vectexs.[0].Texcoord
 5                     let t20 = face.Vectexs.[2].Texcoord - face.Vectexs.[0].Texcoord
 6                     let T = (t20.Y * p10 - t10.Y * p20) / (t10.X*t20.Y - t10.Y*t20.X)
 7                     face.Vectexs |> Array.iter(fun vect ->
 8                         let mutable ind,n = pIndT.[vect.PositionIndex - 1]
 9                         n <- n + T
10                         pIndT.[vect.PositionIndex - 1] <- (ind+1.f,n)
11                     )
12                 )
13             if p.Material.BumpMap <> "" then 
14                 p.Faces.ForEach(fun face -> 
15                     face.Vectexs |> Array.iter(fun v ->
16                         let ind,n = pIndT.[v.PositionIndex - 1]
17                         v.Tangent <- Vector3.Normalize(n / ind)
18                         v.LinkFace <- int ind
19                     )
20                 )                
21             )
求切線

  求得切線后,下一步就是寫入內存,因為我們使用的VBO,那如何才能傳入切線到着色器中了,有二種方式,一種是不用VBO,改用直接用一個一個畫三角形,在Face中指定切線,好吧,我最開始就試的這個,直接卡的換的攝像機都動不上了。那第二種也就是繼續用VBO,傳入的時候我們把切線當顏色傳入,然后在着色器里取出來,頂點着色器主要代碼如下:  

 1 void v_main(float4 position : POSITION,
 2             float3 normal : NORMAL,
 3             float2 texCoord :  TEXCOORD0,
 4             float4 tangent : COLOR,
 5             out float4 oPosition : POSITION,
 6             out float3 objectPos : TEXCOORD0,
 7             out float3 oNormal : TEXCOORD1,
 8             out float2 oTexCoord : TEXCOORD2,
 9             out float3x3 oTNB : TEXCOORD3, 
10             //out float3 oeyePosition: TEXCOORD3,
11             //out float3 olightPosition: TEXCOORD4,
12             //uniform float3 eyePosition,
13             //uniform float3 lightPosition,            
14             uniform float4x4 mvp)
15 {    
16     oPosition = mul(mvp,position);
17     float3 tNormal = normal;
18     float3 tTangent = tangent.xyz;
19     float3 tB = cross(tNormal,tTangent);
20     float3x3 tnb = float3x3(normalize(tTangent),normalize(tB),normalize(tNormal));
21     oTNB = tnb;
22     oNormal = normal;
23     oTexCoord = texCoord;
24     objectPos = position.xyz;
25 
26     //oPosition = mul(mvp,position);
27     //float3 tNormal = normal;
28     //float3 tTangent = tangent.xyz;
29     //float3 tB = cross(tNormal,tTangent);
30     //float3x3 tnb = float3x3(normalize(tTangent),normalize(tB),normalize(tNormal));
31     //oTNB = tnb;
32     //oTexCoord = texCoord;
33     //objectPos = mul(tnb,position).xyz;
34 }
顏色取切線

  片斷着色器如下:

 1 float3 expand(float3 v)
 2 {
 3     return (v-0.5) * 2.0;
 4 }
 5 void f_main(float3 position  : TEXCOORD0,                        
 6             float3 normal    : TEXCOORD1,
 7             float2 texCoord  : TEXCOORD2,
 8             float3x3 tnb : TEXCOORD3, 
 9             //float3 lightPosition : TEXCOORD3,
10             //float3 eyePosition: TEXCOORD4,
11             out float4 color     : COLOR,
12             uniform float3 globalAmbient,
13             uniform float3 lightColor,           
14             uniform float3 Ke,
15             uniform float3 Ka,
16             uniform float3 Kd,
17             uniform float3 Ks,
18             uniform float3 lightPosition,
19             uniform float3 eyePosition,
20             uniform sampler2D dtext,
21             uniform sampler2D maptext,
22             uniform float  shininess
23             )
24 {    
25     float3 P = position;
26  //   float3 normalTex = tex2D(maptext, texCoord).xyz;
27  //   float3 N = expand(normalTex);//normalize(normal); // 
28     //float3 E = mul(tnb,eyePosition);
29     //float3 Light = mul(tnb,lightPosition);
30     float3 normalTex = tex2D(maptext, texCoord).xyz;
31     float3 N =normalize(mul(inverse(tnb),expand(normalTex)));  //normalize(normal);
32     float3 E = eyePosition;
33     float3 Light = lightPosition;
34     // Compute emissive term
35     float3 emissive = Ke;
36     // Compute ambient term
37     float3 ambient = Ka * globalAmbient;
38     // Compute the diffuse term
39     float3 L = normalize(Light - P);
40     float diffuseLight = max(dot(L, N), 0);
41     float3 diffuse = Kd * lightColor * diffuseLight;
42     // Compute the specular term
43     float3 V = normalize(E - P);
44     float3 H = normalize(L + V);
45     float specularLight = pow(max(dot(H, N), 0), shininess);
46     if (diffuseLight <= 0) specularLight = 0;
47     float3 specular = Ks * lightColor * specularLight;
48     //float3 tex =lerp(tex2D(maptext, texCoord).xyz,tex2D(dtext, texCoord).xyz,1.0);
49     float3 tex =tex2D(dtext, texCoord).xyz;//,1.0);
50     float3 light = emissive + ambient + diffuse + specular;
51     color.xyz = light * tex;
52     color.w = 1;
53 }
片斷着色器 切線空間

  上面着色器中,分別有一些注釋的代碼,沒注釋的是在模型空間運算,注釋的是在切線空間運算,效果是一樣的。

  其實還有第三種方法,不需要在CPU里計算切線,可以啟用幾何着色器,幾何着色器位與頂點着色器與片斷着色器之間,在這中間,可以根據頂點着色器中各變量,計算相應結果,如切線,然后傳入片斷着色器,因我的機器太舊,啟用不起來,以后有機會再試。

  下面放出對比效果圖:  

  第一張加上法線貼圖,第二張沒有,這二張是同一個模型,同樣的精度,可以看到法線貼圖的模型看起來細節要比沒有的高不少。

  下面放出源代碼(記的安裝Cg Toolkit):引用DLL 代碼 模型文件部分1 模型文件部分2 模型文件部分3 和前面一樣,其中EDSF前后左右移動,鼠標右鍵加移動鼠標控制方向,空格上升,空格在SHIFT下降。

  因為模型文件有些大,故分開上傳,大家組織好對應目錄應該就可以編譯過了。

  其中大家可以試試求法線或求切線,不按照求平均值的方式,而是在設置面時,對面的每個頂點分別設置時,看看效果,相關部分都有注釋。對這部分有興趣的同學不妨改改相關代碼。加了法線貼圖后,有時會發現在某些角度看有黑點,現在不知是代碼里,相關光照與頂點的計算不在一起的原因還是別的原因,各位如果遇到過這個問題,謝謝指點。

  

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM