Filament渲染引擎剖析 之 通过图元构建几何体

什么是图元

图元是构成图形实体的最小单元,可见物体的表面可以由数量不等的三角形拟合而成,常见的图元类型有点、线、三角形等,无论多么复杂的物体外观,一般都可以用这三类基础图元拟合而成。

OpenGL 常见的图元类型: 点、线、条带线、循环线、三角形、三角形条带、三角形扇面。在OpenGL早期版本中多边形与四边形也是OpenGL 基本图元类型,但在OpenGL3.0 后被废弃了。可以看出OpenGL 图元类型越来越简单易用,OpenGL3.0版本可以看作OpenGL崛起的分水岭,逐步向DirextX 靠齐,化繁为简, 把精力重点放在了渲染上。

DirextX 基本图元类型也类似,有 点、线、条带线、循环线、三角形、三角形条带、三角形扇面这几种类型。

图元类型

filament可绘制的图元类型

/**
 * Primitive types
 */
enum class PrimitiveType : uint8_t {
    // don't change the enums values (made to match GL)
    POINTS      = 0,    //!< points
    LINES       = 1,    //!< lines
    TRIANGLES   = 4,    //!< triangles
    NONE        = 0xFF
};

filament 支持 点、线、三角形这三种基础图元的绘制。

构建图元的工具

先用最简单的例子说明 OpenGL 绘制实体的过程,加深点印象,重点看设置渲数据部分,省略shader相关的代码 。虽然filament 支持多种图形API, 但内部的表示大同小异,OpenGL 也不例外。
设置渲染数据
上面的代码,首先使用glGenvertexArrays()生成 VAO 对象,然后生成跟顶点数组关联的缓存对象, 并用顶点数据初始化缓存对象, 加载并编译shader 并使之生效, 使用 glVertexAttribPointer()设置顶点属性,顶点属性可以是位置、颜色、法相等信息,最后启用顶点数组对象VAO。 使用顶点数组对象时,它会记录渲染相关的信息,比如顶点数据源在哪里,纹理设置信息等等。在绘制时不需要反复调用相关的图形API, 只需要启用顶点数据,调用绘制函数即可完成实体的绘制。
绘制实体
glClear()清屏,启用顶点数组对象,调用glDrawArrays() 命令绘制实体。glFlush 会刷新客户端的渲染命令队列,将命令队列拷贝至GPU服务器端的命令队列中,并立即返回。 OpenGL 还有一个函数 glFinish(), 该函数会等待GPU 直到所有绘制命令执行完毕才返回。

除了glDrawArrays() 按顶点顺序绘制的方式,为了复用顶点,OpenGL 还提供了索引形式的绘制方式:

void 
display(void) 
{
	glClear(GL_COLOR_BUFFER_BITS);
    glBindVertexArray(VAO[Triangles]);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Buffers[IndexBuffer]);
    glDrawElements(GL_TRIANGLES,6, GL_UNSIGNED_SHORT, NULL);
    glFlush();
}

filament 使用索引模式(第二种模式)绘制图元, 那么在filament 渲染引擎中用什么来表示以顶点数组、索引数组,图元组成的可绘制对象呢?三个关键类: VertexBuffer, IndexBuffer, Primitive。

我们看看 Cube 这个基体,filament 是如何绘制的:

