Giter Club home page Giter Club logo

Comments (28)

turanszkij avatar turanszkij commented on August 16, 2024

Hi,

It is possible to render 30k+ lights (without shadows). You will have to see if it's efficient enough for what you want. The following Lua script can be loaded in the Editor easily and will put 32400 point lights in the scene, in a grid slightly above ground level:

local scene = GetScene()
for i=1,180 do
	for j=1,180 do
		local entity = CreateEntity()
		local transform = scene.Component_CreateTransform(entity)
		local light = scene.Component_CreateLight(entity)
		transform.Translate(Vector(i-90, 0.5, j))
		light.SetRange(3)
	end
end

You can save this script as a text file with .lua extension and use the "Load Script" button in the Editor to open the file and execute the script.

Some gotchas:

  • By default, the engine is limiting the number of lights below 30k as optimization for a more usual case. ShaderInterop_Renderer.h has a constant named SHADER_ENTITY_COUNT, which says how many lights, entities can be accessed by shaders at maximum. You can set this value to 33000 for example and it will be able to render up to 33000 lights, but the engine will allocate more memory. You will need to rebuild the engine after this change.
  • Editor will have light picking enabled by default, and in this mode a point light image is rendered on screen for every light, so this will result in 30k+ draw calls. Renderer window -> Pick Lights checkbox can disable this.
  • You can consider using different Render Paths. The best one for this amount of lights will be either Tiled Deferred or Tiled Forward. The simple Deferred could work too. The simple Forward will be very slow.
  • Also, light range will affect performance, so I set it to 3 units. Bigger light range = slower perf.

Thanks for the interest, let me know if you have any problems with this.

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

Thanks for the detailed explanation.

I have tested it and it seems to be too much for the PC/Engine to handle (tested with ~22000 so I dont have to recompile the engine).

I need the lights to be visible using some geometry. Is it possible to use spheres with an emissive pbr material? Can I change the emissive color of the material in realtime?

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

Yes, you can add emissive objects. There is a sample script as well that shows how to load object and set emissive color on a material: scripts/set_material_emissive.lua
If you just want to show lights, there is also a light visualizer thing that can be turned on from the Editor.. but it's not exposed to lua right now.

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

After trying out the script I think an object with emissive material would be a good fit. Now I am wondering how I can create 30k entities in C++ using the same model (a sphere converted to .wiscene in the editor) without loading the model 30k times (like it would when I use LoadModel())

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

In either C++ or LUA script, you can use Scene::Entity_Duplicate(entity). You want to duplicate the entity that has the ObjectComponent, but not the MeshComponents and MaterialComponents. If you do this, all instances will be rendered in 1 draw call.

You can find out the name of the Entity that has the object component by opening in the Editor, selecting the object and the ObjectWindow will display the name and Entity ID. If you have the name, you can use Scene::Entity_FindByName. The Entity ID is not persistent across different runs of the program, so I suggest retrieve entities by name if you load them from file.

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

An other approach - but this will not instance the models - is to LoadModel() into a custom Scene. Two Scenes can be merged with Scene::Merge().
So in this case, you would LoadScene() once, create 30k copies of the Scene that you loaded, then Merge all of them to the main scene.

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

In either C++ or LUA script, you can use Scene::Entity_Duplicate(entity). You want to duplicate the entity that has the ObjectComponent, but not the MeshComponents and MaterialComponents. If you do this, all instances will be rendered in 1 draw call.

You can find out the name of the Entity that has the object component by opening in the Editor, selecting the object and the ObjectWindow will display the name and Entity ID. If you have the name, you can use Scene::Entity_FindByName. The Entity ID is not persistent across different runs of the program, so I suggest retrieve entities by name if you load them from file.

Is it possible to change the emissive color per object with this approach? Or do all share the same material?

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

Right now it's not supported to change emissive color per object. However, you can do it with a small modification to the shaders. In objectHF.hlsli file, you can modify the ApplyEmissive function from this:

inline void ApplyEmissive(in Surface surface, inout Lighting lighting)
{
	lighting.direct.specular += surface.emissiveColor.rgb * surface.emissiveColor.a;
}

To this:

inline void ApplyEmissive(in Surface surface, inout Lighting lighting)
{
	lighting.direct.specular += surface.emissiveColor.rgb * surface.emissiveColor.a * surface.baseColor.rgb;
}

(You will need to build the shaders again)

This will multiply the material emissive color with object's base color, so you can have white material emissive and change the emissive by changing the object's color (ObjectComponent::color). Also, this is not exposed to LUA yet, but it should be, so I will add this later.

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

Alright, I recompiled the shaders and used Entity_Duplicate to clone the object entity.

Changing the color using the object component works as described.

Now I am stuck at setting the transform of the light points (i.e. the cloned entities). Using the transform component of the object entity does not work. Can you give me some pointers?

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

Hm, that should work.. are you modifying the TransformComponent with the functions provided (eg. Translate()), or by writing to data members? Functions will make the Transform dirty, otherwise you have to make it dirty yourself by calling SetDirty(). This way, UpdateTransform() will refresh the transform matrix. UpdateTransform() is called automatically by the engine once per frame for every dirty transform.
If that doesn't solve your problem, could you show how you are trying to obtain the TransformComponent of the object entity?

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

Here you go:

auto entity = CreateModel(render_scene, archive, XMMatrixIdentity(), true);

for(auto id : scene.lights()) // "scene" is the collection of lights I try to visualize
{
    const Eigen::Vector3f pos = (istring::to_eigen(scene.get_position(id)) - center) / scaler;
			   		
    auto light_entity = render_scene.Entity_Duplicate(entity);

    auto transform = render_scene.transforms.GetComponent(light_entity);
    transform->ClearTransform();
    transform->Scale({ .1f, .1f, .1f });
    transform->Translate({ pos.x(), pos.y(), pos.z() });
    transform->ApplyTransform();

    entities.push_back(light_entity);
}

In CreateModel I create the initial entities:

wiECS::Entity CreateModel(wiSceneSystem::Scene& scene, wiArchive& archive, const XMMATRIX& transformMatrix, bool attached)
{
    // Serialize it from file:
    scene.Serialize(archive);

    // First, create new root:
    auto root = wiECS::CreateEntity();
    scene.transforms.Create(root);
    scene.layers.Create(root).layerMask = ~0;

    {
        // Apply the optional transformation matrix to the new scene:

        // Parent all unparented transforms to new root entity
        for (size_t i = 0; i < scene.transforms.GetCount() - 1; ++i) // GetCount() - 1 because the last added was the "root"
        {
            wiECS::Entity entity = scene.transforms.GetEntity(i);
            if (!scene.hierarchy.Contains(entity))
            {
                scene.Component_Attach(entity, root);
            }
        }

        // The root component is transformed, scene is updated:
        scene.transforms.GetComponent(root)->MatrixTransform(transformMatrix);
        scene.Update(0);
    }

    if (!attached)
    {
        // In this case, we don't care about the root anymore, so delete it. This will simplify overall hierarchy
        scene.Component_DetachChildren(root);
        scene.Entity_Remove(root);
        root = wiECS::INVALID_ENTITY;
    }

    return scene.objects.GetEntity(0); // There is only one object in the scene I load
}

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

I think the problem is that you are calling ApplyTransform(). What it does is decompose the world matrix into scale, rotation and translation. However, world matrix is not updated immediately after you call Translate(), so it is probably identity matrix at that time. The world matrix is updated when you call UpdateTransform().

TLDR: I think you might want to do UpdateTransform() instead of ApplyTransform() here. UpdateTransform() is also automatically called when the scene is updated every frame, so you might not even need to call it, unless you really want to use the updated world matrix right away.

I'm sorry that this is not well documented at the moment.

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

Same result after removing ApplyTransform() (I also tried with UpdateTransform())

image

I also tried the alternative approach of using scene.Merge() which worked but it was too slow for larger light scenes

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

Ok, that was a false alarm, it works. Oddly I had to do a full clean/rebuild the project

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

