返回资讯中心
外部精选
软件工程
#社区热议

The Missed Opportunities of Sonic 06

Shadows are also occasionally ill-defined in vanilla. Objects can set whether they contribute to shadow maps, and they sometimes get this wrong, leading to problems like Sonic’s Crisis City snowboard having no shadow, a…

Hacker Newsibobev2026/10/23 · 3 天前17 分钟阅读

以下正文同步自 Hacker News,版权归原站所有,已转换为易读排版。

Shadows are also occasionally ill-defined in vanilla. Objects can set whether they contribute to shadow maps, and they sometimes get this wrong, leading to problems like Sonic’s Crisis City snowboard having no shadow, and Elise’s skirt missing a shadow in one of Sonic’s story cutscenes. These are not as easily patchable, as it would likely require direct edits to the objects themselves, or a complicated hook to find and correct objects in the render code itself.

This is one of the sadder cuts in my opinion. For what it’s worth, Sonic 06 in theory has a pretty flexible lighting system. There’s a couple of fixed ambient lights defined for every object, and then several point lights. These are meant to provide additional small details for objects like torches, they don’t cast shadows, and are rather inexpensive to render.

shared struct { float4 Position; float4 Color; float4 Attenuation;

} g_PointLights[2]

Sonic Team limited the number of point lights per-object to 2, that means at any given time, only 2 lights literally have the space in shader inputs to be rendered. Ordinarily, this sort of limitation would be fine, most forward renderers will have this kind of limit, so long as the designers remained cognisant of the limit when designing stages. Well, uh, they didn’t.

Some stages like Kingdom Valley obey the limit pretty faithfully, and then stages like Flame Core and Dusty Desert utterly blow the limit out of the water, attempting to render 8+ lights per object. Needless to say, this doesn’t work out. The renderer gets confused, and lights will “fight” for priority, causing some to appear or disappear semi-randomly.

Overloaded point lights in Flame Core. Only lights on the left side of the path render.

Sonic Team attempted to provide a general optimisation for point lights, and allocate them effectively to nearby objects, but something in this check went wrong. I suspect a combination of the limit being sharply lowered later in development, and the switching of many per-pixel lit objects to per-vertex, which can vastly alter the contribution area of a point light. The result is point lights rarely work at all in vanilla, even in stages that respect the limit.

In the recomp, we are able to patch out most of the optimisation that breaks them, allowing point lights to render much more stable than in vanilla, however we are still limited to the 2 light limit. The lighting calculations responsible for adding point light contribution are all unrolled/inlined in the final shader bytecode, which makes identifying and modifying this code tricky as it can look different from one shader to the next. This is an area I want to tackle in the future.

Did you know Sonic 06 had real-time planar reflections? Hint: We saw them earlier in Crisis City. I actually don’t have much to complain about here, generally they work really well and add a lot of realism to a scene. Of course, the resolution is severely limited in vanilla to 1/4 the resolution of the framebuffer, but that’s easily patched. There is one area where the game’s visuals get screwed over, and that’s in multiplayer.

Murky fixed cubemap reflection in Kingdom Valley.

To save having to render expensive planar reflections twice in multiplayer, Sonic Team opted to just remove them entirely and replace them with static cubemaps. The problem is these cubemaps are pretty meh. Kingdom Valley goes from looking awesome to more like a depressed swamp. Well no problem, we can just patch it in right? Unfortunately due to how the rendering code works this isn’t so easy.

Sonic 06 uses Lua scripting to build the render graph, and different modes have different scripts. It’s a pretty cool and flexible system, when it works. As expected, singleplayer and multiplayer have their own render scripts, render_gamemode.lua and render_gamemode_multi.lua respectively.

function Build(ARG_0) CreateCSM(ARG_0) RenderCSM(ARG_0, GenerateCSMLevels, GenerateCSMObjects) ApplySceneParams(ARG_0)

RenderReflection(ARG_0)

PrepareColorRender(ARG_0) SetFrameBufferObjectOnce(ARG_0, "framebuffer0", "depthstencil", "all", 0, 0, 0, 0) SetViewport(ARG_0, 0, 0, GetSurfaceWidth(ARG_0, "framebuffer0"), (GetSurfaceHeight(ARG_0, "framebuffer0"))) SetCurrentCamera(ARG_0, "main") AutoSetAspect(ARG_0) Zpass(ARG_0, "depthbuffer0", "depthstencil", "main") RenderMain(ARG_0, "framebuffer0", "depthstencil") PostColorRender(ARG_0) Capture(ARG_0, "framebuffer0") MakeBloom(ARG_0) ApplyBloom(ARG_0) SetCurrentCamera(ARG_0, "mainglare") AutoSetAspect(ARG_0) RenderWorld(ARG_0, "glare") RenderPostprocess(ARG_0, "framebuffer0") SetFrameBufferObject(ARG_0, "postprocess_output", "depthstencil", "none", 0, 0, 0, 0) SetCurrentCamera(ARG_0, "mainafterpp") AutoSetAspect(ARG_0) RenderWorld(ARG_0, "afterpp", "all", "transparent") SetFrameBufferObjectOnce(ARG_0, "backbuffer", "null", "color", 0, 0, 0, 0) SetFrameBufferObject(ARG_0, "backbuffer", "null", "none", 0, 0, 0, 0) ResetViewport(ARG_0) if EnableRasterizeBlur then RasterizeBurnoutBlur(ARG_0, "postprocess_output", "main", 1) else Rasterize(ARG_0, "postprocess_output") end RenderAfterPostprocess(ARG_0) Render2D(ARG_0) end

In render_gamemode.lua we can see that crucial RenderReflection() call, which is unfortunately entirely absent from render_gamemode_multi.lua. As a result, attempting to enable the realtime reflections in multiplayer will just leave us with a black void rather than pretty reflections.

We can’t just swap out render_gamemode_multi.lua for render_gamemode.lua either. The multiplayer renderer has special steps for creating the split-screen, essentially two framebuffers, so for multiplayer to render correctly, the provided render graph needs to be able to render both framebuffers. Even if you modify the multiplayer renderer to have RenderReflection, it’s not set up to work in this split-screen environment, and will, again, not work as intended. As it stands this quickly becomes a rabbit hole that’s not easy to resolve. If in future we have an easier way to apply Lua patches, I may revisit this.

Maybe. Several of these problems have been at least partially addressed, but I’ve yet to figure out the shader edits that would be needed to get us to that beta footage goodness. Some of these fixes are largely, out of scope for the recomp, but whether fixes make it or not, we’re still learning a ton about how this game works on the backend. That still directly contributes to future improvements and patches.

This game has much, much more to give, and it’s honestly sad that Sonic Team were not given the time to finish it. There’s many things that I didn’t get in to in this article including the insanity of the character shader, or the lack of occlusion culling, but it’s long enough already. Have fun with this silly game and happy early 20th anniversary.

Thank you to Gordon Ramsay for fact checking.

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →