Skip to content

Commit

Permalink
box2d, bullet: convert to instanced rendering.
Browse files Browse the repository at this point in the history
I didn't expect this to be *so* straightforward.
  • Loading branch information
mosra committed Apr 9, 2020
1 parent 76cab3b commit 6b55f43
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 37 deletions.
3 changes: 2 additions & 1 deletion doc/box2d.dox
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ namespace Magnum {
@m_footernavigation

Builds a pyramid out of cubes and allows you to expand or destroy it by adding
more. Uses the @ref SceneGraph library directly with Box2D.
more. Uses the @ref SceneGraph library directly with Box2D and renders
everything in a single draw call using instanced @ref Shaders::Flag.

@image html box2d.png

Expand Down
4 changes: 3 additions & 1 deletion doc/bullet.dox
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ namespace Magnum {

A rotating table full of cubes that you can shoot down, showcasing the
@ref BulletIntegration library together with @ref SceneGraph using
@ref BulletIntegration::MotionState. It's also possible to visualize various
@ref BulletIntegration::MotionState, together with visualizing various
properties of the Bullet physics world using @ref BulletIntegration::DebugDraw.
Everything is rendered in at most three draw call using instanced
@ref Shaders::Phong.

@image html bullet.png

Expand Down
2 changes: 2 additions & 0 deletions doc/changelog-examples.dox
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ namespace Magnum {
[mosra/magnum-integration#65](https://github.com/mosra/magnum-integration/pull/65))
- Fixed the @ref examples-picking to not limit object count to 256 (see
[mosra/magnum-examples#79](https://github.com/mosra/magnum-examples/pull/79))
- The @ref examples-box2d and @ref examples-bullet examples are now using
instanced rendering to draw the whole scene in a single draw call.

@subsection changelog-examples-latest-bugfixes Bug fixes

Expand Down
49 changes: 35 additions & 14 deletions src/box2d/Box2DExample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
*/

#include <Box2D/Box2D.h>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Utility/Arguments.h>
#include <Magnum/GL/Context.h>
#include <Magnum/GL/DefaultFramebuffer.h>
Expand All @@ -53,6 +54,11 @@ typedef SceneGraph::Scene<SceneGraph::TranslationRotationScalingTransformation2D

using namespace Math::Literals;

struct InstanceData {
Matrix3 transformation;
Color3 color;
};

class Box2DExample: public Platform::Application {
public:
explicit Box2DExample(const Arguments& arguments);
Expand All @@ -64,7 +70,9 @@ class Box2DExample: public Platform::Application {
b2Body* createBody(Object2D& object, const Vector2& size, b2BodyType type, const DualComplex& transformation, Float density = 1.0f);

GL::Mesh _mesh{NoCreate};
GL::Buffer _instanceBuffer{NoCreate};
Shaders::Flat2D _shader{NoCreate};
Containers::Array<InstanceData> _instanceData;

Scene2D _scene;
Object2D* _cameraObject;
Expand All @@ -75,19 +83,16 @@ class Box2DExample: public Platform::Application {

class BoxDrawable: public SceneGraph::Drawable2D {
public:
explicit BoxDrawable(Object2D& object, GL::Mesh& mesh, Shaders::Flat2D& shader, const Color4& color, SceneGraph::DrawableGroup2D& drawables): SceneGraph::Drawable2D{object, &drawables}, _mesh(mesh), _shader(shader), _color{color} {}
explicit BoxDrawable(Object2D& object, Containers::Array<InstanceData>& instanceData, const Color3& color, SceneGraph::DrawableGroup2D& drawables): SceneGraph::Drawable2D{object, &drawables}, _instanceData(instanceData), _color{color} {}

private:
void draw(const Matrix3& transformationMatrix, SceneGraph::Camera2D& camera) override {
_shader
.setTransformationProjectionMatrix(camera.projectionMatrix()*transformationMatrix)
.setColor(_color)
.draw(_mesh);
void draw(const Matrix3& transformation, SceneGraph::Camera2D&) override {
arrayAppend(_instanceData, Containers::InPlaceInit,
transformation, _color);
}

GL::Mesh& _mesh;
Shaders::Flat2D& _shader;
Color4 _color;
Containers::Array<InstanceData>& _instanceData;
Color3 _color;
};

b2Body* Box2DExample::createBody(Object2D& object, const Vector2& halfSize, const b2BodyType type, const DualComplex& transformation, const Float density) {
Expand Down Expand Up @@ -144,14 +149,22 @@ Box2DExample::Box2DExample(const Arguments& arguments): Platform::Application{ar
/* Create the Box2D world with the usual gravity vector */
_world.emplace(b2Vec2{0.0f, -9.81f});

/* Create the shader and the box mesh */
_shader = Shaders::Flat2D{};
/* Create an instanced shader */
_shader = Shaders::Flat2D{
Shaders::Flat2D::Flag::VertexColor|
Shaders::Flat2D::Flag::InstancedTransformation};

/* Box mesh with an (initially empty) instance buffer */
_mesh = MeshTools::compile(Primitives::squareSolid());
_instanceBuffer = GL::Buffer{};
_mesh.addVertexBufferInstanced(_instanceBuffer, 1, 0,
Shaders::Flat2D::TransformationMatrix{},
Shaders::Flat2D::Color3{});

/* Create the ground */
auto ground = new Object2D{&_scene};
createBody(*ground, {11.0f, 0.5f}, b2_staticBody, DualComplex::translation(Vector2::yAxis(-8.0f)));
new BoxDrawable{*ground, _mesh, _shader, 0xa5c9ea_rgbf, _drawables};
new BoxDrawable{*ground, _instanceData, 0xa5c9ea_rgbf, _drawables};

/* Create a pyramid of boxes */
for(std::size_t row = 0; row != 15; ++row) {
Expand All @@ -160,7 +173,7 @@ Box2DExample::Box2DExample(const Arguments& arguments): Platform::Application{ar
const DualComplex transformation = globalTransformation*DualComplex::translation(
{Float(row)*0.6f + Float(item)*1.2f - 8.5f, Float(row)*1.0f - 6.0f});
createBody(*box, {0.5f, 0.5f}, b2_dynamicBody, transformation);
new BoxDrawable{*box, _mesh, _shader, 0x2f83cc_rgbf, _drawables};
new BoxDrawable{*box, _instanceData, 0x2f83cc_rgbf, _drawables};
}
}

Expand All @@ -179,7 +192,7 @@ void Box2DExample::mousePressEvent(MouseEvent& event) {

auto destroyer = new Object2D{&_scene};
createBody(*destroyer, {0.5f, 0.5f}, b2_dynamicBody, DualComplex::translation(position), 2.0f);
new BoxDrawable{*destroyer, _mesh, _shader, 0xffff66_rgbf, _drawables};
new BoxDrawable{*destroyer, _instanceData, 0xffff66_rgbf, _drawables};
}

void Box2DExample::drawEvent() {
Expand All @@ -192,8 +205,16 @@ void Box2DExample::drawEvent() {
.setTranslation({body->GetPosition().x, body->GetPosition().y})
.setRotation(Complex::rotation(Rad(body->GetAngle())));

/* Populate instance data with transformations and colors */
arrayResize(_instanceData, 0);
_camera->draw(_drawables);

/* Upload instance data to the GPU and draw everything in a single call */
_instanceBuffer.setData(_instanceData, GL::BufferUsage::DynamicDraw);
_mesh.setInstanceCount(_instanceData.size());
_shader.setTransformationProjectionMatrix(_camera->projectionMatrix())
.draw(_mesh);

swapBuffers();
redraw();
}
Expand Down
80 changes: 59 additions & 21 deletions src/bullet/BulletExample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*/

#include <btBulletDynamicsCommon.h>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Pointer.h>
#include <Magnum/Timeline.h>
Expand Down Expand Up @@ -60,6 +61,12 @@ using namespace Math::Literals;
typedef SceneGraph::Object<SceneGraph::MatrixTransformation3D> Object3D;
typedef SceneGraph::Scene<SceneGraph::MatrixTransformation3D> Scene3D;

struct InstanceData {
Matrix4 transformationMatrix;
Matrix3x3 normalMatrix;
Color3 color;
};

class BulletExample: public Platform::Application {
public:
explicit BulletExample(const Arguments& arguments);
Expand All @@ -70,8 +77,10 @@ class BulletExample: public Platform::Application {
void mousePressEvent(MouseEvent& event) override;

GL::Mesh _box{NoCreate}, _sphere{NoCreate};
GL::Buffer _boxInstanceBuffer{NoCreate}, _sphereInstanceBuffer{NoCreate};
Shaders::Phong _shader{NoCreate};
BulletIntegration::DebugDraw _debugDraw{NoCreate};
Containers::Array<InstanceData> _boxInstanceData, _sphereInstanceData;

btDbvtBroadphase _bBroadphase;
btDefaultCollisionConfiguration _bCollisionConfig;
Expand All @@ -98,20 +107,17 @@ class BulletExample: public Platform::Application {

class ColoredDrawable: public SceneGraph::Drawable3D {
public:
explicit ColoredDrawable(Object3D& object, Shaders::Phong& shader, GL::Mesh& mesh, const Color4& color, const Matrix4& primitiveTransformation, SceneGraph::DrawableGroup3D& drawables): SceneGraph::Drawable3D{object, &drawables}, _shader(shader), _mesh(mesh), _color{color}, _primitiveTransformation{primitiveTransformation} {}
explicit ColoredDrawable(Object3D& object, Containers::Array<InstanceData>& instanceData, const Color3& color, const Matrix4& primitiveTransformation, SceneGraph::DrawableGroup3D& drawables): SceneGraph::Drawable3D{object, &drawables}, _instanceData(instanceData), _color{color}, _primitiveTransformation{primitiveTransformation} {}

private:
void draw(const Matrix4& transformation, SceneGraph::Camera3D& camera) override {
_shader.setDiffuseColor(_color)
.setTransformationMatrix(transformation*_primitiveTransformation)
.setProjectionMatrix(camera.projectionMatrix())
.setNormalMatrix(transformation.normalMatrix())
.draw(_mesh);
void draw(const Matrix4& transformation, SceneGraph::Camera3D&) override {
const Matrix4 t = transformation*_primitiveTransformation;
arrayAppend(_instanceData, Containers::InPlaceInit,
t, t.normalMatrix(), _color);
}

Shaders::Phong& _shader;
GL::Mesh& _mesh;
Color4 _color;
Containers::Array<InstanceData>& _instanceData;
Color3 _color;
Matrix4 _primitiveTransformation;
};

Expand Down Expand Up @@ -174,15 +180,27 @@ BulletExample::BulletExample(const Arguments& arguments): Platform::Application(
.setProjectionMatrix(Matrix4::perspectiveProjection(35.0_degf, 1.0f, 0.001f, 100.0f))
.setViewport(GL::defaultFramebuffer.viewport().size());

/* Drawing setup */
_box = MeshTools::compile(Primitives::cubeSolid());
_sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32));
_shader = Shaders::Phong{};
/* Create an instanced shader */
_shader = Shaders::Phong{
Shaders::Phong::Flag::VertexColor|
Shaders::Phong::Flag::InstancedTransformation};
_shader.setAmbientColor(0x111111_rgbf)
.setSpecularColor(0x330000_rgbf)
.setLightPosition({10.0f, 15.0f, 5.0f});
_debugDraw = BulletIntegration::DebugDraw{};
_debugDraw.setMode(BulletIntegration::DebugDraw::Mode::DrawWireframe);

/* Box and sphere mesh, with an (initially empty) instance buffer */
_box = MeshTools::compile(Primitives::cubeSolid());
_sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32));
_boxInstanceBuffer = GL::Buffer{};
_sphereInstanceBuffer = GL::Buffer{};
_box.addVertexBufferInstanced(_boxInstanceBuffer, 1, 0,
Shaders::Phong::TransformationMatrix{},
Shaders::Phong::NormalMatrix{},
Shaders::Phong::Color3{});
_sphere.addVertexBufferInstanced(_sphereInstanceBuffer, 1, 0,
Shaders::Phong::TransformationMatrix{},
Shaders::Phong::NormalMatrix{},
Shaders::Phong::Color3{});

/* Setup the renderer so we can draw the debug lines on top */
GL::Renderer::enable(GL::Renderer::Feature::DepthTest);
Expand All @@ -191,12 +209,14 @@ BulletExample::BulletExample(const Arguments& arguments): Platform::Application(
GL::Renderer::setPolygonOffset(2.0f, 0.5f);

/* Bullet setup */
_debugDraw = BulletIntegration::DebugDraw{};
_debugDraw.setMode(BulletIntegration::DebugDraw::Mode::DrawWireframe);
_bWorld.setGravity({0.0f, -10.0f, 0.0f});
_bWorld.setDebugDrawer(&_debugDraw);

/* Create the ground */
auto* ground = new RigidBody{&_scene, 0.0f, &_bGroundShape, _bWorld};
new ColoredDrawable{*ground, _shader, _box, 0xffffff_rgbf,
new ColoredDrawable{*ground, _boxInstanceData, 0xffffff_rgbf,
Matrix4::scaling({4.0f, 0.5f, 4.0f}), _drawables};

/* Create boxes with random colors */
Expand All @@ -207,7 +227,7 @@ BulletExample::BulletExample(const Arguments& arguments): Platform::Application(
auto* o = new RigidBody{&_scene, 1.0f, &_bBoxShape, _bWorld};
o->translate({i - 2.0f, j + 4.0f, k - 2.0f});
o->syncPose();
new ColoredDrawable{*o, _shader, _box,
new ColoredDrawable{*o, _boxInstanceData,
Color3::fromHsv({hue += 137.5_degf, 0.75f, 0.9f}),
Matrix4::scaling(Vector3{0.5f}), _drawables};
}
Expand Down Expand Up @@ -236,8 +256,25 @@ void BulletExample::drawEvent() {
/* Step bullet simulation */
_bWorld.stepSimulation(_timeline.previousFrameDuration(), 5);

/* Draw the cubes */
if(_drawCubes) _camera->draw(_drawables);
if(_drawCubes) {
/* Populate instance data with transformations and colors */
arrayResize(_boxInstanceData, 0);
arrayResize(_sphereInstanceData, 0);
_camera->draw(_drawables);

_shader.setProjectionMatrix(_camera->projectionMatrix());

/* Upload instance data to the GPU (orphaning the previous buffer
contents) and draw all cubes in one call, and all spheres (if any)
in another call */
_boxInstanceBuffer.setData(_boxInstanceData, GL::BufferUsage::DynamicDraw);
_box.setInstanceCount(_boxInstanceData.size());
_shader.draw(_box);

_sphereInstanceBuffer.setData(_sphereInstanceData, GL::BufferUsage::DynamicDraw);
_sphere.setInstanceCount(_sphereInstanceData.size());
_shader.draw(_sphere);
}

/* Debug draw. If drawing on top of cubes, avoid flickering by setting
depth function to <= instead of just <. */
Expand Down Expand Up @@ -310,7 +347,8 @@ void BulletExample::mousePressEvent(MouseEvent& event) {
object->syncPose();

/* Create either a box or a sphere */
new ColoredDrawable{*object, _shader, _shootBox ? _box : _sphere,
new ColoredDrawable{*object,
_shootBox ? _boxInstanceData : _sphereInstanceData,
_shootBox ? 0x880000_rgbf : 0x220000_rgbf,
Matrix4::scaling(Vector3{_shootBox ? 0.5f : 0.25f}), _drawables};

Expand Down

0 comments on commit 6b55f43

Please sign in to comment.