I have set up a floor using a metallic plane but the reflections (using voxel GI) are white (because the emissive color is white). Is there something I can do to get the color I set in the object component to be reflected?

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

For voxel GI, emissive is computed in a different shader. Look into objectPS_voxelizer.hlsl and find this line:

color.rgb += emissiveColor.rgb * emissiveColor.a;

And change it to this:

color.rgb += emissiveColor.rgb * emissiveColor.a * baseColor.rgb;

(You will need to recompile only this shader)

Let me know if you find any other issues.

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

That worked, thanks!

Looking at the results I am afraid that I will need an orthographic camera. Depending on the resolution, view angle and distance, light points are disappearing/popping up creating artifacts:

image

same scene after maximizing the window (and thus changing the resolution):

image

The second pictures comes close to what it actually looks like. However there are still artifacts as the real grid is regular but in the visualization the light points are not evenly distributed.

Do you have plans to implement/support orthographic cameras? Do you have any other ideas to reduce the artifacts?

Thanks in advance, your comments are always helpful.

Edit

An extrem example using spheres as light points:

image

Maximized:

image

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

Alright, seems like making the camera orthographic is just a matter of using XMMatrixOrthographicLH() in CameraComponent::UpdateCamera() to create the projection matrix. I could add an enum to be able to set the camera type in the camera component and reuse width and height members for the frustum size. I can create a PR for that if u like.

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

So the orthographic camera solved your issue here? It might be a bit more difficult to support orthographic camera properly, just because there are so many systems relying on camera matrix. I am thinking cascaded shadows, light culling, deferred, raytracing, and many more.. It might be challenging to implement and test it for all systems. I'm not against the idea, but if it is to be added to the core engine, all or at least most systems should work with it.

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

Yes it does reduce artifacts quite a bit. For my purposes rendering with the orthopgraphic camera seems fine but I cant say if some render systems are affected.

I did some tests with larger scenes (~23000 light points) and I am hitting a performance bottleneck. Rendering takes about 35 ms to 42 ms which is too slow for realtime visualization. Is there something I can tweak to improve render performance?

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

What kind of RenderPath are you using? You could try an other one and compare performance. Apart from that, you could reduce the resolution scaling to something below 1 for example:

wiRenderer::SetResolutionScale(0.5f);

This would reduce the resolution to half, but upscale it to window resolution at the end of rendering.

Last resort would be actually rewriting lighting shaders to have simpler lighting, and cutting unneeded parts from shaders in general.

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

I've tried some different render paths but it does not really make a difference which one I choose (apart from the path tracer).

Upscaling decreases visual quality too much and does not improve performance significantly.

Maybe its a problem how I call MainComponent::Run(). I am running a fixed interval loop where I first update the lights and then run the main component. Could this be a problem?

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

It sounds like you might have a CPU bottleneck if resolution doesn't affect the framerate significantly. When you update the lights for yourself, are you doing that on a single thread? It might also be the engine's fault internally, I have to say I didn't really test much with that many entities yet.

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

Updating the lights is done in parallel and takes about 10 - 15 ms. However, it does not run concurrently to the MainComponent::Run(), essentially:

while(true)
{
    // uses a parallel algorithm
    // but sequential in respect to MainComponent::Run()
    if (elapsed > 30ms)
        UpdateLights(); 
    
    // this is run every iteration
    ProcessWindowMessages();
    MainComponent::Run(); 
}

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

Yeah, your code looks okay. Although the best place to update would be in RenderPath::Update() function, that would be same performance-wise I suspect. This looks like a limitation of engine currently. Need a round of profiling to find slow parts and optimize them. Not an easy task. Do you have this repo you are working on somewhere online?

from wickedengine.

f-tischler avatar f-tischler commented on August 16, 2024

Alright.

Unfortunately not as it is a closed-source project at work.

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

Hi, have you got anywhere with this?

from wickedengine.

turanszkij avatar turanszkij commented on August 16, 2024

Feel free to reopen if you want to continue the discussion.

from wickedengine.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.