Cube::Cube(Engine& engine, filament::Material const* material, float3 linearColor, bool culling) :
        mEngine(engine),
        mMaterial(material) {

    mVertexBuffer = VertexBuffer::Builder()
            .vertexCount(8)
            .bufferCount(1)
            .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
            .build(engine);

    mIndexBuffer = IndexBuffer::Builder()
            .indexCount(12*2 + 3*2*6)
            .build(engine);

    if (mMaterial) {
        mMaterialInstanceSolid = mMaterial->createInstance();
        mMaterialInstanceWireFrame = mMaterial->createInstance();
        mMaterialInstanceSolid->setParameter("color", RgbaType::LINEAR,
                LinearColorA{linearColor.r, linearColor.g, linearColor.b, 0.05f});
        mMaterialInstanceWireFrame->setParameter("color", RgbaType::LINEAR,
                LinearColorA{linearColor.r, linearColor.g, linearColor.b, 0.25f});
    }

    mVertexBuffer->setBufferAt(engine, 0,
            VertexBuffer::BufferDescriptor(
                    mVertices, mVertexBuffer->getVertexCount() * sizeof(mVertices[0])));

    mIndexBuffer->setBuffer(engine,
            IndexBuffer::BufferDescriptor(
                    mIndices, mIndexBuffer->getIndexCount() * sizeof(uint32_t)));

    utils::EntityManager& em = utils::EntityManager::get();
    mSolidRenderable = em.create();
    RenderableManager::Builder(1)
            .boundingBox({{ 0, 0, 0 },
                          { 1, 1, 1 }})
            .material(0, mMaterialInstanceSolid)
            .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, mVertexBuffer, mIndexBuffer, 0, 3*2*6)
            .priority(7)
            .culling(culling)
            .build(engine, mSolidRenderable);

    mWireFrameRenderable = em.create();
    RenderableManager::Builder(1)
            .boundingBox({{ 0, 0, 0 },
                          { 1, 1, 1 }})
            .material(0, mMaterialInstanceWireFrame)
            .geometry(0, RenderableManager::PrimitiveType::LINES, mVertexBuffer, mIndexBuffer, WIREFRAME_OFFSET, 24)
            .priority(6)
            .culling(culling)
            .build(engine, mWireFrameRenderable);
}

从以上代码可以看出,使用构建者模式创建 VertexBuffer 和 IndexBuffer 缓存,再通过RenderableManager 的 geometry 方法构建 primitive, 一个geometry 对应一个primitive 对象。

VertexBuffer 类的接口,关键是看构造器类:

VertexBuffer

class UTILS_PUBLIC VertexBuffer : public FilamentAPI {
    struct BuilderDetails;

public:
    using AttributeType = backend::ElementType;
    using BufferDescriptor = backend::BufferDescriptor;

    class Builder : public BuilderBase<BuilderDetails> {
        friend struct BuilderDetails;
    public:
        Builder() noexcept;
        Builder(Builder const& rhs) noexcept;
        Builder(Builder&& rhs) noexcept;
        ~Builder() noexcept;
        Builder& operator=(Builder const& rhs) noexcept;
        Builder& operator=(Builder&& rhs) noexcept;

        /**
         * Defines how many buffers will be created in this vertex buffer set. These buffers are
         * later referenced by index from 0 to \p bufferCount - 1.
         *
         * This call is mandatory. The default is 0.
         *
         * @param bufferCount Number of buffers in this vertex buffer set. The maximum value is 8.
         * @return A reference to this Builder for chaining calls.
         */
        Builder& bufferCount(uint8_t bufferCount) noexcept;

        /**
         * Size of each buffer in the set in vertex.
         *
         * @param vertexCount Number of vertices in each buffer in this set.
         * @return A reference to this Builder for chaining calls.
         */
        Builder& vertexCount(uint32_t vertexCount) noexcept;

        /**
         * Sets up an attribute for this vertex buffer set.
         *
         * Using \p byteOffset and \p byteStride, attributes can be interleaved in the same buffer.
         *
         * @param attribute The attribute to set up.
         * @param bufferIndex  The index of the buffer containing the data for this attribute. Must
         *                     be between 0 and bufferCount() - 1.
         * @param attributeType The type of the attribute data (e.g. byte, float3, etc...)
         * @param byteOffset Offset in *bytes* into the buffer \p bufferIndex
         * @param byteStride Stride in *bytes* to the next element of this attribute. When set to
         *                   zero the attribute size, as defined by \p attributeType is used.
         *
         * @return A reference to this Builder for chaining calls.
         *
         * @warning VertexAttribute::TANGENTS must be specified as a quaternion and is how normals
         *          are specified.
         *
         * @see VertexAttribute
         *
         * This is a no-op if the \p attribute is an invalid enum.
         * This is a no-op if the \p bufferIndex is out of bounds.
         *
         */
        Builder& attribute(VertexAttribute attribute, uint8_t bufferIndex,
                AttributeType attributeType,
                uint32_t byteOffset = 0, uint8_t byteStride = 0) noexcept;

        /**
         * Sets whether a given attribute should be normalized. By default attributes are not
         * normalized. A normalized attribute is mapped between 0 and 1 in the shader. This applies
         * only to integer types.
         *
         * @param attribute Enum of the attribute to set the normalization flag to.
         * @param normalize true to automatically normalize the given attribute.
         * @return A reference to this Builder for chaining calls.
         *
         * This is a no-op if the \p attribute is an invalid enum.
         */
        Builder& normalized(VertexAttribute attribute, bool normalize = true) noexcept;

