17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 07:37:28 +01:00

Vulkan ray tracing: shade the heightmap terrain from its layers; longer default SSR range

This commit is contained in:
2026-10-25 17:15:58 +01:00
parent e201c83825
commit 71a6628a4a
10 changed files with 170 additions and 23 deletions

View File

@@ -247,7 +247,7 @@ the environment map only.
| Entry | Default | Meaning |
|---|---|---|
| `gfx.ssr` | `medium` | `no`/`low`/`medium`/`high` (or 0..3): steps of the rays, 16, 32 or 64. `no`: the environment map only (the sky) |
| `gfx.ssr.range` | `250` | 10..2000: the longest ray, metres |
| `gfx.ssr.range` | `1000` | 10..2000: the longest ray, metres; what is farther from the reflecting surface reflects the sky |
| `gfx.ssr.roughness` | `0.6` | 0..1: the roughest surface (perceptual roughness) that reflects the scene |
| `gfx.raytracing yes/no` | `no` | Vulkan renderer only, on a graphics card with ray queries (`VK_KHR_ray_query`): what the screen space reflections don't find is traced through the scenery around the camera instead of reflecting the sky. Read at the start; with `gfx.ssr no` all the reflections of the scenery are traced |
| `gfx.raytracing.range` | `500` | 10..5000: the scenery within this distance from the camera takes part in the traced reflections, and the rays are this long, metres |
@@ -256,7 +256,9 @@ The traced reflections hold what the environment map held before, as `gfx.reflec
and the large shapes of the scenery (0), the tracks, the roads and the models within `gfx.reflections.range_instances` (1)
and the vehicles within `gfx.reflections.range_vehicles` (2, the default now), except the vehicle of the cab. `gfx.reflections.fidelity` and the
two ranges have no other use now; `gfx.reflections.framerate` still says how often the environment map (the sky) is
drawn. The point hit is lit by the sky, by the sun where nothing stands between them (a ray of its own) and by its own light; the lamps and headlights don't light it. Each part of a model gets its structure
drawn. The point hit has the colour of the diffuse texture of its material (the heightmap terrain: its layers blended by their
weights, without the normal and specular maps; the holes cut in it are not), and is lit by the sky, by the sun (its shadow map
where the map holds the point, a ray of its own elsewhere) and by its own light; the lamps and headlights don't light it. Each part of a model gets its structure
for the tracing the first time it's met (a few a frame, so a new place fills in over some frames), and loses it when it
isn't met for half a minute.

View File

@@ -97,12 +97,14 @@ constexpr std::uint32_t POST_EXPOSURE_SSBO = 4;
constexpr std::uint32_t COMPUTE_SET = 1;
// the ray traced reflections (gfx.raytracing, vk14::ray_tracing): set 2 of the pipelines of the scene, one for each frame in flight,
// written with the frame: the acceleration structure of the scenery around the camera, the records of its instances, and the
// textures they read (an array of as many as the device allows, partly bound, the last binding)
// written with the frame: the acceleration structure of the scenery around the camera, the records of its instances, the layers
// of the heightmap terrain (the texture array of their diffuse maps, partly bound) and the textures they read (an array of as many
// as the device allows, partly bound, the last binding)
constexpr std::uint32_t RT_SET = 2;
constexpr std::uint32_t RT_SCENE = 0;
constexpr std::uint32_t RT_RECORDS = 1;
constexpr std::uint32_t RT_TEXTURES = 2;
constexpr std::uint32_t RT_TERRAIN = 2;
constexpr std::uint32_t RT_TEXTURES = 3;
// the push constants of the graphics pipelines of the scene and of the post-processing (vk14::draw_constants): the draw, and
// the parameters of the special shaders (the celestial bodies, the post-processing)

View File

@@ -1785,6 +1785,12 @@ void vulkan14_renderer::render_ray_scene(viewport_config &Viewport)
m_shadowskipvehicle = simulation::Train->Dynamic();
}
auto const colourpass{m_renderpass};
// the chunks of the heightmap terrain are told by their programs (mat_terrain.frag, mat_terrain_holes.frag)
auto const program_of = [&](char const *Name) -> vk14::scene_program const * {
auto const lookup{scene.materials.find(Name)};
return (lookup != scene.materials.end() ? lookup->second.get() : nullptr);
};
m_rtterrain = {program_of("terrain"), program_of("terrain_holes")};
m_rtgather = true;
setup_pass(Viewport, m_renderpass, rendermode::reflections);
setup_matrices();
@@ -1840,15 +1846,30 @@ void vulkan14_renderer::gather_ray_draw(gfx::geometry_handle const &Geometry)
auto const value{std::max({model_ubs.param[0].r, model_ubs.param[0].g, model_ubs.param[0].b})};
ray.colour = (value > 0.f ? colour * (model_ubs.param[1].x / value) : glm::vec3{0.f});
ray.glow = model_ubs.emission;
// the diffuse texture of the material, and its alpha test when it has an alpha channel
if (draw.textures[0] != null_handle)
{
auto &texture{m_context->textures->mark_as_used(draw.textures[0])};
if (texture.create() && (m_context->images.find(texture.get_id()) != nullptr))
// the id of a texture of the material in the image table, 0: none (yet)
auto const image_of = [&](texture_handle const Texture) -> std::uint64_t {
if (Texture == null_handle)
{
ray.texture = texture.get_id();
ray.cutoff = (texture.get_has_alpha() ? std::max(model_ubs.opacity, 0.f) : 0.f);
return 0;
}
auto &texture{m_context->textures->mark_as_used(Texture)};
return ((texture.create() && (m_context->images.find(texture.get_id()) != nullptr)) ? texture.get_id() : 0);
};
if ((draw.program != nullptr) && ((draw.program == m_rtterrain[0]) || (draw.program == m_rtterrain[1])))
{
// a chunk of the heightmap terrain: its layers blended by its weights (mat_terrain.glsl: weights0 1, weights1 2, the array of the
// diffuse maps of the layers 3, the table 6, the placement param[2])
ray.terrain_weights[0] = image_of(draw.textures[1]);
ray.terrain_weights[1] = image_of(draw.textures[2]);
ray.terrain_layers = image_of(draw.textures[3]);
ray.terrain_table = image_of(draw.textures[6]);
ray.placement = model_ubs.param[2];
}
else if (auto const texture{image_of(draw.textures[0])}; texture != 0)
{
// the diffuse texture of the material, and its alpha test when it has an alpha channel
ray.texture = texture;
ray.cutoff = (m_context->textures->mark_as_used(draw.textures[0]).get_has_alpha() ? std::max(model_ubs.opacity, 0.f) : 0.f);
}
glm::mat4 const modelview{model_ubs.modelview};
if (m_current_instance_count > 0)

View File

@@ -37,6 +37,9 @@ struct record
static_assert(sizeof(record) == 48, "the record of an instance of the ray tracing doesn't match the shaders");
std::uint32_t constexpr record_alpha{1};
// a chunk of the heightmap terrain: texture holds the index of its first weights, the bits of cutoff the second weights, the bits of
// glow the table of its layers, colour the placement of the chunk (param[2] of its material)
std::uint32_t constexpr record_terrain{2};
// the bottom level structures built in a frame at most, and their triangles: the rest waits for the next frames
std::uint32_t constexpr builds_per_frame{96};
std::uint64_t constexpr triangles_per_frame{2000000};
@@ -94,11 +97,14 @@ bool ray_tracing::init(device const &Device, allocator &Memory, deletion_queue &
m_texturecount = std::min<std::uint32_t>(Device.caps().max_indexed_textures, 16384);
auto const handle{Device.handle()};
// the structure, the records and the textures, the last binding as many as the set is made with and partly bound
std::array<VkDescriptorSetLayoutBinding, 3> bindings{{{RT_SCENE, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
// the structure, the records, the layers of the terrain (written once a chunk of it is met) and the textures, the last binding as
// many as the set is made with and partly bound
static_assert((RT_TERRAIN == 2) && (RT_TEXTURES == 3), "the bindings of the set of the ray tracing go in this order");
std::array<VkDescriptorSetLayoutBinding, 4> bindings{{{RT_SCENE, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
{RT_RECORDS, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
{RT_TERRAIN, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
{RT_TEXTURES, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, m_texturecount, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}}};
std::array<VkDescriptorBindingFlags, 3> const flags{0, 0, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT};
std::array<VkDescriptorBindingFlags, 4> const flags{0, 0, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT};
VkDescriptorSetLayoutBindingFlagsCreateInfo bindingflags{};
bindingflags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO;
bindingflags.bindingCount = static_cast<std::uint32_t>(flags.size());
@@ -116,7 +122,7 @@ bool ray_tracing::init(device const &Device, allocator &Memory, deletion_queue &
}
name_object(handle, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, handle_value(m_layout), "ray tracing set");
std::array<VkDescriptorPoolSize, 3> const sizes{{{VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, Frames}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, Frames}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, m_texturecount * Frames}}};
std::array<VkDescriptorPoolSize, 3> const sizes{{{VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, Frames}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, Frames}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, (m_texturecount + 1) * Frames}}};
VkDescriptorPoolCreateInfo pool{};
pool.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool.maxSets = Frames;
@@ -597,6 +603,23 @@ void ray_tracing::build(command_stream *Commands, std::uint32_t const Slot, std:
auto *records{reinterpret_cast<record *>(frame.records ? frame.records->mapped() : nullptr)};
auto *instances{reinterpret_cast<VkAccelerationStructureInstanceKHR *>(frame.instances ? frame.instances->mapped() : nullptr)};
std::vector<std::uint32_t> usedtextures;
// the texture array of the layers of the terrain the chunks read: a 2d array of the image table, or the chunks shade with their
// colour (the binding is read only for the chunks marked as terrain)
std::uint64_t terrainlayers{0};
std::uint64_t checkedlayers{0};
image_table::entry const *layersentry{nullptr};
auto const layers_valid = [&](std::uint64_t const Id) {
if (Id != checkedlayers)
{
checkedlayers = Id;
layersentry = Images.find(Id);
if ((layersentry != nullptr) && ((false == bool(layersentry->target)) || (layersentry->target->desc().view_type != VK_IMAGE_VIEW_TYPE_2D_ARRAY) || (layersentry->sampler == VK_NULL_HANDLE)))
{
layersentry = nullptr;
}
}
return (layersentry != nullptr) && ((terrainlayers == 0) || (terrainlayers == Id));
};
std::uint32_t written{0};
for (auto const &draw : Draws)
{
@@ -610,7 +633,8 @@ void ray_tracing::build(command_stream *Commands, std::uint32_t const Slot, std:
continue;
}
auto const &source{draw.source};
auto const texture{texture_index(draw.texture, Frame)};
auto const terrain{draw.terrain_table != 0};
auto const texture{texture_index(terrain ? draw.terrain_weights[0] : draw.texture, Frame)};
if (texture != 0)
{
usedtextures.push_back(texture);
@@ -619,11 +643,36 @@ void ray_tracing::build(command_stream *Commands, std::uint32_t const Slot, std:
entry.vertices = source.vertex_address + vertex_stride * source.vertexfirst;
entry.indices = (lookup->second.index_address != 0 ? lookup->second.index_address : source.indices != VK_NULL_HANDLE ? source.index_address + sizeof(std::uint32_t) * source.first : 0);
entry.texture = texture;
auto const alpha{(texture != 0) && (draw.cutoff > 0.f)};
auto const alpha{(false == terrain) && (texture != 0) && (draw.cutoff > 0.f)};
entry.flags = (alpha ? record_alpha : 0);
entry.cutoff = draw.cutoff;
entry.glow = draw.glow;
entry.colour = glm::vec4{draw.colour, 1.f};
if (terrain)
{
// the shading blends the layers as the terrain shader does, from the weights and the table (indices in the bits of the floats)
auto const weights{texture_index(draw.terrain_weights[1], Frame)};
auto const table{texture_index(draw.terrain_table, Frame)};
for (auto const index : {weights, table})
{
if (index != 0)
{
usedtextures.push_back(index);
}
}
if ((texture != 0) && (weights != 0) && (table != 0) && (draw.terrain_layers != 0) && layers_valid(draw.terrain_layers))
{
entry.flags = record_terrain;
std::memcpy(&entry.cutoff, &weights, sizeof(weights));
std::memcpy(&entry.glow, &table, sizeof(table));
entry.colour = draw.placement;
terrainlayers = draw.terrain_layers;
}
else
{
entry.texture = 0;
}
}
auto &instance{instances[written]};
// the rows of the transform, which glm keeps by the columns
for (int row = 0; row < 3; ++row)
@@ -720,6 +769,25 @@ void ray_tracing::build(command_stream *Commands, std::uint32_t const Slot, std:
write.pImageInfo = &images.back();
writes.push_back(write);
}
VkDescriptorImageInfo terraininfo{};
if ((terrainlayers != 0) && layers_valid(terrainlayers))
{
auto const &target{layersentry->target};
auto &cached{frame.written_terrain};
if ((cached.view != target->view()) || (cached.sampler != layersentry->sampler) || (cached.source.lock() != target))
{
cached = {target->view(), layersentry->sampler, target};
terraininfo = {layersentry->sampler, target->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = frame.set;
write.dstBinding = vk14::bindings::RT_TERRAIN;
write.descriptorCount = 1;
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.pImageInfo = &terraininfo;
writes.push_back(write);
}
}
if (false == writes.empty())
{
vkUpdateDescriptorSets(handle, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);

View File

@@ -43,6 +43,12 @@ struct ray_draw
glm::vec3 colour{1.f}; // the diffuse colour of the material, linear
float cutoff{0.f}; // the alpha test of the texture, 0: none
float glow{0.f}; // the light of its own
// a chunk of the heightmap terrain: its weights (two maps of four layers), its table of the layers (the ids of the images) and
// the texture array of the diffuse maps of the layers, shared by the chunks; placement: param[2] of its material
std::uint64_t terrain_weights[2]{0, 0};
std::uint64_t terrain_table{0};
std::uint64_t terrain_layers{0};
glm::vec4 placement{0.f};
};
class ray_tracing
@@ -122,6 +128,7 @@ class ray_tracing
std::weak_ptr<image> source; // a view made after this one is gone can get its handle
};
std::vector<written_texture> written_textures;
written_texture written_terrain;
};
// a buffer of the frame at least Size bytes, grown when it isn't (the old one stays when the new one can't be made)

View File

@@ -35,6 +35,8 @@ http://mozilla.org/MPL/2.0/.
#include "vehicle/Camera.h"
#include "gl/ubo.h"
#include <array>
class world_environment;
struct VkRenderingInfo;
namespace vk14
@@ -282,6 +284,7 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
bool m_rtgather{false};
bool m_raytracingon{true}; // switched off in the debug window
bool m_rtfoliage{true}; // the alpha tested geometry in the traced reflections, switched off in the debug window
std::array<vk14::scene_program const *, 2> m_rtterrain{nullptr, nullptr}; // the programs of the heightmap terrain, the draws of it noted as such
double m_rtwalkms{0.0}; // the processor time of the walk and of the build of the ray tracing, averaged
double m_rtbuildms{0.0};
std::vector<vk14::ray_draw> m_rtdraws;

View File

@@ -818,6 +818,7 @@ std::string binding_preamble(bool const Raytracing)
define("RT_SET", RT_SET);
define("RT_SCENE", RT_SCENE);
define("RT_RECORDS", RT_RECORDS);
define("RT_TERRAIN", RT_TERRAIN);
define("RT_TEXTURES", RT_TEXTURES);
define("PUSH_CONSTANTS_SIZE", PUSH_CONSTANTS_SIZE);
define("SPEC_OUTPUT_GAMMA", SPEC_OUTPUT_GAMMA);

View File

@@ -76,7 +76,8 @@
#define RT_SET 2
#define RT_SCENE 0
#define RT_RECORDS 1
#define RT_TEXTURES 2
#define RT_TERRAIN 2
#define RT_TEXTURES 3
#define PUSH_CONSTANTS_SIZE 112
#define SPEC_OUTPUT_GAMMA 0
#define SPEC_SHADOWMAP_ENABLED 1

View File

@@ -26,6 +26,8 @@ layout(std430, set = RT_SET, binding = RT_RECORDS) readonly buffer rt_record_ssb
};
layout(set = RT_SET, binding = RT_TEXTURES) uniform sampler2D rt_textures[];
// the diffuse maps of the layers of the heightmap terrain (mat_terrain.glsl, layerdiffuse), read for its chunks only
layout(set = RT_SET, binding = RT_TERRAIN) uniform sampler2DArray rt_terrain_layers;
layout(buffer_reference, std430, buffer_reference_align = 4) readonly buffer rt_vertex_data
{
@@ -38,6 +40,9 @@ layout(buffer_reference, std430, buffer_reference_align = 4) readonly buffer rt_
};
const uint RT_FLAG_ALPHA = 1U;
// a chunk of the heightmap terrain: texindex its first weights, the bits of cutoff its second weights, the bits of glow the table
// of its layers, colour its placement (param[2] of its material)
const uint RT_FLAG_TERRAIN = 2U;
const uint RT_VERTEX_FLOATS = 12U;
// the masks of the instances (vk14::ray_tracing): the opaque ones, the alpha tested ones (the foliage). the shadow rays take the
// opaque ones only, and the reflections leave out the alpha tested ones when the renderer says so (env_params.y, 1)
@@ -74,6 +79,37 @@ vec3 rt_normal(rt_record record, uvec3 triangle, vec2 weights)
return a * (1.0 - weights.x - weights.y) + b * weights.x + c * weights.y;
}
// the colour of a chunk of the heightmap terrain at the texture coordinates Coords: its layers blended by its weights, as
// mat_terrain.glsl does (without the normal and the specular maps), at the level Lod of the maps of the layers
vec3 rt_terrain_albedo(rt_record record, vec2 coords, float lod)
{
vec4 placement = record.colour;
vec2 local = clamp(vec2(coords.x - placement.x, -coords.y - placement.y) * placement.z, 0.0, 1.0);
vec2 paintcoord = (local * placement.w + 0.5) / (placement.w + 1.0);
uint second = floatBitsToUint(record.cutoff);
uint table = floatBitsToUint(record.glow);
vec4 w0 = textureLod(rt_textures[nonuniformEXT(record.texindex)], paintcoord, 0.0);
vec4 w1 = textureLod(rt_textures[nonuniformEXT(second)], paintcoord, 0.0);
float weights[8] = float[8](w0.r, w0.g, w0.b, w0.a, w1.r, w1.g, w1.b, w1.a);
float painted = dot(w0, vec4(1.0)) + dot(w1, vec4(1.0));
vec3 albedo = vec3(0.0);
float total = 0.0;
for (int i = 0; i < 8; ++i)
{
// a chunk without weights shows its first layer
float weight = (painted < 0.004 ? (i == 0 ? 1.0 : 0.0) : weights[i]);
if (weight < 0.004)
continue;
vec4 info = texelFetch(rt_textures[nonuniformEXT(table)], ivec2(i, 0), 0);
vec4 extra = texelFetch(rt_textures[nonuniformEXT(table)], ivec2(i, 1), 0);
vec2 turn = (extra.y == 0.0 && extra.z == 0.0) ? vec2(1.0, 0.0) : extra.yz;
mat2 rotation = mat2(turn.x, -turn.y, turn.y, turn.x);
albedo += textureLod(rt_terrain_layers, vec3(rotation * coords * info.y, info.x), lod).rgb * weight;
total += weight;
}
return albedo / max(total, 0.004);
}
// the share of the sun reaching a point hit (Hit in the world around the camera, Normal facing the ray): the shadow map of the sun
// where it holds the point, which costs no ray, otherwise a ray of its own towards the sun (Sun)
float rt_sun_visible(vec3 hit, vec3 normal, vec3 sun, float distance)
@@ -167,10 +203,16 @@ vec4 rt_trace(vec3 position, vec3 normal, vec3 direction, float roughness)
// the colour of the surface: the farther and the rougher, the blurrier the level of the texture
vec3 albedo = record.colour.rgb;
if (record.texindex != 0U)
float glow = record.glow;
float lod = clamp(log2(1.0 + distance * 0.05) * 2.0 + roughness * 6.0, 0.0, 12.0);
if ((record.flags & RT_FLAG_TERRAIN) != 0U)
{
albedo = rt_terrain_albedo(record, rt_coords(record, triangle, weights), lod);
glow = 0.0;
}
else if (record.texindex != 0U)
{
vec2 coords = rt_coords(record, triangle, weights);
float lod = clamp(log2(1.0 + distance * 0.05) * 2.0 + roughness * 6.0, 0.0, 12.0);
albedo *= textureLod(rt_textures[nonuniformEXT(record.texindex)], coords, lod).rgb;
}
// the light of the sky, the sun where nothing stands between the point and it, and the light of its own
@@ -186,7 +228,7 @@ vec4 rt_trace(vec3 position, vec3 normal, vec3 direction, float roughness)
light += lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * visible * cloud_shadow(hit) * pow(facing, SUN_NDOTL_SHARPNESS));
}
}
vec3 color = albedo * (light + vec3(record.glow));
vec3 color = albedo * (light + vec3(glow));
if (any(isnan(color)) || any(isinf(color)))
return vec4(0.0);
return vec4(max(color, vec3(0.0)), 1.0);

View File

@@ -475,7 +475,7 @@ struct global_settings {
// (the sky) or the ray tracing. 0 none, 1 low, 2 medium, 3 high (steps of the rays); the longest ray, metres, and the
// roughest surface they're traced for (perceptual roughness, rougher ones reflect the environment map)
int gfx_ssr { 2 };
float gfx_ssr_range { 250.f };
float gfx_ssr_range { 1000.f };
float gfx_ssr_roughness { 0.6f };
// (vulkan) ray traced reflections where the screen space ones find nothing, on a gpu with ray queries: the scenery within
// the range, metres, as gfx.reflections.fidelity says. taken at the start