这篇文章给大家讲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模型中,一个顶点会被多个面使用,故我们用简单的方式来处理,取这点所有面的法线平均.下面是主要代码.  

  //和顶点数组同样长的数组,指定,如果这个数组的下标和顶点数组的下标一样,
//则这数组里存放的数据就是顶点数组里的顶点的关联面数,所有法线长度.
let pIndN = Array.create this.Positions.Count (.f,Vector3.Zero)
//根据索引信息来给对应的顶点,法线,纹理坐标赋值
groups |>List.iter (fun p ->
p.Faces.ForEach(fun face ->
face.Vectexs |> Array.iter(fun vect ->
if vect.PositionIndex > then vect.Position <-this.Positions.[vect.PositionIndex-]
if vect.TexcoordIndex > then vect.Texcoord <- this.Texcoords.[vect.TexcoordIndex-]
if vect.NormalIndex > then vect.Normal <- this.Normals.[vect.NormalIndex-]
)
if this.IsAutoNormal && this.Normals.Count < then
let faceNormal =
let p1 =Vector3.Subtract(face.Vectexs.[].Position, face.Vectexs.[].Position)
let p2 =Vector3.Subtract(face.Vectexs.[].Position, face.Vectexs.[].Position)
Vector3.Cross(p1,p2)
face.Vectexs |> Array.iter(fun v ->
let mutable ind,n = pIndN.[v.PositionIndex - ]
n <- n + faceNormal
pIndN.[v.PositionIndex - ] <- (ind+.f,n)
)
)
let mater = this.Materials.Find(fun m -> m.Name = p.Mtllib)
if box(mater) <> null then
let mitem = mater.Items.Find(fun i -> i.Name = p.Usemtl)
if box(mitem) <> null then
p.Material <- mitem
p.Path <- this.Path
p.IsHaveMaterial <- true
)
if this.IsAutoNormal && this.Normals.Count < then
groups |>List.iter (fun p ->
p.Faces.ForEach(fun face ->
face.Vectexs |> Array.iter(fun v ->
let ind,n = pIndN.[v.PositionIndex - ]
v.Normal <- Vector3.Normalize(n / ind)
v.LinkFace <- int ind
)
)
)

顶点的法线.

 针对原来的处理,增加了十几行的代码,先声明一个和顶点一样长的数组,在这里,我们这样定义,这个数组里存放的数据的下标是和顶点数组中对应顶点的下标一样,这样我们就能直接对应顶点与顶点的共面数,共有法线的信息.相当于天然的HashMap.可以去掉平常算法中的比对过程,如let ind,n = pIndN.[v.PositionIndex - 1]可以直接用自己的下标定位到求得的共面信息与法线总和.

  有个法向量后,我们来完善另一个地方,我们原来是物体一直是放在原点下的,也就是模型坐标系和世界坐标系是重和的,我们如果移动,翻转物体后,他就需要自己的模型坐标系了,用来表示他自己与世界坐标系的对应关系.如下代码.

     let mutable m = Matrix4.Identity
let mutable inv = Matrix4.Identity
let getLazyModelMatrix() =
let tr = Matrix4.CreateTranslation(translation)
let ro = if rotate = Vector3.Zero then Matrix4.Identity else Matrix4.CreateFromAxisAngle(rotate,rotateAngle)
if bFirstRotate then
m <- Matrix4.Mult(tr,ro)
else
m <- Matrix4.Mult(ro,tr)
inv <- Matrix4.Invert(m)
m,inv
member this.IsFirstRotate with get() = bFirstRotate and set(value) = bFirstRotate <- value
member this.Translation
with get() = translation
and set(value) =
translation <- value
getLazyModelMatrix() |> ignore
member this.Rotate
with get() = rotate
and set(value) =
rotate <- value
getLazyModelMatrix() |> ignore
member this.RotateAngle
with get() = rotateAngle
and set(value) =
rotateAngle <- value
getLazyModelMatrix() |> ignore
member this.ModelMatrix with get() = m
member this.InvertMatrix with get() = inv