        /**
         * Creates the VertexBuffer object and returns a pointer to it.
         *
         * @param engine Reference to the filament::Engine to associate this VertexBuffer with.
         *
         * @return pointer to the newly created object or nullptr if exceptions are disabled and
         *         an error occurred.
         *
         * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of
         *            memory or other resources.
         * @exception utils::PreConditionPanic if a parameter to a builder function was invalid.
         */
        VertexBuffer* build(Engine& engine);

    private:
        friend class details::FVertexBuffer;
    };

    /**
     * Returns the vertex count.
     * @return Number of vertices in this vertex buffer set.
     */
    size_t getVertexCount() const noexcept;

    /**
     * Asynchronously copy-initializes the specified buffer from the given buffer data.
     *
     * @param engine Reference to the filament::Engine to associate this VertexBuffer with.
     * @param bufferIndex Index of the buffer to initialize. Must be between 0
     *                    and Builder::bufferCount() - 1.
     * @param buffer A BufferDescriptor representing the data used to initialize the buffer at
     *               index \p bufferIndex. BufferDescriptor points to raw, untyped data that will
     *               be copied as-is into the buffer.
     * @param byteOffset Offset in *bytes* into the buffer at index \p bufferIndex of this vertex
     *                   buffer set.
     */
    void setBufferAt(Engine& engine, uint8_t bufferIndex, BufferDescriptor&& buffer,
            uint32_t byteOffset = 0);

    /**
     * Specifies the quaternion type for the "populateTangentQuaternions" utility.
     */
    enum QuatType {
        HALF4,  //!< 2 bytes per component as half-floats (8 bytes per quat)
        SHORT4, //!< 2 bytes per component as normalized integers (8 bytes per quat)
        FLOAT4, //!< 4 bytes per component as floats (16 bytes per quat)
    };

    /**
     * Specifies the parameters for the "populateTangentQuaternions" utility.
     */
    struct QuatTangentContext {
        QuatType quatType;                      //!< desired quaternion type (required)
        size_t quatCount;                       //!< number of quaternions (required)
        void* outBuffer;                        //!< pre-allocated output buffer (required)
        size_t outStride;                       //!< desired stride in bytes (optional)
        const math::float3* normals;  //!< source normals (required)
        size_t normalsStride;                   //!< normals stride in bytes (optional)
        const math::float4* tangents; //!< source tangents (optional)
        size_t tangentsStride;                  //!< tangents stride in bytes (optional)
    };

    /**
     * Convenience function that consumes normal vectors (and, optionally, tangent vectors) and
     * produces quaternions that can be passed into a TANGENTS buffer.
     *
     * The given output buffer must be preallocated with at least quatCount * outStride bytes.
     *
     * Normals are required but tangents are optional, in which case this function tries to generate
     * reasonable tangents. The given normals should be unit length.
     *
     * If supplied, the tangent vectors should be unit length and should be orthogonal to the
     * normals. The w component of the tangent is a sign (-1 or +1) indicating handedness of the
     * basis.
     *
     * @param ctx An initialized QuatTangentContext structure.
     *
     * @deprecated Instead please use filament::geometry::SurfaceOrientation from libgeometry, it
     * has additional capabilities and a daisy-chain API. Be sure to explicitly link libgeometry
     * since its dependency might be removed in future versions of libfilament.
     */
    static void populateTangentQuaternions(const QuatTangentContext& ctx);
};

} // namespace filament

bufferCount(), vertexCount() , attribute() … ,下面说明这些接口表示的含义。
首先,我们看创建一个cube 顶点应该有哪些属性:
Vertex 属性:
Position0, Position1,Position2 … PositionN;
Color0, Color1, Color2 … ColorN;
Normal0, Normal1, Normal2… NormalN;

如果不考虑优化, 每个顶点属性可以关联一个VBO 缓存对象,按这种形式, 当我们创建 VertexBuffer 对象时,bufferCount(3) , 顶点个数为 VertexCount(N), 使用 attribute 接口描述顶点属性:


enum VertexAttribute : uint8_t {
    // Update hasIntegerTarget() in VertexBuffer when adding an attribute that will
    // be read as integers in the shaders

