[2.3] Per Object Motion Blur Topic is solved

Discussion area about developing with Ogre-Next (2.1, 2.2 and beyond)


przemir
Halfling
Posts: 68
Joined: Sun May 11, 2014 7:55 pm
Location: Poland
x 21

[2.3] Per Object Motion Blur

Post by przemir »

Hi! I want to make motion blur to fast moving objects.

I have already seen https://ogldev.org/www/tutorial41/tutorial41.html which describe general algorithm.
How to achieve that with Ogre? I am asking about connecting code with hlms and compositor. Right now I am thinking about:
1) Setting property "motion_blur_enabled" inside subclass of HlmsPbs for items I intend to use motion blur. Can I set and unset this property for the same item depending on whether I use motion blur at that moment (enable it only during fast animation)?
2) Passing vector of items previous matrix transforms to pass buffer in HlmsListener::preparePassBuffer (I assume the size of vector won't be big).
3) Writing HLMS piece for vertex and fragment shader (custom_vs_uniformDeclaration, custom_vs_posExecution etc.) which fill texture with movement vector for each pixel. But how do I identify which matrix from 2) to use for given item inside a shader? And how to declare this movement texture for pbs?
4) Using movement vector texture during compositor phase. How to connect movement texture from 3) to compositor?

Is this the correct way to make object motion blur in Ogre?

Thanks!

przemir
Halfling
Posts: 68
Joined: Sun May 11, 2014 7:55 pm
Location: Poland
x 21

Re: [2.3] Per Object Motion Blur

Post by przemir »

Turns out it is correct way, although I still have something wrong.

As for questions:

przemir wrote: Sun Nov 06, 2022 11:57 am

Can I set and unset this property for the same item depending on whether I use motion blur at that moment (enable it only during fast animation)?

Seems I have to change datablock to enforce calculateHashForPreCreate (I had to do it even after creation of item as I couldn't set renderable mCustomProperties to subItems there). So yes, I can enable/disable motion blur by changing datablock (I may set/remove mCustomProperties just before then).

przemir wrote: Sun Nov 06, 2022 11:57 am

But how do I identify which matrix from 2) to use for given item inside a shader?

Using buffer for renderable and filling place used by PLANAR_REFLECTION (it is not used if not build with OGRE_BUILD_COMPONENT_PLANAR_REFLECTIONS).

Code: Select all

    Ogre::uint32 fillBuffersFor(const Ogre::HlmsCache* cache, const Ogre::QueuedRenderable& queuedRenderable,
        bool casterPass, Ogre::uint32 lastCacheHash,
        Ogre::CommandBuffer* commandBuffer, bool isV1)
    {
        // Keep in mind that only the first one has to be correct, as Ogre's auto instancing will increment the base value automatically.
        Ogre::uint32 drawId = Ogre::HlmsPbs::fillBuffersFor(cache, queuedRenderable, casterPass, lastCacheHash, commandBuffer, isV1);

    // After Ogre::HlmsUnlit::fillBuffersFor const buffer was already filled. There was one
    // unused value though at position [3]. Buffer  was shifted by 4.

    // Beware that const buffer .w may collide with PLANAR_REFLECTIONS if enabled...
    Ogre::uint32 * RESTRICT_ALIAS currentMappedConstBuffer = mCurrentMappedConstBuffer;
    *(currentMappedConstBuffer-1) = queuedRenderable.renderable->mCustomParameter;

    return drawId;
}

In vertex shader InstanceBuffer isn't declared by default. You need to @insertpiece( InstanceStructDecl ) inside @piece( custom_vs_uniformDeclaration ). Then it can be accesed by worldMaterialIdx[finalDrawId].w:

przemir wrote: Sun Nov 06, 2022 11:57 am

And how to declare this movement texture for pbs?

It is called MRT (Multi Render Target). It can be declared in compositor like that:

Code: Select all

    texture rt0           target_width target_height PFG_RGBA8_UNORM_SRGB
    texture motionTexture target_width target_height PFG_RG32_FLOAT

rtv mrt
{
    // For MRT (Multiple Render Target) write multiple textures here
    colour	rt0 motionTexture
}

For motion blur float2 texture is needed.
For pixel shader to declare custom second target you need to modify file from Ogre: inside RenderDepthOnly_piece_ps.hlsl add @insertpiece( custom_ps_output_types ) just after @insertpiece( ExtraOutputTypes ).

