精华内容
下载资源
问答
  • obj模型

    千次阅读 2015-08-15 16:57:52
    这篇文章给大家讲Obj模型里一些基本功能的完善,包含Cg着色语言,矩阵转换,光照,多重纹理,法线贴图的运用.  在上篇中,我们用GLSL实现了基本的phong光照,这里用Cg着色语言来实现另一钟Blinn-phong光照模型,平常我们说...

    这篇文章给大家讲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下降。

      因为模型文件有些大,故分开上传,大家组织好对应目录应该就可以编译过了。

      其中大家可以试试求法线或求切线,不按照求平均值的方式,而是在设置面时,对面的每个顶点分别设置时,看看效果,相关部分都有注释。对这部分有兴趣的同学不妨改改相关代码。加了法线贴图后,有时会发现在某些角度看有黑点,现在不知是代码里,相关光照与顶点的计算不在一起的原因还是别的原因,各位如果遇到过这个问题,谢谢指点。

    展开全文
  • 三维OBJ模型

    2021-01-10 02:30:33
    3D OBJ 模型 有需要研究obj格式的可以下载 3D OBJ 模型 有需要研究obj格式的可以下载 3D OBJ 模型 有需要研究obj格式的可以下载 3D OBJ 模型 有需要研究obj格式的可以下载
  • obj模型加载

    2018-08-27 10:39:50
    用于学习learnOpenGL时加载外部obj模型所实现的简单的c++类,本人是用codeblocks编写的,所以打包了codeblocks项目,类函数也在其中,包含测试代码。 注:此c++类只能加载三角面模型!!
  • obj模型读取

    2016-01-06 15:54:57
    obj模型的读取!学习obj在openGL中的显示的人可以下载。文件内容包含一个obj文件
  • Obj模型文件读取文件

    2020-09-15 22:00:22
    加载Obj模型,将其显示,使用C++ OpenGL/glut读取,代码量200行 加载Obj模型,将其显示,使用C++ OpenGL/glut读取,代码量200行
  • u4d导入obj模型给threejs使用obj模型打组问题,在做threejs加载obj模型导出有加载信息但是场景中不显示模型得问题 1.模型没有打组。 2.一组模型中只能由一个组。 3.导出得模型不能组中套组这样也不是显示得。 ...

    u4d导入obj模型给threejs使用obj模型打组问题,在做threejs加载obj模型导出有加载信息但是场景中不显示模型得问题

    1.模型没有打组。

    2.一组模型中只能由一个组。

    3.导出得模型不能组中套组这样也不是显示得。

    展开全文
  • opengl加载obj模型

    2019-10-13 16:09:19
    这是一个简单obj模型 其中使用到的只有三个文件 还带有示例代码 大家随便下载
  • 人体obj模型

    热门讨论 2012-09-26 16:30:12
    使用visual studio 2010或者用C++6.0进行载入obj模型
  • 3D_obj模型

    2017-10-12 17:31:03
    可以用three.js加载的Obj模型,可以用photoshop打开下。
  • 兔子obj模型

    热门讨论 2012-04-25 17:31:58
    兔子三维obj模型,可以直接导入OpenGL。
  • 读取obj模型文件

    2016-10-07 21:49:34
    Qt搭载界面,OpenGL对obj模型文件进行渲染(针对不同模型,请自行修改相应的模型和纹理路径及平移、视角等参数)
  • three.js加载obj模型

    2018-02-06 10:46:08
    three.js加载obj模型
  • 最近的一个项目需要在HTML中对OBJ模型进行大量的变更,修改为其他模型。基于这个需求下,这里有一种解决方案。 另外在各大论坛上的相关博客基本都是那几种导入obj例子····并没有对后续操作进行说明。 比如因为...

    最近的一个项目需要在HTML中对OBJ模型进行大量的变更,修改为其他模型。基于这个需求下,这里有一种解决方案。

    另外在各大论坛上的相关博客基本都是那几种导入obj例子····并没有对后续操作进行说明。

    比如因为灯光、 模型大小、照相机位置等原因而导致模型不可见的问题,这篇文章会大致说明。

     

    首先是用到的JS

    HTML:

     

     

     

    CSS:

     

     

     

     

     

    JS:

    前提:使用file协议打开模型需要做浏览器跨域,自行百度谷歌浏览器跨域。或者搭个服务器,把整个项目拖上去发布,就不用跨域了。

    定义模型地址:

     Address是obj模型以及mtl模型的父地址 如:f:\XX\FF

     

     

    导入模型:

    第一步:创建渲染器,可以理解为模型在哪个范围内展示。这里是以一个<div id="canvas-frame"></div> 为区域。

     

    第二步:创建照相机,你现在直接用眼睛是看不到页面中的模型,而需要照相机给模型“录像”,然后在照相机的内存中看到录像

     

     

     

     

     

     

     

     

     

    图中的距离不一定每个模型都这样。(创建照相机的中的参数自行百度,要抄的话将20改为45吧,45是默认值。)

    到这一步你首先需要将图中的 X,Y,Z三个数据全部归0,作用就是将照相机放到原点(这时候是看不到的模型的)。后续会说明如何调节。

    第三步:导入模型:刚开始不要设置模型的位置,它会在正中间!·····

    第四步:做好辅助线,便于调节照相机、灯光的位置

    200那个参数是辅助线的长度

     

     

     

     

     

    第五步:灯光,可以理解为:照相机如果要“录像”,那么就需要光,没光的话就是一片黑,录制下来你也看不到。

    (各大灯光效果请百度three.js光源,我这里只用了一个平行光和一个环境光。).

    到了这一步就有了 模型的展示范围,再给模型补上光,再通过照相机去“录像”。这三个条件满足后可以看到模型了,如果没看到可以先F12,看看有没有红色。没有的话再进行 “录像角度”(照相机)和灯光位置的调节(下一步会说明)。

    第六步:调节照相机和灯光的位置说明。两者的位置调节原理一致。

    照相机的位置(X,Y,Z)如果为全0的情况下,它是在原点(模型的正中间)的。此时你可以调节Z轴,比如Z = 200,

    在这里暂时把(X称为左右,Y称为上下,Z称为前后),照相机会向靠近你的位置移动,就是靠近你的位置为正。远离你的位置为负。而照相机镜头永远是向着模型的。 可以根据上面做的辅助线来想象一下,

    先把平行光的位置调整在(0,0,1),意思就是从前往后照亮模型。建议刚开始就用平行光,这个光源比较简单·····

    然后再调节照相机位置,开始时可以不用动X轴和Y轴,只动Z轴的距离。

    调用以上函数之前记得把坐标轴和照相机添加进去

    到这里F12页面,不出红色的情况下,模型就可以展示出来了。

     

    接下来是模型控制的两种方式,用于模型的旋转,缩放等

    第一种: 

                    //创建控件并绑定在相机上
                    trackballControls = new THREE.TrackballControls(camera);
                    //旋转速率
                    trackballControls.rotateSpeed = 0.2;
                    //滚轮放大缩小速率
                    trackballControls.zoomSpeed = 0.2;

                    //平移速率
                    // trackballControls.panSpeed = 0.5;
                    //关闭放大缩小
                    trackballControls.noZoom=false;
                    //关闭平移
                    trackballControls.noPan=false;

    第二种:

     var controls = new THREE.OrbitControls( camera, renderer.domElement ); 
                    //旋转速度
                    controls.rotateSpeed  = 0.3;
                    //设置相机距离原点的最远距离 
                     controls.minDistance = 200; 
                     //设置相机距离原点的最远距离 
                     controls.maxDistance = 1000; 
                     controls.enableDamping = true;
                    controls.dampingFactor = 1;

    renderer(渲染器).domElement 这个参数是指定的在渲染器所渲染的范围内起用鼠标操作模型会起作用。如果不设置的情况下,默认全屏的任何一个位置都可以控制模型的旋转等。 你自己可以试试就知道了。其他的参数自行百度·······

     

    文章的最后就是 为了解决用户要导入不同的obj模型,如何删除当前OBJ模型,而导入新的OBJ模型。 并且页面中的展示出来的,用户修改的那部分数据不变(不变成初始值)。(保证这个就不能更换OBJ的地址,再刷新页面这么操作了····),初步我设想的是重新执行一下导入模型的那部分JS,发现新导入的模型会和旧的模型重合,而我各种狗狗和百度都找不到删除旧OBJ模型的方式······

    以下是解决方案:

    有故事的小伙伴都注意到了,在id="canvas-frame"的DIV外面有一个outer这个DIV。

    为得就是TMD把这个"canvas-frame"节点全部给删了(所以才创建了外层的id="outer"这个DIV,用于删除子节点)。理所当前的渲染器的范围在DIV内,所以,这个canvas-frame没有了,

    渲染器的范围也就不存在,模型就不可见了,此时在重新创建一个id="canvas-frame"的节点。再重新执行一遍从创建渲染器开始的所有的JS代码,这样就完成了本次需求。

    最外层那个id="outer"的DIV还保证了 创建新的id="canvas-frame"的这个DIV的位置不变,也就可以保证变更成新的OBJ模型之后的页面布局不会改变。

    更改模型的源码如下:

     

    不太清楚这样的方式会不会有什么弊端、如果小伙伴们的实现方式有更好的选择,比如three.js有内置的方法这样、请留言··

     

     

     

     

    展开全文
  • opengl导入obj模型

    2019-09-21 22:08:42
    在经过查阅各种资料以及各种bug之后,终于成功的实现了导入基本的obj模型。 首相介绍一下什么是obj模型 一.什么是OBJ模型 obj文件实际上是一个文本文档,主要有以下数据,一般可以通过blender软件导出模型的obj...

    在经过查阅各种资料以及各种bug之后,终于成功的实现了导入基本的obj模型。

    首相介绍一下什么是obj模型

    一.什么是OBJ模型

    obj文件实际上是一个文本文档,主要有以下数据,一般可以通过blender软件导出模型的obj文件。

    在3d图形处理中,一个模型(model)通常由一个或者多个Mesh(网格)组成,一个Mesh是可绘制的独立实体。例如复杂的人物模型,可以分别划分为头部,四肢等各个部分来建模,这些Mesh组合在一起最终形成人物模型。

    obj的文本内容一般包括以下数据

    usemtl和mtllib表示的材质相关数据,解析材质数据稍微繁琐,本节我们只是为了说明加载模型的原理,不做讨论。

    o 引入一个新的object

    v 表示顶点位置

    vt 表示顶点纹理坐标

    vn 表示顶点法向量

    f 表示一个面,面使用1/2/8这样格式,表示顶点位置/纹理坐标/法向量的索引,这里索引的是前面用v,vt,vn定义的数据 注意这里Obj的索引是从1开始的,而不是0

    二 利用opengl导入obj模型

    新建一个ObjLoader

    1 class ObjLoader{
    2 public:
    3     ObjLoader(string filename);//构造函数
    4     void Draw();//绘制函数
    5 private:
    6     vector<vector<GLfloat>>vSets;//存放顶点(x,y,z)坐标
    7     vector<vector<GLint>>fSets;//存放面的三个顶点索引
    8 };

    一个构造函数ObjLoader::ObjLoader用于导入obj文本内容

    Draw用于在opengl中绘制模型

    vector<vector<GLfloat>>vSets;//存放顶点(x,y,z)坐标
    vector<vector<GLint>>fSets;//存放面的三个顶点索引

    ObjLoader::ObjLoader(string filename)
    {
        std::ifstream file(filename);
        std::string line;
    while (getline(file, line))
    {
        if (line.substr(0, 2) == "vt")
        {
    
        }
        else if (line.substr(0, 2) == "vn")
        {
    
        }
        else if (line.substr(0, 1) == "v")
        {
            vector<GLfloat> Point;
            GLfloat x, y, z;
            std::istringstream s(line.substr(2));
            s >> x; s >> y; s >> z;
            Point.push_back(x);
            Point.push_back(y);
            Point.push_back(z);
            vSets.push_back(Point);
    
        }
        else if (line.substr(0, 1) == "f")
        {
            vector<GLint> vIndexSets;
            GLint u, v, w;
            std::istringstream vtns(line.substr(2));
            vtns >> u; vtns >> v; vtns>> w;
            vIndexSets.push_back(u-1);
            vIndexSets.push_back(v-1);
            vIndexSets.push_back(w-1);
            fSets.push_back(vIndexSets);
        }
        else if (line.substr(0, 1) == "#")
        {
    
        }
        else
        {
    
        }
    }
    file.close();
    }
    
    
    
    void ObjLoader::Draw(){
    
        glBegin(GL_TRIANGLES);//开始绘制
        for (int i = 0; i < fSets.size(); i++) {
            GLfloat VN[3];
            //三个顶点
            GLfloat SV1[3];
            GLfloat SV2[3];
            GLfloat SV3[3];
    
            if ((fSets[i]).size() != 3) {
                cout << "the fSetsets_Size is not correct" << endl;
            }
            else {
                    GLint firstVertexIndex = (fSets[i])[0];//取出顶点索引
                    GLint secondVertexIndex = (fSets[i])[1];
                    GLint thirdVertexIndex = (fSets[i])[2];
    
                    SV1[0] = (vSets[firstVertexIndex])[0];//第一个顶点
                    SV1[1] = (vSets[firstVertexIndex])[1];
                    SV1[2] = (vSets[firstVertexIndex])[2];
    
                    SV2[0] = (vSets[secondVertexIndex])[0]; //第二个顶点
                    SV2[1] = (vSets[secondVertexIndex])[1];
                    SV2[2] = (vSets[secondVertexIndex])[2];
    
                    SV3[0] = (vSets[thirdVertexIndex])[0]; //第三个顶点
                    SV3[1] = (vSets[thirdVertexIndex])[1];
                    SV3[2] = (vSets[thirdVertexIndex])[2];
    
    
                    GLfloat vec1[3], vec2[3], vec3[3];//计算法向量
                    //(x2-x1,y2-y1,z2-z1)
                    vec1[0] = SV1[0] - SV2[0];
                    vec1[1] = SV1[1] - SV2[1];
                    vec1[2] = SV1[2] - SV2[2];
    
                    //(x3-x2,y3-y2,z3-z2)
                    vec2[0] = SV1[0] - SV3[0];
                    vec2[1] = SV1[1] - SV3[1];
                    vec2[2] = SV1[2] - SV3[2];
    
                    //(x3-x1,y3-y1,z3-z1)
                    vec3[0] = vec1[1] * vec2[2] - vec1[2] * vec2[1];
                    vec3[1] = vec2[0] * vec1[2] - vec2[2] * vec1[0];
                    vec3[2] = vec2[1] * vec1[0] - vec2[0] * vec1[1];
    
                    GLfloat D = sqrt(pow(vec3[0], 2) + pow(vec3[1], 2) + pow(vec3[2], 2));
    
                    VN[0] = vec3[0] / D;
                    VN[1] = vec3[1] / D;
                    VN[2] = vec3[2] / D;
               
                    glNormal3f(VN[0], VN[1], VN[2]);//绘制法向量
    
                    glVertex3f(SV1[0], SV1[1], SV1[2]);//绘制三角面片
                    glVertex3f(SV2[0], SV2[1], SV2[2]);
                    glVertex3f(SV3[0], SV3[1], SV3[2]);    
            }
        }
        glEnd();
    }

    然后我们写一个主函数

    //模型路径
    string filePath = "../data/monkey.obj";
    
    ObjLoader objModel = ObjLoader(filePath);
    //实现移动鼠标观察模型所需变量
    static float c = 3.1415926 / 180.0f;
    static float r = 1.0f;
    static int degree = 90;
    static int oldPosY = -1;
    static int oldPosX = -1;
    
    //安置光源
    void setLightRes() {
        GLfloat lightPosition[] = { 0.0f, 0.0f, 1.0f, 0.0f };
        glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
        glEnable(GL_LIGHTING); //启用光源
        glEnable(GL_LIGHT0);   //使用指定灯光
    }
    
    //初始化
    void init() {
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
        glutInitWindowSize(500, 500);
        glutCreateWindow("ObjLoader");
        glEnable(GL_DEPTH_TEST);
        glShadeModel(GL_SMOOTH);
        setLightRes();
        glEnable(GL_DEPTH_TEST);
    }
    
    void display()
    {
        glColor3f(1.0, 1.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glMatrixMode(GL_MODELVIEW);                            
        glLoadIdentity();                                     
        glTranslatef(0.0f, 0.0f, -5.0f);                                                              
        setLightRes();
        glPushMatrix();
    
        gluLookAt(r*cos(c*degree), 0, r*sin(c*degree), 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    
    
        objModel.Draw();//绘制obj模型
        glPopMatrix();
        glutSwapBuffers();
    }
    
    void reshape(int width, int height)
    {
        glViewport(0, 0, width, height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(60.0f, (GLdouble)width / (GLdouble)height, 1.0f, 200.0f);
        glMatrixMode(GL_MODELVIEW);
    }
    
    //移动鼠标360观察模型
    void moseMove(int button, int state, int x, int y)
    {
        if (state == GLUT_DOWN) {
            oldPosX = x; oldPosY = y;
        }
    }
    void changeViewPoint(int x, int y)
    {
        int temp = x - oldPosX;
        degree += temp;
        oldPosX = x;
        oldPosY = y;
    }
    
    void myIdle()
    {
        glutPostRedisplay();
    }
    
    int main(int argc, char* argv[])
    {
        glutInit(&argc, argv);
        init();
        glutDisplayFunc(display);
        glutReshapeFunc(reshape);
        glutMouseFunc(moseMove);
        glutMotionFunc(changeViewPoint);
        glutIdleFunc(myIdle);
        glutMainLoop();
        return 0;
    }

    这份代码只适合比较简单的obj文件,针对更为复杂的obj文件读取,比如说顶点法向量和纹理的载入等,可在这基础上进行改进。

    运行效果:

     

    转载于:https://www.cnblogs.com/feifanrensheng/p/9416717.html

    展开全文
  • objTo3d-tiles:将obj模型文件转换为三维瓦片
  • QT+OpenGL读obj模型

    2019-02-28 21:42:32
    可以用QT运行,加载OpenGL,读取obj模型,并加载多幅纹理。
  • OBJ模型读取并显示

    2016-03-22 16:37:21
    VS2010行编写的OBJ模型读取。
  • obj模型,java3d加载

    2018-08-13 22:12:16
    通过java3d加载obj模型,已经成功的测试过,都可以加载。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,333
精华内容 1,333
关键字:

obj模型