Dust3D开源项目分析——渲染与材质部分 | 纹理生成 Part1
通过阅读源码文件和查阅相关资料,笔者发现Dust 3D使用的图形引擎是(基于Qt的)OpenGL,或者说QOpenGL。就Qt 5.0 而言,QOpenGL是建立在OpenGL ES(Embeded System) 2.0规范上的,可以说是OpenGL ES 3.0的弱化版本。事实上OpenGL ES 2.0是2004年发布的一个相当古老的版本,并不支持部分相对先进的OpenGL方法、数据格式、快捷算法等功能。笔者认为之所以选用2.0版本,一方面是因为Dust 3D是一个跨平台的开源项目,而OpenGL ES 2.0规范基本上被所有移动平台和现代PC平台支持,因此选用QOpenGL可以在很大程度上保证Dust 3D的可移植性,为跨平台开发提供更多便利。另一方面因为Dust 3D本身目的是借助辅助建模、辅助动画等功能加速游戏开发或影视制作流程,所以对渲染方面没有太多的要求,只需要保证软件的交互式操作视窗能够流畅运行即可,即使不用高版本的OpenGL方法也可以实现。
由于渲染模块涉及到多个CPP源文件,笔者会结合不同文件之间的调用关系和重要性对这些文件展开分析。
代码分析
texturetype.h 和 texturetype.cpp
Texturetype文件中定义了Dust 3D所使用的贴图类型和一些转换函数。根据枚举类TextureType,可知这些类型包括None-空类型、 BaseColor-颜色贴图(准确来说应该是Diffuse-漫射贴图)、Normal-法线贴图、Metallic-光泽度贴图、Roughness-粗糙度贴图、Ambient Occlusion-环境遮蔽贴图。Count在后面的函数暂时没有看到被使用,推测是用于计数之类的目的。
enum class TextureType
{
None,
BaseColor,
Normal,
Metallic,
Roughness,
AmbientOcclusion,
Count
};
头文件中有三个转换函数:QString TextureTypeToDispName(TextureType type), const char *TextureTypeToString(TextureType type), TextureType TextureTypeFromString(const char *typeString),用途是TextureType类型和字符串的相互转换。
其中TextureTypeToDispName()将TextureType类型转换成用于界面显示的字符串,调用了tr()函数,例如QObject::tr("Base Color")。tr()函数是Qt中软件国际化的核心功能,只要通过tr()传递将要显示在用户界面上的字符串,Qt就会根据用户的语言环境将字符串替换成翻译人员使用Qt Linguist做好本地化的版本。
其他两个函数是将材质类型字符串和对应的枚举类型相互转换,功能比较简单,这里不再赘述。
QString TextureTypeToDispName(TextureType type)
{
switch (type) {
case TextureType::BaseColor:
return QObject::tr("Base Color");
case TextureType::Normal:
return QObject::tr("Normal Map");
case TextureType::Metallic:
return QObject::tr("Metallic");
case TextureType::Roughness:
return QObject::tr("Roughness");
case TextureType::AmbientOcclusion:
return QObject::tr("Ambient Occlusion");
case TextureType::None:
return QObject::tr("None");
default:
return "";
}
}
const char *TextureTypeToString(TextureType type);
#define IMPL_TextureTypeToString
const char *TextureTypeToString(TextureType type)
{
switch (type) {
case TextureType::BaseColor:
return "BaseColor";
case TextureType::Normal:
return "Normal";
case TextureType::Metallic:
return "Metallic";
case TextureType::Roughness:
return "Roughness";
case TextureType::AmbientOcclusion:
return "AmbientOcclusion";
case TextureType::None:
return "None";
default:
return "";
}
}
TextureType TextureTypeFromString(const char *typeString);
#define IMPL_TextureTypeFromString
TextureType TextureTypeFromString(const char *typeString)
{
QString type = typeString;
if (type == "BaseColor")
return TextureType::BaseColor;
if (type == "Normal")
return TextureType::Normal;
if (type == "Metallic")
return TextureType::Metallic;
if (type == "Metalness")
return TextureType::Metallic;
if (type == "Roughness")
return TextureType::Roughness;
if (type == "AmbientOcclusion")
return TextureType::AmbientOcclusion;
return TextureType::None;
}
#endif
texturegenerator.h 和 texturegenerator.cpp
这两个文件是实现添加纹理这一功能的核心部分。texturegenerator.h对纹理生成器类做了定义。
class TextureGenerator : public QObject
{
Q_OBJECT
public:
TextureGenerator(const Object &object, Snapshot *snapshot=nullptr);
~TextureGenerator();
QImage *takeResultTextureColorImage();
QImage *takeResultTextureNormalImage();
QImage *takeResultTextureRoughnessImage();
QImage *takeResultTextureMetalnessImage();
QImage *takeResultTextureAmbientOcclusionImage();
Object *takeObject();
Model *takeResultMesh();
bool hasTransparencySettings();
void addPartColorMap(QUuid partId, const QImage *image, float tileScale);
void addPartNormalMap(QUuid partId, const QImage *image, float tileScale);
void addPartMetalnessMap(QUuid partId, const QImage *image, float tileScale);
void addPartRoughnessMap(QUuid partId, const QImage *image, float tileScale);
void addPartAmbientOcclusionMap(QUuid partId, const QImage *image, float tileScale);
void generate();
static QImage *combineMetalnessRoughnessAmbientOcclusionImages(QImage *metalnessImage,
QImage *roughnessImage,
QImage *ambientOcclusionImage);
signals:
void finished();
public slots:
void process();
public:
static QColor m_defaultTextureColor;
private:
void prepare();
private:
Object *m_object = nullptr;
QImage *m_resultTextureColorImage = nullptr;
QImage *m_resultTextureNormalImage = nullptr;
QImage *m_resultTextureRoughnessImage = nullptr;
QImage *m_resultTextureMetalnessImage = nullptr;
QImage *m_resultTextureAmbientOcclusionImage = nullptr;
Model *m_resultMesh = nullptr;
std::map<QUuid, std::pair<QImage, float>> m_partColorTextureMap;
std::map<QUuid, std::pair<QImage, float>> m_partNormalTextureMap;
std::map<QUuid, std::pair<QImage, float>> m_partMetalnessTextureMap;
std::map<QUuid, std::pair<QImage, float>> m_partRoughnessTextureMap;
std::map<QUuid, std::pair<QImage, float>> m_partAmbientOcclusionTextureMap;
std::set<QUuid> m_countershadedPartIds;
Snapshot *m_snapshot = nullptr;
bool m_hasTransparencySettings = false;
int m_textureSize = Preferences::instance().textureSize();
};
首先说明一下该类的数据成员。
其中以m_result开头的成员都是处理完成的贴图变量或mesh模型变量。
以m_part***TextureMap命名的成员都是map类型的键值对关联容器,借助一个唯一的QUuid关键字和相应的pair类型值存储对应通道的贴图。其中pair的first成员为QImage类型图像文件,second成员为float类型的纹理平铺尺度数值。
m_object是项目自定义的一种Object对象(详见object.h和object.cpp),它的属性有节点、边、顶点、三角面、法线等几何信息,在生成函数generate()中被使用;m_snapshot是项目使用的快照机制,存储某一时间点软件的画布信息等状态。
布尔变量m_hasTransparencySettings用来标记是否有透明度选项。
m_textureSize是纹理分辨率,m_countershadedPartIds是一个存储QUuid值的集合容器。
看过数据成员后,我们倒回去查看函数成员。函数成员中比较核心的函数有三个:TextureGenerator类的构造函数、预处理函数prepare()以及纹理生成函数generate(),这些函数相对复杂,笔者将在下一篇博客单独进行分析。
TextureGenerator的析构函数比较简单,只对数据成员做一些清理操作。
TextureGenerator::~TextureGenerator()
{
delete m_object;
delete m_resultTextureColorImage;
delete m_resultTextureNormalImage;
delete m_resultTextureRoughnessImage;
delete m_resultTextureMetalnessImage;
delete m_resultTextureAmbientOcclusionImage;
delete m_resultMesh;
delete m_snapshot;
}
下*干以takeResultTexture开头的函数作用是在完成纹理生成后取出相应的结果。因为它们结构完全一样,这里只列举第一个。该函数定义了一个QImage指针,将生成好的颜色贴图传递给它,然后清空m_resultTextureColorImage的值。
QImage *TextureGenerator::takeResultTextureColorImage()
{
QImage *resultTextureColorImage = m_resultTextureColorImage;
m_resultTextureColorImage = nullptr;
return resultTextureColorImage;
}
下面两个函数实现的也是类似的功能,分别传出m_object的值和m_resultMesh的值。
Object *TextureGenerator::takeObject()
{
Object *object = m_object;
m_object = nullptr;
return object;
}
Model *TextureGenerator::takeResultMesh()
{
Model *resultMesh = m_resultMesh;
m_resultMesh = nullptr;
return resultMesh;
}
hasTransparencySettings()返回对应的成员值,用于检查是否有透明度设置。
bool TextureGenerator::hasTransparencySettings()
{
return m_hasTransparencySettings;
}
以addPart**Map命名的函数功能比较相似,这里以addPartColorMap()为例。当传入的图像不为空时,调用make_pair方法以*image和titleScale的值创建新的pair对象,以partId为关键字存储在map容器m_partColorTextureMap中。
void TextureGenerator::addPartColorMap(QUuid partId, const QImage *image, float tileScale)
{
if (nullptr == image)
return;
m_partColorTextureMap[partId] = std::make_pair(*image, tileScale);
}
combineMetalnessRoughnessAmbientOcclusionImages()是一个纹理整合函数,用于把三个贴图整合到同一张图中。能这么处理的依据是,在图像渲染领域中Metalness、Roughness、AO通道贴图一般使用灰度图,每张图只用一个通道进行表达就足够了。因而可以将一张RGB贴图的三个通道分别用于存储Metalness、Roughness、AO,这样可以节省开销。
函数首先检查传入的三张贴图是否为空,然后获取图像尺寸并用255, 255, 0初始化结果图片textureMetalnessRoughnessAmbientOcclusionImage。接着遍历每一个像素,把Metalness贴图、Roughness贴图、AO贴图对应坐标处的灰度值分别放到B通道、G通道、R通道,最后返回textureMetalnessRoughnessAmbientOcclusionImage的值。
QImage *TextureGenerator::combineMetalnessRoughnessAmbientOcclusionImages(QImage *metalnessImage,
QImage *roughnessImage,
QImage *ambientOcclusionImage)
{
QImage *textureMetalnessRoughnessAmbientOcclusionImage = nullptr;
if (nullptr != metalnessImage ||
nullptr != roughnessImage ||
nullptr != ambientOcclusionImage) {
int textureSize = 0;
if (nullptr != metalnessImage)
textureSize = metalnessImage->height();
if (nullptr != roughnessImage)
textureSize = roughnessImage->height();
if (nullptr != ambientOcclusionImage)
textureSize = ambientOcclusionImage->height();
if (textureSize > 0) {
textureMetalnessRoughnessAmbientOcclusionImage = new QImage(textureSize, textureSize, QImage::Format_ARGB32);
textureMetalnessRoughnessAmbientOcclusionImage->fill(QColor(255, 255, 0));
for (int row = 0; row < textureMetalnessRoughnessAmbientOcclusionImage->height(); ++row) {
for (int col = 0; col < textureMetalnessRoughnessAmbientOcclusionImage->width(); ++col) {
QColor color(255, 255, 0);
if (nullptr != metalnessImage)
color.setBlue(qGray(metalnessImage->pixel(col, row)));
if (nullptr != roughnessImage)
color.setGreen(qGray(roughnessImage->pixel(col, row)));
if (nullptr != ambientOcclusionImage)
color.setRed(qGray(ambientOcclusionImage->pixel(col, row)));
textureMetalnessRoughnessAmbientOcclusionImage->setPixelColor(col, row, color);
}
}
}
}
return textureMetalnessRoughnessAmbientOcclusionImage;
}
未完待续...