Class: Doom::Render::RayTracingRenderer

Inherits:
HardwareRenderer show all
Defined in:
lib/doom/render/ray_tracing_renderer.rb

Overview

GPU ray tracer hosted in Gosu's OpenGL context. World triangles are kept in a floating-point data texture and intersected by a fragment shader; the old hardware renderer is inherited only for texture/sprite/UI glue.

Constant Summary collapse

DATA_WIDTH =
1024
NODE_DATA_WIDTH =
1024
TEXELS_PER_TRIANGLE =
7
TEXELS_PER_NODE =
3
BVH_LEAF_SIZE =
8
MAX_RAY_LIGHTS =
8
SHADOW_SAMPLES =

Soft shadows: each shadow test fires this many rays at jittered points on a small light disc (SHADOW_SOFTNESS map units across) and averages them, so edges get a penumbra instead of a hard cut. More samples = smoother but costs a BVH ray each; radius widens the penumbra.

5
SHADOW_SOFTNESS =
12.0
AMBIENT_LEVEL =

Atmosphere: how much the sector light level fills unlit surfaces, and how bright the sky reads. Both kept low so shadows stay deep and the flashlight carries the scene -- lower is darker/moodier, higher is flatter.

0.18
SKY_BRIGHTNESS =

was 0.32

0.28
FOG_DENSITY =

Distance fog gives depth: without it a bright sector reads evenly lit all the way down a corridor. Higher density swallows distance sooner; FOG_MAX is how completely far surfaces fade into the dark fog colour.

0.0011
FOG_MAX =

was 0.00075

0.94
RAY_WIDTH =

was 0.82

640
RAY_HEIGHT =
480
MAX_TRIANGLES =
4096
ATLAS_SIZE =
2048
VERTEX_SHADER =
"#version 120\nvarying vec2 screen_uv;\nvoid main() {\n  screen_uv = gl_MultiTexCoord0.xy;\n  gl_Position = gl_Vertex;\n}\n"
FRAGMENT_SHADER =
"#version 120\nvarying vec2 screen_uv;\nuniform sampler2D triangle_data;\nuniform sampler2D bvh_data;\nuniform sampler2D material_atlas;\nuniform sampler2D sky_texture;\nuniform float data_height;\nuniform float bvh_height;\nuniform int node_count;\nuniform vec3 camera_position;\nuniform vec3 camera_forward;\nuniform vec3 camera_right;\nuniform vec3 camera_up;\nuniform float aspect_ratio;\nuniform int light_count;\nuniform vec4 light_positions[\#{MAX_RAY_LIGHTS}];\nuniform vec4 light_colors[\#{MAX_RAY_LIGHTS}];\nuniform int fog_enabled;\nuniform int flashlight_enabled;\nuniform int bounces_enabled;\n\nvec4 datum(float index) {\n  float x = mod(index, \#{DATA_WIDTH}.0);\n  float y = floor(index / \#{DATA_WIDTH}.0);\n  return texture2D(triangle_data,\n    vec2((x + 0.5) / \#{DATA_WIDTH}.0, (y + 0.5) / data_height));\n}\n\nvec4 node_datum(float index) {\n  float x = mod(index, \#{NODE_DATA_WIDTH}.0);\n  float y = floor(index / \#{NODE_DATA_WIDTH}.0);\n  return texture2D(bvh_data,\n    vec2((x + 0.5) / \#{NODE_DATA_WIDTH}.0, (y + 0.5) / bvh_height));\n}\n\nbool intersect_box(vec3 origin, vec3 inverse_direction, vec3 minimum,\n                   vec3 maximum, float distance_limit) {\n  vec3 near_values = (minimum - origin) * inverse_direction;\n  vec3 far_values = (maximum - origin) * inverse_direction;\n  vec3 low = min(near_values, far_values);\n  vec3 high = max(near_values, far_values);\n  float near_distance = max(max(low.x, low.y), max(low.z, 0.0));\n  float far_distance = min(min(high.x, high.y), high.z);\n  return near_distance <= far_distance && near_distance < distance_limit;\n}\n\nbool intersect_triangle(vec3 origin, vec3 direction, float base,\n                        out float distance, out vec2 barycentric) {\n  vec3 a = datum(base).xyz;\n  vec3 b = datum(base + 1.0).xyz;\n  vec3 c = datum(base + 2.0).xyz;\n  vec3 edge1 = b - a;\n  vec3 edge2 = c - a;\n  vec3 p = cross(direction, edge2);\n  float determinant = dot(edge1, p);\n  if (abs(determinant) < 0.00001) return false;\n  float inverse = 1.0 / determinant;\n  vec3 t = origin - a;\n  float u = dot(t, p) * inverse;\n  if (u < 0.0 || u > 1.0) return false;\n  vec3 q = cross(t, edge1);\n  float v = dot(direction, q) * inverse;\n  if (v < 0.0 || u + v > 1.0) return false;\n  distance = dot(edge2, q) * inverse;\n  barycentric = vec2(u, v);\n  return distance > 0.01;\n}\n\nbool accepts_surface(float base, vec2 barycentric) {\n  float flags = datum(base + 3.0).w;\n  float masked = floor(mod(flags, 4096.0) / 2048.0);\n  if (masked < 0.5) return true;\n  vec4 uv0_uv1 = datum(base + 4.0);\n  vec4 uv2_rect = datum(base + 5.0);\n  vec4 rect_size = datum(base + 6.0);\n  float w = 1.0 - barycentric.x - barycentric.y;\n  vec2 uv = uv0_uv1.xy * w + uv0_uv1.zw * barycentric.x +\n            uv2_rect.xy * barycentric.y;\n  vec2 wrapped = fract(uv / rect_size.zw);\n  vec2 atlas_uv = rect_size.xy + wrapped * uv2_rect.zw;\n  return texture2D(material_atlas, atlas_uv).a > 0.5;\n}\n\nbool shadowed(vec3 origin, vec3 direction, float maximum) {\n  vec3 inverse_direction = 1.0 / direction;\n  int node_index = 0;\n  while (node_index < node_count) {\n    float node_base = float(node_index * \#{TEXELS_PER_NODE});\n    vec4 minimum_escape = node_datum(node_base);\n    vec4 maximum_start = node_datum(node_base + 1.0);\n    int escape = int(minimum_escape.w + 0.5);\n    if (!intersect_box(origin, inverse_direction, minimum_escape.xyz,\n                       maximum_start.xyz, maximum)) {\n      node_index = escape;\n      continue;\n    }\n    if (maximum_start.w >= 0.0) {\n      int start = int(maximum_start.w + 0.5);\n      int count = int(node_datum(node_base + 2.0).x + 0.5);\n      for (int offset = 0; offset < \#{BVH_LEAF_SIZE}; ++offset) {\n        if (offset >= count) break;\n        float distance;\n        vec2 barycentric;\n        float triangle_base = float((start + offset) * \#{TEXELS_PER_TRIANGLE});\n        if (intersect_triangle(origin, direction, triangle_base,\n                               distance, barycentric) && distance < maximum &&\n            accepts_surface(triangle_base, barycentric)) return true;\n      }\n      node_index = escape;\n    } else {\n      node_index += 1;\n    }\n  }\n  return false;\n}\n\n// Average several shadow rays fired at jittered points on a small light\n// disc so shadow edges soften into a penumbra. Returns visibility [0,1].\nfloat shadow_visibility(vec3 origin, vec3 light_position) {\n  float lit = 0.0;\n  for (int s = 0; s < \#{SHADOW_SAMPLES}; ++s) {\n    float angle = float(s) * 2.39996323;                       // golden-angle spread\n    float disc_radius = \#{SHADOW_SOFTNESS} * sqrt((float(s) + 0.5) / float(\#{SHADOW_SAMPLES}));\n    vec3 jittered = light_position + camera_right * (cos(angle) * disc_radius) +\n                                     camera_up * (sin(angle) * disc_radius);\n    vec3 delta = jittered - origin;\n    float light_dist = length(delta);\n    if (!shadowed(origin, delta / max(light_dist, 0.001), light_dist - 0.1)) lit += 1.0;\n  }\n  return lit / float(\#{SHADOW_SAMPLES});\n}\n\nbool trace_scene(vec3 origin, vec3 direction, float distance_limit,\n                 int ignored_triangle,\n                 out int hit, out float nearest, out vec2 hit_barycentric) {\n  nearest = distance_limit;\n  hit = -1;\n  hit_barycentric = vec2(0.0);\n  vec3 inverse_direction = 1.0 / direction;\n  int node_index = 0;\n  while (node_index < node_count) {\n    float node_base = float(node_index * \#{TEXELS_PER_NODE});\n    vec4 minimum_escape = node_datum(node_base);\n    vec4 maximum_start = node_datum(node_base + 1.0);\n    int escape = int(minimum_escape.w + 0.5);\n    if (!intersect_box(origin, inverse_direction, minimum_escape.xyz,\n                       maximum_start.xyz, nearest)) {\n      node_index = escape;\n      continue;\n    }\n    if (maximum_start.w >= 0.0) {\n      int start = int(maximum_start.w + 0.5);\n      int count = int(node_datum(node_base + 2.0).x + 0.5);\n      for (int offset = 0; offset < \#{BVH_LEAF_SIZE}; ++offset) {\n        if (offset >= count) break;\n        int triangle_index = start + offset;\n        if (triangle_index == ignored_triangle) continue;\n        float distance;\n        vec2 barycentric;\n        float triangle_base = float(triangle_index * \#{TEXELS_PER_TRIANGLE});\n        if (intersect_triangle(origin, direction, triangle_base,\n                               distance, barycentric) && distance < nearest &&\n            accepts_surface(triangle_base, barycentric)) {\n          nearest = distance;\n          hit = triangle_index;\n          hit_barycentric = barycentric;\n        }\n      }\n      node_index = escape;\n    } else {\n      node_index += 1;\n    }\n  }\n  return hit >= 0;\n}\n\nvec3 sky_radiance(vec3 direction) {\n  float sky_u = atan(direction.y, direction.x) * 2.0 / 3.14159265;\n  float sky_v = 0.5 - asin(clamp(direction.z, -1.0, 1.0)) / 3.14159265;\n  return texture2D(sky_texture, vec2(sky_u, sky_v * (200.0 / 128.0))).rgb * \#{SKY_BRIGHTNESS};\n}\n\nvec3 secondary_radiance(vec3 origin, vec3 direction, int source_triangle) {\n  int secondary_hit;\n  float secondary_distance;\n  vec2 barycentric;\n  if (!trace_scene(origin, direction, 1.0e20, source_triangle, secondary_hit,\n                   secondary_distance, barycentric))\n    return sky_radiance(direction);\n\n  float base = float(secondary_hit * \#{TEXELS_PER_TRIANGLE});\n  vec4 normal_light = datum(base + 3.0);\n  vec4 uv0_uv1 = datum(base + 4.0);\n  vec4 uv2_rect = datum(base + 5.0);\n  vec4 rect_size = datum(base + 6.0);\n  float w = 1.0 - barycentric.x - barycentric.y;\n  vec2 uv = uv0_uv1.xy * w + uv0_uv1.zw * barycentric.x + uv2_rect.xy * barycentric.y;\n  vec2 atlas_uv = rect_size.xy + fract(uv / rect_size.zw) * uv2_rect.zw;\n  vec3 albedo = texture2D(material_atlas, atlas_uv).rgb;\n  float emission = floor(mod(normal_light.w, 2048.0) / 1024.0);\n  float sector = clamp(mod(normal_light.w, 1024.0) / 255.0, 0.10, 1.0);\n  return albedo * (vec3(sector * \#{AMBIENT_LEVEL}) + emission * vec3(0.18, 0.62, 0.12));\n}\n\nfloat noise(vec3 point) {\n  return fract(sin(dot(point, vec3(12.9898, 78.233, 37.719))) * 43758.5453);\n}\n\nvec3 diffuse_bounce_direction(vec3 normal, float surface_id) {\n  vec3 helper = abs(normal.z) < 0.9 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n  vec3 tangent = normalize(cross(helper, normal));\n  vec3 bitangent = cross(normal, tangent);\n  // Keep the sample fixed to the triangle. Hashing the continuously\n  // moving hit point makes indirect light sparkle as the camera moves.\n  float angle = noise(vec3(surface_id, surface_id * 0.37, 1.0)) * 6.2831853;\n  float radius = 0.65;\n  return normalize(normal * sqrt(1.0 - radius * radius) +\n                   tangent * cos(angle) * radius + bitangent * sin(angle) * radius);\n}\n\nvoid main() {\n  vec2 plane = screen_uv * 2.0 - 1.0;\n  plane.y /= aspect_ratio;\n  vec3 direction = normalize(camera_forward + camera_right * plane.x + camera_up * plane.y);\n  float nearest = 1.0e20;\n  int hit = -1;\n  vec2 hit_barycentric = vec2(0.0);\n  vec3 inverse_direction = 1.0 / direction;\n  int node_index = 0;\n  while (node_index < node_count) {\n    float node_base = float(node_index * \#{TEXELS_PER_NODE});\n    vec4 minimum_escape = node_datum(node_base);\n    vec4 maximum_start = node_datum(node_base + 1.0);\n    int escape = int(minimum_escape.w + 0.5);\n    if (!intersect_box(camera_position, inverse_direction, minimum_escape.xyz,\n                       maximum_start.xyz, nearest)) {\n      node_index = escape;\n      continue;\n    }\n    if (maximum_start.w >= 0.0) {\n      int start = int(maximum_start.w + 0.5);\n      int count = int(node_datum(node_base + 2.0).x + 0.5);\n      for (int offset = 0; offset < \#{BVH_LEAF_SIZE}; ++offset) {\n        if (offset >= count) break;\n        int triangle_index = start + offset;\n        float distance;\n        vec2 barycentric;\n        float triangle_base = float(triangle_index * \#{TEXELS_PER_TRIANGLE});\n        if (intersect_triangle(camera_position, direction, triangle_base,\n                               distance, barycentric) && distance < nearest &&\n            accepts_surface(triangle_base, barycentric)) {\n          nearest = distance;\n          hit = triangle_index;\n          hit_barycentric = barycentric;\n        }\n      }\n      node_index = escape;\n    } else {\n      node_index += 1;\n    }\n  }\n  if (hit < 0) {\n    // Match HardwareRenderer::draw_sky and Doom's SKY1 density:\n    // repeat the 256px panorama four times around the player and map\n    // 200 sky texels over the full view height.\n    float sky_u = atan(direction.y, direction.x) * 2.0 / 3.14159265;\n    float sky_v = (1.0 - screen_uv.y) * (200.0 / 128.0);\n    vec3 sky = texture2D(sky_texture, vec2(sky_u, sky_v)).rgb;\n    gl_FragColor = vec4(sky * \#{SKY_BRIGHTNESS}, 1.0);\n    return;\n  }\n  float base = float(hit * \#{TEXELS_PER_TRIANGLE});\n  vec4 normal_light = datum(base + 3.0);\n  vec4 uv0_uv1 = datum(base + 4.0);\n  vec4 uv2_rect = datum(base + 5.0);\n  vec4 rect_size = datum(base + 6.0);\n  float w = 1.0 - hit_barycentric.x - hit_barycentric.y;\n  vec2 uv = uv0_uv1.xy * w + uv0_uv1.zw * hit_barycentric.x + uv2_rect.xy * hit_barycentric.y;\n  vec2 wrapped = fract(uv / rect_size.zw);\n  vec2 atlas_uv = rect_size.xy + wrapped * uv2_rect.zw;\n  vec3 albedo = texture2D(material_atlas, atlas_uv).rgb;\n  vec3 normal = normalize(normal_light.xyz);\n  if (dot(normal, direction) > 0.0) normal = -normal;\n  vec3 point = camera_position + direction * nearest;\n  float emission = floor(mod(normal_light.w, 2048.0) / 1024.0);\n  float sector = clamp(mod(normal_light.w, 1024.0) / 255.0, 0.10, 1.0);\n  vec3 ambient = albedo * sector * \#{AMBIENT_LEVEL};\n  vec3 direct = vec3(0.0);\n  float strongest_score = 0.0;\n  float second_score = 0.0;\n  vec3 strongest_direct = vec3(0.0);\n  vec3 second_direct = vec3(0.0);\n  vec3 strongest_direction = vec3(0.0);\n  vec3 second_direction = vec3(0.0);\n  float strongest_distance = 0.0;\n  float second_distance = 0.0;\n  for (int light_index = 0; light_index < \#{MAX_RAY_LIGHTS}; ++light_index) {\n    if (light_index >= light_count) break;\n    vec3 to_light = light_positions[light_index].xyz - point;\n    float light_distance = length(to_light);\n    vec3 light_direction = to_light / max(light_distance, 0.001);\n    float diffuse = max(dot(normal, light_direction), 0.0);\n    // A gentler physically-shaped falloff keeps distant visible lamps\n    // contributing instead of crossing an apparent hard threshold.\n    float attenuation = 1.0 / (1.0 + light_distance * 0.0015 +\n                               light_distance * light_distance * 0.000004);\n    float contribution = diffuse * attenuation;\n    if (contribution < 0.003) continue;\n    vec3 light_direct = albedo * light_colors[light_index].rgb * contribution * 1.8;\n    direct += light_direct;\n    float score = contribution * dot(light_colors[light_index].rgb, vec3(0.30, 0.59, 0.11));\n    if (score > strongest_score) {\n      second_score = strongest_score;\n      second_direct = strongest_direct;\n      second_direction = strongest_direction;\n      second_distance = strongest_distance;\n      strongest_score = score;\n      strongest_direct = light_direct;\n      strongest_direction = light_direction;\n      strongest_distance = light_distance;\n    } else if (score > second_score) {\n      second_score = score;\n      second_direct = light_direct;\n      second_direction = light_direction;\n      second_distance = light_distance;\n    }\n  }\n  // Flashlight beam coverage for this point: wide and feathered so the\n  // beam edge is soft, not a hard disc.\n  float flashlight_beam = 0.0;\n  if (flashlight_enabled != 0) {\n    flashlight_beam = smoothstep(0.65, 0.95,\n      dot(normalize(point - camera_position), camera_forward));\n  }\n\n  // All lights illuminate, but only the two dominant contributors launch\n  // expensive BVH shadow rays for this surface.\n  if (strongest_score > 0.003) {\n    float strongest_visibility = shadow_visibility(point + normal * 0.08,\n      point + strongest_direction * strongest_distance);\n    direct -= strongest_direct * 0.92 * (1.0 - strongest_visibility);\n  }\n  if (second_score > 0.003) {\n    float second_visibility = shadow_visibility(point + normal * 0.08,\n      point + second_direction * second_distance);\n    direct -= second_direct * 0.92 * (1.0 - second_visibility);\n  }\n\n  if (flashlight_enabled != 0) {\n    // The flashlight sits slightly to the side of and below the eye, not\n    // exactly at it. A light co-located with the camera can never cast a\n    // visible shadow: the shadow of any caster falls directly behind it,\n    // hidden from the eye, and the shadow ray back to the camera retraces\n    // the (empty by construction) primary ray. Offsetting it like a\n    // handheld lantern throws shadows to the side where they show.\n    vec3 flashlight_position = camera_position + camera_right * 14.0 - camera_up * 10.0;\n\n    // The beam still points where you look; reuse the wide coverage.\n    float cone = flashlight_beam;\n\n    // Lighting and shadowing come from the offset lantern position.\n    vec3 to_flashlight = flashlight_position - point;\n    float flashlight_distance = length(to_flashlight);\n    vec3 flashlight_direction = to_flashlight / max(flashlight_distance, 0.001);\n    float facing = max(dot(normal, flashlight_direction), 0.0);\n    float flashlight_attenuation = 1.0 / (1.0 + flashlight_distance * 0.0015 +\n                                          flashlight_distance * flashlight_distance * 0.000002);\n    float flashlight_strength = cone * facing * flashlight_attenuation;\n    if (flashlight_strength > 0.004) {\n      float flashlight_visible = mix(0.06, 1.0,\n        shadow_visibility(point + normal * 0.08, flashlight_position));\n      direct += albedo * vec3(1.0, 0.88, 0.68) * flashlight_strength *\n                flashlight_visible * 2.2;\n    }\n  }\n\n  vec3 shaded = ambient + direct;\n  if (emission > 0.5)\n    shaded += albedo * vec3(0.18, 0.62, 0.12);\n  if (bounces_enabled != 0) {\n    float reflective = floor(mod(normal_light.w, 8192.0) / 4096.0);\n    float refractive = floor(mod(normal_light.w, 16384.0) / 8192.0);\n    if (reflective > 0.5) {\n      vec3 reflected = secondary_radiance(point + normal * 0.12,\n                                          reflect(direction, normal), hit);\n      float fresnel = pow(1.0 - max(dot(-direction, normal), 0.0), 5.0);\n      float reflection_mix = refractive > 0.5\n        ? mix(0.34, 0.72, fresnel)\n        : mix(0.42, 0.68, fresnel);\n      shaded = mix(shaded, reflected, reflection_mix);\n    } else {\n      vec3 bounced = secondary_radiance(point + normal * 0.12,\n        diffuse_bounce_direction(normal, float(hit)), hit);\n      shaded += albedo * bounced * 0.18;\n    }\n    if (refractive > 0.5) {\n      vec3 transmitted_direction = refract(direction, normal, 1.0 / 1.33);\n      if (length(transmitted_direction) > 0.01) {\n        vec3 transmitted = secondary_radiance(point - normal * 0.12,\n                                              transmitted_direction, hit);\n        shaded = mix(shaded, transmitted * vec3(0.72, 0.92, 0.74), 0.08);\n      }\n    }\n  }\n  if (fog_enabled != 0) {\n    float fog = clamp(1.0 - exp(-nearest * \#{FOG_DENSITY}), 0.0, \#{FOG_MAX});\n    shaded = mix(shaded, vec3(0.020, 0.025, 0.035), fog);\n  }\n  gl_FragColor = vec4(shaded, 1.0);\n}\n"
SPRITE_VERTEX_SHADER =
"#version 120\nvarying vec2 sprite_uv;\nvarying vec3 world_position;\nvoid main() {\n  sprite_uv = gl_MultiTexCoord0.xy;\n  world_position = gl_Vertex.xyz;\n  gl_Position = ftransform();\n}\n"
SPRITE_FRAGMENT_SHADER =
"#version 120\nvarying vec2 sprite_uv;\nvarying vec3 world_position;\nuniform sampler2D sprite_texture;\nuniform float sector_light;\nuniform int light_count;\nuniform vec4 light_positions[\#{MAX_RAY_LIGHTS}];\nuniform vec4 light_colors[\#{MAX_RAY_LIGHTS}];\nuniform vec3 camera_position;\nuniform vec3 camera_forward;\nuniform int fog_enabled;\nuniform int flashlight_enabled;\n\nvoid main() {\n  vec4 texel = texture2D(sprite_texture, sprite_uv);\n  if (texel.a < 0.01) discard;\n  vec3 shaded = texel.rgb * clamp(sector_light, 0.10, 1.0) * \#{AMBIENT_LEVEL};\n  for (int light_index = 0; light_index < \#{MAX_RAY_LIGHTS}; ++light_index) {\n    if (light_index >= light_count) break;\n    float distance_to_light = length(light_positions[light_index].xyz - world_position);\n    float attenuation = 1.0 / (1.0 + distance_to_light * 0.0015 +\n                               distance_to_light * distance_to_light * 0.000004);\n    shaded += texel.rgb * light_colors[light_index].rgb * attenuation * 0.75;\n  }\n  vec3 camera_to_point = world_position - camera_position;\n  float distance_to_camera = length(camera_to_point);\n  if (flashlight_enabled != 0 && distance_to_camera > 0.001) {\n    float cone = smoothstep(0.80, 0.96,\n      dot(camera_to_point / distance_to_camera, camera_forward));\n    float attenuation = 1.0 / (1.0 + distance_to_camera * 0.0015 +\n                               distance_to_camera * distance_to_camera * 0.000002);\n    shaded += texel.rgb * vec3(1.0, 0.88, 0.68) * cone * attenuation * 1.5;\n  }\n  if (fog_enabled != 0) {\n    float fog = clamp(1.0 - exp(-distance_to_camera * \#{FOG_DENSITY}), 0.0, \#{FOG_MAX});\n    shaded = mix(shaded, vec3(0.020, 0.025, 0.035), fog);\n  }\n  gl_FragColor = vec4(shaded, texel.a);\n}\n"

