Creating a custom HLMS to add support for wind and fog

  • Ogre3D 2.0
  • shader templates with hlsl
  • custom HLMS
  • grass rendering

There are a three different shader template files:

  • 500.Structs_piece_vs_piece_ps.any
  • 800.PixelShader_piece_ps.any
  • 800.VertexShader_piece_vs.any

500.Structs_piece_vs_piece_ps.any

@piece( custom_passBuffer )
	//Fog
	float4 fogParams;
	float4 fogColor;
	//wind
	float windStrength;
	float globalTime;
@end

800.PixelShader_piece_ps.any

@piece(custom_ps_posExecution)
@property(!hlms_shadowcaster)
    //Linear Fog
    float fogDistance = length(inPs.pos);
    float fogFactor = 1.0 - saturate((passBuf.fogParams.y - fogDistance) / (passBuf.fogParams.y - passBuf.fogParams.x));
    float3  fogColor = float3(passBuf.fogColor.xyz);
    outPs_colour0.xyz = lerp(finalColour, fogColor, fogFactor);

@end
@end

800.VertexShader_piece_vs.any

@piece( custom_vs_uniformDeclaration)
	Texture3D texPerlinNoise : register( t14 );
	SamplerState texPerlinNoiseSampler : register( s14);
@end

@piece( custom_vs_preTransform  )
@property(wind_enabled)

	float windFactor = 1.0 - input.uv0.y; 

	float random = texPerlinNoise.SampleLevel( texPerlinNoiseSampler,
					0.2*worldPos.xyz +
					float3( 0.3, 0.3, 0.3 ) * passBuf.globalTime, 0 ).xyz - float3( 0.2, 0.2, 0.2 );

  	worldPos.xyz +=  random * windFactor * passBuf.windStrength;

@end
@end

The HLMS uses a HLMS listener to pass the fog and wind parameters to the shaders, it also manages one texture and one sampler. The first texture is the perlin noise texture, used to randomize the geometry.

class HlmsWindListener : public Ogre::HlmsListener {


	float mWindStrength{ 0.5 };
	float mGlobalTime{ 0 };

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

	void setTime(float time) {
		mGlobalTime = time;
	}
	void addTime(float time) {
		mGlobalTime += time;
	}

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

		Ogre::uint32 size = 0;

		if (!casterPass) {

			unsigned int fogSize = sizeof(float) * 4 * 2; // float4 + float4 in bytes
			unsigned int windSize = sizeof(float);
			unsigned int timeSize = sizeof(float);

			size += fogSize + windSize + timeSize;
		}
		return size;
	}

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

		if (!casterPass )
		{
			//Linear Fog parameters
			*passBufferPtr++ = sceneManager->getFogStart();
			*passBufferPtr++ = sceneManager->getFogEnd();
			*passBufferPtr++ = 0.0;
			*passBufferPtr++ = 0.0f;

			*passBufferPtr++ = sceneManager->getFogColour().r;
			*passBufferPtr++ = sceneManager->getFogColour().g;
			*passBufferPtr++ = sceneManager->getFogColour().b;
			*passBufferPtr++ = 1.0f;

			*passBufferPtr++ = mWindStrength;

			*passBufferPtr++ = mGlobalTime;
		}
		return passBufferPtr;
	}
};

class HlmsWind : public Ogre::HlmsPbs {

	HlmsWindListener mWindListener;

	Ogre::HlmsSamplerblock const* mNoiseSamplerBlock{ nullptr };

	Ogre::TextureGpu* mNoiseTexture{ nullptr };


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

		HlmsPbs::calculateHashForPreCreate(renderable, inOutPieces);

		setProperty("wind_enabled", 1);
	}

	void loadTexturesAndSamplers(Ogre::SceneManager* manager) {

		Ogre::TextureGpuManager* textureManager =
			manager->getDestinationRenderSystem()->getTextureGpuManager();

		Ogre::HlmsSamplerblock samplerblock;
		samplerblock.mU = Ogre::TextureAddressingMode::TAM_WRAP;
		samplerblock.mV = Ogre::TextureAddressingMode::TAM_WRAP;
		samplerblock.mW = Ogre::TextureAddressingMode::TAM_WRAP;
		samplerblock.mMaxAnisotropy = 8;
		samplerblock.mMagFilter = Ogre::FO_ANISOTROPIC;
		mNoiseSamplerBlock = Ogre::Root::getSingleton().getHlmsManager()->getSamplerblock(samplerblock);

		mNoiseTexture = textureManager->createOrRetrieveTexture(
			"windNoise.dds",
			Ogre::GpuPageOutStrategy::Discard,
			Ogre::TextureFlags::PrefersLoadingFromFileAsSRGB,
			Ogre::TextureTypes::Type3D,
			Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME);

		mNoiseTexture->scheduleTransitionTo(Ogre::GpuResidency::Resident);

	}

