mirror of https://github.com/axmolengine/axmol.git
[PROPOSAL] Add wireframe rendering and function classification improvements. (#778)
* Add wireframe rendering and function classification improvements. * Fix platform compilation. * Update CommandBufferGL.cpp GL_LINE and GL_FILL are no present in mobile devices, so the raw value has been used instead. * Update CommandBufferGL.cpp * Try fix IOS compilation * Update CommandBufferMTL.h [skip ci] * GLES & D3D11 wireframe Added crude but okay wireframe mode for GLES and D3D11 devices.
This commit is contained in:
parent
c21408cd73
commit
399dac7478
|
@ -111,8 +111,6 @@ static Texture2D* getDummyTexture()
|
|||
Mesh::Mesh()
|
||||
: _skin(nullptr)
|
||||
, _visible(true)
|
||||
, _isTransparent(false)
|
||||
, _force2DQueue(false)
|
||||
, meshIndexFormat(CustomCommand::IndexFormat::U_SHORT)
|
||||
, _meshIndexData(nullptr)
|
||||
, _blend(BlendFunc::ALPHA_NON_PREMULTIPLIED)
|
||||
|
@ -390,12 +388,13 @@ void Mesh::draw(Renderer* renderer,
|
|||
uint32_t flags,
|
||||
unsigned int lightMask,
|
||||
const Vec4& color,
|
||||
bool forceDepthWrite)
|
||||
bool forceDepthWrite,
|
||||
bool wireframe)
|
||||
{
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
bool isTransparent = (_isTransparent || color.w < 1.f);
|
||||
bool isTransparent = (_material->isTransparent() || color.w < 1.f);
|
||||
float globalZ = isTransparent ? 0 : globalZOrder;
|
||||
if (isTransparent)
|
||||
flags |= Node::FLAGS_RENDER_AS_3D;
|
||||
|
@ -416,8 +415,6 @@ void Mesh::draw(Renderer* renderer,
|
|||
else
|
||||
_material->getStateBlock().setDepthWrite(true);
|
||||
|
||||
_material->getStateBlock().setBlend(_force2DQueue || isTransparent);
|
||||
|
||||
// set default uniforms for Mesh
|
||||
// 'u_color' and others
|
||||
const auto scene = Director::getInstance()->getRunningScene();
|
||||
|
@ -441,7 +438,8 @@ void Mesh::draw(Renderer* renderer,
|
|||
command.init(globalZ, transform);
|
||||
command.setSkipBatching(isTransparent);
|
||||
command.setTransparent(isTransparent);
|
||||
command.set3D(!_force2DQueue);
|
||||
command.set3D(!_material->isForce2DQueue());
|
||||
command.setWireframe(wireframe);
|
||||
}
|
||||
|
||||
_meshIndexData->setPrimitiveType(_material->_drawPrimitive);
|
||||
|
|
|
@ -224,7 +224,8 @@ public:
|
|||
uint32_t flags,
|
||||
unsigned int lightMask,
|
||||
const Vec4& color,
|
||||
bool forceDepthWrite);
|
||||
bool forceDepthWrite,
|
||||
bool wireframe);
|
||||
|
||||
/**skin setter*/
|
||||
void setSkin(MeshSkin* skin);
|
||||
|
@ -239,11 +240,6 @@ public:
|
|||
*/
|
||||
void calculateAABB();
|
||||
|
||||
/**
|
||||
* force set this Mesh renderer to 2D render queue
|
||||
*/
|
||||
void setForce2DQueue(bool force2D) { _force2DQueue = force2D; }
|
||||
|
||||
std::string getTextureFileName() { return _texFile; }
|
||||
|
||||
Mesh();
|
||||
|
@ -257,8 +253,7 @@ protected:
|
|||
std::map<NTextureData::Usage, Texture2D*> _textures; // textures that submesh is using
|
||||
MeshSkin* _skin; // skin
|
||||
bool _visible; // is the submesh visible
|
||||
bool _isTransparent; // is this mesh transparent, it is a property of material in fact
|
||||
bool _force2DQueue; // add this mesh to 2D render queue
|
||||
|
||||
CustomCommand::IndexFormat meshIndexFormat;
|
||||
|
||||
std::string _name;
|
||||
|
|
|
@ -276,7 +276,9 @@ MeshRenderer::MeshRenderer()
|
|||
, _lightMask(-1)
|
||||
, _shaderUsingLight(false)
|
||||
, _forceDepthWrite(false)
|
||||
, _wireframe(false)
|
||||
, _usingAutogeneratedGLProgram(true)
|
||||
, _transparentMaterialHint(false)
|
||||
{}
|
||||
|
||||
MeshRenderer::~MeshRenderer()
|
||||
|
@ -408,7 +410,7 @@ MeshRenderer* MeshRenderer::createMeshRendererNode(NodeData* nodedata, ModelData
|
|||
texParams.sAddressMode = textureData->wrapS;
|
||||
texParams.tAddressMode = textureData->wrapT;
|
||||
tex->setTexParameters(texParams);
|
||||
mesh->_isTransparent = (materialData->getTextureData(NTextureData::Usage::Transparency) != nullptr);
|
||||
_transparentMaterialHint = materialData->getTextureData(NTextureData::Usage::Transparency) != nullptr;
|
||||
}
|
||||
}
|
||||
textureData = materialData->getTextureData(NTextureData::Usage::Normal);
|
||||
|
@ -512,6 +514,7 @@ void MeshRenderer::genMaterial(bool useLight)
|
|||
for (auto&& mesh : _meshes)
|
||||
{
|
||||
auto material = materials[mesh->getMeshIndexData()->getMeshVertexData()];
|
||||
material->setTransparent(_transparentMaterialHint);
|
||||
// keep original state block if exist
|
||||
auto oldmaterial = mesh->getMaterial();
|
||||
if (oldmaterial)
|
||||
|
@ -573,8 +576,7 @@ void MeshRenderer::createNode(NodeData* nodedata, Node* root, const MaterialData
|
|||
texParams.sAddressMode = textureData->wrapS;
|
||||
texParams.tAddressMode = textureData->wrapT;
|
||||
tex->setTexParameters(texParams);
|
||||
mesh->_isTransparent =
|
||||
(materialData->getTextureData(NTextureData::Usage::Transparency) != nullptr);
|
||||
_transparentMaterialHint = materialData->getTextureData(NTextureData::Usage::Transparency) != nullptr;
|
||||
}
|
||||
}
|
||||
textureData = materialData->getTextureData(NTextureData::Usage::Normal);
|
||||
|
@ -811,7 +813,7 @@ void MeshRenderer::draw(Renderer* renderer, const Mat4& transform, uint32_t flag
|
|||
for (auto&& mesh : _meshes)
|
||||
{
|
||||
mesh->draw(renderer, _globalZOrder, transform, flags, _lightMask, Vec4(color.r, color.g, color.b, color.a),
|
||||
_forceDepthWrite);
|
||||
_forceDepthWrite, _wireframe);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -946,14 +948,6 @@ Mesh* MeshRenderer::getMesh() const
|
|||
return _meshes.at(0);
|
||||
}
|
||||
|
||||
void MeshRenderer::setForce2DQueue(bool force2D)
|
||||
{
|
||||
for (const auto& mesh : _meshes)
|
||||
{
|
||||
mesh->setForce2DQueue(force2D);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
MeshRendererCache* MeshRendererCache::_cacheInstance = nullptr;
|
||||
MeshRendererCache* MeshRendererCache::getInstance()
|
||||
|
|
|
@ -178,6 +178,11 @@ public:
|
|||
void setLightMask(unsigned int mask) { _lightMask = mask; }
|
||||
unsigned int getLightMask() const { return _lightMask; }
|
||||
|
||||
/** enables wireframe rendering mode for this mesh renderer only, this can be very useful for debugging and
|
||||
understanding generated meshes. */
|
||||
void setWireframe(bool value) { _wireframe = value; }
|
||||
bool isWireframe() const { return _wireframe; }
|
||||
|
||||
/** render all meshes within this mesh renderer */
|
||||
virtual void draw(Renderer* renderer, const Mat4& transform, uint32_t flags) override;
|
||||
|
||||
|
@ -194,15 +199,11 @@ public:
|
|||
*/
|
||||
void setMaterial(Material* material, int meshIndex);
|
||||
|
||||
/** Adds a new material to a particular mesh in this mesh renderer.
|
||||
* if meshIndex == -1, then it will be applied to all the meshes that belong to this mesh renderer.
|
||||
/** Gets the material of a specific mesh in this mesh renderer.
|
||||
*
|
||||
* @param meshIndex Index of the mesh to apply the material to.
|
||||
* @param meshIndex Index of the mesh to get the material from. 0 is the default index.
|
||||
*/
|
||||
Material* getMaterial(int meshIndex) const;
|
||||
|
||||
/** force render this mesh renderer in 2D queue. */
|
||||
void setForce2DQueue(bool force2D);
|
||||
Material* getMaterial(int meshIndex = 0) const;
|
||||
|
||||
/** Get list of meshes used in this mesh renderer. */
|
||||
const Vector<Mesh*>& getMeshes() const { return _meshes; }
|
||||
|
@ -265,7 +266,9 @@ protected:
|
|||
unsigned int _lightMask;
|
||||
bool _shaderUsingLight; // Is the current shader using lighting?
|
||||
bool _forceDepthWrite; // Always write to depth buffer
|
||||
bool _wireframe; // render in wireframe mode
|
||||
bool _usingAutogeneratedGLProgram;
|
||||
bool _transparentMaterialHint; // Generate transparent materials when building from files
|
||||
|
||||
struct AsyncLoadParam
|
||||
{
|
||||
|
|
|
@ -506,6 +506,18 @@ std::string Material::getName() const
|
|||
return _name;
|
||||
}
|
||||
|
||||
void Material::setTransparent(bool value)
|
||||
{
|
||||
_isTransparent = value;
|
||||
getStateBlock().setBlend(_force2DQueue || _isTransparent);
|
||||
}
|
||||
|
||||
void Material::setForce2DQueue(bool value)
|
||||
{
|
||||
_force2DQueue = value;
|
||||
getStateBlock().setBlend(_force2DQueue || _isTransparent);
|
||||
}
|
||||
|
||||
Material::Material() : _name(""), _currentTechnique(nullptr), _target(nullptr) {}
|
||||
|
||||
Material::~Material() {}
|
||||
|
|
|
@ -155,6 +155,27 @@ public:
|
|||
*/
|
||||
axis::backend::PrimitiveType getPrimitiveType() const { return _drawPrimitive; }
|
||||
|
||||
/**
|
||||
* Enable material transparent rendering.
|
||||
* WARNING: depth testing will not work.
|
||||
*/
|
||||
void setTransparent(bool value);
|
||||
|
||||
/**
|
||||
* Is material transparent?
|
||||
*/
|
||||
bool isTransparent() const { return _isTransparent; }
|
||||
|
||||
/**
|
||||
* Enable material 2D queue rendering.
|
||||
*/
|
||||
void setForce2DQueue(bool value);
|
||||
|
||||
/**
|
||||
* Is material in 2D render queue?
|
||||
*/
|
||||
bool isForce2DQueue() const { return _force2DQueue; }
|
||||
|
||||
protected:
|
||||
Material();
|
||||
~Material();
|
||||
|
@ -189,6 +210,9 @@ protected:
|
|||
std::unordered_map<std::string, int> _textureSlots;
|
||||
int _textureSlotIndex = 0;
|
||||
|
||||
bool _isTransparent = false; // is this mesh transparent.
|
||||
bool _force2DQueue = false; // render meshes using this material in 2D render queue.
|
||||
|
||||
axis::backend::PrimitiveType _drawPrimitive =
|
||||
axis::backend::PrimitiveType::TRIANGLE; // primitive draw type for meshes
|
||||
};
|
||||
|
|
|
@ -92,6 +92,10 @@ public:
|
|||
void set3D(bool value) { _is3D = value; }
|
||||
/**Get the depth by current model view matrix.*/
|
||||
float getDepth() const { return _depth; }
|
||||
/**Whether the command should be rendered in wireframe mode.*/
|
||||
bool isWireframe() const { return _isWireframe; }
|
||||
/**Set wireframe render mode for this command.*/
|
||||
void setWireframe(bool value) { _isWireframe = value; }
|
||||
/// Can use the result to change the descriptor content.
|
||||
inline PipelineDescriptor& getPipelineDescriptor() { return _pipelineDescriptor; }
|
||||
|
||||
|
@ -126,6 +130,9 @@ protected:
|
|||
/** Depth from the model view matrix. */
|
||||
float _depth = 0.f;
|
||||
|
||||
/** Polygon render mode set to LINE, which represents wireframe mode. */
|
||||
bool _isWireframe = false;
|
||||
|
||||
Mat4 _mv;
|
||||
|
||||
PipelineDescriptor _pipelineDescriptor;
|
||||
|
|
|
@ -731,12 +731,13 @@ void Renderer::drawCustomCommand(RenderCommand* command)
|
|||
{
|
||||
_commandBuffer->setIndexBuffer(cmd->getIndexBuffer());
|
||||
_commandBuffer->drawElements(cmd->getPrimitiveType(), cmd->getIndexFormat(), cmd->getIndexDrawCount(),
|
||||
cmd->getIndexDrawOffset());
|
||||
cmd->getIndexDrawOffset(), cmd->isWireframe());
|
||||
_drawnVertices += cmd->getIndexDrawCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandBuffer->drawArrays(cmd->getPrimitiveType(), cmd->getVertexDrawStart(), cmd->getVertexDrawCount());
|
||||
_commandBuffer->drawArrays(cmd->getPrimitiveType(), cmd->getVertexDrawStart(), cmd->getVertexDrawCount(),
|
||||
cmd->isWireframe());
|
||||
_drawnVertices += cmd->getVertexDrawCount();
|
||||
}
|
||||
_drawnBatches++;
|
||||
|
|
|
@ -153,7 +153,10 @@ public:
|
|||
* @param count For each instance, the number of indexes to draw
|
||||
* @see `drawElements(PrimitiveType primitiveType, IndexFormat indexType, unsigned int count, unsigned int offset)`
|
||||
*/
|
||||
virtual void drawArrays(PrimitiveType primitiveType, std::size_t start, std::size_t count) = 0;
|
||||
virtual void drawArrays(PrimitiveType primitiveType,
|
||||
std::size_t start,
|
||||
std::size_t count,
|
||||
bool wireframe = false) = 0;
|
||||
|
||||
/**
|
||||
* Draw primitives with an index list.
|
||||
|
@ -167,7 +170,8 @@ public:
|
|||
virtual void drawElements(PrimitiveType primitiveType,
|
||||
IndexFormat indexType,
|
||||
std::size_t count,
|
||||
std::size_t offset) = 0;
|
||||
std::size_t offset,
|
||||
bool wireframe = false) = 0;
|
||||
|
||||
/**
|
||||
* Do some resources release.
|
||||
|
|
|
@ -143,8 +143,10 @@ public:
|
|||
* @param start For each instance, the first index to draw
|
||||
* @param count For each instance, the number of indexes to draw
|
||||
* @see `drawElements(PrimitiveType primitiveType, IndexFormat indexType, unsigned int count, unsigned int offset)`
|
||||
*
|
||||
* TODO: Implement a wireframe mode for METAL devices. Refer to: https://forums.ogre3d.org/viewtopic.php?t=95089
|
||||
*/
|
||||
virtual void drawArrays(PrimitiveType primitiveType, std::size_t start, std::size_t count) override;
|
||||
virtual void drawArrays(PrimitiveType primitiveType, std::size_t start, std::size_t count, bool wireframe) override;
|
||||
|
||||
/**
|
||||
* Draw primitives with an index list.
|
||||
|
@ -154,11 +156,14 @@ public:
|
|||
* @param offset Byte offset within indexBuffer to start reading indexes from.
|
||||
* @see `setIndexBuffer(Buffer* buffer)`
|
||||
* @see `drawArrays(PrimitiveType primitiveType, unsigned int start, unsigned int count)`
|
||||
*
|
||||
* TODO: Implement a wireframe mode for METAL devices. Refer to: https://forums.ogre3d.org/viewtopic.php?t=95089
|
||||
*/
|
||||
virtual void drawElements(PrimitiveType primitiveType,
|
||||
IndexFormat indexType,
|
||||
std::size_t count,
|
||||
std::size_t offset) override;
|
||||
std::size_t offset,
|
||||
bool wireframe) override;
|
||||
|
||||
/**
|
||||
* Do some resources release.
|
||||
|
|
|
@ -290,7 +290,7 @@ void CommandBufferMTL::setIndexBuffer(Buffer* buffer)
|
|||
[_mtlIndexBuffer retain];
|
||||
}
|
||||
|
||||
void CommandBufferMTL::drawArrays(PrimitiveType primitiveType, std::size_t start, std::size_t count)
|
||||
void CommandBufferMTL::drawArrays(PrimitiveType primitiveType, std::size_t start, std::size_t count, bool wireframe /* unused */)
|
||||
{
|
||||
prepareDrawing();
|
||||
[_mtlRenderEncoder drawPrimitives:toMTLPrimitive(primitiveType) vertexStart:start vertexCount:count];
|
||||
|
@ -299,7 +299,8 @@ void CommandBufferMTL::drawArrays(PrimitiveType primitiveType, std::size_t start
|
|||
void CommandBufferMTL::drawElements(PrimitiveType primitiveType,
|
||||
IndexFormat indexType,
|
||||
std::size_t count,
|
||||
std::size_t offset)
|
||||
std::size_t offset,
|
||||
bool wireframe /* unused */)
|
||||
{
|
||||
prepareDrawing();
|
||||
[_mtlRenderEncoder drawIndexedPrimitives:toMTLPrimitive(primitiveType)
|
||||
|
|
|
@ -211,24 +211,40 @@ void CommandBufferGL::setProgramState(ProgramState* programState)
|
|||
_programState = programState;
|
||||
}
|
||||
|
||||
void CommandBufferGL::drawArrays(PrimitiveType primitiveType, std::size_t start, std::size_t count)
|
||||
void CommandBufferGL::drawArrays(PrimitiveType primitiveType, std::size_t start, std::size_t count, bool wireframe)
|
||||
{
|
||||
prepareDrawing();
|
||||
#ifdef AX_USE_GL // glPolygonMode is only supported in Desktop OpenGL
|
||||
if (wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||
#else
|
||||
if (wireframe) primitiveType = PrimitiveType::LINE;
|
||||
#endif
|
||||
glDrawArrays(UtilsGL::toGLPrimitiveType(primitiveType), start, count);
|
||||
|
||||
#ifdef AX_USE_GL // glPolygonMode is only supported in Desktop OpenGL
|
||||
if (wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
#endif
|
||||
cleanResources();
|
||||
}
|
||||
|
||||
void CommandBufferGL::drawElements(PrimitiveType primitiveType,
|
||||
IndexFormat indexType,
|
||||
std::size_t count,
|
||||
std::size_t offset)
|
||||
std::size_t offset,
|
||||
bool wireframe)
|
||||
{
|
||||
prepareDrawing();
|
||||
#ifdef AX_USE_GL // glPolygonMode is only supported in Desktop OpenGL
|
||||
if (wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||
#else
|
||||
if (wireframe) primitiveType = PrimitiveType::LINE;
|
||||
#endif
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer->getHandler());
|
||||
glDrawElements(UtilsGL::toGLPrimitiveType(primitiveType), count, UtilsGL::toGLIndexType(indexType),
|
||||
(GLvoid*)offset);
|
||||
CHECK_GL_ERROR_DEBUG();
|
||||
#ifdef AX_USE_GL // glPolygonMode is only supported in Desktop OpenGL
|
||||
if (wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
#endif
|
||||
cleanResources();
|
||||
}
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ public:
|
|||
* @param count For each instance, the number of indexes to draw
|
||||
* @see `drawElements(PrimitiveType primitiveType, IndexFormat indexType, unsigned int count, unsigned int offset)`
|
||||
*/
|
||||
virtual void drawArrays(PrimitiveType primitiveType, std::size_t start, std::size_t count) override;
|
||||
virtual void drawArrays(PrimitiveType primitiveType, std::size_t start, std::size_t count, bool wireframe = false) override;
|
||||
|
||||
/**
|
||||
* Draw primitives with an index list.
|
||||
|
@ -153,7 +153,8 @@ public:
|
|||
virtual void drawElements(PrimitiveType primitiveType,
|
||||
IndexFormat indexType,
|
||||
std::size_t count,
|
||||
std::size_t offset) override;
|
||||
std::size_t offset,
|
||||
bool wireframe = false) override;
|
||||
|
||||
/**
|
||||
* Do some resources release.
|
||||
|
|
|
@ -2258,7 +2258,7 @@ int lua_axis_3d_Mesh_draw(lua_State* tolua_S)
|
|||
#endif
|
||||
|
||||
argc = lua_gettop(tolua_S)-1;
|
||||
if (argc == 7)
|
||||
if (argc == 8)
|
||||
{
|
||||
axis::Renderer* arg0;
|
||||
double arg1;
|
||||
|
@ -2267,6 +2267,7 @@ int lua_axis_3d_Mesh_draw(lua_State* tolua_S)
|
|||
unsigned int arg4;
|
||||
axis::Vec4 arg5;
|
||||
bool arg6;
|
||||
bool arg7;
|
||||
|
||||
ok &= luaval_to_object<axis::Renderer>(tolua_S, 2, "ax.Renderer",&arg0, "ax.Mesh:draw");
|
||||
|
||||
|
@ -2281,12 +2282,15 @@ int lua_axis_3d_Mesh_draw(lua_State* tolua_S)
|
|||
ok &= luaval_to_vec4(tolua_S, 7, &arg5, "ax.Mesh:draw");
|
||||
|
||||
ok &= luaval_to_boolean(tolua_S, 8, &arg6, "ax.Mesh:draw");
|
||||
|
||||
ok &= luaval_to_boolean(tolua_S, 9, &arg7, "ax.Mesh:draw");
|
||||
|
||||
if(!ok)
|
||||
{
|
||||
tolua_error(tolua_S,"invalid arguments in function 'lua_axis_3d_Mesh_draw'", nullptr);
|
||||
return 0;
|
||||
}
|
||||
cobj->draw(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
|
||||
cobj->draw(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
||||
lua_settop(tolua_S, 1);
|
||||
return 1;
|
||||
}
|
||||
|
@ -2627,7 +2631,7 @@ int lua_axis_3d_Mesh_setForce2DQueue(lua_State* tolua_S)
|
|||
tolua_error(tolua_S,"invalid arguments in function 'lua_axis_3d_Mesh_setForce2DQueue'", nullptr);
|
||||
return 0;
|
||||
}
|
||||
cobj->setForce2DQueue(arg0);
|
||||
cobj->getMaterial()->setForce2DQueue(arg0);
|
||||
lua_settop(tolua_S, 1);
|
||||
return 1;
|
||||
}
|
||||
|
@ -4480,7 +4484,7 @@ int lua_axis_3d_MeshRenderer_setForce2DQueue(lua_State* tolua_S)
|
|||
tolua_error(tolua_S,"invalid arguments in function 'lua_axis_3d_MeshRenderer_setForce2DQueue'", nullptr);
|
||||
return 0;
|
||||
}
|
||||
cobj->setForce2DQueue(arg0);
|
||||
cobj->getMaterial()->setForce2DQueue(arg0);
|
||||
lua_settop(tolua_S, 1);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -2214,7 +2214,7 @@ MeshRendererClippingTest::MeshRendererClippingTest()
|
|||
auto animation = Animation3D::create("MeshRendererTest/orc.c3b");
|
||||
auto animate = Animate3D::create(animation);
|
||||
mesh3D->runAction(RepeatForever::create(animate));
|
||||
mesh3D->setForce2DQueue(true);
|
||||
mesh3D->getMaterial()->setForce2DQueue(true);
|
||||
}
|
||||
|
||||
MeshRendererClippingTest::~MeshRendererClippingTest() {}
|
||||
|
|
Loading…
Reference in New Issue