Constants inherited from HardwareRenderer

HardwareRenderer::ANIMATED_DECORATIONS, HardwareRenderer::MAX_LIGHTS, HardwareRenderer::STATIC_LIGHTS, HardwareRenderer::VERTEX_STRIDE

Constants inherited from Renderer

Doom::Render::Renderer::SKY_TEXTUREMID, Doom::Render::Renderer::SKY_XSCALE, Doom::Render::Renderer::SKY_YSCALE

Instance Attribute Summary collapse

Attributes inherited from HardwareRenderer

#mesh

Attributes inherited from Renderer

#animations, #colormap, #combat, #cos_angle, #flats, #framebuffer, #hidden_things, #leveltime, #map, #monster_ai, #palette, #player_angle, #player_x, #player_y, #player_z, #players, #sin_angle, #skip_background_fill, #sprites, #textures, #view_player, #wad

Instance Method Summary collapse

Methods inherited from HardwareRenderer

#hardware?

Methods inherited from Renderer

#apply_view, #build_native_renderer, #check_plane, #draw_all_visplanes, #draw_floor_ceiling_background, #draw_sky_plane, #draw_span, #fill_uncovered_with_sector, #find_or_create_visplane, #native_wall_pass, #precompute_column_data, #render_visplane_spans, #set_player, #sprite_diagnostics

Constructor Details

#initializeRayTracingRenderer

Returns a new instance of RayTracingRenderer.



517
518
519
520
521
522
523
# File 'lib/doom/render/ray_tracing_renderer.rb', line 517

def initialize(...)
  super
  @ray_materials = RayTracing::MaterialState.new(@flats, @animations)
  @fog_enabled = true
  @flashlight_enabled = true
  @bounces_enabled = true
end

Instance Attribute Details

#bounces_enabledObject

Returns the value of attribute bounces_enabled.



515
516
517
# File 'lib/doom/render/ray_tracing_renderer.rb', line 515

def bounces_enabled
  @bounces_enabled
end

#flashlight_enabledObject

Returns the value of attribute flashlight_enabled.



515
516
517
# File 'lib/doom/render/ray_tracing_renderer.rb', line 515

def flashlight_enabled
  @flashlight_enabled
end

#fog_enabledObject

Returns the value of attribute fog_enabled.



515
516
517
# File 'lib/doom/render/ray_tracing_renderer.rb', line 515

def fog_enabled
  @fog_enabled
end

Instance Method Details

#draw_hardware(viewport_width, viewport_height) ⇒ Object



536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
# File 'lib/doom/render/ray_tracing_renderer.rb', line 536

def draw_hardware(viewport_width, viewport_height)
  Gosu.gl do
    load_opengl_library
    current_animation = @ray_materials.animation_signature
    if @ray_animation_signature != current_animation
      @ray_animation_signature = current_animation
      @ray_materials_dirty = true if @ray_data_texture
    end
    build_ray_scene if @ray_program.nil? || @ray_scene_dirty
    if @ray_materials_dirty
      upload_triangle_data
      @ray_materials_dirty = false
    end
    ensure_ray_target
    viewport = [0, 0, 0, 0].pack('l4')
    glGetIntegerv(GL_VIEWPORT, viewport)
    viewport_x, viewport_y, physical_width, physical_height = viewport.unpack('l4')
    glDisable(GL_DEPTH_TEST)
    glDisable(GL_CULL_FACE)
    glDisable(GL_LIGHTING)
    glClearColor(0.0, 0.0, 0.0, 1.0)
    glBindFramebuffer(GL_FRAMEBUFFER, @ray_framebuffer)
    glViewport(0, 0, RAY_WIDTH, RAY_HEIGHT)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    draw_ray_pass(RAY_WIDTH, RAY_HEIGHT)
    glBindFramebuffer(GL_FRAMEBUFFER, 0)
    glViewport(viewport_x, viewport_y, physical_width, physical_height)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    draw_ray_target
    setup_camera(viewport_width, viewport_height)
    rebuild_gpu_batches if @gpu_batches.nil? || @gpu_batches_dirty
    glClear(GL_DEPTH_BUFFER_BIT)
    draw_occluder_depth_prepass(include_ceilings: true)
    draw_sprites
    capture_frame if ENV['DOOM_GL_CAPTURE'] && !@frame_captured
    # Gosu draws the weapon, HUD and pause menu immediately after this
    # block. Do not leak sprite-shader or modulation state into its 2D
    # pipeline, otherwise menu text inherits scene lighting.
    glUseProgram(0)
    glActiveTexture(GL_TEXTURE0)
    glBindTexture(GL_TEXTURE_2D, 0)
    glColor4f(1.0, 1.0, 1.0, 1.0)
    glDisable(GL_LIGHTING)
    glDisable(GL_ALPHA_TEST)
    glDisable(GL_BLEND)
    glDisable(GL_TEXTURE_2D)
    glDisable(GL_DEPTH_TEST)
  end
