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

  • 什么是图元
  • filament可绘制的图元类型
  • 构建图元的工具
    • VertexBuffer
    • IndexBuffer
    • Primitive

什么是图元

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

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,    //!< pointsLINES       = 1,    //!< linesTRIANGLES   = 4,    //!< trianglesNONE        = 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 shadersPOSITION        = 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 nounrollfor (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 buffersize_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_COUNTuint32_t vertexCount{};               //   4uint8_t bufferCount{};                //   1uint8_t attributeCount{};             //   1uint8_t padding[2]{};                 //   2 -> total struct is 136 bytesHwVertexBuffer() 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 bytesstd::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... NNattribute(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 indicesUINT = 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 VAOgl.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渲染引擎剖析 之 通过图元构建几何体相关推荐

  1. Filament 渲染引擎剖析 之 FrameGraph 2 动态构建渲染管线

    一.渲染通道的设计与实现 1 Frostbite 构建FrameGraph的准则 我们先看下Frostbite 构建FrameGraph原则,包括三个阶段: 设置阶段 setup.编译阶段compil ...

  2. Filament 渲染引擎剖析 之 FrameGraph 1 虚拟资源的定义与创建

    Filament 使用了可扩展渲染管线(FrameGraph)来组织渲染通道和管理渲染资源,网上也搜了下可扩展渲染管线的相关的文章,一般认为可扩展渲染管线是次时代渲染引擎应该具备的比较先进的管线组织架 ...

  3. Filament 渲染引擎剖析 之 多线程渲染 2

    Filament 渲染一帧流程 Filament 是一款针对多核硬件系统开发的并行渲染引擎,内部通过多线程的合理调度,尤其在android移动平台上针对大小核心架构做了很多优化设计,比如通过设置线程亲 ...

  4. Filament 渲染引擎剖析 之 创建渲染对象 1

    Filament 渲染对象的创建 Filament 将实体(Entity)属性看成组件(component)的形式,实体可以是相机,光源,可绘制实体, 全局光照(IBL)实体等类型.一个场景包含最多. ...

  5. Filament 渲染引擎剖析 之 ECS模式

    一.什么是 ECS 模式 ECS Entity-Component-System(实体-组件-系统) 是基于组合优于继承原则的一种模式,每一渲染对象都是一个实体,每个实体由一个或者多个组件构成,每个组 ...

  6. pyqt创建窗口没有句柄_Filament 渲染引擎剖析 之 FrameGraph 1 虚拟资源的定义与创建...

    Filament 使用了可扩展渲染管线(FrameGraph)来组织渲染通道和管理渲染资源,网上也搜了下可扩展渲染管线的相关的文章,一般认为可扩展渲染管线是次时代渲染引擎应该具备的比较先进的管线组织架 ...

  7. 【我的渲染技术进阶之旅】基于Filament渲染引擎绘制一个不停旋转的彩色矩形

    一.绘制三角形回顾 在上一篇博客 [我的渲染技术进阶之旅]Google开源的基于物理的实时渲染引擎Filament源码分析:Android版本的Filament第一个示例:sample-hello-t ...

  8. Filament 渲染引擎简介

    Filament 是一款基于物理的实时渲染引擎.引擎核心主要使用C++开发完成.支持平台包括 Android,IOS,Linux,macOS, Windows, and WebGL.尤其对Androi ...

  9. Cryengine渲染引擎剖析

    ms,OpenGPU这些天又冷下去了, 转篇Napoleon314 大牛的分析,排版好乱,见谅,耐心读,这是个好东西,注意看他自己的实现,是个技术狂人啊,Ogre焕发次时代的光芒啊~~~努力 ---- ...

最新文章

  1. vue代码上传服务器后背景图片404解决方法
  2. python 简单网页_Python爬虫 (一):爬取一个简单的静态网页
  3. Android与服务器端数据交互(基于SOAP协议整合android+webservice)
  4. 【学生信息管理系统】——问题篇
  5. linux原理 培训,Linux容器技术原理和使用
  6. Rust 中的继承与代码复用
  7. 收藏 | 神经网络中,设计loss function有哪些技巧?
  8. Nginx+tomcat+memcached缓存共享session
  9. Swift coreAnimation 加计时器写的游戏《飞机大战》
  10. matlab怎么还原默认界面???
  11. 5.4 创建 WBS
  12. mysql 安装与连接数据库_Mysql 安装、登录以及链接数据库
  13. 辨析 dB、dBm、dBw
  14. c# rc4算法,加密解密类
  15. html制作美容热点产品,美容热点产品.html
  16. Android 内存泄露分析
  17. oracle EBS 基于Host并发程序的开发(转)
  18. windows开启休眠
  19. 深度学习入门笔记(二):神经网络基础
  20. [CodeForces908G]New Year and Original Order

热门文章

  1. 多个excel合并到一个excel的不同sheet中
  2. 1602自定义字模方法 CGRAM
  3. in-place运算总结
  4. 【CV】FPN:用于目标检测的特征金字塔网络
  5. 如何对图像进行卷积操作
  6. VMware内CentOS-7-Minimal的安装与配置(详细图文教程)
  7. 数据结构与算法 上机实验报告
  8. 绿色版的谷歌浏览器chrome
  9. Oracle对象——视图之简单视图与视图约束
  10. HTML背景图片和背景渐变