public:
	HlmsWind(Ogre::Archive* dataFolder, Ogre::ArchiveVec* libraryFolders)
		: Ogre::HlmsPbs(dataFolder, libraryFolders)
	{
		mType = Ogre::HLMS_USER0;
		mTypeName = "Wind";
		mTypeNameStr = "Wind";

		setListener(&mWindListener);

		mReservedTexSlots = 2u;
	}

	virtual ~HlmsWind() = default;

	void addTime(float time) {
		mWindListener.addTime(time);
	}

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

	void shutdown(Ogre::SceneManager* manager) {
		Ogre::TextureGpuManager* textureManager = manager->getDestinationRenderSystem()->getTextureGpuManager();
		textureManager->destroyTexture(mNoiseTexture);
		mNoiseTexture = nullptr;

		Ogre::Root::getSingleton().getHlmsManager()->destroySamplerblock(mNoiseSamplerBlock);
		mNoiseSamplerBlock = nullptr;

	}

	void notifyPropertiesMergedPreGenerationStep(void){

		HlmsPbs::notifyPropertiesMergedPreGenerationStep();

		setTextureReg(Ogre::VertexShader, "texPerlinNoise", 14);
	}

	static void getDefaultPaths(Ogre::String& outDataFolderPath, 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/Common/" + shaderSyntax);
		outLibraryFoldersPaths.push_back("Hlms/Common/Any");
		outLibraryFoldersPaths.push_back("Hlms/Pbs/Any");
		outLibraryFoldersPaths.push_back("Hlms/Pbs/Any/Main");
		outLibraryFoldersPaths.push_back("Hlms/Wind/Any");

		//Fill the data folder path
		outDataFolderPath = "Hlms/pbs/" + shaderSyntax;
	}
    
    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)
    {

		*commandBuffer->addCommand<Ogre::CbTexture>() = Ogre::CbTexture(14, const_cast<Ogre::TextureGpu*>(mNoiseTexture), mNoiseSamplerBlock);

		return Ogre::HlmsPbs::fillBuffersFor(cache, queuedRenderable, casterPass, lastCacheHash, commandBuffer, isV1);
    }

};

Note getDefaultPaths, “Hlms/Wind/Any”, is used as the shader script folder.

So to setup the HLMS, it first needs to be registered like any other HLMS.

    Ogre::String shaderSyntax = "GLSL";
    if (renderSystem->getName() == "OpenGL ES 2.x Rendering Subsystem")
        shaderSyntax = "GLSLES";
    if (renderSystem->getName() == "Direct3D11 Rendering Subsystem")
        shaderSyntax = "HLSL";
    else if (renderSystem->getName() == "Metal Rendering Subsystem")
        shaderSyntax = "Metal";


    auto createHlms = [&]<typename T>(T * &hlms) {
        Ogre::String mainFolderPath;
        Ogre::StringVector libraryFolderPaths;

        T::getDefaultPaths(mainFolderPath, libraryFolderPaths);

        Ogre::ArchiveVec archive;

        for (Ogre::String str : libraryFolderPaths) {

            Ogre::Archive* archiveLibrary = Ogre::ArchiveManager::getSingleton().load(rootHlmsFolder + str, "FileSystem", true);

            archive.push_back(archiveLibrary);
        }

        Ogre::Archive* archiveUnlit = Ogre::ArchiveManager::getSingleton().load(rootHlmsFolder + mainFolderPath, "FileSystem", true);
        hlms = OGRE_NEW T(archiveUnlit, &archive);
        Ogre::Root::getSingleton().getHlmsManager()->registerHlms(hlms);
    };

    Ogre::HlmsUnlit* hlmsUnlit = nullptr;
    createHlms(hlmsUnlit);

    Ogre::HlmsPbs* hlmsPbs = nullptr;
    createHlms(hlmsPbs);

    HlmsWind* hlmsWind = nullptr;
    createHlms(hlmsWind);

Once HlmsWind has been registered, you need to call its setup like so:

    HlmsWind* hlmsWind = dynamic_cast<HlmsWind*>(Ogre::Root::getSingleton().getHlmsManager()->getHlms(Ogre::HLMS_USER0));
    hlmsWind->setup(mSceneManager);

Note: HLMS_USER0 was set in the HlmsWind ctor.

Later to update the time inside the sim loop:

	HlmsWind* hlmsWind = dynamic_cast<HlmsWind*>(Ogre::Root::getSingleton().getHlmsManager()->getHlms(Ogre::HLMS_USER0));
	hlmsWind->addTime(timeSinceLastFrameInMS);

The material file must reference HlmsWind.

hlms Grass1 Wind
{
	scene_blend	alpha_blend
	depth_write	off
	diffuse_map	grass1_diffuse.png
	specular_map	grass1_diffuse.png
	normal_map	grass1_normal.png
}

Complete Source and Resources:

https://github.com/nicholaskomsa/ogredemos2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: