feat: terrain
This commit is contained in:
parent
f86b4ada13
commit
81474b6bca
13 changed files with 251 additions and 279 deletions
|
|
@ -28,14 +28,13 @@ const DIFFUSE = 0.6
|
|||
* Per triangle the pipeline is: transform to clip space, clip against the near
|
||||
* plane, perspective-divide to screen pixels (optionally snapping vertices to a
|
||||
* grid), then fill with an edge-function / barycentric scan. Per pixel it
|
||||
* interpolates depth as 1/w, texture coords (affine or perspective-correct, see
|
||||
* `fillTriangle`), and applies flat shading plus distance fog.
|
||||
* interpolates depth as 1/w, perspective-correct texture coords, and applies
|
||||
* flat shading plus distance fog.
|
||||
*
|
||||
* The period-accurate rough edges are deliberate, not unfinished: no mipmaps
|
||||
* (so distant textures shimmer/moire), no antialiasing (jagged silhouettes),
|
||||
* and affine texturing by default (the texture "swim"). Depth is a plain 1/w
|
||||
* z-buffer and triangles are drawn double-sided (no backface culling), so mesh
|
||||
* winding can never cause surfaces to drop out.
|
||||
* (so distant textures shimmer/moire) and no antialiasing (jagged silhouettes).
|
||||
* Depth is a plain 1/w z-buffer and triangles are drawn double-sided (no
|
||||
* backface culling), so mesh winding can never cause surfaces to drop out.
|
||||
*/
|
||||
export namespace Rasterizer {
|
||||
/** Draw an indexed mesh into the framebuffer through a view-projection
|
||||
|
|
@ -159,7 +158,6 @@ export namespace Rasterizer {
|
|||
const maxX = Math.min(fb.width - 1, Math.ceil(Math.max(a.sx, b.sx, c.sx)))
|
||||
const minY = Math.max(0, Math.floor(Math.min(a.sy, b.sy, c.sy)))
|
||||
const maxY = Math.min(fb.height - 1, Math.ceil(Math.max(a.sy, b.sy, c.sy)))
|
||||
const pc = config.perspectiveCorrect
|
||||
const fog = config.fog
|
||||
for (let y = minY; y <= maxY; y++) {
|
||||
for (let x = minX; x <= maxX; x++) {
|
||||
|
|
@ -180,22 +178,11 @@ export namespace Rasterizer {
|
|||
if (invW <= fb.depth[idx]) {
|
||||
continue
|
||||
}
|
||||
// Two ways to interpolate texture coords across the triangle:
|
||||
// affine - linear in screen space. This is what hardware without a
|
||||
// perspective divide does. It is exact ONLY when the three vertices
|
||||
// share a depth (a face viewed head-on). On a receding surface (the
|
||||
// floor, or a wall turned into the periphery) the depth gradient
|
||||
// makes it diverge, bending the texture along the triangle diagonal
|
||||
// -- the signature PS1 "texture swim".
|
||||
// persp - divide the interpolated u/w by the interpolated 1/w to
|
||||
// undo foreshortening. Geometrically correct, no swim.
|
||||
// perspectiveCorrect (0..1) lerps between them, so the look is a dial.
|
||||
const uAff = w0 * a.u + w1 * b.u + w2 * c.u
|
||||
const vAff = w0 * a.v + w1 * b.v + w2 * c.v
|
||||
const uPer = (w0 * a.u * a.invW + w1 * b.u * b.invW + w2 * c.u * c.invW) / invW
|
||||
const vPer = (w0 * a.v * a.invW + w1 * b.v * b.invW + w2 * c.v * c.invW) / invW
|
||||
const u = uAff + (uPer - uAff) * pc
|
||||
const v = vAff + (vPer - vAff) * pc
|
||||
// Perspective-correct texture coords: divide the interpolated u/w and
|
||||
// v/w by the interpolated 1/w to undo foreshortening, so textures sit
|
||||
// flat on receding surfaces with no affine "swim".
|
||||
const u = (w0 * a.u * a.invW + w1 * b.u * b.invW + w2 * c.u * c.invW) / invW
|
||||
const v = (w0 * a.v * a.invW + w1 * b.v * b.invW + w2 * c.v * c.invW) / invW
|
||||
// Alpha cutout: discard transparent texels so sprites read as cutouts,
|
||||
// not rectangles. Opaque world textures are alpha 255, so unaffected.
|
||||
const texel = Texture.sample(texture, u, v, config.textureFilter)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue