引擎设计跟踪(九.14.2a) 导出插件问题修复和 Tangent Space 裂缝修复

时间:2022-06-28 20:11:43

由于工作很忙, 近半年的业余时间没空搞了, 不过工作马上忙完了, 趁十一有时间修了一些小问题.

这次更新跟骨骼动画无关, 修复了一个之前的, 关于tangent space裂缝的问题:

引擎设计跟踪(九) 3DS MAX 导出插件

引擎设计跟踪(九.10) Max插件更新,地形问题备忘

这里说明一下修复方法, 并且做一个总结.

之前的做法都不算错, 但是不完善. 这里有缝, 主要是因为那个战争机器3的模型本身已经复制了顶点( 左半部分和右半部分是不同的mesh, 有重合的顶点),

接缝处的顶点虽然坐标一模一样, 但是他们是"完全不同的两个顶点", 这个不是平滑组, 而是强制分的硬边. 这导致的问题是导出插件没有合并接缝处的法线,

也就是说, 因为接缝处有两个顶点而且法线不一样, 所以不管用不用法线贴图, 都是不光滑,有缝的. 这个问题跟镜像UV没有关系.

解决办法是, 合并(累积)位置相同的顶点的法线. 比如我是方案是用vertex position 作为key:

    struct VertexNormalLess
{
bool operator()(const Vertex& lhs, const Vertex& rhs) const
{
return (lhs.position.x < rhs.position.x)
|| (lhs.position.x == rhs.position.x && lhs.position.y < rhs.position.y)
|| (lhs.position.x == rhs.position.x && lhs.position.y == rhs.position.y && lhs.position.z < rhs.position.z);
}
};
typedef Map<Vertex, VertexNormal, VertexNormalLess> VertexNormalMap;

这样把法线按权重叠加到位置相同的顶点上, 最后即便重复了焊接处的顶点, 他们的法线仍然是唯一的.

总结:

max的顶点一般来说, 除非故意没有焊接的顶点(上面的情况), 顶点都是唯一的. 上面的情况是顶点故意没有焊接, 顶点坐标一样但并不唯一. 至于为什么这么做, 我问过美术, 镜像UV的处理,有的导出插件/引擎没有做特殊处理(复制镜像处的顶点并导出不同的切空间), 所以需要美术手动加硬边.

max的面会引用模型的顶点, 这些面可能有共享顶点, 但uv和法线可能会不一样, 也就是说, 同一个顶点可能会有不同的uv和法线.

  • 如果同一个顶点有不同的法线, 那么如果要做平滑处理, 可以把法线按权重叠加; 否则需要复制顶点, 存储不同的法线.
  • 如果有不同的UV, 那么需要复制顶点.
    比如前两天到一个情况, UV的分布大致如下:
    0.9 --- 1,0 --- 0.1
    A          B        C
    其中B点是UV接缝处, 有两个UV. 如果不复制B顶点, 那么会丢掉一个UV,变成 0.9 --- 0 --- 0.1
    注意0.9到0相当于另一个uv范围, 相当于又贴了一次纹理. 比如我遇到的, 角色的整个脸部, 拉伸以后出现在后脑勺的中线.
    所以储存顶点时, 要复制uv不同的顶点. 比如我的处理是, 以坐标和uv为key:
     inline bool operator<(const Vertex& rhs) const
    {
    const Vertex& lhs = *this; return (lhs.position.x < rhs.position.x)
    || (lhs.position.x == rhs.position.x && lhs.position.y < rhs.position.y)
    || (lhs.position.x == rhs.position.x && lhs.position.y == rhs.position.y && lhs.position.z < rhs.position.z)
    || (lhs.position.x == rhs.position.x && lhs.position.y == rhs.position.y && lhs.position.z == rhs.position.z && lhs.uv[].x < rhs.uv[].x)
    || (lhs.position.x == rhs.position.x && lhs.position.y == rhs.position.y && lhs.position.z == rhs.position.z && lhs.uv[].x == rhs.uv[].x && lhs.uv[].y < rhs.uv[].y);
    }

    我的代码为了简化, 只按第一组UV分组.

  • UV镜像的情况又略微不同, 镜像处的顶点UV通常都一样, 但是顶点所在的两个面, U的方向不一样, 即tangent向量不一样导致tangent space不一样, 也需要复制顶点, 这个之前说过, 不多说了.
  • 因为最终导出的顶点和法线是多对一的, 所以法线和顶点要分开存储, 即上面单独存法线的VertexNormalMap.