模型坐标系

  增加一个表示旋转与移动的向量,以及旋转的角度与是否先旋转,先旋转还是先移动生成的模型坐标系是不一样的,注意矩阵相乘的顺序,矩阵不满足交换律的,先后顺序的不同一般会得到不同的矩阵,这里因为在Cg中,最好先经过转置,转置后再乘,就变成如下了,如先R后T,则Matrix4.Mult(T,R),这个顺序非常重要,后面的坐标系变换都要用到.这样我们就生成了模型坐标系,这个坐标系的作用就是把以模型坐标系里的坐标变成世界坐标系的坐标,如果我们需要把世界坐标系的坐标变为模型坐标系的,可以直接用上面的模型坐标系的逆矩阵,具体运行过程大家可以查找相关资料,这些只说下,矩阵与逆矩阵相乘等于单位矩阵,就是对角线都是1,别的位置都是0的矩阵,我们一般定义一个矩阵,默认应该都用单元矩阵.

  这里先说下,3D的变换过程大致如下,物体的坐标(经模型坐标系变换成)世界坐标(经过视图坐标系变换成)视图模型坐标系(经透视矩阵变换成)屏幕上的坐标(这里说下,Z值并有没消失,被非线性插值到-1,1之间).这个具体过程,大家想了解可以查找相关资料.下面看一段具体代码.  

         GL.Clear (ClearBufferMask.ColorBufferBit ||| ClearBufferMask.DepthBufferBit)
//生成一个视图矩阵
let mutable v = Matrix4.LookAt(caram.Eye,caram.Target,Vector3.UnitY)
//定位物体在世界坐标系的位置
model.Translation <- Vector3(.f,.f,.f)
model.Rotate <- Vector3(1.0f,.f,.f)
model.RotateAngle <- float32 (-Math.PI/2.0)
//模型矩阵
let m = model.ModelMatrix
//Cg与HLSL一样,使用是行矩阵,与OpenGL的列矩阵需要转置才能对应.
//如果相应矩阵是传给Cg着色器的,则他在进行相关运行前一定要转置,如果在OpenGL本身运算,则不需要.
m.Transpose()
v.Transpose()
//生成模型视图矩阵
let mv = Matrix4.Mult(v,m)
//启用相关配置
cgContext.EnableProfile()
//得到我们设置的视图矩阵.
let mutable p = Matrix4.Identity
GL.GetFloat(GetPName.ProjectionMatrix,&p)
p.Transpose()
//生成模型视图透视矩阵
let mvp = Matrix4.Mult(p,mv)
//传递值.
cgContext.VertexParameter("mvp").SetMatrix(MatrixToArray1 mvp)
//眼睛的位置由世界坐标转换成模型坐标系.
let modelEye = Vector3.Transform(caram.Eye,model.InvertMatrix)
cgContext.FragmentParameter("eyePosition").Set(modelEye)
//灯光的位置由世界坐标转换成模型坐标系
let modelLight = Vector3.Transform(lightPosition,model.InvertMatrix)
cgContext.FragmentParameter("lightPosition").Set(modelLight)
//针对模型的各参数设置值.
model.Groups |> List.iteri (fun i p ->
if i < then
cgContext.FragmentParameter("Ke").Set(p.Material.Emissive)
cgContext.FragmentParameter("Ka").Set(p.Material.Ambient)
cgContext.FragmentParameter("Kd").Set(p.Material.Diffuse)
cgContext.FragmentParameter("Ks").Set(p.Material.Specular)
cgContext.FragmentParameter("shininess").Set(p.Material.Shiness)
cgContext.FragmentParameter("dtext").SetTexture(p.Material.DiffuseID)
cgContext.FragmentParameter("dtext").EnableTexture()
cgContext.FragmentParameter("maptext").SetTexture(p.Material.BumpID)
cgContext.FragmentParameter("maptext").EnableTexture()
cgContext.UpdateParameter()
//p.DrawVBO(cgContext,cgContext.FragmentParameter("tt"))
p.DrawVBO()
cgContext.FragmentParameter("dtext").DisableTexture()
cgContext.FragmentParameter("maptext").DisableTexture()
)

模型在Cg各参数设置

  这段代码里相关的操作我做了比较详细的注释,在这段代码里,我们没看到相关如加载视图矩阵的操作了,以及针对模型操作调用如GL.Translation,GL.Rotate等操作,这些算法全是我们自己来处理并放入我们写的着色器里来操作,操作的顺序就如上面写的物体的坐标变换的顺序一样,注意矩阵相乘的顺序.过程如果反着来,如世界坐标变成模型坐标,则乘以对应矩阵的逆.

  需要说明的几点是,在Cg操作中,矩阵的顺序与DX是一样的,都是行矩阵,而Opengl用的列矩阵,那么如果我们相应的矩阵以及操作过后的顺序给Cg,那么需要在取出来时就先做转置的操作,把列矩阵顺序变成行矩阵的排列顺序.在着色器语言操作中,各个顶点用的坐标系一定要是同一个坐标系,要么都是模型坐标系,要么都是世界坐标系,要么都是视角坐标系,如果不同,显示的效果可能会与你要得到的效果天差之别,如在上面,我们设置灯的位置在世界坐标里的(0,5,8)处,可以看到代码位置,我们用模型的逆矩阵求把对应的世界坐标变成了模型坐标,如果不调用这句,后面会有啥结果了,大家可以先想一下.

  写到这个,大家一定好奇相应的Cg的顶点着色器与片断着色器的处理了吧.顶点差色器的处理如下:

 void v_main(float4 position : POSITION,
float3 normal : NORMAL,
float2 texCoord : TEXCOORD0,
out float4 oPosition : POSITION,
out float4 objectPos : TEXCOORD0,
out float3 oNormal : TEXCOORD1,
out float2 oTexCoord : TEXCOORD2,
uniform float4x4 modelView,
uniform float4x4 mvp)
{
oPosition = mul(mvp,position);
objectPos = position;
oNormal = normal;
oTexCoord = texCoord;
}

顶点着色器

  顶点差色器我们可以看到后面有一些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一样,用符合人体视角的坐标系.

  上在的顶点着色器处理后,就到我们的片断着色器,代码主要过程如下:

 float3 expand(float3 v)
{
return (v-0.5) * 2.0;
} void f_main(float4 position : TEXCOORD0,
float3 normal : TEXCOORD1,
float2 texCoord : TEXCOORD2,
out float4 color : COLOR,
uniform float3 globalAmbient,
uniform float3 lightColor,
uniform float3 lightPosition,
uniform float3 eyePosition,
uniform float3 Ke,
uniform float3 Ka,
uniform float3 Kd,
uniform float3 Ks,
uniform sampler2D dtext,
uniform sampler2D maptext,
uniform float3 tt,
uniform float shininess
)
{
float3 N = normal;
// Compute emissive term
float3 emissive = Ke;
// Compute ambient term
float3 ambient = Ka * globalAmbient;
// Compute the diffuse term
float3 L = normalize(lightPosition - P);
float diffuseLight = max(dot(L, N), );
float3 diffuse = Kd * lightColor * diffuseLight;
// Compute the specular term
float3 V = normalize(eyePosition - P);
float3 H = normalize(L + V);
float specularLight = pow(max(dot(H, N), ), shininess);
if (diffuseLight <= ) specularLight = ;
float3 specular = Ks * lightColor * specularLight;
//float3 tex =lerp(tex2D(maptext, texCoord).xyz,tex2D(dtext, texCoord).xyz,1.0);
float3 tex = tex2D(dtext, texCoord).xyz;
float3 light = emissive + ambient + diffuse + specular;
color.xyz = light * tex;
// color.xyz = lerp(light,tex,0.5);
// color.xyz = light;
color.w = ;
}

片断着色器.

  在这里,前面的参数也有很多关键词,和前面大部分是一样的,就是在类型没有前缀,后面又带着后缀的,如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来操作。

  知道算法后,我们就可以求得切线了,和求法线一样,需要先求得顶点的各个切线,然后取平均。下面给出主要代码。

                 p.Faces.ForEach(fun face ->
let p10 = face.Vectexs.[].Position - face.Vectexs.[].Position
let p20 = face.Vectexs.[].Position - face.Vectexs.[].Position
let t10 = face.Vectexs.[].Texcoord - face.Vectexs.[].Texcoord
let t20 = face.Vectexs.[].Texcoord - face.Vectexs.[].Texcoord
let T = (t20.Y * p10 - t10.Y * p20) / (t10.X*t20.Y - t10.Y*t20.X)
face.Vectexs |> Array.iter(fun vect ->
let mutable ind,n = pIndT.[vect.PositionIndex - ]
n <- n + T
pIndT.[vect.PositionIndex - ] <- (ind+.f,n)
)
)
if p.Material.BumpMap <> "" then
p.Faces.ForEach(fun face ->
face.Vectexs |> Array.iter(fun v ->
let ind,n = pIndT.[v.PositionIndex - ]
v.Tangent <- Vector3.Normalize(n / ind)
v.LinkFace <- int ind
)
)
)