end

#light_color_at(x, y, z) ⇒ Object

Approximate RGB light reaching a point: sector ambient, the nearest point lights by distance falloff, and the flashlight (always on a held object right in front of the eye). Used to tint the weapon so lamps and the beam colour the player's hand. Values may exceed 1.0; the caller clamps.



777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
# File 'lib/doom/render/ray_tracing_renderer.rb', line 777

def light_color_at(x, y, z)
  sector = @map.sector_at(x, y)
  ambient = ((sector&.light_level || 128).to_f / 255.0) * AMBIENT_LEVEL
  color = [ambient, ambient, ambient]

  ray_lights.first(MAX_RAY_LIGHTS).each do |light|
    dx = light[:x] - x
    dy = light[:y] - y
    dz = light[:z] - z
    distance_sq = (dx * dx) + (dy * dy) + (dz * dz)
    attenuation = 1.0 / (1.0 + (Math.sqrt(distance_sq) * 0.0015) + (distance_sq * 0.000004))
    light[:color].each_with_index { |channel, index| color[index] += channel * attenuation }
  end

  if @flashlight_enabled
    color[0] += 0.70
    color[1] += 0.62
    color[2] += 0.48
  end

  color
end

#ray_tracing?Boolean

Returns:

  • (Boolean)


511
512
513
# File 'lib/doom/render/ray_tracing_renderer.rb', line 511

def ray_tracing?
  true
end

#render_frameObject



525
526
527
528
529
530
531
532
533
534
# File 'lib/doom/render/ray_tracing_renderer.rb', line 525

def render_frame
  signature = geometry_signature
  if signature != @geometry_signature
    @mesh = WorldMesh.new(@map, @textures)
    @geometry_signature = signature
    @ray_scene_dirty = true
    @gpu_batches_dirty = true
  end
  @framebuffer.fill(0)
end