56 lines
3.1 KiB
Typst
56 lines
3.1 KiB
Typst
#let post_slug = "dynamic-vision-shader"
|
|
#let post_preview_image = "image2.png"
|
|
#let post_summary = "Using prompt injection to extract Gemini's system prompt"
|
|
#let post_date = "2026-07-28"
|
|
|
|
= Dynamic Vision
|
|
|
|
== The problem
|
|
In 3d, objects occlude your vision, and you cannot see behind them. This isn't really implemented in most 2D games as it can be disorienting, confusing, and therefore unnecessary.
|
|
|
|
For Orebit, disorientation is a mechanic. Careless piloting in unfamiliar territory may cost the player a collision with the terrain or hostiles. Lack of spatial awareness can make it hard to detect your neighbors, which is great for hiding from potential enemies. As much of the players time is underground, the claustrophobia induced by occluding vision is also useful. With this in mind, it adds to the immersion to not know what lies around corners, and dynamic 2D vision is necessary.
|
|
|
|
|
|
== Solution 1
|
|
Lighting mechanics can simulate dynamic vision, using darkness to represent hidden areas. First, the whole world needs to be dark. Then, emit light where the player should be able to see. Objects with light occluders on cast shadows, which effectively blocks vision behind objects like terrain.
|
|
|
|
In Godot, this is implemented by applying a black CanvasModulate node to a scene to create darkness. Objects with collision polygons can reuse their polygon in a light occluder. PointLights using a circular texture can emit light around the character.
|
|
// #image("assets/image.png")
|
|
|
|
== Solution 1 Flaws
|
|
The world in Orebit is massive, which becomes a problem when trying to light the entire area. The radius of the circular texture used must be at least as large as the farthest point visible, which can be tens of thousands of pixels away. This means a texture tens of thousands of pixels squared (for the x and y axis) must be used, which severely impacts performance even on high end hardware.
|
|
|
|
The solution to this is to scale up a lower resolution light texture, but this has its own issues. The texture is made of discrete pixels, each one black or white if it is occluded or not occluded. As these pixels scale the shadow edges go from smooth, high resolution edges to pixelated, jagged edges. The other problem with scaling light maps is that light clips through objects close to the light source.
|
|
|
|
|
|
== Solution 2
|
|
Another way to understand the problem is to think of every vision occluding polygon as a set of vision occluding line segments ($overline(A B)$). Visible or not, each line segment occludes a specific shape: a quad (a shape with four vertices) composed of one point on each end of the line segment, and another point at each line end projected away from the character.
|
|
|
|
|
|
#import "@preview/cetz:0.5.2"
|
|
#import "@preview/cetz-plot:0.1.4"
|
|
|
|
#cetz.canvas({
|
|
import cetz.draw: *
|
|
|
|
line((0, 0), (2, 1), (3, 4), (2, 5), (1, 4), (0, 4), (-1, 2), (0, 0))
|
|
})
|
|
|
|
|
|
$
|
|
p_1 &= A\
|
|
p_2 &= A + d tan^(-1)(A-C)\
|
|
p_3 &= B\
|
|
p_4 &= B + d tan^(-1)(A-C)
|
|
$
|
|
|
|
|
|
|
|
Solution 2 can be implemented in Godot using ImmediateMesh's for each line in the polygon, with two triangles composing each quad for each line in the occluders polygon.
|
|
|
|
(Solution 1 overlaid on solution 2)
|
|
// #image("assets/image2.png")
|
|
|
|
|
|
|