求切线

  求得切线后,下一步就是写入内存,因为我们使用的VBO,那如何才能传入切线到着色器中了,有二种方式,一种是不用VBO,改用直接用一个一个画三角形,在Face中指定切线,好吧,我最开始就试的这个,直接卡的换的摄像机都动不上了。那第二种也就是继续用VBO,传入的时候我们把切线当颜色传入,然后在着色器里取出来,顶点着色器主要代码如下:  

 void v_main(float4 position : POSITION,
float3 normal : NORMAL,
float2 texCoord : TEXCOORD0,
float4 tangent : COLOR,
out float4 oPosition : POSITION,
out float3 objectPos : TEXCOORD0,
out float3 oNormal : TEXCOORD1,
out float2 oTexCoord : TEXCOORD2,
out float3x3 oTNB : TEXCOORD3,
//out float3 oeyePosition: TEXCOORD3,
//out float3 olightPosition: TEXCOORD4,
//uniform float3 eyePosition,
//uniform float3 lightPosition,
uniform float4x4 mvp)
{
oPosition = mul(mvp,position);
float3 tNormal = normal;
float3 tTangent = tangent.xyz;
float3 tB = cross(tNormal,tTangent);
float3x3 tnb = float3x3(normalize(tTangent),normalize(tB),normalize(tNormal));
oTNB = tnb;
oNormal = normal;
oTexCoord = texCoord;
objectPos = position.xyz; //oPosition = mul(mvp,position);
//float3 tNormal = normal;
//float3 tTangent = tangent.xyz;
//float3 tB = cross(tNormal,tTangent);
//float3x3 tnb = float3x3(normalize(tTangent),normalize(tB),normalize(tNormal));
//oTNB = tnb;
//oTexCoord = texCoord;
//objectPos = mul(tnb,position).xyz;
}

颜色取切线

  片断着色器如下:

 float3 expand(float3 v)
{
return (v-0.5) * 2.0;
}
void f_main(float3 position : TEXCOORD0,
float3 normal : TEXCOORD1,
float2 texCoord : TEXCOORD2,
float3x3 tnb : TEXCOORD3,
//float3 lightPosition : TEXCOORD3,
//float3 eyePosition: TEXCOORD4,
out float4 color : COLOR,
uniform float3 globalAmbient,
uniform float3 lightColor,
uniform float3 Ke,
uniform float3 Ka,
uniform float3 Kd,
uniform float3 Ks,
uniform float3 lightPosition,
uniform float3 eyePosition,
uniform sampler2D dtext,
uniform sampler2D maptext,
uniform float shininess
)
{
float3 P = position;
// float3 normalTex = tex2D(maptext, texCoord).xyz;
// float3 N = expand(normalTex);//normalize(normal); //
//float3 E = mul(tnb,eyePosition);
//float3 Light = mul(tnb,lightPosition);
float3 normalTex = tex2D(maptext, texCoord).xyz;
float3 N =normalize(mul(inverse(tnb),expand(normalTex))); //normalize(normal);
float3 E = eyePosition;
float3 Light = lightPosition;
// Compute emissive term
float3 emissive = Ke;
// Compute ambient term
float3 ambient = Ka * globalAmbient;
// Compute the diffuse term
float3 L = normalize(Light - P);
float diffuseLight = max(dot(L, N), );
float3 diffuse = Kd * lightColor * diffuseLight;
// Compute the specular term
float3 V = normalize(E - P);
float3 H = normalize(L + V);
float specularLight = pow(max(dot(H, N), ), shininess);
if (diffuseLight <= ) specularLight = ;
float3 specular = Ks * lightColor * specularLight;
//float3 tex =lerp(tex2D(maptext, texCoord).xyz,tex2D(dtext, texCoord).xyz,1.0);
float3 tex =tex2D(dtext, texCoord).xyz;//,1.0);
float3 light = emissive + ambient + diffuse + specular;
color.xyz = light * tex;
color.w = ;
}

片断着色器 切线空间

  上面着色器中,分别有一些注释的代码,没注释的是在模型空间运算,注释的是在切线空间运算,效果是一样的。

  其实还有第三种方法,不需要在CPU里计算切线,可以启用几何着色器,几何着色器位与顶点着色器与片断着色器之间,在这中间,可以根据顶点着色器中各变量,计算相应结果,如切线,然后传入片断着色器,因我的机器太旧,启用不起来,以后有机会再试。

  下面放出对比效果图:  

  第一张加上法线贴图,第二张没有,这二张是同一个模型,同样的精度,可以看到法线贴图的模型看起来细节要比没有的高不少。

  下面放出源代码(记的安装Cg Toolkit):引用DLL 代码 模型文件部分1 模型文件部分2 模型文件部分3 和前面一样,其中EDSF前后左右移动,鼠标右键加移动鼠标控制方向,空格上升,空格在SHIFT下降。

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

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

  

  

  

