Tarnished House - Very First Scene

Hello everyone,

I’ve been working on my very first Three.js scene for a while now, and I plan to keep going as I learn more.

Source Code

Scene

At this point, I’d really appreciate any pro/amateur tips or critiques.

What I’ve done so far:

  • I mostly used pre-made assets, but I’ve optimized them by reducing vertex counts, changing textures, and making other tweaks.
  • The point particle system is completely built from scratch by me. The flame particle effect is adapted from this source, ported in GLSL2, and further improved.
  • Shadows are fully dynamic with customizable options, nothing is baked, which impacts performance and requires further optimization.
  • I’ve also added some post-processing effects like bloom and antialiasing (basic for now).
  • The scene is Souls-like themed, originally inspired by the Three.js Journey - Haunted House scene.
  • Trees, bushes, and graves are randomly generated, and their counts can be adjusted through the settings panel.
  • I also created a themed UI and an some text in the style of Souls-like or Elden Ring.

About Techs:

The project is built with TypeScript, mainly for better error handling and type safety (for future sanity :sweat_smile:).
It’s currently developed using Vite + Three.js, which really helps simplify the workflow.

The UI is made with CSS, and the dev panel is handled via lil-gui.
There’s no physics engine or else for now…

Future plans:

My first goal is to expand the scene with procedural generation, using the same meshes and models but with more variety in terrain shapes, powered by a procedural shader. I’ve already tried to limit the visible area of the scene using fog.

I’m also aiming to turn this into a playable, browser-based Souls-like game. I know there’s still a long road ahead, basic player controls and physics aren’t too difficult, but animations, enemy behavior, and especially optimization are the real challenges.

I’ve started working on performance optimization by providing different quality presets for mobile and desktop, but there’s still a lot to improve.

For example, I tried using “InstancedMesh” for trees and other randomly generated objects several times, but in my case, it actually increased draw calls instead of reducing them. I suspect that’s because frustum culling doesn’t work effectively with instanced meshes, and proper optimization in that area likely requires more advanced tweaking. That’s why I’m currently cloning each mesh and placing them randomly.

I’m open to any kind of critique, advice, or ideas. If you have time, feel free to critique the code structure, implementantions or just share your thoughts on how the scene looks from the end user’s perspective.

Thanks for reading this far!

———————————-
Update:
Reworked bonfire model, visually improved it a bit and optimized it. Fixed a bug in the random mesh spawn positioner (it was stupid mistake), and improved spawn logic, no longer clears all meshes, now spawns only the diff of random mesh counts. So count options became intensity control.

I would really appreciate any feedback on performance. So far, I’ve only tested on a Linux desktop with a gaming setup and on an Android mobile device. I’m curious how it performs on laptops with integrated gpu and on other systems.

11 Likes

Amazing how fire the web has come… My ovenPhone’s batch cache used all 3 GPU cores in 8+1 workgroups! :sweat_smile: Pleasantly your normal textures retain fidelity at extreme zoom, as does the scrub bush. This seems disconnected from the shadows which (1) on the bush are low-resolution & flicker in the extreme shallow flat angle, and (2) the perimeter pulses unnaturally with a sharp outer falloff. The sword itself is excluded from lighting nuance which might otherwise make it the central hero!

The showcase may benefit from more emphasis on light/shadow. For example a motivated knight’s ghostly silhouette on the wall. Or a more voluminous swordgrass bush spells trouble for nearby blacksmurfths. I double-tapped the H in HELP as the menu said and waited for the secret moment.

Thank you :folded_hands:t2:

1 Like

First of all, I should be the one thanking you for the feedback. It was actually super helpful.

TLDR;

“The showcase may benefit from more emphasis on light/shadow.”

Yeah, for now it’s pretty much just that, with a bit of shader work and some random mesh placement. The plan is to eventually have playable mechanics, a character, animations, and a procedural map (high hopes…).

“For example a motivated knight’s ghostly silhouette on the wall.”

That’s honestly a great idea. The controllable character will definitely be a proper, impactful souls-like char, and once there’s a bonfire you can sit at as a save location, why not add this kind of shadow play? Would be pretty cool.

“Or a more voluminous swordgrass bush spells trouble for nearby blacksmurfths.”

To be honest, I didn’t quite get this part :sweat_smile: but yeah, I definitely plan to diversify environmental elements and improve the map composition beyond just random meshes.

“I double-tapped the H in HELP as the menu said and waited for the secret moment.”

This one’s about the little puzzle in the settings;

Souls-Like Code Book Rule Number One: Nothing should be easy to understand.

So I figured I’d hide a small puzzle for the dev GUI :rofl:. Nothing much there yet, but maybe I could make it a bit longer, like an old-school cheat code combo… hmm. :thinking:

Regarding the bonfire,