    POSITION        = 0, //!< XYZ position (float3)
    TANGENTS        = 1, //!< tangent, bitangent and normal, encoded as a quaternion (float4)
    COLOR           = 2, //!< vertex color (float4)
    UV0             = 3, //!< texture coordinates (float2)
    UV1             = 4, //!< texture coordinates (float2)
    BONE_INDICES    = 5, //!< indices of 4 bones, as unsigned integers (uvec4)
    BONE_WEIGHTS    = 6, //!< weights of the 4 bones (normalized float4)
    // -- we have 1 unused slot here --
    CUSTOM0         = 8,
    CUSTOM1         = 9,
    CUSTOM2         = 10,
    CUSTOM3         = 11,
    CUSTOM4         = 12,
    CUSTOM5         = 13,
    CUSTOM6         = 14,
    CUSTOM7         = 15,

    // Aliases for vertex morphing.
    MORPH_POSITION_0 = CUSTOM0,
    MORPH_POSITION_1 = CUSTOM1,
    MORPH_POSITION_2 = CUSTOM2,
    MORPH_POSITION_3 = CUSTOM3,
    MORPH_TANGENTS_0 = CUSTOM4,
    MORPH_TANGENTS_1 = CUSTOM5,
    MORPH_TANGENTS_2 = CUSTOM6,
    MORPH_TANGENTS_3 = CUSTOM7,

    // this is limited by driver::MAX_VERTEX_ATTRIBUTE_COUNT
};

enum class ElementType : uint8_t {
    BYTE,
    BYTE2,
    BYTE3,
    BYTE4,
    UBYTE,
    UBYTE2,
    UBYTE3,
    UBYTE4,
    SHORT,
    SHORT2,
    SHORT3,
    SHORT4,
    USHORT,
    USHORT2,
    USHORT3,
    USHORT4,
    INT,
    UINT,
    FLOAT,
    FLOAT2,
    FLOAT3,
    FLOAT4,
    HALF,
    HALF2,
    HALF3,
    HALF4,
};

   Builder& attribute(VertexAttribute attribute, uint8_t bufferIndex,
                ElementType attributeType,
                uint32_t byteOffset = 0, uint8_t byteStride = 0) noexcept;

可以看到VertexAttribute 是顶点属性的索引, bufferIndex 是 VBO 的索引,attributeType 是数据类型, byteOffset 是取数据的偏移量, byteStride 是相邻顶点数据的偏移量。 那么针对上面三个顶点属性,

attribute 属性可以这样设置:

attribute(POSITION, 0, FLOAT3, 0, 0);
attribute(COLOR, 1,FLOAT3, 0, 0);
attribute(TANGENTS, 2, FLOAT3, 0, 0);
....

filament 在创建VertexBuffer 时 如何解析并生成VBO :

FVertexBuffer::FVertexBuffer(FEngine& engine, const VertexBuffer::Builder& builder)
        : mVertexCount(builder->mVertexCount), mBufferCount(builder->mBufferCount) {
    std::copy(std::begin(builder->mAttributes), std::end(builder->mAttributes), mAttributes.begin());

    mDeclaredAttributes = builder->mDeclaredAttributes;
    uint8_t attributeCount = (uint8_t) mDeclaredAttributes.count();

    AttributeArray attributeArray;

    static_assert(attributeArray.size() == MAX_VERTEX_ATTRIBUTE_COUNT,
            "Attribute and Builder::Attribute arrays must match");

    static_assert(sizeof(Attribute) == sizeof(AttributeData),
            "Attribute and Builder::Attribute must match");

    auto const& declaredAttributes = mDeclaredAttributes;
    auto const& attributes = mAttributes;
    #pragma nounroll
    for (size_t i = 0, n = attributeArray.size(); i < n; ++i) {
        if (declaredAttributes[i]) {
            attributeArray[i].offset = attributes[i].offset;
            attributeArray[i].stride = attributes[i].stride;
            attributeArray[i].buffer = attributes[i].buffer;
            attributeArray[i].type   = attributes[i].type;
            attributeArray[i].flags  = attributes[i].flags;
        }
    }

    // Backends do not (and should not) know the semantics of each vertex attribute, but they
    // need to know whether the vertex shader consumes them as integers or as floats.
    // NOTE: This flag needs to be set regardless of whether the attribute is actually declared.
    attributeArray[BONE_INDICES].flags |= Attribute::FLAG_INTEGER_TARGET;

    FEngine::DriverApi& driver = engine.getDriverApi();
    mHandle = driver.createVertexBuffer(
            mBufferCount, attributeCount, mVertexCount, attributeArray, backend::BufferUsage::STATIC);
}
void OpenGLDriver::createVertexBufferR(
        Handle<HwVertexBuffer> vbh,
        uint8_t bufferCount,
        uint8_t attributeCount,
        uint32_t elementCount,
        AttributeArray attributes,
        BufferUsage usage) {
    DEBUG_MARKER()

    auto& gl = mContext;
    GLVertexBuffer* vb = construct<GLVertexBuffer>(vbh,
            bufferCount, attributeCount, elementCount, attributes);

    GLsizei n = GLsizei(vb->bufferCount);

    assert(n <= (GLsizei)vb->gl.buffers.size());
    glGenBuffers(n, vb->gl.buffers.data());

    for (GLsizei i = 0; i < n; i++) {
        // figure out the size needed for each buffer
        size_t size = 0;
        for (auto const& item : attributes) {
            if (item.buffer == i) {
                size_t end = item.offset + elementCount * item.stride;
                size = std::max(size, end);
            }
        }
        gl.bindBuffer(GL_ARRAY_BUFFER, vb->gl.buffers[i]);
        glBufferData(GL_ARRAY_BUFFER, size, nullptr, getBufferUsage(usage));
    }

    CHECK_GL_ERROR(utils::slog.e)
}

