diff --git a/CMakeLists.txt b/CMakeLists.txt index 15933f20..c0cdcf32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,6 +257,7 @@ set(SCENE_HEADERS src/scene/node/lightnode.h src/scene/node/modelnodescenenode.h src/scene/node/modelscenenode.h + src/scene/node/particlenode.h src/scene/node/scenenode.h src/scene/pipeline/control.h src/scene/pipeline/world.h @@ -270,6 +271,7 @@ set(SCENE_SOURCES src/scene/node/lightnode.cpp src/scene/node/modelnodescenenode.cpp src/scene/node/modelscenenode.cpp + src/scene/node/particlenode.cpp src/scene/node/scenenode.cpp src/scene/pipeline/control.cpp src/scene/pipeline/world.cpp diff --git a/src/render/emitter.h b/src/render/emitter.h index 56c4efa5..21785162 100644 --- a/src/render/emitter.h +++ b/src/render/emitter.h @@ -40,9 +40,16 @@ public: BillboardToWorldZ }; -public: + template + struct Constraints { + T start; + T mid; + T end; + }; + UpdateType updateType() const { return _updateType; } RenderType renderType() const { return _renderType; } + int renderOrder() const { return _renderOrder; } std::shared_ptr texture() const { return _texture; } int gridWidth() const { return _gridWidth; } @@ -51,7 +58,9 @@ public: int frameEnd() const { return _frameEnd; } const glm::vec2 &size() const { return _size; } - float sizeStart() const { return _sizeStart; } + const Constraints &particleSize() const { return _particleSize; } + const Constraints &color() const { return _color; } + const Constraints &alpha() const { return _alpha; } int birthrate() const { return _birthrate; } int lifeExpectancy() const { return _lifeExpectancy; } float velocity() const { return _velocity; } @@ -60,6 +69,7 @@ public: private: UpdateType _updateType { UpdateType::Invalid }; RenderType _renderType { RenderType::Invalid }; + int _renderOrder { 0 }; std::shared_ptr _texture; int _gridWidth { 0 }; @@ -68,12 +78,15 @@ private: int _frameEnd { 0 }; glm::vec2 _size { 0.0f }; - float _sizeStart { 0.0f }; int _birthrate { 0 }; /**< rate of particle birth per second */ int _lifeExpectancy { 0 }; /**< life of each particle in seconds */ float _velocity { 0.0f }; float _randomVelocity { 0.0f }; + Constraints _particleSize; + Constraints _color; + Constraints _alpha; + friend class MdlFile; }; diff --git a/src/render/mesh/billboard.cpp b/src/render/mesh/billboard.cpp index 8c3567c0..e180d802 100644 --- a/src/render/mesh/billboard.cpp +++ b/src/render/mesh/billboard.cpp @@ -24,10 +24,10 @@ namespace reone { namespace render { static vector g_vertices = { - -0.5f, 0.0f, -0.5f, 0.0f, 0.0f, - 0.5f, 0.0f, -0.5f, 1.0f, 0.0f, - 0.5f, 0.0f, 0.5f, 1.0f, 1.0f, - -0.5f, 0.0f, 0.5f, 0.0f, 1.0f + -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, + -0.5f, 0.5f, 0.0f, 0.0f, 1.0f }; static vector g_indices = { diff --git a/src/render/model/mdlfile.cpp b/src/render/model/mdlfile.cpp index 1feedf37..28308516 100644 --- a/src/render/model/mdlfile.cpp +++ b/src/render/model/mdlfile.cpp @@ -58,6 +58,8 @@ enum class ControllerType { Position = 8, Orientation = 20, Color = 76, + AlphaEnd = 80, + AlphaStart = 84, Radius_Birthrate = 88, SelfIllumColor = 100, FrameEnd = 108, @@ -66,9 +68,15 @@ enum class ControllerType { Alpha = 132, Multiplier_RandomVelocity = 140, SizeStart = 144, + SizeEnd = 148, Velocity = 168, SizeX = 172, - SizeY = 176 + SizeY = 176, + AlphaMid = 216, + SizeMid = 232, + ColorMid = 284, + ColorEnd = 380, + ColorStart = 392 }; MdlFile::MdlFile(GameVersion version) : BinaryFile(kSignatureSize, kSignature), _version(version) { @@ -320,11 +328,6 @@ void MdlFile::readControllers(uint32_t keyCount, uint32_t keyOffset, const vecto readRandomVelocityController(dataIndex, data, node); } break; - case ControllerType::SizeStart: - if (node._flags & kNodeHasEmitter) { - readSizeStartController(dataIndex, data, node); - } - break; case ControllerType::Velocity: if (node._flags & kNodeHasEmitter) { readVelocityController(dataIndex, data, node); @@ -340,6 +343,55 @@ void MdlFile::readControllers(uint32_t keyCount, uint32_t keyOffset, const vecto readSizeYController(dataIndex, data, node); } break; + + case ControllerType::SizeStart: + if (node._flags & kNodeHasEmitter) { + readSizeStartController(dataIndex, data, node); + } + break; + case ControllerType::SizeMid: + if (node._flags & kNodeHasEmitter) { + readSizeMidController(dataIndex, data, node); + } + break; + case ControllerType::SizeEnd: + if (node._flags & kNodeHasEmitter) { + readSizeEndController(dataIndex, data, node); + } + break; + + case ControllerType::ColorStart: + if (node._flags & kNodeHasEmitter) { + readColorStartController(dataIndex, data, node); + } + break; + case ControllerType::ColorMid: + if (node._flags & kNodeHasEmitter) { + readColorMidController(dataIndex, data, node); + } + break; + case ControllerType::ColorEnd: + if (node._flags & kNodeHasEmitter) { + readColorEndController(dataIndex, data, node); + } + break; + + case ControllerType::AlphaStart: + if (node._flags & kNodeHasEmitter) { + readAlphaStartController(dataIndex, data, node); + } + break; + case ControllerType::AlphaMid: + if (node._flags & kNodeHasEmitter) { + readAlphaMidController(dataIndex, data, node); + } + break; + case ControllerType::AlphaEnd: + if (node._flags & kNodeHasEmitter) { + readAlphaEndController(dataIndex, data, node); + } + break; + default: debug(boost::format("MDL: unsupported controller type: \"%s\" %d") % _name % static_cast(type), 3); break; @@ -459,7 +511,45 @@ void MdlFile::readLifeExpectancyController(uint16_t dataIndex, const vector &data, ModelNode &node) { - node._emitter->_sizeStart = data[dataIndex]; + node._emitter->_particleSize.start = data[dataIndex]; +} + +void MdlFile::readSizeMidController(uint16_t dataIndex, const vector &data, ModelNode &node) { + node._emitter->_particleSize.mid = data[dataIndex]; +} + +void MdlFile::readSizeEndController(uint16_t dataIndex, const vector &data, ModelNode &node) { + node._emitter->_particleSize.end = data[dataIndex]; +} + +void MdlFile::readColorStartController(uint16_t dataIndex, const vector &data, ModelNode &node) { + node._emitter->_color.start.r = data[dataIndex + 0]; + node._emitter->_color.start.g = data[dataIndex + 1]; + node._emitter->_color.start.b = data[dataIndex + 2]; +} + +void MdlFile::readColorMidController(uint16_t dataIndex, const vector &data, ModelNode &node) { + node._emitter->_color.mid.r = data[dataIndex + 0]; + node._emitter->_color.mid.g = data[dataIndex + 1]; + node._emitter->_color.mid.b = data[dataIndex + 2]; +} + +void MdlFile::readColorEndController(uint16_t dataIndex, const vector &data, ModelNode &node) { + node._emitter->_color.end.r = data[dataIndex + 0]; + node._emitter->_color.end.g = data[dataIndex + 1]; + node._emitter->_color.end.b = data[dataIndex + 2]; +} + +void MdlFile::readAlphaStartController(uint16_t dataIndex, const vector &data, ModelNode &node) { + node._emitter->_alpha.start = data[dataIndex]; +} + +void MdlFile::readAlphaMidController(uint16_t dataIndex, const vector &data, ModelNode &node) { + node._emitter->_alpha.mid = data[dataIndex]; +} + +void MdlFile::readAlphaEndController(uint16_t dataIndex, const vector &data, ModelNode &node) { + node._emitter->_alpha.end = data[dataIndex]; } void MdlFile::readSizeXController(uint16_t dataIndex, const vector &data, ModelNode &node) { @@ -733,7 +823,11 @@ void MdlFile::readEmitter(ModelNode &node) { node._emitter->_texture = Textures::instance().get(boost::to_lower_copy(readCString(32)), TextureType::Diffuse); - ignore(56); + ignore(24); + + node._emitter->_renderOrder = readUint16(); + + ignore(30); } } // namespace render diff --git a/src/render/model/mdlfile.h b/src/render/model/mdlfile.h index 1b19502f..5b35ee8a 100644 --- a/src/render/model/mdlfile.h +++ b/src/render/model/mdlfile.h @@ -61,8 +61,14 @@ private: // Controllers void readAlphaController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); + void readAlphaEndController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); + void readAlphaMidController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); + void readAlphaStartController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readBirthrateController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readColorController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); + void readColorEndController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); + void readColorMidController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); + void readColorStartController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readFrameEndController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readFrameStartController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readLifeExpectancyController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); @@ -72,6 +78,8 @@ private: void readRadiusController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readRandomVelocityController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readSelfIllumColorController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); + void readSizeEndController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); + void readSizeMidController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readSizeStartController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readSizeXController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); void readSizeYController(uint16_t dataIndex, const std::vector &data, render::ModelNode &node); diff --git a/src/render/shaders.cpp b/src/render/shaders.cpp index d27ae595..f2bbb754 100644 --- a/src/render/shaders.cpp +++ b/src/render/shaders.cpp @@ -73,6 +73,8 @@ layout(std140) uniform General { uniform vec2 uBlurResolution; uniform vec2 uBlurDirection; uniform vec2 uBillboardGridSize; + uniform vec2 uBillboardSize; + uniform vec4 uParticleCenter; uniform int uBillboardFrame; }; @@ -162,6 +164,29 @@ void main() { } )END"; +static const GLchar kSourceVertexBillboard[] = R"END( +uniform mat4 uProjection; +uniform mat4 uView; + +layout(location = 0) in vec3 aPosition; +layout(location = 2) in vec2 aTexCoords; + +out vec2 fragTexCoords; + +void main() { + vec3 cameraRight = vec3(uView[0][0], uView[1][0], uView[2][0]); + vec3 cameraUp = vec3(uView[0][1], uView[1][1], uView[2][1]); + + vec3 position = + uParticleCenter.xyz + + cameraRight * aPosition.x * uBillboardSize.x + + cameraUp * aPosition.y * uBillboardSize.y; + + gl_Position = uProjection * uView * vec4(position, 1.0); + fragTexCoords = aTexCoords; +} +)END"; + static const GLchar kSourceGeometryDepth[] = R"END( const int NUM_CUBE_FACES = 6; @@ -398,7 +423,8 @@ void main() { texCoords.x += oneOverGridX * (uBillboardFrame % int(uBillboardGridSize.x)); } - fragColor = texture(uTexture, texCoords); + vec4 textureSample = texture(uTexture, texCoords); + fragColor = vec4(uColor.rgb * textureSample.rgb, uAlpha * textureSample.a); } )END"; @@ -430,6 +456,7 @@ void Shaders::initGL() { initShader(ShaderName::VertexGUI, GL_VERTEX_SHADER, kSourceVertexGUI); initShader(ShaderName::VertexModel, GL_VERTEX_SHADER, kSourceVertexModel); initShader(ShaderName::VertexDepth, GL_VERTEX_SHADER, kSourceVertexDepth); + initShader(ShaderName::VertexBillboard, GL_VERTEX_SHADER, kSourceVertexBillboard); initShader(ShaderName::GeometryDepth, GL_GEOMETRY_SHADER, kSourceGeometryDepth); initShader(ShaderName::FragmentWhite, GL_FRAGMENT_SHADER, kSourceFragmentWhite); initShader(ShaderName::FragmentGUI, GL_FRAGMENT_SHADER, kSourceFragmentGUI); @@ -447,7 +474,7 @@ void Shaders::initGL() { initProgram(ShaderProgram::GUIDebugShadows, 2, ShaderName::VertexGUI, ShaderName::FragmentDebugShadows); initProgram(ShaderProgram::ModelWhite, 2, ShaderName::VertexModel, ShaderName::FragmentWhite); initProgram(ShaderProgram::ModelModel, 2, ShaderName::VertexModel, ShaderName::FragmentModel); - initProgram(ShaderProgram::ModelBillboard, 2, ShaderName::VertexModel, ShaderName::FragmentBillboard); + initProgram(ShaderProgram::BillboardBillboard, 2, ShaderName::VertexBillboard, ShaderName::FragmentBillboard); initProgram(ShaderProgram::DepthDepth, 3, ShaderName::VertexDepth, ShaderName::GeometryDepth, ShaderName::FragmentDepth); glGenBuffers(1, &_generalUbo); diff --git a/src/render/shaders.h b/src/render/shaders.h index 65abac11..69867dea 100644 --- a/src/render/shaders.h +++ b/src/render/shaders.h @@ -44,7 +44,7 @@ enum class ShaderProgram { GUIDebugShadows, ModelWhite, ModelModel, - ModelBillboard, + BillboardBillboard, DepthDepth }; @@ -90,8 +90,10 @@ struct GeneralUniforms { glm::vec2 blurResolution { 0.0f }; glm::vec2 blurDirection { 0.0f }; glm::vec2 billboardGridSize { 0.0f }; + glm::vec2 billboardSize { 0.0f }; + glm::vec4 particleCenter { 0.0f }; int billboardFrame { 0 }; - char padding3[4]; + char padding3[12]; }; struct SkeletalUniforms { @@ -140,6 +142,7 @@ private: VertexGUI, VertexModel, VertexDepth, + VertexBillboard, FragmentWhite, FragmentGUI, FragmentModel, diff --git a/src/scene/node/emitternode.cpp b/src/scene/node/emitternode.cpp index 89e744b4..664de77b 100644 --- a/src/scene/node/emitternode.cpp +++ b/src/scene/node/emitternode.cpp @@ -17,20 +17,11 @@ #include "emitternode.h" -#include #include -#include "glm/gtx/euler_angles.hpp" -#include "glm/gtx/matrix_decompose.hpp" - #include "../../common/random.h" -#include "../../render/mesh/billboard.h" -#include "../../render/shaders.h" -#include "../../render/util.h" -#include "../scenegraph.h" - -#include "cameranode.h" +#include "particlenode.h" using namespace std; @@ -41,16 +32,11 @@ namespace reone { namespace scene { constexpr int kMaxParticleCount = 16; -constexpr float kParticleFrameInterval = 0.2f; -EmitterSceneNode::EmitterSceneNode(const shared_ptr &modelNode, const shared_ptr &emitter, SceneGraph *sceneGraph) : +EmitterSceneNode::EmitterSceneNode(const shared_ptr &emitter, SceneGraph *sceneGraph) : SceneNode(sceneGraph), - _modelNode(modelNode), _emitter(emitter) { - if (!modelNode) { - throw invalid_argument("modelNode must not be null"); - } if (!emitter) { throw invalid_argument("emitter must not be null"); } @@ -64,6 +50,7 @@ void EmitterSceneNode::init() { void EmitterSceneNode::update(float dt) { if (_emitter->updateType() != Emitter::UpdateType::Fountain) return; + if (_emitter->renderType() != Emitter::RenderType::Normal) return; spawnParticles(dt); updateParticles(dt); @@ -76,82 +63,32 @@ void EmitterSceneNode::spawnParticles(float dt) { if (_particles.size() < kMaxParticleCount) { float halfW = 0.005f * _emitter->size().x; float halfH = 0.005f * _emitter->size().y; + glm::vec3 position(random(-halfW, halfW), random(-halfH, halfH), 0.0f); - auto particle = make_shared(); - particle->position = _absoluteTransform * glm::vec4(random(-halfW, halfW), random(-halfH, halfH), 0.0f, 1.0f); - particle->frame = _emitter->frameStart(); - particle->frameTimer.reset(kParticleFrameInterval); - particle->normal = getPlaneNormal(); - particle->velocity = (_emitter->velocity() + random(0.0f, _emitter->randomVelocity())); - particle->lifetime = _emitter->lifeExpectancy(); + float velocity = (_emitter->velocity() + random(0.0f, _emitter->randomVelocity())); - _particles.push_back(move(particle)); + auto particle = make_shared(position, velocity, _emitter, _sceneGraph); + _particles.push_back(particle); + addChild(particle); } _birthTimer.reset(_birthInterval); } } -glm::vec3 EmitterSceneNode::getPlaneNormal() const { - static glm::vec3 up(0.0f, 0.0f, 1.0f); - - glm::vec3 scale, translation, skew; - glm::quat orientation; - glm::vec4 perspective; - - glm::decompose(_modelNode->absoluteTransform(), scale, orientation, translation, skew, perspective); - - return glm::normalize(orientation * up); -} - void EmitterSceneNode::updateParticles(float dt) { for (auto it = _particles.begin(); it != _particles.end(); ) { auto &particle = (*it); + particle->update(dt); - // Expire the particle - particle->lifetime = glm::max(0.0f, particle->lifetime - dt); - if (particle->lifetime == 0.0f) { + if (particle->isExpired()) { + removeChild(*particle); it = _particles.erase(it); } else { - // Update the particle position - particle->position += particle->velocity * particle->normal * dt; // TODO: use velocity? - - // Animate the particle - particle->frameTimer.update(dt); - if (particle->frameTimer.hasTimedOut()) { - ++particle->frame; - if (particle->frame > _emitter->frameEnd()) { - particle->frame = _emitter->frameStart(); - } - particle->frameTimer.reset(kParticleFrameInterval); - } - ++it; } } } -void EmitterSceneNode::renderSingle(bool shadowPass) const { - if (shadowPass) return; - - for (auto &particle : _particles) { - glm::mat4 transform(1.0f); - transform = glm::translate(transform, particle->position); - transform = glm::scale(transform, glm::vec3(_emitter->sizeStart())); - - LocalUniforms locals; - locals.general.model = move(transform); - locals.general.billboardGridSize = glm::vec2(_emitter->gridWidth(), _emitter->gridHeight()); - locals.general.billboardFrame = particle->frame; - - Shaders::instance().activate(ShaderProgram::ModelBillboard, locals); - - setActiveTextureUnit(0); - _emitter->texture()->bind(); - - BillboardMesh::instance().renderTriangles(); - } -} - } // namespace scene } // namespace reone diff --git a/src/scene/node/emitternode.h b/src/scene/node/emitternode.h index 36ef6721..32f94b14 100644 --- a/src/scene/node/emitternode.h +++ b/src/scene/node/emitternode.h @@ -24,48 +24,33 @@ #include "glm/vec3.hpp" #include "../../common/timer.h" - #include "../../render/emitter.h" -#include "../../render/model/modelnode.h" namespace reone { namespace scene { +class ParticleSceneNode; + class EmitterSceneNode : public SceneNode { public: - EmitterSceneNode(const std::shared_ptr &modelNode, const std::shared_ptr &emitter, SceneGraph *sceneGraph); + EmitterSceneNode(const std::shared_ptr &emitter, SceneGraph *sceneGraph); void update(float dt); - void renderSingle(bool shadowPass) const override; - std::shared_ptr emitter() const { return _emitter; } private: - struct Particle { - glm::vec3 position { 0.0f }; - float cameraDistance { 0.0f }; - int frame { 0 }; - Timer frameTimer; - glm::vec3 normal { 0.0f }; - float lifetime { 0.0f }; - float velocity { 0.0f }; - }; - - std::shared_ptr _modelNode; std::shared_ptr _emitter; float _birthInterval { 0.0f }; Timer _birthTimer; - std::vector> _particles; + std::vector> _particles; void init(); void spawnParticles(float dt); void updateParticles(float dt); - - glm::vec3 getPlaneNormal() const; }; } // namespace scene diff --git a/src/scene/node/modelscenenode.cpp b/src/scene/node/modelscenenode.cpp index 71c9a653..b3708e9c 100644 --- a/src/scene/node/modelscenenode.cpp +++ b/src/scene/node/modelscenenode.cpp @@ -76,7 +76,7 @@ void ModelSceneNode::initModelNodes() { shared_ptr emitter(child->emitter()); if (emitter) { - auto emitterNode = make_shared(child, emitter, _sceneGraph); + auto emitterNode = make_shared(emitter, _sceneGraph); childNode->addChild(emitterNode); _emitters.push_back(emitterNode); } diff --git a/src/scene/node/particlenode.cpp b/src/scene/node/particlenode.cpp new file mode 100644 index 00000000..f78919e1 --- /dev/null +++ b/src/scene/node/particlenode.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2021 The reone project contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "particlenode.h" + +#include + +#include "glm/common.hpp" +#include "glm/gtc/matrix_transform.hpp" + +#include "../../render/mesh/billboard.h" +#include "../../render/shaders.h" +#include "../../render/util.h" + +using namespace std; + +using namespace reone::render; + +namespace reone { + +namespace scene { + +ParticleSceneNode::ParticleSceneNode(glm::vec3 position, float velocity, const shared_ptr &emitter, SceneGraph *sceneGraph) : + SceneNode(sceneGraph), + _position(position), + _velocity(velocity), + _emitter(emitter) { + + if (!emitter) { + throw invalid_argument("emitter must not be null"); + } + + init(); +} + +void ParticleSceneNode::init() { + _renderOrder = _emitter->renderOrder(); + _frame = _emitter->frameStart(); + + updateLocalTransform(); +} + +void ParticleSceneNode::updateLocalTransform() { + setLocalTransform(glm::translate(glm::mat4(1.0f), _position)); +} + +void ParticleSceneNode::update(float dt) { + _lifetime = glm::min(_lifetime + dt, static_cast(_emitter->lifeExpectancy())); + + if (_lifetime < _emitter->lifeExpectancy()) { + _position.z += _velocity * dt; + updateLocalTransform(); + } + + updateAnimation(dt); +} + +template +static T interpolateConstraints(const Emitter::Constraints &constraints, float t) { + T result; + if (t < 0.5f) { + float tt = 2.0f * t; + result = (1.0f - tt) * constraints.start + tt * constraints.mid; + } else { + float tt = 2.0f * (t - 0.5f); + result = (1.0f - tt) * constraints.mid + tt * constraints.end; + } + return result; +} + +void ParticleSceneNode::updateAnimation(float dt) { + float maturity = _lifetime / static_cast(_emitter->lifeExpectancy()); + _frame = glm::ceil(_emitter->frameStart() + maturity * (_emitter->frameEnd() - _emitter->frameStart())); + _size = interpolateConstraints(_emitter->particleSize(), maturity); + _color = interpolateConstraints(_emitter->color(), maturity); + _alpha = interpolateConstraints(_emitter->alpha(), maturity); +} + +void ParticleSceneNode::renderSingle(bool shadowPass) const { + if (shadowPass) return; + + LocalUniforms locals; + locals.general.color = glm::vec4(_color, 1.0f); + locals.general.alpha = _alpha; + locals.general.billboardGridSize = glm::vec2(_emitter->gridWidth(), _emitter->gridHeight()); + locals.general.billboardSize = glm::vec2(_size); + locals.general.particleCenter = _absoluteTransform[3]; + locals.general.billboardFrame = _frame; + + Shaders::instance().activate(ShaderProgram::BillboardBillboard, locals); + + setActiveTextureUnit(0); + _emitter->texture()->bind(); + + BillboardMesh::instance().renderTriangles(); +} + +bool ParticleSceneNode::isExpired() const { + return _lifetime >= _emitter->lifeExpectancy(); +} + +int ParticleSceneNode::renderOrder() const { + return _renderOrder; +} + +} // namespace scene + +} // namespace reone diff --git a/src/scene/node/particlenode.h b/src/scene/node/particlenode.h new file mode 100644 index 00000000..d30237eb --- /dev/null +++ b/src/scene/node/particlenode.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020-2021 The reone project contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "scenenode.h" + +#include "../../render/emitter.h" + +namespace reone { + +namespace scene { + +class ParticleSceneNode : public SceneNode { +public: + ParticleSceneNode(glm::vec3 position, float velocity, const std::shared_ptr &emitter, SceneGraph *sceneGraph); + + void update(float dt); + + void renderSingle(bool shadowPass) const override; + + bool isExpired() const; + + int renderOrder() const; + +private: + std::shared_ptr _emitter; + glm::vec3 _position { 0.0f }; + float _velocity { 0.0f }; + int _renderOrder { 0 }; + + float _lifetime { 0.0f }; + int _frame { 0 }; + float _size { 1.0f }; + glm::vec3 _color { 1.0f }; + float _alpha { 1.0f }; + + void init(); + void updateAnimation(float dt); + void updateLocalTransform(); +}; + +} // namespace scene + +} // namespace reone diff --git a/src/scene/scenegraph.cpp b/src/scene/scenegraph.cpp index 56cc2157..19bcc867 100644 --- a/src/scene/scenegraph.cpp +++ b/src/scene/scenegraph.cpp @@ -23,10 +23,10 @@ #include "../render/mesh/quad.h" #include "node/cameranode.h" -#include "node/emitternode.h" #include "node/lightnode.h" #include "node/modelnodescenenode.h" #include "node/modelscenenode.h" +#include "node/particlenode.h" using namespace std; @@ -36,7 +36,7 @@ namespace reone { namespace scene { -static bool g_emittersEnabled = false; +static bool g_emittersEnabled = true; SceneGraph::SceneGraph(const GraphicsOptions &opts) : _opts(opts) { } @@ -62,7 +62,7 @@ void SceneGraph::build() { void SceneGraph::prepareFrame() { if (!_activeCamera) return; - refreshMeshesLightsAndEmitters(); + refreshNodeLists(); refreshShadowLight(); for (auto &root : _roots) { @@ -71,9 +71,10 @@ void SceneGraph::prepareFrame() { modelNode->updateLighting(); } } + + // Sort transparent meshes unordered_map cameraDistances; glm::vec3 cameraPosition(_activeCamera->absoluteTransform()[3]); - for (auto &mesh : _transparentMeshes) { cameraDistances.insert(make_pair(mesh, mesh->distanceTo(cameraPosition))); } @@ -89,14 +90,27 @@ void SceneGraph::prepareFrame() { return leftDistance > rightDistance; }); + + // Sort particles + unordered_map particlesZ; + for (auto &particle : _particles) { + glm::vec4 screen(_activeCamera->projection() * _activeCamera->view() * particle->absoluteTransform()[3]); + screen /= screen.w; + particlesZ.insert(make_pair(particle, screen.z)); + } + sort(_particles.begin(), _particles.end(), [&particlesZ](auto &left, auto &right) { + float leftZ = particlesZ.find(left)->second; + float rightZ = particlesZ.find(right)->second; + return leftZ > rightZ; + }); } -void SceneGraph::refreshMeshesLightsAndEmitters() { +void SceneGraph::refreshNodeLists() { _opaqueMeshes.clear(); _transparentMeshes.clear(); _shadowMeshes.clear(); _lights.clear(); - _emitters.clear(); + _particles.clear(); for (auto &root : _roots) { stack nodes; @@ -128,9 +142,9 @@ void SceneGraph::refreshMeshesLightsAndEmitters() { if (light) { _lights.push_back(light); } else if (g_emittersEnabled) { - auto emitter = dynamic_cast(node); - if (emitter) { - _emitters.push_back(emitter); + auto particle = dynamic_cast(node); + if (particle) { + _particles.push_back(particle); } } } @@ -188,12 +202,12 @@ void SceneGraph::renderNoGlobalUniforms(bool shadowPass) const { for (auto &mesh : _opaqueMeshes) { mesh->renderSingle(false); } - for (auto &emitter : _emitters) { - emitter->renderSingle(false); - } for (auto &mesh : _transparentMeshes) { mesh->renderSingle(false); } + for (auto &particle : _particles) { + particle->renderSingle(false); + } } void SceneGraph::getLightsAt(const glm::vec3 &position, vector &lights) const { diff --git a/src/scene/scenegraph.h b/src/scene/scenegraph.h index 29f65b23..783db079 100644 --- a/src/scene/scenegraph.h +++ b/src/scene/scenegraph.h @@ -30,9 +30,9 @@ namespace reone { namespace scene { class CameraSceneNode; -class EmitterSceneNode; class LightSceneNode; class ModelNodeSceneNode; +class ParticleSceneNode; class SceneNode; class SceneGraph { @@ -75,7 +75,7 @@ private: std::vector _transparentMeshes; std::vector _shadowMeshes; std::vector _lights; - std::vector _emitters; + std::vector _particles; std::shared_ptr _activeCamera; glm::vec3 _ambientLightColor { 0.5f }; uint32_t _textureId { 0 }; @@ -86,7 +86,7 @@ private: SceneGraph(const SceneGraph &) = delete; SceneGraph &operator=(const SceneGraph &) = delete; - void refreshMeshesLightsAndEmitters(); + void refreshNodeLists(); void refreshShadowLight(); };