I tried boosting the emissive to make it look nicer, did some fine-tuning on the effect and the light on it, also tweaked the bloom to push the emissive look more, and improved the ground texture. My plan for today was to create a Bonfire class, hook all the effects into it, instantiate it properly, and work on the code structure for a big-map game feature… but you know, dreams vs reality :rofl:. JK, it actually turned out great. Jumping into game-like features before improving the core structure would be wrong anyway, so I wanted to share and get feedback first.

And the main thing, shadows and optimization:
I’ve been working on this but still haven’t found a perfect solution for better shadow quality without killing performance. For the flicker you mentioned, the low-res jaggies on the edges, and the issues that can show up depending on camera movement right now, other than tweaking bias and shadow map size, I haven’t been able to do much. I also tried adjusting the lights’ shadow.radius and blurSamples to improve things, but yeah, even with the highest resolution, some small details still get shadow artifacts.

To address that, I implemented different bias settings based on shadow resolution finding the best bias for each resolution and locking it in the config. Default shadow res is 2K for desktop, still 512px for mobile. I also disabled MoonLight on mobile in the settings, which avoids generating a lot of unnecessary shadows, but honestly, learning and developing more advanced techniques that update shadows based on camera angle would be a way more permanent solution. First, I just need to figure out the exact keyword/term for that technique :smile: … if anyone has suggestions, I’d love to hear them. I can always open a separate thread for the implementation details or just dig into it myself.

As for the “pulse” part, that’s intentional. I tried giving the bonfire light a small animation to simulate flame flicker; not a bug, a feature :rofl:. Right now it’s only changing in terms of light distance and height, nothing fancy. I probably need to give it a more realistic flame-style flicker so it actually conveys the intended effect better.

So yeah, until I learn more advanced techniques, the scene will probably keep burning GPU for a while. Honestly, on Google Chrome it holds 144 FPS without stressing the GPU too much on the current default settings, but Firefox is a different story, and I have no idea how it behaves in other browsers or systems. And to be fair, the GPU I tested on isn’t the best choice for getting a general idea anyway (RTX 3080)…

Thanks again for the feedback.

And yeah… guilty as charged, I did add a couple of unnecessary options instead of focusing on the main bug :rofl:

1 Like

The bugfix ambient light helped, like a skybox. It fixed the harsh nature of the fire’s radial shadow fanning directly into the camera. The flat shadow noise was detracting, looking through sprite tree branches at mesh bushes. :smiling_face_with_sunglasses: Hot off the heels of seeing a quality spiral sword, that commanded attention. I thought it would be a skeleton from under the graves roots.

TBH since you’re profiling a SoulsLike, I gather the most valuable data is random user platform metrics… not if my role-playing fantasy was titular OP.

1 Like

I like the overall mood.

But as you collect ideas for improvements, here are a few. Just keep in mind they are purely subjective.

  1. Then entry screen text is hard to read. Decorative fonts are good for short texts, but are not suitable for a page-long text.

  2. The FPS on integrated Intel GPU is fps 40 (solid fps 144 on NVIDIA)

  3. The pulsation of the shadows of the stones around the campfire are too rhythmic and too synhronous, maybe some perlin/simplex noise could make them more natural

  4. Sometimes branches look dashed

  5. The shadow of the bar looks like shifted to the right

  6. There are some alias artefacts (when antialiasing is on, they are much less, but still present),

  7. It is fun going underground (although it lasts just a fraction of a second)

  8. I missed some bats or crows

Do not worry, these are just some picky observations. No need to react on any of them.

2 Likes

Thank you for your feedback and give your attention,

No worries, it’s not about reacting, I’m open to criticism and improvement. What you said is actually quite useful.

Since we started from the end, let’s keep going that way:

Absolutely, at least crows should be there! If I manage the performance issues, they’ll definitely be in!

I fixed this by updating the camera limits. For performance reasons, I had made the floor one-sided.

This one is actually not caused by antialiasing, it’s a UV seam artifact issue. I rebaked the house model at a higher resolution with a margin, and the normal map was also problematic, so this shouldn’t happen anymore. If the user cares about quality, then I guess a small increase in loading time won’t be a problem, since the texture size got bigger :laughing: . Also, the antialiasing you see is just the basic renderer AA; MSAA or FXAA options are planned.

This is entirely due to low shadow resolution. If I don’t push the normal bias that much, it causes heavy flickering.
Like this:

I’ve reduced it a bit now, but it’s still not 100% accurate. At 1024 shadow res or higher, this issue doesn’t happen.

That’s because the tree branches aren’t 3D meshes, they’re 2D textures. At certain angles they look like this. It’s actually a pretty good performance trick. Under normal planned conditions, with proper user control, it won’t be possible to see them this close very often. But maybe with some advanced tricks I could make them face the camera or try something else. :thinking: If you have any idea it would great…