可以看到createVertexBufferR 根据attribute偏移属性,大小, 累加VBO 数组的大小,最终创建符合真正大小的VBO 数组。

每个 VertexBuffer 、 IndexBuffer 最终实例化的时候都会生成一个驱动相关的硬件资源。filament 为了实现并行渲染,解耦了渲染逻辑部分与调用图形API具体绘制部分,在CPU端调用的绘制函数时,只是生成硬件资源句柄,在渲染线程中才真正调用图形API 进行绘制。所以当你阅读代码时经常看到调用 CommandStream 做为绘制命令的转发接口。CommandStream对一般对图形API 的调用封装成两部分, 函数尾带S 只是创建并初始化硬件相关的资源,并将资源内存转换成句柄的形式返回,这一步在调用时立即执行。 当在渲染线程中执行渲染时,调用带R(reality?)的具体函数。

GLVertexBuffer 是 OpenGL 版本的VBO 版本:

struct HwVertexBuffer : public HwBase {
    AttributeArray attributes{};          //   8 * MAX_VERTEX_ATTRIBUTE_COUNT
    uint32_t vertexCount{};               //   4
    uint8_t bufferCount{};                //   1
    uint8_t attributeCount{};             //   1
    uint8_t padding[2]{};                 //   2 -> total struct is 136 bytes

    HwVertexBuffer() noexcept = default;
    HwVertexBuffer(uint8_t bufferCount, uint8_t attributeCount, uint32_t elementCount,
            AttributeArray const& attributes) noexcept
            : attributes(attributes),
              vertexCount(elementCount),
              bufferCount(bufferCount),
              attributeCount(attributeCount) {
    }
};

struct GLVertexBuffer : public backend::HwVertexBuffer {
     using HwVertexBuffer::HwVertexBuffer;
     struct {
         // 4 * MAX_VERTEX_ATTRIBUTE_COUNT bytes
         std::array<GLuint, backend::MAX_VERTEX_ATTRIBUTE_COUNT> buffers{};  // OpenGL VBOs...
     } gl;
 };
mHandle = driver.createVertexBuffer( mBufferCount, attributeCount, mVertexCount, 
	attributeArray, backend::BufferUsage::STATIC);

以上创建多个VBO缓存对应不同的顶点属性的方式,虽然意义明确、直观,但效率不高。一般我们会通过偏移量来创建交错数组的形式来创建顶点数据,这种形式更高效,而且减少 VBO的创建个数。
用交错数组的形式定义 VertexBuffer :

Vertex:

POSITION0, COLOR0, NORMAL0, POSITION1, COLOR1, NORMAL1....POSITIONN,COLORN, NORMALN

按这种形式, 当我们创建 VertexBuffer 对象时,bufferCount(1) , 顶点个数为 VertexCount(N), 使用 attribute 接口描述顶点属性:

attribute(POSITION, 0, FLOAT3, 0, sizeof(FLOAT3)*3);
attribute(COLOR, 0,FLOAT3, sizeof(FLOAT3), sizeof(FLOAT3)*3);
attribute(TANGENTS, 0, FLOAT3, sizeof(FLOAT3)*2, sizeof(FLOAT3)*3);
....

或者 做成分段数据的形式存储为一个VBO
POSITION0, COLOR0, NORMAL0, POSITION1, COLOR1, NORMAL1…POSITIONN,COLORN, NORMALN


P0, P1... PN, C0, C1... CN, N0, N1... NN

attribute(POSITION, 0, FLOAT3, 0, 0);
attribute(COLOR, 0,FLOAT3, color_offset, 0);
attribute(TANGENTS, 0, FLOAT3, normal_offset, 0);
....

IndexBuffer

IndexBuffer 的构建相对于VertexBuffer 的构建简单的多,因为IndexBuffer 只有一个数组,只要指定绘制顶点的个数和数据类型即可计算出ElementBuffer 的大小。

class UTILS_PUBLIC IndexBuffer : public FilamentAPI {
    struct BuilderDetails;

public:
    using BufferDescriptor = backend::BufferDescriptor;

    /**
     * Type of the index buffer
     */
    enum class IndexType : uint8_t {
        USHORT = uint8_t(backend::ElementType::USHORT),  //!< 16-bit indices
        UINT = uint8_t(backend::ElementType::UINT),      //!< 32-bit indices
    };

    class Builder : public BuilderBase<BuilderDetails> {
        friend struct BuilderDetails;
    public:
        Builder() noexcept;
        Builder(Builder const& rhs) noexcept;
        Builder(Builder&& rhs) noexcept;
        ~Builder() noexcept;
        Builder& operator=(Builder const& rhs) noexcept;
        Builder& operator=(Builder&& rhs) noexcept;

        /**
         * Size of the index buffer in elements.
         * @param indexCount Number of indices the IndexBuffer can hold.
         * @return A reference to this Builder for chaining calls.
         */
        Builder& indexCount(uint32_t indexCount) noexcept;

        /**
         * Type of the index buffer, 16-bit or 32-bit.
         * @param indexType Type of indices stored in the IndexBuffer.
         * @return A reference to this Builder for chaining calls.
         */
        Builder& bufferType(IndexType indexType) noexcept;

        /**
         * Creates the IndexBuffer object and returns a pointer to it. After creation, the index
         * buffer is uninitialized. Use IndexBuffer::setBuffer() to initialized the IndexBuffer.
         *
         * @param engine Reference to the filament::Engine to associate this IndexBuffer with.
         *
         * @return pointer to the newly created object or nullptr if exceptions are disabled and
         *         an error occurred.
         *
         * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of
         *            memory or other resources.
         * @exception utils::PreConditionPanic if a parameter to a builder function was invalid.
         *
         * @see IndexBuffer::setBuffer
         */
        IndexBuffer* build(Engine& engine);
    private:
        friend class details::FIndexBuffer;
    };

    /**
     * Asynchronously copy-initializes a region of this IndexBuffer from the data provided.
     *
     * @param engine Reference to the filament::Engine to associate this IndexBuffer with.
     * @param buffer A BufferDescriptor representing the data used to initialize the IndexBuffer.
     *               BufferDescriptor points to raw, untyped data that will be interpreted as
     *               either 16-bit or 32-bits indices based on the Type of this IndexBuffer.
     * @param byteOffset Offset in *bytes* into the IndexBuffer
     */
    void setBuffer(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset = 0);

    /**
     * Returns the size of this IndexBuffer in elements.
     * @return The number of indices the IndexBuffer holds.
     */
    size_t getIndexCount() const noexcept;
};

以上构建出了VBO, EBO, 图元如何构建出来呢?我们看看RenderableManger 构建器geometry 接口:

Primitive

RenderableManager::Builder& RenderableManager::Builder::geometry(size_t index,
        PrimitiveType type, VertexBuffer* vertices, IndexBuffer* indices) noexcept {
    return geometry(index, type, vertices, indices,
            0, 0, vertices->getVertexCount() - 1, indices->getIndexCount());
}

RenderableManager::Builder& RenderableManager::Builder::geometry(size_t index,
        PrimitiveType type, VertexBuffer* vertices, IndexBuffer* indices,
        size_t offset, size_t count) noexcept {
    return geometry(index, type, vertices, indices, offset,
            0, vertices->getVertexCount() - 1, count);
}

RenderableManager::Builder& RenderableManager::Builder::geometry(size_t index,
        PrimitiveType type, VertexBuffer* vertices, IndexBuffer* indices,
        size_t offset, size_t minIndex, size_t maxIndex, size_t count) noexcept {
    std::vector<Entry>& entries = mImpl->mEntries;
    if (index < entries.size()) {
        entries[index].vertices = vertices;
        entries[index].indices = indices;
        entries[index].offset = offset;
        entries[index].minIndex = minIndex;
        entries[index].maxIndex = maxIndex;
        entries[index].count = count;
        entries[index].type = type;
    }
    return *this;
}

看上去比较直观,每个geometry 填充一个entry , 描述primitive 的绘制引用的vbo, ebo , 偏移量,绘制索引范围, 绘制顶点个数,绘制的图元类型。我们再看看entries 是如何展开的,那我们就去构造函数和驱动类driver 中去找具体的实现:

void FRenderPrimitive::init(backend::DriverApi& driver,
        const RenderableManager::Builder::Entry& entry) noexcept {

    assert(entry.materialInstance);

    mHandle = driver.createRenderPrimitive();
    mMaterialInstance = upcast(entry.materialInstance);
    mBlendOrder = entry.blendOrder;

    if (entry.indices && entry.vertices) {
        FVertexBuffer* vertexBuffer = upcast(entry.vertices);
        FIndexBuffer* indexBuffer = upcast(entry.indices);

        AttributeBitset enabledAttributes = vertexBuffer->getDeclaredAttributes();

        auto const& ebh = vertexBuffer->getHwHandle();
        auto const& ibh = indexBuffer->getHwHandle();

        driver.setRenderPrimitiveBuffer(mHandle, ebh, ibh, (uint32_t)enabledAttributes.getValue());
        driver.setRenderPrimitiveRange(mHandle, entry.type,
                (uint32_t)entry.offset, (uint32_t)entry.minIndex, (uint32_t)entry.maxIndex,
                (uint32_t)entry.count);

        mPrimitiveType = entry.type;
        mEnabledAttributes = enabledAttributes;
    }
}

驱动层创建图元接口的实现:

void OpenGLDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph,
        Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
        uint32_t enabledAttributes) {
    DEBUG_MARKER()
    auto& gl = mContext;

    if (rph) {
        GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
        GLVertexBuffer const* const eb = handle_cast<const GLVertexBuffer*>(vbh);
        GLIndexBuffer const* const ib = handle_cast<const GLIndexBuffer*>(ibh);

        assert(ib->elementSize == 2 || ib->elementSize == 4);

        gl.bindVertexArray(&rp->gl);
        CHECK_GL_ERROR(utils::slog.e)

        rp->gl.indicesType = ib->elementSize == 4 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
        rp->maxVertexCount = eb->vertexCount;
        for (size_t i = 0, n = eb->attributes.size(); i < n; i++) {
            if (enabledAttributes & (1U << i)) {
                uint8_t bi = eb->attributes[i].buffer;
                assert(bi != 0xFF);
                gl.bindBuffer(GL_ARRAY_BUFFER, eb->gl.buffers[bi]);
                if (UTILS_UNLIKELY(eb->attributes[i].flags & Attribute::FLAG_INTEGER_TARGET)) {
                    glVertexAttribIPointer(GLuint(i),
                            getComponentCount(eb->attributes[i].type),
                            getComponentType(eb->attributes[i].type),
                            eb->attributes[i].stride,
                            (void*) uintptr_t(eb->attributes[i].offset));
                } else {
                    glVertexAttribPointer(GLuint(i),
                            getComponentCount(eb->attributes[i].type),
                            getComponentType(eb->attributes[i].type),
                            getNormalization(eb->attributes[i].flags & Attribute::FLAG_NORMALIZED),
                            eb->attributes[i].stride,
                            (void*) uintptr_t(eb->attributes[i].offset));
                }

                gl.enableVertexAttribArray(GLuint(i));
            } else {

                // In some OpenGL implementations, we must supply a properly-typed placeholder for
                // every integer input that is declared in the vertex shader.
                if (UTILS_UNLIKELY(eb->attributes[i].flags & Attribute::FLAG_INTEGER_TARGET)) {
                    glVertexAttribI4ui(GLuint(i), 0, 0, 0, 0);
                } else {
                    glVertexAttrib4f(GLuint(i), 0, 0, 0, 0);
                }

                gl.disableVertexAttribArray(GLuint(i));
            }
        }
        // this records the index buffer into the currently bound VAO
        gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->gl.buffer);

        CHECK_GL_ERROR(utils::slog.e)
    }
}

void OpenGLDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
        PrimitiveType pt, uint32_t offset,
        uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
    DEBUG_MARKER()

    if (rph) {
        GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
        rp->type = pt;
        rp->offset = offset * ((rp->gl.indicesType == GL_UNSIGNED_INT) ? 4 : 2);
        rp->count = count;
        rp->minIndex = minIndex;
        rp->maxIndex = maxIndex > minIndex ? maxIndex : rp->maxVertexCount - 1; // sanitize max index
    }
}

注意 handle_cast 这个函数的妙用, 将句柄转换成资源内存地址!

最后我们看看filament 绘制时如何利用这些信息:

void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph) {
    DEBUG_MARKER()
    auto& gl = mContext;

    OpenGLProgram* p = handle_cast<OpenGLProgram*>(state.program);

    // If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
    // during the draw call when the program is invalid. The shader compile error has already been
    // dumped to the console at this point, so it's fine to simply return early.
    if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) {
        return;
    }

    useProgram(p);

    const GLRenderPrimitive* rp = handle_cast<const GLRenderPrimitive *>(rph);
    gl.bindVertexArray(&rp->gl);

    setRasterState(state.rasterState);

    gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);

    setViewportScissor(state.scissor);

    glDrawRangeElements(GLenum(rp->type), rp->minIndex, rp->maxIndex, rp->count,
            rp->gl.indicesType, reinterpret_cast<const void*>(rp->offset));

    CHECK_GL_ERROR(utils::slog.e)
}

这是多么熟悉的场景, 到此filament 创建图元的整个过程剖析完毕。有了这些知识,我们再去创建几何图元就轻车熟路了。

那么我们准备的几何数据是如何装填到VBO , EBO 这些缓存对象中去呢?

  mVertexBuffer->setBufferAt(engine, 0,
            VertexBuffer::BufferDescriptor(
                    mVertices, mVertexBuffer->getVertexCount() * sizeof(mVertices[0])));

    mIndexBuffer->setBuffer(engine,
            IndexBuffer::BufferDescriptor(
                    mIndices, mIndexBuffer->getIndexCount() * sizeof(uint32_t)));

VertexBuffer 可以根据顶点属性的不同创建多个缓冲区VBO,所以更新数据时,要指定更新哪个缓冲区,调用setBufferAt()方法。 索引缓冲区只有一个,直接调用 setBuffer() 方法即可。

Filament引擎是一种现代化的渲染引擎,用于实时渲染图形,特别是用于移动设备和虚拟现实平台。它是由Google公司开发的,旨在为开发者提供高质量、高性能的渲染功能。 Filament引擎的一个重要特点是其卓越的可视效果。它使用基于物理的材质系统,能够模拟光照、阴影、反射等视觉效果,使得渲染出来的图像更加逼真和细腻。同时,Filament引擎还支持更高级的图形功能,如屏幕空间反射、全局光照和泛光效果等,以提供更出色的视觉体验。 除了可视效果外,Filament引擎还注重性能。它采用了先进的渲染技术和算法,以提高渲染速度和效率。该引擎能够在移动设备和虚拟现实平台上运行,而不影响用户体验,并且可以自动适应不同硬件设备的性能和功能。 另外,Filament引擎还提供了灵活的工具和API,使开发者能够更轻松地创建和定制渲染效果。开发者可以使用Filament的材质编辑器和场景编辑器来调整和优化图形效果,也可以通过使用Filament的C++ API来实现更高级的自定义需求。 总之,Filament引擎是一款功能强大、易于使用且性能优越的渲染引擎。它将高质量的视觉效果和高性能相结合,为开发者提供了创建令人惊叹的实时渲染图形的能力。无论是用于移动游戏、虚拟现实应用还是其他图形渲染需求,Filament引擎都能够发挥重要作用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值