最后导出插件加了一步额外的的工作: 先按位置合并顶点, 累积法线, 保证接缝处有唯一的法线

然后就按照镜像(tagent space的手相)复制顶点, 这样接缝处的顶点有相同的法线, 而有不同的切空间.

最后贴一张修复的图和以前的对比:

这个是以前normal map的世界空间法线输出:

引擎设计跟踪(九.14.2a) 导出插件问题修复和 Tangent Space 裂缝修复

这是修复以后的法线, 可以看到脖子处的接缝, 腰带和腰包的缝都没有了:

引擎设计跟踪(九.14.2a) 导出插件问题修复和 Tangent Space 裂缝修复

另外, 修复了之前遗留的几个问题:

引擎设计跟踪(九.2) 3DS MAX 导出插件 继续

以前遇到的IGame的UV不对, 需要手动翻转. 通过仔细阅读文档, 解决了之前的疑惑: 只有GetTexVerts得到的坐标是IGameConversionManager设置的UV坐标系,

但是我用的是GetMapVerts, 它得到的坐标, 文档上说了, 可能是顶点色, 自发光, Alpha, 或者UV, 所以这个MapVerts没有做转换.

    //! Get the active mapping channels
/*! Extracts the active mapping channels in use by the object. Starting with 3DXI V2.0 this returns all active channels
including the standard ones such as Texture Coordinates, Vertex Colors, Illum, and Alpha.
\return A tab containing the active Mapping channels.
*/
virtual Tab<int> GetActiveMapChannelNum() = ;

而且这个MapVerts未必就是UV, 也就是我之前遇到的, 使用GetMapVerts得到的"UV"是坏的,

解决方法是检查IGameTextureMap的UVGen

     //!Access to the Coordinate Rollout
/*!If the developer needs access to the transforms applied to the texture, then this can be accessed here.
\returns A pointer to IGameUVGen
*/
virtual IGameUVGen * GetIGameUVGen()=;

如果IGameUVGen是空的, 那么通常这个通道不是UV. 这个是在网上找到的方法.

另外之前遇到的导出贴图的问题, 一个map没有包含贴图, 而是有子map的问题.

这种情况下, 这个map的贴图字符串是坏的, 需要调用 IsEntitySupported() 跳过.

但是跳过以后, 子map也不能导出了,需要跳过IGame, 直接用Max查找子贴图:

     for (int texMapIdx = ; texMapIdx < numTexMaps; texMapIdx++)
{
IGameTextureMap* map = material->GetIGameTextureMap(texMapIdx);
int mapType = map->GetStdMapSlot();
const tchar* file = map->GetBitmapFileName(); if(stdmat == NULL || !stdmat->MapEnabled(mapType) )
continue; bool mapValid = (map != NULL && map->IsEntitySupported()); //find the first valid sub map if any. TODO: what if multiple sub maps exist?
if( !mapValid && map != NULL )
{
Texmap* maxMap = map->GetMaxTexmap();
int n = maxMap->NumSubTexmaps();
for(int j = ; j < n; ++j)
{
Texmap* subMap = maxMap->GetSubTexmap(j);
if(subMap != NULL && subMap->ClassID() == Class_ID(BMTEX_CLASS_ID, ) && maxMap->SubTexmapOn(j) )
{
file = ((BitmapTex *)subMap)->GetMapName();
mapValid = true;
break;
}
}
} if( !mapValid )
continue;
//....
}

模型导出和切空间目前就到一段落了, 后面继续骨骼和动画导出.