przemir wrote: Sun Nov 06, 2022 11:57 am

How to connect movement texture from 3) to compositor?

Just use motionTexture from code above.

Now I get this:
Image
It is behaving like cube was moving in different direction.

All my pieces:

500.Structs_piece_vs_piece_ps.hlsl

Code: Select all

@piece( custom_passBuffer )
    // custom_passBuffer
    @property(motion_blur_enabled)
    float4x4 prevMatrix[20];
    //float3x4 prevMatrix[20];
    //float4x3 prevMatrix[20];
    float motionBlurInfluence[20];
    @end
@end

@piece( custom_VStoPS )
    // custom_VStoPS
    @property(motion_blur_enabled)
        INTERPOLANT( float4 currClipSpacePos0, @counter(texcoord) );
        INTERPOLANT( float4 prevClipSpacePos0, @counter(texcoord) );
        INTERPOLANT( float motionBlurInfluence, @counter(texcoord) );
    @end
@end

800.VertexShader_piece_vs.hlsl

Code: Select all

@piece( custom_vs_uniformDeclaration )
    // custom_vs_uniformDeclaration
    @property(motion_blur_enabled)
    @insertpiece( InstanceStructDecl )
    @end
@end

@piece( custom_vs_attributes )
    // custom_vs_attributes
@end

@piece( custom_vs_preExecution )
    // custom_vs_preExecution
@end

@piece( custom_vs_posExecution )
    // custom_vs_posExecution
    
@property(motion_blur_enabled) uint motionBlurDataIndex = worldMaterialIdx[finalDrawId].w; float4x4 prevWorldMat = passBuf.prevMatrix[motionBlurDataIndex]; outVs.currClipSpacePos0 = outVs_Position; // float4 worldPos = float4( mul(inVs_vertex, worldMat ).xyz, 1.0f ); float4 prevWorldPos = float4( mul(inVs_vertex, prevWorldMat).xyz, 1.0f ); // float4 prevWorldPos = mul(inVs_vertex, prevWorldMat);
// outVs.currClipSpacePos0 = mul( worldPos , passBuf.viewProj ); outVs.prevClipSpacePos0 = mul( prevWorldPos, passBuf.viewProj ); outVs.motionBlurInfluence = passBuf.motionBlurInfluence[motionBlurDataIndex];
@end @end

800.PixelShader_piece_ps.any

Code: Select all

@piece( custom_ps_uniformDeclaration )
    // custom_ps_uniformDeclaration
@end

@piece( custom_ps_output_types )
    // custom_ps_output_types
    @property(motion_blur_enabled)
    float2 motion : SV_Target@counter(rtv_target);
    @end
@end

@piece( custom_ps_preExecution )
    // custom_ps_preExecution
@end

@piece( custom_ps_posExecution )
    // custom_ps_posExecution
    @property(motion_blur_enabled)
    float3 NDCPos = (inPs.currClipSpacePos0 / inPs.currClipSpacePos0.w).xyz;
    float3 PrevNDCPos = (inPs.prevClipSpacePos0 / inPs.prevClipSpacePos0.w).xyz;
    outPs.motion = (NDCPos - PrevNDCPos).xy * inPs.motionBlurInfluence;
    @end
@end

Previous matrix was from mSceneNode->_getFullTransformUpdated() before changing it to new position, and my hlms code:

Code: Select all

#ifndef HLMSMOTIONBLURPBS_H
#define HLMSMOTIONBLURPBS_H

#include <OgreMatrix4.h>
#include <OgreHlmsPbs.h>

#include "OgreHlmsListener.h"
#include "OgreSceneManager.h"

#include <OgreItem.h>
#include <OgreSubItem.h>
#include <OgreRenderQueue.h>

namespace Ogre {
    class SceneManager;
    class CompositorShadowNode;

}

class HlmsMotionBlurPbsListener : public Ogre::HlmsListener {

public:

static const int MaxMotionBlurItemCount = 20;

struct MotionBlurData
{
    Ogre::Matrix4 mPrevMatrix;
    float mInfluence;
};

std::vector<MotionBlurData> mMotionBlurDataList;

public:
    HlmsMotionBlurPbsListener() = default;
    virtual ~HlmsMotionBlurPbsListener() = default;

virtual Ogre::uint32 getPassBufferSize(const Ogre::CompositorShadowNode* shadowNode, bool casterPass,
    bool dualParaboloid, Ogre::SceneManager* sceneManager) const {

    Ogre::uint32 size = 0;

    if (!casterPass) {
        Ogre::uint32 motionBlurPrevMatrixSize = sizeof(float) * 4 * 4;
        Ogre::uint32 motionBlurInfluenceSize = sizeof(float);
        size += motionBlurPrevMatrixSize * MaxMotionBlurItemCount;
        size += motionBlurInfluenceSize * MaxMotionBlurItemCount;
    }
    return size;
}

virtual float* preparePassBuffer(const Ogre::CompositorShadowNode* shadowNode, bool casterPass,
    bool dualParaboloid, Ogre::SceneManager* sceneManager,
    float* passBufferPtr) {

    if (!casterPass)
    {
        size_t maxListSize = mMotionBlurDataList.size();
        if(maxListSize > MaxMotionBlurItemCount) {
            maxListSize = MaxMotionBlurItemCount;
        }
        for (size_t i = 0; i < maxListSize; ++i) {
            const MotionBlurData& motionDataBlur = mMotionBlurDataList[i];
//                for( size_t j=0; j<16; ++j )
//                    *passBufferPtr++ = (float)motionDataBlur.mPrevMatrix[0][j];

            for( int y = 0; y < 4; ++y )
            {
                for( int x = 0; x < 4; ++x )
                {
                    *passBufferPtr++ = (float)motionDataBlur.mPrevMatrix[ y ][ x ];
                }
            }
        }
        for (size_t i = mMotionBlurDataList.size(); i < MaxMotionBlurItemCount; ++i) {
            for( size_t j=0; j<16; ++j )
                *passBufferPtr++ = (float)0.0f;
        }
        for (size_t i = 0; i < maxListSize; ++i) {
            const MotionBlurData& motionDataBlur = mMotionBlurDataList[i];
            *passBufferPtr++ = (float)motionDataBlur.mInfluence;
        }
        for (size_t i = mMotionBlurDataList.size(); i < MaxMotionBlurItemCount; ++i) {
            *passBufferPtr++ = (float)0.0f;
        }
    }
    return passBufferPtr;
}
};

