3D顶点转换和法线转换

 

Part1  转换流程之顶点转换

  物体顶点是相对于物体自身坐标系而言的数据,要进行一系列转换才最后显示在屏幕上:

1.物体坐标系到世界坐标系。

  乘以World矩阵,包含了物体的平移旋转和缩放。

2.世界坐标系到相机坐标系。

  乘以View矩阵。

  该矩阵实际上是相机相对于世界坐标系转换的逆矩阵。

  所以相机的世界坐标系位置可以用View矩阵的逆矩阵的平移向量表示出来。

3.相机坐标系到标准视体空间的变换。

  乘以Projection矩阵。

  根据Projection矩阵的不同,有正交投影变换两种结果。

  这步操作的实际意义是将视锥空间内的点变换进入一个标准观察体,从而方便剪裁。

  不同API标准观察体的设置不同:

  DirectX的标准观察体最小拐角(-1,-1,0)最大拐角(1,1,1),

  OpenGL最小拐角(-1,-1,-1)最大拐角(1,1,1),二者区别在于Z的范围不同。

  注意透视变换是非线性变换,会使得w值变化,不再是1。

 

4.完成剪裁后接着对应到屏幕空间。

  这步完成3D到2D的映射,对应成屏幕像素显示。

 

  说明:以上只是对顶点变换的简单介绍,还有很多没有详细讨论的地方。

  因为关于3D流水线的很多地方感觉理解的不是很实在,所以以后有空的时候再总结吧。

 

Part2 转换流程之法向量变换

  光照计算常常需要用法向量,但是法向量的转换和顶点转换并不完全相同

  不同点主要体现在上述过程中的第一步。即从物体坐标系变换到世界坐标系。

  首先法向量表示的是方向,平移是不必要的。(由于法向量多是3D向量或第四位为0,其实变换矩阵的平移部分是不起作用的)。

  那么法向量变换就只剩旋转和缩放。

 

  而法向量的旋转和顶点的旋转保持一致即可,因为转过的角度是相同的。

  但是直接用World矩阵直接对法向量进行变换有可能是不对的!!

 

  为什么呢,主要就是在于缩放有可能不是三个坐标方向的等比例缩放

  因为如果模型被不成比例的缩放,在不同方向上会有不同程度的拉伸或者压缩,顶点的坐标会因而伸缩。

  但是如果对法向量进行同样的伸缩,那么法向量将不再垂直于对应的表面顶点的法向量是该顶点所在几个表面法向量的平均值)。

 

  此时要对法向量进行正确的变换,应该是对其进行相反的缩放(比如xy是1:2缩放,那么法向量xy就要2:1缩放。)

  所以法向量的变换矩阵中缩放矩阵应该是原来顶点变换缩放矩阵逆矩阵

 

  这个怎么做到呢,如前所述,缩放矩阵并不是正交矩阵,不能通过转置来得到逆矩阵。直接对原来的world矩阵求逆的话,旋转的部分也同时被求逆了。但是旋转又要求用原来的旋转。

  但是旋转矩阵是正交矩阵,转置后即可得到逆矩阵。

 

   于是可以采用这样的方法:对World矩阵求逆,这样就同时得到了旋转和缩放的逆,然后进行转置再将旋转矩阵变回来,因为缩放系数在对角线上所以不会有影响。这样得到的变换矩阵就可以用来正确变换法向量了。

  关于转置,可以变换矩阵和向量相乘的顺序,这样就相当于和转置矩阵相乘了。

  所以要做的只是求出逆矩阵,然后变换矩阵和向量原来的相乘顺序。

 

  注意就是因为法向量变换的问题是由于非等比例缩放引起的,如果原来的World矩阵三个方向缩放系数相等,那么用原来的World矩阵变换法向量是没有问题的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要合并多个OBJ模型并处理顶点法线、纹理坐标信息,可以采用以下步骤: 1. 将多个OBJ文件加载到内存中,可以使用第三方库如Assimp.NET进行加载。 2. 遍历每个OBJ文件的顶点法线和纹理坐标信息,将它们存储到一个大的顶点缓冲区中。 3. 对于每个OBJ文件的面信息,将其转换为三角形,并计算每个三角形的法线向量。 4. 对于每个OBJ文件的材质信息,将其转换为纹理,并将纹理ID存储到顶点缓冲区中。 5. 将所有的顶点法线、纹理坐标信息组成一个大的顶点数组,并将其存储到一个新的OBJ文件中。 6. 将新的OBJ文件保存到硬盘中,可以使用第三方库如ObjLoader进行保存。 以下是一个示例代码: ``` using System.Collections.Generic; using Assimp; using ObjLoader.Loader.Loaders; using ObjLoader.Loader.Data.Elements; using ObjLoader.Loader.Data.VertexData; using ObjLoader.Loader.Loaders.Loaders; // 加载多个OBJ文件 List<Mesh> meshes = new List<Mesh>(); var importer = new AssimpContext(); foreach (var objFile in objFiles) { var scene = importer.ImportFile(objFile); var mesh = scene.Meshes[0]; meshes.Add(mesh); } // 合并顶点法线和纹理坐标信息 List<VertexPositionNormalTexture> vertices = new List<VertexPositionNormalTexture>(); List<uint> indices = new List<uint>(); foreach (var mesh in meshes) { for (int i = 0; i < mesh.VertexCount; i++) { var vertex = mesh.Vertices[i]; var normal = mesh.Normals[i]; var textureCoordinate = mesh.TextureCoordinateChannels[0][i]; vertices.Add(new VertexPositionNormalTexture( new Vector3(vertex.X, vertex.Y, vertex.Z), new Vector3(normal.X, normal.Y, normal.Z), new Vector2(textureCoordinate.X, textureCoordinate.Y))); } for (int i = 0; i < mesh.FaceCount; i++) { var face = mesh.Faces[i]; indices.Add(face.Indices[0]); indices.Add(face.Indices[1]); indices.Add(face.Indices[2]); } } // 将顶点法线和纹理信息存储到新的OBJ文件中 var objData = new Obj(); for (int i = 0; i < vertices.Count; i++) { var vertex = vertices[i]; objData.Vertices.Add(new Vertex(vertex.Position.X, vertex.Position.Y, vertex.Position.Z)); objData.Normals.Add(new Normal(vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z)); objData.TextureVertices.Add(new TextureVertex(vertex.TextureCoordinate.X, vertex.TextureCoordinate.Y)); objData.Groups.Add(new Group("Group", true, i * 3, i * 3 + 2)); } var objExporter = new ObjExporter(); objExporter.Export(objData, "merged.obj"); ``` 这段代码使用Assimp.NET库加载每个OBJ文件,然后遍历每个文件的顶点法线和纹理坐标信息,并将它们存储到一个大的顶点数组中。接着,遍历每个文件的面信息,将其转换为三角形,并计算每个三角形的法线向量。最后,将所有的顶点法线、纹理坐标信息组成一个大的顶点数组,并将其存储到一个新的OBJ文件中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值