Obj模型功能完善(物体材质,光照,法线贴图).Cg着色语言+OpenTK+F#实现.的更多相关文章

  1. 一键批量添加材质的法线贴图-unity插件

    有时候材质做完后需要更改贴图,或者增加贴图,数量少的时候可以一张张添加和修改,数量多的时候就只能代码生成了.原理是通过名字的关联:主贴图和法线贴图大多数只是后缀的不同上,如果不是那是美术规范没做好啊, ...

  2. 三维引擎导入obj模型全黑总结

    最近有客户试用我们的三维平台,在导入模型的时候,会出现模型全黑和不可见的情况.本文说下全黑的情况. 经过测试,发现可能有如下几种情况. obj 模型没有法线向量 如果obj模型导出的时候没有导出法线向 ...

  3. Unity 着色器训练营(2) - MVP转换和法线贴图

    https://mp.weixin.qq.com/s/Qf4qT15s9bWjbVGh7H32lw 我们刚刚公布了Unity 2018.1中,Unity将会内置可视化编程工具Shader Graph, ...

  4. (转)Unity3D 游戏贴图(法线贴图,漫反射贴图,高光贴图)

    原帖网址http://www.u3dpro.com/read.php?tid=207  感谢jdk900网友的辛苦编写 我们都知道,一个三维场景的画面的好坏,百分之四十取决于模型,百分之六十取决于贴图 ...

  5. WebGL学习之法线贴图

    实际效果请看demo:纹理贴图 为了增加额外细节,提升真实感,我们使用了漫反射贴图和高光贴图,它们都是向三角形进行附加纹理.但是从光的视角来看是表面法线向量使表面被视为平坦光滑的表面.以光照算法的视角 ...

  6. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图 学习目标 理解为什么需要法线贴图: 学习法线贴图如 ...

  7. Unity 通用透明物体漫反射Shader(双面渲染&多光源&光照衰减&法线贴图&凹凸透明度控制)

    Shader "MyUnlit/AlphaBlendDiffuse" { Properties { _Color("Color Tint(贴图染色)",Colo ...

  8. OpenGL OBJ模型加载.

    在我们前面绘制一个屋,我们可以看到,需要每个立方体一个一个的自己来推并且还要处理位置信息.代码量大并且要时间.现在我们通过加载模型文件的方法来生成模型文件,比较流行的3D模型文件有OBJ,FBX,da ...

  9. raycaster选取捕获obj模型&&选中高亮代码

    目录 raycaster选取捕获obj模型&&选中高亮代码 raycaster关键代码 选中高亮代码 obj整体上色 raycaster选取捕获obj模型&&选中高亮代 ...

随机推荐

  1. Retina屏的移动设备如何实现真正1px的线

    前些日子总被人问起 iOS Retina 屏,设置 1px 边框,实际显示 2px,如何解决?原来一直没在意,源于自己根本不是像素眼……今天仔细瞅了瞅原生实现的边框和CSS设置的边框,确实差距不小…… ...

  2. android笔记---百度地图api应用 (一)

    package com.example.bdtest; import com.baidu.mapapi.MKEvent; import com.baidu.mapapi.MKPlanNode; imp ...

  3. [Windows Azure] Windows Azure SQL Database library

    Microsoft Windows Azure SQL Database extends SQL Server capabilities to the cloud. SQL Database offe ...

  4. matlab M文件分析工具使用(Code Analyzer and Profiler)

    Code Analyzer and Profiler Matlab中,对写在m文件(.m文件)里的代码有分析的工具,可以进行优化,这里做一个简单的介绍. Code Analyzer Code Anal ...

  5. 【Java】Java复习笔记-第一部分

    配置java环境变量 JAVA_HOME:配置JDK的目录 CLASSPATH:指定到哪里去找运行时需要用到的类代码(字节码) PATH:指定可执行程序的位置 LINUX系统 (在" .ba ...

  6. 【Socket】linux下http服务器开发

    1.mystery引入 1)超文本传输协议(HTTP)是一种应用于分布式.合作式.多媒体信息系统的应用层协议 2)工作原理 1)客户端一台客户机与服务器建立连接后,会发送一个请求给服务器,请求方式的格 ...

  7. Android getWindow().setFlags方法

    //设置窗体全屏 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams ...

  8. Linux下字符集的安装

    目前环境中经常会遇到编码转化的问题,UTF-8跟GB2312也有问题.只得在Linux上安装GB2312(在Linux操作系统上又称zh_CN.GB2312)的字符集,具体请看下文. Linux下几个 ...

  9. idea 换主题

    换背景 . 选中行变色

  10. FastText算法原理解析

    1. 前言 自然语言处理(NLP)是机器学习,人工智能中的一个重要领域.文本表达是 NLP中的基础技术,文本分类则是 NLP 的重要应用.fasttext是facebook开源的一个词向量与文本分类工 ...