本节介绍软件光栅器的OBJ和MTL文件加载,转载请注明出处。

  在管线的应用程序阶段,我们需要设置光栅器所渲染的模型数据。这些模型数据包括模型顶点的坐标、纹理、法线和材质等等,可以由我们手动编写,也可以通过文件读取。OBJ文件就是一种常用的存储模型数据的文件格式,它内部包含有所有渲染所需的信息。

  比如:

  mtllib myteapot.mtl 表示模型的材质文件名

  v  7.0000 12.0000 -0.0000 顶点位置

  vn 0.9667 -0.2557 0.0105 法线向量

  vt 0.5000 1.9000 0.0000 纹理坐标

  f 1/1/1 2/2/2 3/3/3 f开头的行,表示一个三角面的三个顶点信息索引值,比如1/1/1表示顶点索引1的点,法线信息取vn的第1个,纹理信息取vt的第1个。

  在模型加载时调用相关方法,从设定的路径读取obj文件即可:

    GeometryGenerator::GetInstance()->LoadOBJModel(m_box, "Models/myteapot.obj");
//从obj文件加载茶壶模型 Models/myteapot.obj Models/probeColor.obj

  其中m_box为自定义的网格数据结构GeometryGenerator::MeshData* :

class GeometryGenerator
{
private:
GeometryGenerator() {} public:
static GeometryGenerator* GetInstance()//饿汉
{
static GeometryGenerator instance;
return &instance;
} //基本网络结构:顶点集合+索引集合
struct MeshData
{
std::vector<VertexIn> vertices;
std::vector<UINT> indices;
}; //创建一个立方体:指定宽(X方向)、高(Y方向)、深(Z方向)
void CreateBox(float width, float height, float depth, MeshData &mesh);
void LoadOBJModel(MeshData &mesh, char* filename);
};

  LoadOBJModel()方法就是用于加载OBJ文件的核心方法:

void GeometryGenerator::LoadOBJModel(MeshData &mesh,char* filename){
//载入obj文件,获取顶点纹理法线数据
ObjParser* pObjParser = new ObjParser();
pObjParser->SetFileName(filename);//myteapot.obj probeColor
pObjParser->ReadFileCounts();//统计顶点纹理和法线数mVertexCount、mTexcoordCount等等 //将数据传入顶点、纹理、法线和面数组
// After get enough memory, clear these counts to 0, use it as index
VertexType* vertices = new VertexType[pObjParser->mVertexCount];
VertexType* texcoords = new VertexType[pObjParser->mTexcoordCount];
VertexType* normals = new VertexType[pObjParser->mNormalCount];
FaceType* faces = new FaceType[pObjParser->mFaceCount]; std::ifstream fin;
fin.open(pObjParser->mInFile);
if (fin.fail())
{
pObjParser->mErr << "Open input file : Failed." << std::endl;
return;
}
char input;
char ignore;
int iver = , itex = , inor = , ifac = ;
fin.get(input);
while (!fin.eof())
{
if (input == 'v')
{
fin.get(input);
switch (input)
{
case ' ':
{
fin >> vertices[iver].x >> vertices[iver].y >> vertices[iver].z;//写入顶点数组
//vertices[iver].z *= -1.0f; // RH->LH
iver++;
break;
}
case 't':
{
fin >> texcoords[itex].x >> texcoords[itex].y >>texcoords[itex].z;//写入纹理数组
//texcoords[itex].y = 1.0f - texcoords[itex].y; // RH->LH
itex++;
break;
}
case 'n':
{
fin >> normals[inor].x >> normals[inor].y >> normals[inor].z;//写入法线数组
//normals[inor].z *= -1.0f; // RH->LH
inor++;
break;
}
}
}
if (input == 'f')
{
fin.get(input);
if (input == ' ')
{//写入三角面的数组,换个旋转方向,要不然会被背面剔除
fin >> faces[ifac].vIndex1 >> ignore >> faces[ifac].tIndex1 >> ignore >> faces[ifac].nIndex1;
fin >> faces[ifac].vIndex2 >> ignore >> faces[ifac].tIndex2 >> ignore >> faces[ifac].nIndex2;
fin >> faces[ifac].vIndex3 >> ignore >> faces[ifac].tIndex3 >> ignore >> faces[ifac].nIndex3;
ifac++;
}
}
while (input != '\n')
fin.get(input); fin.get(input);
}
fin.close();
//以上代码将数据读取到vertices、texcoords、normals和faces数组中 mesh.vertices.clear();
mesh.indices.clear();
mesh.vertices.resize(pObjParser->mFaceCount * );
//一共36个索引(每面6个)
mesh.indices.resize(pObjParser->mFaceCount * );
int i = , j = ;
for (; i < pObjParser->mFaceCount * ;){
mesh.indices[i] = i;
mesh.indices[i + ] = i + ;
mesh.indices[i + ] = i + ; mesh.vertices[i].pos.x = vertices[faces[j].vIndex1 - ].x;//绑定顶点坐标
mesh.vertices[i].pos.y = vertices[faces[j].vIndex1 - ].y;
mesh.vertices[i].pos.z = vertices[faces[j].vIndex1 - ].z;
mesh.vertices[i].pos.w = ;
ZCVector tmp1 = mesh.vertices[i].pos;
mesh.vertices[i].tex.u = texcoords[faces[j].tIndex1 - ].x;//绑定纹理坐标
mesh.vertices[i].tex.v = texcoords[faces[j].tIndex1 - ].y;
mesh.vertices[i].normal.x = normals[faces[j].nIndex1 - ].x;//绑定法线
mesh.vertices[i].normal.y = normals[faces[j].nIndex1 - ].y;
mesh.vertices[i].normal.z = normals[faces[j].nIndex1 - ].z;
mesh.vertices[i].normal.w = ;
mesh.vertices[i].color = ZCVector(.f, .f, .f, .f);//绑定颜色
mesh.vertices[i].color = ZCVector(.f, .f, .f, .f);
mesh.vertices[i].color = ZCVector(.f, .f, .f, .f); mesh.vertices[i + ].pos.x = vertices[faces[j].vIndex2 - ].x;
mesh.vertices[i + ].pos.y = vertices[faces[j].vIndex2 - ].y;
mesh.vertices[i + ].pos.z = vertices[faces[j].vIndex2 - ].z;
mesh.vertices[i + ].pos.w = ;
ZCVector tmp2 = mesh.vertices[i+].pos;
mesh.vertices[i + ].tex.u = texcoords[faces[j].tIndex2 - ].x;
mesh.vertices[i + ].tex.v = texcoords[faces[j].tIndex2 - ].y;
mesh.vertices[i + ].normal.x = normals[faces[j].nIndex2 - ].x;
mesh.vertices[i + ].normal.y = normals[faces[j].nIndex2 - ].y;
mesh.vertices[i + ].normal.z = normals[faces[j].nIndex2 - ].z;
mesh.vertices[i + ].normal.w = ;
mesh.vertices[i + ].color = ZCVector(.f, .f, .f, .f);
mesh.vertices[i + ].color = ZCVector(.f, .f, .f, .f);
mesh.vertices[i + ].color = ZCVector(.f, .f, .f, .f); mesh.vertices[i + ].pos.x = vertices[faces[j].vIndex3 - ].x;
mesh.vertices[i + ].pos.y = vertices[faces[j].vIndex3 - ].y;
mesh.vertices[i + ].pos.z = vertices[faces[j].vIndex3 - ].z;
mesh.vertices[i + ].pos.w = ;
ZCVector tmp3 = mesh.vertices[i+].pos;
mesh.vertices[i + ].tex.u = texcoords[faces[j].tIndex3 - ].x;
mesh.vertices[i + ].tex.v = texcoords[faces[j].tIndex3 - ].y;
mesh.vertices[i + ].normal.x = normals[faces[j].nIndex3 - ].x;
mesh.vertices[i + ].normal.y = normals[faces[j].nIndex3 - ].y;
mesh.vertices[i + ].normal.z = normals[faces[j].nIndex3 - ].z;
mesh.vertices[i + ].normal.w = ;
mesh.vertices[i + ].color = ZCVector(.f, .f, .f, .f);
mesh.vertices[i + ].color = ZCVector(.f, .f, .f, .f);
mesh.vertices[i + ].color = ZCVector(.f, .f, .f, .f); i += ;
j++;
}
}

  以上代码,先遍历一遍,将该obj文件的顶点数量、纹理数量等等计算出来,确定各属性数组的大小;然后再遍历一遍obj文件的索引,将将数据读取到vertices、texcoords、normals和faces数组中;然后第三次遍历时,根据f开头的行表示的索引,将顶点信息组装成mesh网格信息。

  其中ReadFileCounts()方法用于遍历一遍顶点,记录下该obj文件的顶点数量、纹理数量等等:

bool ObjParser::ReadFileCounts()
{
char input;
std::ifstream fin;
fin.open(mInFile);
if (fin.fail())
{
mErr << "Open input file : Failed." << std::endl;
return false;
} fin.get(input);
while (!fin.eof())
{
if (input == 'v')
{
fin.get(input);
switch (input)
{
case ' ':
{
mVertexCount++;
break;
}
case 't':
{
mTexcoordCount++;
break;
}
case 'n':
{
mNormalCount++;
break;
}
}
}
if (input == 'f')
{
fin.get(input);
if (input == ' ')
mFaceCount++;
} // otherwise read in the remainder of the line.
while (input != '\n')
fin.get(input); // start reading the beginning of the next line.
fin.get(input);
}
fin.close(); return true;
}

  在导入OBJ模型后,还可以导入mtl材质文件,mtl文件一般可以通过3dsMax生成模型时设置附带生成,记录了模型的材质信息。光栅器中通过该方法导入:

LoadMaterial("Models/myteapot.mtl");//从mtl文件获得bmp纹理贴图的位置,获取模型的材质 3dsmax导出myteapot.mtl

  LoadMaterial()方法的实现:

void BoxDemo::LoadMaterial(char* fileName){
std::stringstream ss;
string mtlfile;
ss << fileName;
ss >> mtlfile;
std::ifstream fin;
std::ofstream mErr;
fin.open(mtlfile);
if (fin.fail())
{
mErr << "Open input file : Failed." << std::endl;
}
char input;
fin.get(input);
while (!fin.eof())
{
if (input == '\t'){
fin.get(input);
}
if (input == 'K')
{
fin.get(input);
switch (input)
{
case 'a':
{
//fin >> vertices[mVertexCount].x >> vertices[mVertexCount].y >> vertices[mVertexCount].z;
fin >> m_material.ambient.x >> m_material.ambient.y >> m_material.ambient.z;
break;
}
case 'd':
{
fin >> m_material.diffuse.x >> m_material.diffuse.y >> m_material.diffuse.z;
break;
}
case 's':
{
fin >> m_material.specular.x >> m_material.specular.y >> m_material.specular.z;
m_material.specular.w = .f;
break;
}
}
}
if (input == 'm'){
fin.get(input); fin.get(input); fin.get(input); fin.get(input); fin.get(input);
if (input == 'd'){//获得漫反射贴图
fin.get(input); fin.get(input);
stringstream ss2;
while (input != '\n'){
ss2 << input;
fin.get(input);
}
ss2 >> bmplocation;
}
} // otherwise read in the remainder of the line.
while (input != '\n')
fin.get(input); // start reading the beginning of the next line.
fin.get(input);
} }

  以上代码通过遍历mtl材质文件,将指向该模型的环境光系数、漫反射系数、镜面反射系数、高光系数和纹理、法线贴图等路径加载到光栅器维护的数据容器中,供之后的PS像素着色器阶段使用。

  myteapot.obj和myteapot.mtl文件最终的渲染效果:

  

 (完)

