欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Dust3D开源项目分析——渲染与材质部分 | 纹理生成 Part1

程序员文章站 2022-03-26 08:49:13
...

 [email protected]

        通过阅读源码文件和查阅相关资料,笔者发现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;
}

未完待续...