This is just a simple fire light (point light) animation, no shader or advanced techniques. I can’t say I know much yet, but I think with shaders I could make the shadow appearance subtly flicker, which I assume is what you meant, and yes, I should definitely try pushing it with shaders. On mobile/low settings, the only active light is currently the fire light, and if I disable the shadow entirely it would give much better performance. Which brings me to the integrated GPU topic, it’s probably already applying low-end settings, and still 40 FPS isn’t that great, so I need to work on that…

As for the text readability, it didn’t feel that hard to read for me, but if everyone agrees I can change it. Later on I’m thinking of having the in-game text appear in the same font (plus an optional plain font) at the bottom of the screen. Then again, who even reads game text these days?

Soul-like code book rule #2: Story and text aren’t easy to understand, so use archaic English and fonts the player won’t understand.

so technically, I’m following the book :rofl: ..

Thanks again for the feedback. If you have any ideas about shadows or performance tricks, even just some keywords, they’d be super helpful…

1 Like


I’ve added interactive updates and refactored the structure/codebase to prepare for the next round of features. Thoughts?

Changelog:

New Bonfire class: A self-contained scene object that composes the flame, sparks, and smoke systems and internal fire light.

Interactive ignition: An in-world “Ignite” button (raycasted 3D mesh) triggers the effect startup.

Sparks attach quickly; smoke attaches in timed increments for a natural buildup.

Flame sync & placement: Volumetric flame now syncs its inverse model matrix on placement so shader space follows the Bonfire root. Works correctly with attach/parent transforms.

Particle parenting: Particles are snapped to Bonfire world transform and then attached.

Emissive ramp: Sword blade and hilt materials smoothly ramp emissiveIntensity from 0 to 11 with an ease-in curve, starting on ignition.

API ergonomics: async constructor that waits for the GLTF template; getters for smoke, sparks, flame, fireLight, with step function, updates all sub-systems.

Misc: Minor utilities & cleanup and type improvements.

No performance lost. (I guess :laughing: )

Edit: I forgot to add the positional fire sound; I’ll add it tomorrow…

Performance falls off a cliff as one approaches the flame. For a Soulslike, this is worse than a magic dragon using 10 million polygons to defeat you, at the precise moment your druid casts Speed 0 from 1 unit away. Arguably the GameMaster should limit showcasing shadow-casting in the Unilateral Tournament. Perhaps a magical mipmap could drop a hitbox of flame retardant?

~ Proverb “rational” Treatment

Okay, I’ll turn off the bonfire’s stones’ shadow, it doesn’t add much with the current animation. Maybe later I’ll improve it with a baked shadow and a tiny animated wobble, or even just AO.

None of the particles cast shadows anyway; there’s a fire light and a moon light in the scene.

I already detect lowend pc or mobile devices and apply special settings, but since the particle system changed recently, the low-end particle counts aren’t adjusted yet. I’ll do that, which should help more.

I don’t think the flame has a texture issue; mipmaps are enabled by default and the size settings are in place. I could also disable tone mapping for an extra win but i don’t think it is effect much. The flame’s whole trick is animating via a ray-marching shader and it is cost a lot but still works on many modern phones or mid-level gpu ~140 fps (via google chrome :grin:).

Also, I’m not aiming for a 10-million-polygon dragon :rofl: and I can’t even view the link you sent, because webGPU isn’t supported on most Linux browsers yet and not ready to use…

Sorry, that wasn’t me; I’m working hard at work. :joy_cat: My cat Bossmode got on the keyboard looking for hotbuttons.

I just mean the simple fire asset (much less 2+ fires – or a time-sensitive NPC) may scale. Or at least invert/justify the detail:proximity ratio (especially if it’s essentially asleep).

Since you clarified we’re reviewing a game engine and this clarification is rather cat-and-mouse.

No worries, no need to sorry, I understand.

Particles are culled when they’re off-camera, so they don’t draw and the GPU doesn’t do any work. I might add a small tweak on the CPU side for the update loop, and as I scale the map with procedural systems I’m planning to fully despawn distant objects, or relocate them.

Even if two bonfires end up close enough to see each other, my plan is to run the effects on only one of them; alternatively, I can just move the existing instance instead of spawning a new one. That obviously requires solid level design.

I’ve also disabled tone mapping and lowered the ray-march iterations for low-end. Still, I should (and will) research and apply more advanced optimizations for shadows and effects. :grinning_face_with_smiling_eyes:

Also, thanks, although the video is UE-based, it looks useful for understanding GPU fundamentals.

By the way I remade v0.4 update because some thing was very wrong and it wasn’t suitable for v0.4 :rofl: ..

That’s way better! 60fps, sword shadow, believable fire radius.

1 Like