class HlmsMotionBlurPbs : public Ogre::HlmsPbs {

public:
    static const int MotionBlurParameterIndex = 1238;

private:

HlmsMotionBlurPbsListener mBlademasterPbsListener;

void calculateHashForPreCreate(Ogre::Renderable* renderable, Ogre::PiecesMap* inOutPieces) override {

    HlmsPbs::calculateHashForPreCreate(renderable, inOutPieces);

    if(renderable->hasCustomParameter(MotionBlurParameterIndex)) {
        setProperty("motion_blur_enabled", 1);
    }
}

public:
    HlmsMotionBlurPbs(Ogre::Archive* dataFolder, Ogre::ArchiveVec* libraryFolders)
        : Ogre::HlmsPbs(dataFolder, libraryFolders)
    {
//        mType = Ogre::HLMS_PBS;
//        mTypeName = "pbs";
//        mTypeNameStr = "pbs";

    setListener(&mBlademasterPbsListener);
}

virtual ~HlmsMotionBlurPbs() = default;

void setup(Ogre::SceneManager* manager) {
}

void shutdown(Ogre::SceneManager* manager) {
}

void notifyPropertiesMergedPreGenerationStep(void) {
    HlmsPbs::notifyPropertiesMergedPreGenerationStep();
}

static void getDefaultPaths(Ogre::String& outDataFolderPath, Ogre::StringVector& outLibraryFoldersPaths) {

    HlmsPbs::getDefaultPaths(outDataFolderPath, outLibraryFoldersPaths);

    //We need to know what RenderSystem is currently in use, as the
    //name of the compatible shading language is part of the path
    Ogre::RenderSystem* renderSystem = Ogre::Root::getSingleton().getRenderSystem();
    Ogre::String shaderSyntax = "GLSL";
    if (renderSystem->getName() == "Direct3D11 Rendering Subsystem")
        shaderSyntax = "HLSL";
    else if (renderSystem->getName() == "Metal Rendering Subsystem")
        shaderSyntax = "Metal";


    //Fill the library folder paths with the relevant folders
    outLibraryFoldersPaths.clear();
    outLibraryFoldersPaths.push_back("Hlms/Common/" + shaderSyntax);
    outLibraryFoldersPaths.push_back("Hlms/Common/Any");
    outLibraryFoldersPaths.push_back("Hlms/Pbs/Any");
    outLibraryFoldersPaths.push_back("Hlms/Pbs/Any/Main");

    //Fill the data folder path
    outDataFolderPath = "Hlms/pbs/" + shaderSyntax;
}

static void getAdditionalPaths(Ogre::StringVector& outLibraryFoldersPaths) {
    //We need to know what RenderSystem is currently in use, as the
    //name of the compatible shading language is part of the path
    Ogre::RenderSystem* renderSystem = Ogre::Root::getSingleton().getRenderSystem();
    Ogre::String shaderSyntax = "GLSL";
    if (renderSystem->getName() == "Direct3D11 Rendering Subsystem")
        shaderSyntax = "HLSL";
    else if (renderSystem->getName() == "Metal Rendering Subsystem")
        shaderSyntax = "Metal";


    //Fill the library folder paths with the relevant folders
    outLibraryFoldersPaths.clear();
    outLibraryFoldersPaths.push_back("Hlms/Pbs/" + shaderSyntax);
    outLibraryFoldersPaths.push_back("Hlms/Pbs/Any");
}

Ogre::uint32 fillBuffersForV1(const Ogre::HlmsCache* cache,
    const Ogre::QueuedRenderable& queuedRenderable,
    bool casterPass, Ogre::uint32 lastCacheHash,
    Ogre::CommandBuffer* commandBuffer)
{
    return fillBuffersFor(cache, queuedRenderable, casterPass,
        lastCacheHash, commandBuffer, true);
}
//-----------------------------------------------------------------------------------
Ogre::uint32 fillBuffersForV2(const Ogre::HlmsCache* cache,
    const Ogre::QueuedRenderable& queuedRenderable,
    bool casterPass, Ogre::uint32 lastCacheHash,
    Ogre::CommandBuffer* commandBuffer)
{
    return fillBuffersFor(cache, queuedRenderable, casterPass,
        lastCacheHash, commandBuffer, false);
}
//-----------------------------------------------------------------------------------
Ogre::uint32 fillBuffersFor(const Ogre::HlmsCache* cache, const Ogre::QueuedRenderable& queuedRenderable,
    bool casterPass, Ogre::uint32 lastCacheHash,
    Ogre::CommandBuffer* commandBuffer, bool isV1)
{
    // Keep in mind that only the first one has to be correct, as Ogre's auto instancing will increment the base value automatically.
    Ogre::uint32 drawId = Ogre::HlmsPbs::fillBuffersFor(cache, queuedRenderable, casterPass, lastCacheHash, commandBuffer, isV1);

    // After Ogre::HlmsUnlit::fillBuffersFor const buffer was already filled. There was one
    // unused value though at position [3]. Buffer  was shifted by 4.

    // Beware that const buffer .w may collide with PLANAR_REFLECTIONS if enabled...
    Ogre::uint32 * RESTRICT_ALIAS currentMappedConstBuffer = mCurrentMappedConstBuffer;
    *(currentMappedConstBuffer-1) = queuedRenderable.renderable->mCustomParameter;

    return drawId;
}

public:

void clearMotionBlurData() {
    mBlademasterPbsListener.mMotionBlurDataList.clear();
}

void addItemMotionBlur(Ogre::Item* item, const Ogre::Matrix4& prevMatrix, float influence) {

    if(mBlademasterPbsListener.mMotionBlurDataList.size() >= HlmsMotionBlurPbsListener::MaxMotionBlurItemCount) {
        return;
    }

    HlmsMotionBlurPbsListener::MotionBlurData motionBlurData;
    motionBlurData.mPrevMatrix = prevMatrix;
    motionBlurData.mInfluence = influence;
    mBlademasterPbsListener.mMotionBlurDataList.push_back(motionBlurData);

    int motionBlurDataIndex = (int)mBlademasterPbsListener.mMotionBlurDataList.size()-1;
    for (size_t i = 0; i < item->getNumSubItems(); ++i) {
        Ogre::SubItem* subItem = item->getSubItem(i);
        subItem->mCustomParameter = motionBlurDataIndex;
    }
}

};

#endif
przemir
Halfling
Posts: 68
Joined: Sun May 11, 2014 7:55 pm
Location: Poland
x 21

Re: [2.3] Per Object Motion Blur

Post by przemir »

Solved. I needed to change sign of motionVector.y inside MotionBlur_ps.hlsl (or alternatively inside custom_ps_posExecution piece).

Final result:

Image