引擎设计跟踪(九.14.2a) 导出插件问题修复和 Tangent Space 裂缝修复的更多相关文章

  1. 引擎设计跟踪&lpar;九&period;14&period;3&period;4&rpar; mile stone 2 - model和fbx导入的补漏

    之前milestone2已经做完的工作, 现在趁有时间记下笔记. 1.设计 这里是指兼容3ds max导出/fbx格式转换等等一系列工作的设计. 最开始, Blade的3dsmax导出插件, 全部代码 ...

  2. 引擎设计跟踪&lpar;九&period;14&period;2j&rpar; TableView工具填坑以及多国语言

    Blade的UI都是预定义的接口, 然后由插件来负责实现, 目前只有MFC的插件. 最近加上了TableView的视图, 用于一些文件的查看和编辑, 比如前面在文件包的笔记中提到需写一个package ...

  3. 引擎设计跟踪&lpar;九&period;14&period;2g&rpar; 将GNUMake集成到Visual Studio

    最近在做纹理压缩工具, 以及数据包的生成. shader编译已经在vs工程里面了, 使用custom build tool, build命令是调用BladeShaderComplier, 并且每个文件 ...

  4. 引擎设计跟踪&lpar;九&period;14&period;2b&rpar; 骨骼动画基本完成

    首先贴一个介绍max的sdk和骨骼动画的文章, 虽然很早的文章, 但是很有用, 感谢前辈们的贡献: 3Ds MAX骨骼动画导出插件编写 1.Dual Quaternion 关于Dual Quatern ...

  5. 引擎设计跟踪&lpar;九&period;14&period;2c&rpar; 最近一些小的更新

    1. bump map与normal map 昨天拿了crytek sponza(http://www.crytek.com/cryengine/cryengine3/downloads)场景测试, ...

  6. 引擎设计跟踪&lpar;九&period;14&period;2f&rpar; 最近更新&colon; OpenGL ES &amp&semi; tools

    之前骨骼动画的IK暂时放一放, 最近在搞GLES的实现. 之前除了GLES没有实现, Android的代码移植已经完毕: [原]跨平台编程注意事项(三): window 到 android 的 移植 ...

  7. 引擎设计跟踪&lpar;九&period;14&period;2 final&rpar; Inverse Kinematics&colon; CCD 在Blade中的实现

    因为工作忙, 好久没有记笔记了, 但是有时候发现还得翻以前的笔记去看, 所以还是尽量记下来备忘. 关于IK, 读了一些paper, 觉得之前翻译的那篇, welman的paper (http://gr ...

  8. 引擎设计跟踪&lpar;九&period;14&period;2i&rpar; Android GLES 3&period;0 完善

    最近把渲染设备对应的GLES的API填上了. 主要有IRenderDevice/IShader/ITexture/IGraphicsResourceManager/IIndexBuffer/IVert ...

  9. 引擎设计跟踪&lpar;九&period;14&period;2e&rpar; DelayLoaded DLLs &lpar;&sol;DELAYLOAD&rpar;

    关于DLL的delay load: http://msdn.microsoft.com/en-us/library/151kt790.aspx 最近在做GLES的shader compiler, 把现 ...

随机推荐

  1. 解决poshytip 表单高度大于屏幕高端 显示问题

    Poshy Tip是一款非常友好的信息提示工具,它基于jQuery,当鼠标滑向链接时,会出现一个信息提示条.信息的内容直接可以在HTML里设定也可以是从服务端调用的数据,该插件还提供了很多属性和方法. ...

  2. 准备上线&comma;切换到master分支&comma;报错

    切换到master分支,准备上线,把上次上线sourceTree保存的修改拉出来: 运行,报错了: *一搜说要删除旧的: 我show in finder 把他删了,然后双击安装 ...

  3. update field

    UPDATE dbo.HotelPolicy  SET HPFactorMark=TB.MarkValue FROM (select HPF.HPFRPolicyId AS ID ,CONVERT(i ...

  4. 陈正冲老师讲c语言之声明和定义的区别

    什么是定义?什么是声明?它们有何区别? 举个例子: A)int i; B)extern int i;(关于extern,后面解释) 哪个是定义?哪个是声明?或者都是定义或者都是声明?我所教过的学生几乎 ...

  5. 【FPGA】高斯白噪声的Verilog实现

    本文章主要讨论高斯白噪声的FPGA实现.简单的方法可以采用在Matlab中产生服从一定均值和方差的I.Q两路噪声信号.然后将两组数据存在FPGA中进行回放,以此来产生高斯白噪声.这种方法优点是产生方法 ...

  6. Mybatis if test 中int判断非空的坑

    Mybatis 中,alarmType 是int类型.如果alarmType 为0的话,条件判断返回结果为false,其它值的话,返回true. <if test="alarmType ...

  7. 结构体变量的 extern 使用方法,转--

    要求如下,在.h文件中这样定义: typedef struct typFNT_GB16     // 汉字字模数据结构 {     signed ];        // 汉字内码索引     ]; ...

  8. 静态初始化块和main方法哪个先被执行?

    直接看代码 public class BlockAndMain { public static void main(String[] args) { System.out.println(" ...

  9. Forms Authentication and Role based Authorization&colon; A Quicker&comma; Simpler&comma; and Correct Approach

    https://www.codeproject.com/Articles/36836/Forms-Authentication-and-Role-based-Authorization Problem ...

  10. asp&period;net Log4Net错误日志个人总结

    1)创建Global.asax protected void Application_Start(object sender, EventArgs e) { log4net.Config.XmlCon ...