软件光栅器实现(四、OBJ文件加载)的更多相关文章

  1. php四种文件加载语句

    https://mp.weixin.qq.com/s/Wsn4grDRxVIgMfu__E_oWQ 1.include 2.require 3.include_once 4.require_once ...

  2. 13)PHP,文件加载(include和require)

    有四种文件加载的语法形式(注意,不是函数): include,  include_once,  require, require_once; 他们的本质是一样的,都是用于加载/引入/包含/载入一个外部 ...

  3. 插件化框架解读之so 文件加载机制(四)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 提问 本文的结论是跟着 System.loadlibrary() ...

  4. 软件光栅器实现(二、VS和PS的运作,法线贴图,切空间的计算)

    二.软件光栅器的VS和PS的输入.输出和运作,实现法线贴图效果的版本.转载请注明出处. 这里介绍的VS和PS是实现法线映射的版本,本文仅介绍实现思路,并给出代码供参考.切空间计算.光照模型等相关公式不 ...

  5. Android 的 so 文件加载机制

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 最近碰到一些 so 文件问题,顺便将相关知识点梳理一下. 提问 本文的结论是跟着 System.loadlibrary() 一层层源 ...

  6. 文件加载---理解一个project的第一步

    当我最开始写php的时候,总是担心这个问题:我在这儿new的一个class能加载到对应的类文件吗?毕竟一运行就报Fatal Error,什么**文件没找到,类无法实例化等等是一种很“低级”的错误,怕别 ...

  7. OpenGL OBJ模型加载.

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

  8. Swift语法基础入门四(构造函数, 懒加载)

    Swift语法基础入门四(构造函数, 懒加载) 存储属性 具备存储功能, 和OC中普通属性一样 // Swfit要求我们在创建对象时必须给所有的属性初始化 // 如果没办法保证在构造方法中初始化属性, ...

  9. OGG初始化之将数据从文件加载到Replicat

    要使用Replicat建立目标数据,可以使用初始加载Extract从源表中提取源记录,并将它们以规范格式写入提取文件.从该文件中,初始加载Replicat使用数据库接口加载数据.在加载过程中,更改同步 ...

随机推荐

  1. H5页面移动端IOS键盘收起焦点错位

    出现场景:IOS端,在弹出层点击input时调起键盘页面会被顶上去document.body.scrollOffset大于0,收起键盘时scrollOffset不变,造成焦点错位. 注:安卓手机点击时 ...

  2. IDEAL启动项目的时候报java.lang.NoClassDefFoundError: javax/servlet/Filter错误

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring- ...

  3. spring @Autowired与@Resource的区别

    1.@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上. 2.@Autowired默认按类型装配(这个注解是属业spring的),默认情况下必 ...

  4. Eclipse设置所有新创建文件默认格式为UTF-8

    一.为什么需要设置所有新创建文件默认格式为UTF-8 Eclipse编码默认是ISO-8859-1,不支持中文.而很多时候,我们的文件中含有中文,或者需要在创建文件时就需要是UTF-8编码格式的.在创 ...

  5. Windows+Apache+mod_wsgi+Flask部署方法

    环境:windows7 64bit 1.python版本3.6.5,32位 2.下载Apache,版本httpd-2.4.33-o102o-x86-vc14-r2,32位,vc14编译 3.下载mod ...

  6. Java框架spring 学习笔记(四):BeanPostProcessor接口

    如果我们需要在Spring容器完成Bean的实例化,配置和其他的初始化前后后添加一些自己的逻辑处理. 编写InitHelloWorld.java package com.example.spring; ...

  7. php 安装最新的redis连接扩展

    用于与redis连接的Php扩展[RC表示公测,我们用的是不带RC的稳定版本]下载包地址:http://pecl.php.net/package/redis 最新稳定版本:4.3.0 下载包:# wg ...

  8. vue环境项目启动后因为eslint语法限制报错

    报错太多,截取了一部分. 解决方法找到项目根目录的build 找到webpack.base.conf.js 打开js文件找到下图的位置 再重新启动项目就好了

  9. 电话号自动识别之bug解决汇总

    今天测试一个defect: “联系我们”页显示的电话号码,在不同浏览器显示效果不统一,有些浏览器自动识别电话号码并强制添加了样式. 网络搜索发现,其它website 也有类似问题,例如:http:// ...

  10. Redux 检测状态树变更

    一 概述 Redux只是检测引用是否改变. 如果状态树的某个值是对象.数组等,在reducer中需要生成一个新对象.新数组,才能被Redux检测到变更. let fruits = ['apple',' ...