17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 06:49:29 +01:00

Vulkan: scene rendering (D1–D7) on a scene walk shared with GL (N6a, N6b)

This commit is contained in:
2026-10-24 20:10:24 +02:00
parent bd589f272d
commit 9b7d02e5fb
107 changed files with 14511 additions and 3924 deletions

View File

@@ -309,6 +309,8 @@ set(SOURCES
"gl/cubemap.cpp"
"gl/glsl_common.cpp"
"rendering/common/shadersource.cpp"
"rendering/common/scenewalk.cpp"
"rendering/common/locallights.cpp"
"gl/buffer.cpp"
"gl/fence.cpp"
"gl/pbo.cpp"
@@ -394,7 +396,6 @@ if (WITH_OPENGL_MODERN)
"rendering/opengl46/opengl46weather.cpp"
"rendering/opengl46/opengl46exposure.cpp"
"rendering/opengl46/opengl46ao.cpp"
"rendering/opengl46/opengl46locallights.cpp"
"rendering/opengl46/opengl46localshadows.cpp"
"rendering/opengl46/opengl46instancecull.cpp"
"rendering/opengl46/opengl46hiz.cpp"
@@ -433,6 +434,9 @@ if (WITH_VULKAN)
"rendering/vulkan14/vulkan14texture.cpp"
"rendering/vulkan14/vulkan14imgui.cpp"
"rendering/vulkan14/vulkan14renderer.cpp"
"rendering/vulkan14/vulkan14geometry.cpp"
"rendering/vulkan14/vulkan14scene.cpp"
"rendering/vulkan14/vulkan14frame.cpp"
"ref/volk/volk.c")
set(DEFINITIONS ${DEFINITIONS} "WITH_VULKAN")

View File

@@ -116,7 +116,15 @@ bool check_ogl46(std::string const &Name)
bool check_vk14(std::string const &Name)
{
std::cout << "spir-v..." << std::flush;
auto const suc{check_text(gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, {}, {}, false, vk14_dir), "-V --target-env " + vulkan_env)};
auto const source{gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, {}, {}, false, vk14_dir)};
auto suc{check_text(source, "-V --target-env " + vulkan_env)};
// the variant of the passes without the velocity target, of the shaders which have one
if (source.text.find("NO_MOTION_OUTPUT") != std::string::npos)
{
std::cout << "done, no motion output..." << std::flush;
if (!check_text(gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, "#define NO_MOTION_OUTPUT 1\n", {}, false, vk14_dir), "-V --target-env " + vulkan_env))
suc = false;
}
std::cout << "done" << std::flush;
return suc;
}

View File

@@ -94,7 +94,7 @@ int opengl_material::texture_slot( std::string const &Key )
return lookup->second;
}
// ignore unrecognized texture bindings in legacy render mode, it's most likely data for more advanced shaders
if( Global.GfxRenderer == "default" ) {
if( ( Global.GfxRenderer == "default" ) || ( Global.GfxRenderer == "vulkan" ) ) {
log_error( "unknown texture binding: " + key );
is_good = false;
}
@@ -175,7 +175,7 @@ void opengl_material::finalize(bool Loadnow)
}
// TBD, TODO: move material validation to renderer, to eliminate branching?
if( Global.GfxRenderer == "default" ) {
if( ( Global.GfxRenderer == "default" ) || ( Global.GfxRenderer == "vulkan" ) ) {
if( !shader ) {
is_good = false;
@@ -222,7 +222,7 @@ void opengl_material::finalize(bool Loadnow)
parse_info.reset();
}
if( Global.GfxRenderer != "default" ) {
if( ( Global.GfxRenderer != "default" ) && ( Global.GfxRenderer != "vulkan" ) ) {
// basic texture validation for legacy branch
for( auto texturehandle : textures ) {
if( texturehandle == null_handle ) {

View File

@@ -9,7 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/opengl46/opengl46locallights.h"
#include "rendering/common/locallights.h"
#include <algorithm>
#include <array>
@@ -107,7 +107,7 @@ void extend_bounds(TSubModel const *Submodel, glm::mat4 const &Transform, gfx::g
}
} // namespace
void opengl46_local_lights::gather(glm::dvec3 const &Camera, scene::basic_region &Region, gfx::geometrybank_manager const &Geometry, unsigned int const Framestamp)
void gfx::local_lights::gather(glm::dvec3 const &Camera, scene::basic_region &Region, gfx::geometrybank_manager const &Geometry, unsigned int const Framestamp)
{
auto const start{std::chrono::steady_clock::now()};
@@ -185,7 +185,7 @@ void opengl46_local_lights::gather(glm::dvec3 const &Camera, scene::basic_region
m_gathertime = (m_gathertime == 0.0 ? elapsed : m_gathertime * 0.95 + elapsed * 0.05);
}
void opengl46_local_lights::reset()
void gfx::local_lights::reset()
{
m_lights.clear();
m_cablampon = false;
@@ -196,7 +196,7 @@ void opengl46_local_lights::reset()
m_scenerycount = m_vehiclecount = m_compartmentcount = m_placed = m_dropped = 0;
}
void opengl46_local_lights::gather_instance(TAnimModel *Instance, glm::dvec3 const &Camera, float const Brightness, unsigned int const Framestamp)
void gfx::local_lights::gather_instance(TAnimModel *Instance, glm::dvec3 const &Camera, float const Brightness, unsigned int const Framestamp)
{
if ((false == Instance->m_visible) || (Instance->pModel == nullptr))
{
@@ -226,7 +226,7 @@ void opengl46_local_lights::gather_instance(TAnimModel *Instance, glm::dvec3 con
m_scenerycount += static_cast<std::uint32_t>(m_lights.size() - count);
}
void opengl46_local_lights::gather_vehicle(TDynamicObject *Vehicle, glm::dvec3 const &Camera, gfx::geometrybank_manager const &Geometry, float const Freespotbrightness,
void gfx::local_lights::gather_vehicle(TDynamicObject *Vehicle, glm::dvec3 const &Camera, gfx::geometrybank_manager const &Geometry, float const Freespotbrightness,
float const Compartmentbrightness)
{
if ((false == Global.render_cab) && (Global.pCamera.m_owner == Vehicle))
@@ -303,7 +303,7 @@ void opengl46_local_lights::gather_vehicle(TDynamicObject *Vehicle, glm::dvec3 c
}
}
void opengl46_local_lights::gather_cab_lamp(gfx::geometrybank_manager const &Geometry)
void gfx::local_lights::gather_cab_lamp(gfx::geometrybank_manager const &Geometry)
{
// the cab is drawn, Render_cab() conditions
if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr))
@@ -343,7 +343,7 @@ void opengl46_local_lights::gather_cab_lamp(gfx::geometrybank_manager const &Geo
m_cablampon = (max_component(m_cablamp.color) > 0.f);
}
void opengl46_local_lights::walk(TSubModel *Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness)
void gfx::local_lights::walk(TSubModel *Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness)
{
// visibility, lod ranges, transforms and animations as in opengl46_renderer::Render_Alpha(TSubModel *)
for (auto *submodel{Submodel}; submodel != nullptr; submodel = submodel->Next)
@@ -375,7 +375,7 @@ void opengl46_local_lights::walk(TSubModel *Submodel, glm::mat4 const &Transform
}
}
void opengl46_local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness)
void gfx::local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness)
{
// lit points: with self-illumination, past its threshold. the compartments of vehicles set it with their lights
if ((Submodel.f4Emision.a <= 0.f) || (Global.fLuminance >= Submodel.fLight) || (Submodel.fVisible <= 0.f))
@@ -409,7 +409,7 @@ void opengl46_local_lights::add_light_point(TSubModel const &Submodel, glm::mat4
m_lights.emplace_back(lightpoint);
}
bool opengl46_local_lights::has_light_points(TModel3d *Model)
bool gfx::local_lights::has_light_points(TModel3d *Model)
{
auto lookup{m_modellights.find(Model)};
if (lookup != m_modellights.end())
@@ -439,7 +439,7 @@ bool opengl46_local_lights::has_light_points(TModel3d *Model)
return found;
}
opengl46_local_lights::cell_data const &opengl46_local_lights::cell_instances(scene::basic_cell &Cell, bool const Rebuild)
gfx::local_lights::cell_data const &gfx::local_lights::cell_instances(scene::basic_cell &Cell, bool const Rebuild)
{
// light points are drawn in the translucent phase, so the instances which have them are on the translucent list of the cell
auto &instances{Cell.m_instancetranslucent};
@@ -460,7 +460,7 @@ opengl46_local_lights::cell_data const &opengl46_local_lights::cell_instances(sc
return data;
}
opengl46_local_lights::section_data const &opengl46_local_lights::section(TModel3d *Model, TSubModel *Section, gfx::geometrybank_manager const &Geometry)
gfx::local_lights::section_data const &gfx::local_lights::section(TModel3d *Model, TSubModel *Section, gfx::geometrybank_manager const &Geometry)
{
auto lookup{m_sections.find(Section)};
if (lookup != m_sections.end())
@@ -483,7 +483,7 @@ opengl46_local_lights::section_data const &opengl46_local_lights::section(TModel
return m_sections.emplace(Section, data).first->second;
}
opengl46_local_lights::section_data const &opengl46_local_lights::model_bounds(TModel3d *Model, gfx::geometrybank_manager const &Geometry)
gfx::local_lights::section_data const &gfx::local_lights::model_bounds(TModel3d *Model, gfx::geometrybank_manager const &Geometry)
{
auto lookup{m_models.find(Model)};
if (lookup != m_models.end())
@@ -502,14 +502,14 @@ opengl46_local_lights::section_data const &opengl46_local_lights::model_bounds(T
return m_models.emplace(Model, data).first->second;
}
std::string opengl46_local_lights::info() const
std::string gfx::local_lights::info() const
{
return "local lights: " + std::to_string(m_lights.size()) + " (scenery " + std::to_string(m_scenerycount) + ", vehicles " + std::to_string(m_vehiclecount) + ", compartments " +
std::to_string(m_compartmentcount) + ")" + (m_cablampon ? " + cab lamp" : "") + ", placed " + std::to_string(m_placed) + ", over capacity " + std::to_string(m_dropped) +
", gather " + to_string(m_gathertime, 2) + " ms";
}
void opengl46_local_lights::draw_debug_ui()
void gfx::local_lights::draw_debug_ui()
{
ImGui::TextUnformatted("local lights (need clustered lights)");
ImGui::Checkbox("light points of scenery models", &Global.gfx_lights_scenery);

View File

@@ -28,15 +28,18 @@ class TSubModel;
class TAnimModel;
class TDynamicObject;
// lights of the opengl 4.6 renderer other than the sun and the vehicle headlights, which go to the light clusters:
// lights of the scene other than the sun and the vehicle headlights, which go to the light clusters of the renderers:
// - the light points (freespot lights) of scenery models and of vehicles, taken the way the experimental renderer takes them:
// a point which is visible, has self-illumination (emission) and is past its self-illumination threshold shines as a spot
// light along the -z axis of its submodel, with the cone of its glare. lamps of scenery models switched with Light_On / Light_Off
// and the self-illumination of vehicle compartments (TDynamicObject::Sections) work through the same state
// - the lit compartments of vehicles (low poly interior sections) without light points of their own: a point light in the middle
// - the lamp of the occupied cab: a point light under the top of the cab model, in place of the ambient light it used to add
// gathered once per frame around the camera, written to the light buffer by opengl46_renderer::Update_Lights()
class opengl46_local_lights
// gathered once per frame around the camera, written to the light buffers by the renderers (Update_Lights())
namespace gfx
{
class local_lights
{
public:
struct light
@@ -53,9 +56,9 @@ class opengl46_local_lights
void const *part{nullptr};
};
opengl46_local_lights() = default;
opengl46_local_lights(const opengl46_local_lights &) = delete;
opengl46_local_lights &operator=(const opengl46_local_lights &) = delete;
local_lights() = default;
local_lights(const local_lights &) = delete;
local_lights &operator=(const local_lights &) = delete;
// collects the lights within Global.gfx_lights_range of Camera, for the frame about to be drawn. it sets the state of the
// shared models of the instances and vehicles it goes through, as the render does before it draws each of them.
@@ -139,3 +142,5 @@ class opengl46_local_lights
std::uint32_t m_dropped{0};
double m_gathertime{0.0}; // milliseconds, smoothed
};
} // namespace gfx

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,346 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the walk of the scene shared by the renderers (stage N6 of claude/vulkan14-renderer-plan.md, decision K11): the decisions of
// what is drawn in a pass (the sections, cells, tracks, shapes, models, instances, vehicles, the cab, the translucent parts),
// in which order, with which material, transform and level of detail. a renderer derives from it and gets the draws through
// the walk_* methods: the opengl renderer makes them at once (the same gl calls in the same order as before the walk was
// shared), the vulkan renderer records them. what the backend may draw itself (the gpu culling of the opengl renderer) is
// asked about with the walk_* methods which do nothing by default.
//
// the walk keeps the state of the draw in model_ubs (the block of the model of the shaders) and the transform on the matrix
// stack of OpenGLMatrices (a cpu stack, despite the name)
#pragma once
#include "rendering/geometrybank.h"
#include "rendering/openglcamera.h"
#include "scene/scene.h"
#include "world/Track.h"
#include "world/TrackSleepers.h"
#include "model/material.h"
#include "gl/ubo.h"
#include <unordered_map>
#include <utility>
#include <vector>
class TAnimModel;
class TDynamicObject;
class TModel3d;
class TSubModel;
class TTraction;
class TMemCell;
struct material_data;
struct lighting_data;
namespace gfx
{
class scene_walk
{
public:
virtual ~scene_walk() = default;
protected:
// types
enum class rendermode
{
none,
color,
shadows,
reflections,
pickcontrols,
pickscenery
};
struct debug_stats
{
int dynamics{0};
int models{0};
int submodels{0};
int paths{0};
int traction{0};
int shapes{0};
int lines{0};
int particles{0};
int drawcalls{0};
int instanced_drawcalls{0}; // drawcalls issued via instanced render path (one per TModel3d batch)
int instances{0}; // total instances drawn via instanced render path
int triangles{0};
debug_stats &operator+=(const debug_stats &Right)
{
dynamics += Right.dynamics;
models += Right.models;
submodels += Right.submodels;
paths += Right.paths;
traction += Right.traction;
shapes += Right.shapes;
lines += Right.lines;
particles += Right.particles;
drawcalls += Right.drawcalls;
instanced_drawcalls += Right.instanced_drawcalls;
instances += Right.instances;
return *this;
}
};
using section_sequence = std::vector<scene::basic_section *>;
using distancecell_pair = std::pair<double, scene::basic_cell *>;
using cell_sequence = std::vector<distancecell_pair>;
struct renderpass_config
{
opengl_camera pass_camera;
opengl_camera viewport_camera;
rendermode draw_mode{rendermode::none};
float draw_range{0.0f};
// depth range of the projection of a color pass; the far plane is only the end of the range which is drawn,
// as the projection matrix itself has none
float znear{0.0f};
float zfar{0.0f};
debug_stats draw_stats;
};
// an instance of a bucket of a scene cell, queued to be drawn by Render_Instanced()
struct queued_instance
{
scene::basic_cell::instance_bucket const *bucket;
std::size_t index; // of the instance and its record in the bucket
};
// the programs of the draws which aren't drawn with the programs of their materials
enum class walk_shader
{
pick, // a colour for the picking of the scenery or the controls
pick_surface, // the gradient of a touch screen for the picking of the controls
line, // the wires of the traction and the lines
billboard, // the glare of a lamp
freespot // the point of light of a lamp
};
// how far the reflection pass draws the simplified versions of things, as if they were farther away
static int constexpr EU07_REFLECTIONFIDELITYOFFSET{250};
// the walk
void Render(scene::basic_region *Region);
void Render(section_sequence::iterator First, section_sequence::iterator Last);
void Render(cell_sequence::iterator First, cell_sequence::iterator Last);
void Render(scene::shape_node const &Shape, bool const Ignorerange);
void Render(TAnimModel *Instance);
// batched render path for many TAnimModel instances that share the same TModel3d.
// Caller guarantees every instance was instanceable when bucketed (no per-instance lights,
// no submodel animation, same replacable skins); the records of the instances tell which
// ones still are. Instances are individually frustum/distance culled by their records, the
// TAnimModel itself is read only for the first instance drawn. Each lod band drawn counts as
// one instanced_drawcall in draw_stats; the instances drawn are added to the instances counter.
// Bandcounts, when given, gets the number of instances drawn in each lod band.
void Render_Instanced(TModel3d *Model, std::vector<queued_instance> const &Instances, std::vector<std::uint32_t> *Bandcounts = nullptr);
// adds the bucketed instances of a scene cell to the frame-level buckets drawn by Render_Instanced(),
// less the ones their records show to be out of sight in the current render pass
void Queue_Instances(scene::basic_cell const *Cell);
// makes the records of the instances held by a bucket of a scene cell, with locations relative to Origin
void Update_Instance_Records(TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket, glm::dvec3 const &Origin);
// whether none of the instances of a bucket can pass the culling of the current pass, by the bounds of their records
bool Bucket_Culled(scene::basic_cell::instance_bucket const &Bucket) const;
// the sphere around the locations of the visible instances of a bucket, with a margin for the precision of the records
static scene::bounding_area Bucket_Area(scene::basic_cell::instance_bucket const &Bucket);
bool Render(TDynamicObject *Dynamic);
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance);
void Render(TSubModel *Submodel);
void Render(TTrack *Track);
void Render(scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last);
// renders the per-track sleeper instances (TTrack::m_sleeper_local_transforms) via GPU instancing.
// caller must already have the camera-relative world-space transform set on the matrix stack.
// no-op if the track has no sleepermodel, Global.SleeperDistance is 0, or the camera is beyond
// Global.SleeperDistance from the track origin.
void Render_Sleepers(TTrack *Track);
// renders the sleepers and switch timbers of the tracks in range (TTrack::m_sleepers3d), laying them first where they're out of date.
// the parts of the model are drawn in instanced batches for the whole run of tracks, the runs of beams out of sight left out
void Render_Timbers(scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last);
bool Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false);
bool Render_interior(bool const Alpha = false);
bool Render_lowpoly(TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha = false);
bool Render_coupler_adapter(TDynamicObject *Dynamic, float const Squaredistance, int const End, bool const Alpha = false);
void Render(TMemCell *Memcell);
void Render_Alpha(scene::basic_region *Region);
void Render_Alpha(cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last);
void Render_Alpha(TAnimModel *Instance);
void Render_Alpha(TTraction *Traction);
void Render_Alpha(scene::lines_node const &Lines);
bool Render_Alpha(TDynamicObject *Dynamic);
bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance);
void Render_Alpha(TSubModel *Submodel);
void Render_Alpha_text(TSubModel *Submodel); // characters of a text display
// the colour of the picking of the item of the index, and the index of a colour read back
static glm::vec3 pick_color(std::size_t const Index);
static std::size_t pick_index(glm::ivec3 const &Color);
// the range the top view of the editor draws the tracks to, and the parts of the scenery it leaves out
static float editor_track_view_range(float const Range, bool const Apply);
static bool editor_ballast_hidden(TTrack const *Track, bool const Colour);
static bool editor_far_tracks_only(bool const Apply, scene::basic_cell const *Cell, glm::dvec3 const &Camera, double const SceneryLimit);
// material assigned to the submodel, resolving replacable skins. null_handle if no skin set is bound
static material_handle submodel_material(TSubModel const *Submodel);
// what the backend does with the draws of the walk
// binds the material for the colour passes: its program, its textures and its parameters in model_ubs (opacity, alpha_mult,
// the params of the submodel or of the lighting of a shape)
virtual void walk_material(material_handle const Material, TSubModel const *Submodel = nullptr, lighting_data const *Lighting = nullptr) = 0;
// the same for the shadow passes: the program of the shadows, with the alpha test of the diffuse texture when there is one
virtual void walk_shadow_material(material_handle const Material) = 0;
virtual opengl_material const &walk_material_data(material_handle const Material) const = 0;
virtual opengl_material const &walk_material_data(TSubModel const *Submodel) const = 0;
virtual void walk_program(walk_shader const Program) = 0;
virtual void walk_texture(std::size_t const Unit, texture_handle const Texture) = 0;
// what follows is translucent (blended, without the depth written) or opaque, culled as the pass culls
virtual void walk_blending(bool const Alpha) = 0;
// the strength of the sun and the ambient light, for things in the shade (tunnels, vehicles in them), 1: the full light
virtual void walk_sunlight(float const Factor = 1.f) = 0;
virtual void walk_environment(TEnvironmentType const Environment = e_flat) = 0;
// the ambient light of the cab: kept, added to (the lamp of the cab), put back
virtual void walk_ambient_save() = 0;
virtual void walk_ambient_add(glm::vec3 const &Addition) = 0;
virtual void walk_ambient_restore() = 0;
// the ambient light of the sun, which lights the lines
virtual glm::vec4 walk_sun_ambient() const = 0;
// a draw of the chunk with the state of the walk: model_ubs with the modelview of OpenGLMatrices, and the instances of
// m_current_instance_count / m_current_instance_base when there are any. the second draws the chunks together
virtual void walk_draw(gfx::geometry_handle const &Geometry) = 0;
virtual void walk_draw(std::vector<gfx::geometrybank_handle>::iterator First, std::vector<gfx::geometrybank_handle>::iterator Last) = 0;
// places the modelview matrices of instances in the instance buffer of the frame. returns: base instance of the first of them
virtual std::uint32_t walk_instances(glm::mat4 const *Modelviews, std::size_t const Count) = 0;
virtual void walk_cull_face(bool const Enabled) = 0;
// the glare of a lamp: drawn without the depth test, added to what is there; drawn only where the point of the light was
// seen (walk_light_query() of the last frame, when the backend has it)
virtual void walk_glare(bool const Enabled) = 0;
virtual void walk_glare_draw(TSubModel *Submodel, gfx::geometry_handle const &Geometry) = 0;
virtual void walk_light_query(TSubModel *Submodel, bool const Begin) = 0;
// what the backend may draw itself (the gpu culling of the opengl renderer); by default the walk draws everything
// the start of the walk of a region
virtual void walk_region_begin() {}
// the opaque ground of the colour pass is drawn, the editor takes the position of the cursor from the depth
virtual void walk_editor_cursor() {}
// a bucket of instances of a cell: false when the backend took the instances (the walk leaves them out); Area is the cell's
virtual bool walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area)
{
return true;
}
// a bucket left out of the pass as wholly out of sight
virtual void walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket) {}
// a model drawn on its own: true when the backend took it. Inrange: the cell of the instance lies within the range of the pass
virtual bool walk_queue_model(TAnimModel *Instance, bool const Inrange)
{
return false;
}
// the sleepers of a track: true when the backend took them
virtual bool walk_queue_sleepers(TTrack *Track)
{
return false;
}
// the sleepers the walk drew for a track, by their squared distances, and the lod bounds of their model
virtual void walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn) {}
// the instances of the cells are about to be drawn: whether the walk reports what it drew of each bucket (walk_instances_compare())
virtual bool walk_instances_begin()
{
return false;
}
virtual void walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts) {}
virtual void walk_instances_end() {}
// members
renderpass_config m_renderpass; // parameters for current render pass
section_sequence m_sectionqueue; // list of sections in current render pass
cell_sequence m_cellqueue;
// frame-level accumulation of per-cell opaque instance buckets. Each visited
// cell's buckets are merged here keyed by (TModel3d*, skins), so that
// Render_Instanced() runs once per unique model across the whole pass instead
// of once per cell -- collapsing many tiny instanced draws into a few large
// batches. Reused every pass; the vectors are emptied (not freed) at the top of Render(scene::basic_region*).
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<queued_instance>, scene::basic_cell::instance_bucket_key_hash> m_frame_instance_buckets;
// geometry of neighbouring tracks of a cell which share the material, gathered to be drawn together. reused by each Render(path_sequence)
std::vector<gfx::geometrybank_handle> m_pathbatch;
// instance records kept by the buckets of scene cells are good as long as they carry this number.
// it changes with each frame drawn with the scenery editor active, as the editor moves and resizes instances in place,
// and whenever scene::node_state_version does, as instances are shown, hidden or drawn on their own
unsigned int m_instancerecordsversion{1};
// the model block of the draw being made
gl::model_ubs model_ubs;
unsigned int m_framestamp{0}; // id of currently rendered gfx frame
unsigned int m_timbersweepframe{0}; // the frame the beams of the tracks far away were last let go in
float m_fogrange{2000.0f};
// when > 0, the walk is inside a Render_Instanced() call: the draws are made for this many instances from
// m_current_instance_base onwards. The vertex shader reads per-instance modelview from the instance buffer
std::size_t m_current_instance_count{0};
std::uint32_t m_current_instance_base{0};
// camera-space transforms of the sleepers and timbers drawn by Render_Timbers, by the model and its part or its level of
// detail; kept from frame to frame to reuse their allocations
struct timber_bucket
{
track_sleepers::beam_model const *model{nullptr};
int part{-1}; // of a model put together from parts; -1: the whole model
float band{0.f}; // the squared distance the level of detail of the whole model is seen from
std::vector<glm::mat4> instances;
};
std::vector<timber_bucket> m_timberbuckets;
// persistent scratch buffer for Render_Instanced(): holds the per-instance
// camera-space root modelview matrices for the batch currently being built.
// Kept as a member rather than a function-local so its heap allocation is
// reused across calls -- clear() retains capacity, so once it has grown to
// the largest batch seen, steady-state frames perform no allocation here.
std::vector<glm::mat4> m_instance_modelviews;
// further persistent scratch buffers for Render_Instanced(): culled instances paired with their
// squared distance, and the lod bounds of the drawn model which split them into lod bands
struct instance_survivor
{
glm::mat4 modelview;
float distancesquared;
std::uint32_t band;
};
std::vector<instance_survivor> m_instance_survivors;
std::vector<float> m_instance_lodbounds;
std::vector<TSubModel const *> m_instance_lodpending;
// per sort key (lod band and distance slot): first index in m_instance_modelviews (with end sentinel)
// and fill cursor; per lod band: nearest distance
std::vector<std::size_t> m_instance_bandstarts;
std::vector<std::size_t> m_instance_bandcursors;
std::vector<float> m_instance_banddistances;
std::vector<std::uint32_t> m_instance_bandcounts; // reported by Render_Instanced() for the comparison
// comparison of the gpu culling: the key of the model Render( TAnimModel * ) is called for, and the models it drew in
// the pass with their scaled squared distances
scene::basic_cell::instance_bucket_key const *m_modelcomparekey{nullptr};
std::vector<std::pair<scene::basic_cell::instance_bucket_key, float>> m_modelcpudistances;
bool m_indirectchanging{false}; // the walk under way met a submodel which may look different in the next frame
// a vehicle drawn in the colour pass of this frame moved (motion blur)
bool m_vehiclesmoving{false};
// set while a preview is drawn: submodels faded out entirely are skipped, instead of being drawn invisible over the stencil
bool m_previewing{false};
// the last lighting of the pass placed the cab lamp among the lights: the cab is lit by it, not by an addition to the ambient light
bool m_cablamplit{false};
// the shadow pass draws the kept scenery part of the map of a lamp: no vehicles, and not the model of the lamp itself
bool m_cubescenery{false};
TAnimModel const *m_cubeskipinstance{nullptr};
// the shadow pass leaves this vehicle out (the one the headlights of the map being drawn belong to)
TDynamicObject const *m_shadowskipvehicle{nullptr};
// what the picking passes drew, by the index of their colours
std::vector<TSubModel const *> m_pickcontrolsitems;
std::vector<TSubModel const *> m_picksurfaceitems;
std::vector<scene::basic_node *> m_picksceneryitems;
// the quad of the glare of lamps and its texture
gfx::geometry_handle m_billboardgeometry{0, 0};
texture_handle m_glaretexture{-1};
private:
static float editor_ortho_reach();
};
} // namespace gfx

View File

@@ -350,7 +350,7 @@ glm::vec4 opengl46_local_shadows::weights() const
return glm::vec4{static_cast<float>(CAB_SLOT), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
}
void opengl46_local_shadows::update_lamps(std::vector<opengl46_local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp)
void opengl46_local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp)
{
m_frame = Framestamp;
m_wanted.clear();
@@ -368,7 +368,7 @@ void opengl46_local_shadows::update_lamps(std::vector<opengl46_local_lights::lig
m_sceneryleft = setup(level).sceneryperframe;
auto const reach{static_cast<double>(Global.gfx_lights_shadows_lamps_range)};
// the lights come the most important first: the first ones within reach get the slots
std::vector<opengl46_local_lights::light const *> picked;
std::vector<gfx::local_lights::light const *> picked;
for (auto const &light : Lights)
{
if (picked.size() >= m_slots.size())
@@ -382,7 +382,7 @@ void opengl46_local_shadows::update_lamps(std::vector<opengl46_local_lights::lig
picked.emplace_back(&light);
}
// lamps which already have a slot keep it
std::vector<opengl46_local_lights::light const *> unplaced;
std::vector<gfx::local_lights::light const *> unplaced;
for (auto const *light : picked)
{
auto placed{false};
@@ -520,7 +520,7 @@ void opengl46_local_shadows::lamps_timer_end()
m_lampscputime = (m_lampscputime == 0.0 ? elapsed : m_lampscputime * 0.9 + elapsed * 0.1);
}
opengl46_local_shadows::lamp_slot const *opengl46_local_shadows::lamp(opengl46_local_lights::light const &Light, std::size_t &Slot) const
opengl46_local_shadows::lamp_slot const *opengl46_local_shadows::lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const
{
if (Light.owner == nullptr)
{

View File

@@ -18,7 +18,7 @@ http://mozilla.org/MPL/2.0/.
#include "gl/shader.h"
#include "gl/query.h"
#include "rendering/opengl46/opengl46locallights.h"
#include "rendering/common/locallights.h"
#include "rendering/lightarray.h"
class TDynamicObject;
@@ -98,7 +98,7 @@ class opengl46_local_shadows
// a lamp with a slot
struct lamp_slot
{
void const *owner{nullptr}; // the light it is, see opengl46_local_lights::light
void const *owner{nullptr}; // the light it is, see gfx::local_lights::light
void const *part{nullptr};
glm::dvec3 position{0.0}; // world space
float range{0.f}; // of the light, metres
@@ -111,7 +111,7 @@ class opengl46_local_shadows
// picks the lamps which get shadows in this frame from the gathered lights (the most important first): the scenery lamps
// within reach of Camera. a lamp keeps its slot while it is picked; one which got a slot or moved, and all of them when the
// scene changes (Version), need their scenery drawn. (re)makes the maps when their count or size changed
void update_lamps(std::vector<opengl46_local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp);
void update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp);
// forgets the lamps: the scene they come from is about to be replaced (a scenery being loaded)
void reset_lamps();
// slots of the lamps picked in this frame
@@ -141,7 +141,7 @@ class opengl46_local_shadows
void lamps_timer_begin();
void lamps_timer_end();
// the slot of the lamp the light is, if it has its shadows ready, nullptr otherwise
lamp_slot const *lamp(opengl46_local_lights::light const &Light, std::size_t &Slot) const;
lamp_slot const *lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const;
// the headlight_weights of the light of the lamp in the slot, see weights()
glm::vec4 lamp_weights(std::size_t const Slot) const;

File diff suppressed because it is too large Load Diff

View File

@@ -40,14 +40,15 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/opengl46/opengl46weather.h"
#include "rendering/opengl46/opengl46exposure.h"
#include "rendering/opengl46/opengl46ao.h"
#include "rendering/opengl46/opengl46locallights.h"
#include "rendering/common/locallights.h"
#include "rendering/opengl46/opengl46localshadows.h"
#include "rendering/opengl46/opengl46instancecull.h"
#include "rendering/opengl46/opengl46hiz.h"
#include "rendering/opengl46/opengl46shadowfit.h"
#include "rendering/common/scenewalk.h"
// bare-bones render controller, in lack of anything better yet
class opengl46_renderer : public gfx_renderer {
class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
public:
// constructors
opengl46_renderer() = default;
@@ -189,64 +190,42 @@ class opengl46_renderer : public gfx_renderer {
static gfx_backend_desc backend_desc();
private:
using gfx::scene_walk::Render;
using gfx::scene_walk::Render_Alpha;
// the draws of the shared walk of the scene
void walk_material(material_handle const Material, TSubModel const *Submodel = nullptr, lighting_data const *Lighting = nullptr) override;
void walk_shadow_material(material_handle const Material) override;
opengl_material const &walk_material_data(material_handle const Material) const override;
opengl_material const &walk_material_data(TSubModel const *Submodel) const override;
void walk_program(walk_shader const Program) override;
void walk_texture(std::size_t const Unit, texture_handle const Texture) override;
void walk_blending(bool const Alpha) override;
void walk_sunlight(float const Factor = 1.f) override;
void walk_environment(TEnvironmentType const Environment = e_flat) override;
void walk_ambient_save() override;
void walk_ambient_add(glm::vec3 const &Addition) override;
void walk_ambient_restore() override;
glm::vec4 walk_sun_ambient() const override;
void walk_draw(gfx::geometry_handle const &Geometry) override;
void walk_draw(std::vector<gfx::geometrybank_handle>::iterator First, std::vector<gfx::geometrybank_handle>::iterator Last) override;
std::uint32_t walk_instances(glm::mat4 const *Modelviews, std::size_t const Count) override;
void walk_cull_face(bool const Enabled) override;
void walk_glare(bool const Enabled) override;
void walk_glare_draw(TSubModel *Submodel, gfx::geometry_handle const &Geometry) override;
void walk_light_query(TSubModel *Submodel, bool const Begin) override;
// the gpu culling of the instances, models and sleepers, the depth pass of the whole scene
void walk_region_begin() override;
void walk_editor_cursor() override;
bool walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area) override;
void walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket) override;
bool walk_queue_model(TAnimModel *Instance, bool const Inrange) override;
bool walk_queue_sleepers(TTrack *Track) override;
void walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn) override;
bool walk_instances_begin() override;
void walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts) override;
void walk_instances_end() override;
// types
enum class rendermode
{
none,
color,
shadows,
reflections,
pickcontrols,
pickscenery
};
struct debug_stats
{
int dynamics{0};
int models{0};
int submodels{0};
int paths{0};
int traction{0};
int shapes{0};
int lines{0};
int particles{0};
int drawcalls{0};
int instanced_drawcalls{0}; // drawcalls issued via instanced render path (one per TModel3d batch)
int instances{0}; // total instances drawn via instanced render path
int triangles{0};
debug_stats& operator+=( const debug_stats& Right ) {
dynamics += Right.dynamics;
models += Right.models;
submodels += Right.submodels;
paths += Right.paths;
traction += Right.traction;
shapes += Right.shapes;
lines += Right.lines;
particles += Right.particles;
drawcalls += Right.drawcalls;
instanced_drawcalls += Right.instanced_drawcalls;
instances += Right.instances;
return *this; }
};
using section_sequence = std::vector<scene::basic_section *>;
using distancecell_pair = std::pair<double, scene::basic_cell *>;
using cell_sequence = std::vector<distancecell_pair>;
struct renderpass_config
{
opengl_camera pass_camera;
opengl_camera viewport_camera;
rendermode draw_mode{rendermode::none};
float draw_range{0.0f};
// depth range of the projection of a color pass; the far plane is only the end of the range which is drawn,
// as the projection matrix itself has none
float znear{0.0f};
float zfar{0.0f};
debug_stats draw_stats;
};
struct viewport_config {
int width;
int height;
@@ -349,33 +328,6 @@ class opengl46_renderer : public gfx_renderer {
// Position in world space, Rotation from world axes to its axes; Near is the distance of the near plane from the projector
void Headlight_Projector(light_array::light_record const &Light, glm::dvec3 &Position, glm::mat3 &Rotation, glm::mat4 &Projection, float &Near, float &Fieldofview);
bool Render(world_environment *Environment);
void Render(scene::basic_region *Region);
void Render(section_sequence::iterator First, section_sequence::iterator Last);
void Render(cell_sequence::iterator First, cell_sequence::iterator Last);
void Render(scene::shape_node const &Shape, bool const Ignorerange);
void Render(TAnimModel *Instance);
// an instance of a bucket of a scene cell, queued to be drawn by Render_Instanced()
struct queued_instance {
scene::basic_cell::instance_bucket const *bucket;
std::size_t index; // of the instance and its record in the bucket
};
// batched render path for many TAnimModel instances that share the same TModel3d.
// Caller guarantees every instance was instanceable when bucketed (no per-instance lights,
// no submodel animation, same replacable skins); the records of the instances tell which
// ones still are. Instances are individually frustum/distance culled by their records, the
// TAnimModel itself is read only for the first instance drawn. Each lod band drawn counts as
// one instanced_drawcall in draw_stats; the instances drawn are added to the instances counter.
// Bandcounts, when given, gets the number of instances drawn in each lod band.
void Render_Instanced( TModel3d *Model, std::vector<queued_instance> const &Instances, std::vector<std::uint32_t> *Bandcounts = nullptr );
// adds the bucketed instances of a scene cell to the frame-level buckets drawn by Render_Instanced(),
// less the ones their records show to be out of sight in the current render pass
void Queue_Instances( scene::basic_cell const *Cell );
// makes the records of the instances held by a bucket of a scene cell, with locations relative to Origin
void Update_Instance_Records( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket, glm::dvec3 const &Origin );
// whether none of the instances of a bucket can pass the culling of the current pass, by the bounds of their records
bool Bucket_Culled( scene::basic_cell::instance_bucket const &Bucket ) const;
// the sphere around the locations of the visible instances of a bucket, with a margin for the precision of the records
static scene::bounding_area Bucket_Area( scene::basic_cell::instance_bucket const &Bucket );
// places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them
std::uint32_t Upload_Instance_Modelviews( glm::mat4 const *Modelviews, std::size_t const Count );
// binds the instance buffer of the frame for the vertex shaders (INSTANCE_SSBO_BINDING)
@@ -479,39 +431,20 @@ class opengl46_renderer : public gfx_renderer {
std::array<std::optional<std::array<glm::vec3, 8>>, gl::MAX_CASCADES> &Areas );
// whether the depth pass of the whole scene draws the material bound last (Bind_Material()) and the geometry
bool Scene_Depth_Draws( gfx::geometry_handle const &Geometry ) const;
bool Render(TDynamicObject *Dynamic);
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance);
void Render(TSubModel *Submodel);
void Render(TTrack *Track);
void Render(scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last);
// renders the per-track sleeper instances (TTrack::m_sleeper_local_transforms) via GPU instancing.
// caller must already have the camera-relative world-space transform set on the matrix stack.
// no-op if the track has no sleepermodel, Global.SleeperDistance is 0, or the camera is beyond
// Global.SleeperDistance from the track origin.
void Render_Sleepers( TTrack *Track );
// queues the sleepers of a track in the gpu culling of the pass (Global.gfx_gpucull_sleepers), with the gates of
// Render_Sleepers(), making their records first when they're missing or out of date
void Queue_Sleepers( TTrack *Track );
// renders the sleepers and switch timbers of the tracks in range (TTrack::m_sleepers3d), laying them first where they're out of date.
// the parts of the model are drawn in instanced batches for the whole run of tracks, the runs of beams out of sight left out
void Render_Timbers( scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last );
// queues a model instance drawn on its own in the gpu culling of the pass (Global.gfx_gpucull_models), as a bucket of
// its own, making its record first when it's missing or out of date. Inrange: the cell of the instance lies within the
// range of the pass. returns true when the instance isn't drawn by the cpu in the pass: it's drawn from the result of the
// culling, or it was culled and recorded by the depth pass of the whole scene, or it lies out of range
bool Queue_Model( TAnimModel *Instance, bool const Inrange );
bool Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false);
bool Render_interior( bool const Alpha = false );
bool Render_lowpoly( TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha = false );
bool Render_coupler_adapter( TDynamicObject *Dynamic, float const Squaredistance, int const End, bool const Alpha = false );
// releases gl textures used only by vehicles farther than gfx.textures.releasedistance times the draw range
void Update_Texture_Release();
// textures used by materials of the sub-models of specified model, replaceable skins excluded
std::vector<texture_handle> const &Model_Textures( TModel3d *Model );
// has the data of the textures read ahead of the camera, with gfx.textures.streaming
void Update_Texture_Prefetch();
void Render(TMemCell *Memcell);
void Render_particles();
void Render_precipitation();
// the view of the colour pass being drawn, for the particles of the gpu
@@ -519,24 +452,14 @@ class opengl46_renderer : public gfx_renderer {
// the grass of the heightmap terrain around the camera of the main view; Future: the matrix of the motion vectors
void Render_grass(glm::mat4 const &Future);
void Render_vr_models();
void Render_Alpha(scene::basic_region *Region);
void Render_Alpha(cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last);
void Render_Alpha(TAnimModel *Instance);
void Render_Alpha(TTraction *Traction);
void Render_Alpha(scene::lines_node const &Lines);
void Render_editor_overlay();
bool Render_Alpha(TDynamicObject *Dynamic);
bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance);
void Render_Alpha(TSubModel *Submodel);
void Render_Alpha_text(TSubModel *Submodel); // characters of a text display
// writes the lighting of the pass about to be drawn to the light buffer: the sun, the ambient light and the fog, and the headlights of Lights
// unless Headlights is false, as in the pass which draws the environment map, where lights of vehicles would only get baked into the map.
// Clustered tells the shading to take the headlights from the lists of the clusters (see Cull_Lights()) and not to go through the first few.
// with Headlights and Clustered the cab lamp (first) and the lamps of models and vehicles (after the headlights) are written as well
void Update_Lights(light_array &Lights, bool const Headlights = true, bool const Clustered = false);
// writes a light of a model or a vehicle, or the cab lamp, to the slot of the light buffer, in the view space of the pass
void Write_Local_Light(gl::light_element &Light, opengl46_local_lights::light const &Source, glm::dvec3 const &Camera, glm::mat4 const &View);
void Write_Local_Light(gl::light_element &Light, gfx::local_lights::light const &Source, glm::dvec3 const &Camera, glm::mat4 const &View);
// brings the light cluster grid of the viewport up to date with the projection of the color pass being drawn.
// Targetsize is the size in pixels of what the pass draws to, which gl_FragCoord of the shading counts in
void Update_Clusters(viewport_config &vp, glm::ivec2 const &Targetsize);
@@ -547,8 +470,6 @@ class opengl46_renderer : public gfx_renderer {
void Render_cluster_heatmap(viewport_config &vp, gl::framebuffer *Target, glm::ivec2 const &Targetsize);
// ambient occlusion from the resolved depth (vp.main_texd), multiplied into the image of Target. Mode: Global.gfx_postfx_ao
void Render_ambient_occlusion(viewport_config &vp, gl::framebuffer &Target, int const Mode);
glm::vec3 pick_color(std::size_t const Index);
std::size_t pick_index(glm::ivec3 const &Color);
bool init_viewport(viewport_config &vp);
@@ -590,8 +511,6 @@ class opengl46_renderer : public gfx_renderer {
/*
float m_sunandviewangle; // cached dot product of sunlight and camera vectors
*/
gfx::geometry_handle m_billboardgeometry{0, 0};
texture_handle m_glaretexture{-1};
texture_handle m_suntexture{-1};
texture_handle m_moontexture{-1};
texture_handle m_smoketexture{-1};
@@ -612,8 +531,6 @@ class opengl46_renderer : public gfx_renderer {
opengl46_gpu_particles m_gpuparticles; // the smoke, rain and snow of the gpu (gfx.smoke.gpu, gfx.precipitation.gpu)
bool m_particlesoft{false}; // the depth for the soft particles is bound in the colour pass being drawn
unsigned int m_framestamp; // id of currently rendered gfx frame
unsigned int m_timbersweepframe { 0 }; // the frame the beams of the tracks far away were last let go in
float m_framerate;
double m_updateaccumulator{0.0};
std::string m_debugtimestext;
@@ -638,29 +555,12 @@ class opengl46_renderer : public gfx_renderer {
float m_specularopaquescalefactor{1.f};
float m_speculartranslucentscalefactor{1.f};
float m_fogrange = 2000.0f;
renderpass_config m_renderpass; // parameters for current render pass
section_sequence m_sectionqueue; // list of sections in current render pass
cell_sequence m_cellqueue;
// frame-level accumulation of per-cell opaque instance buckets. Each visited
// cell's buckets are merged here keyed by (TModel3d*, skins), so that
// Render_Instanced() runs once per unique model across the whole pass instead
// of once per cell -- collapsing many tiny instanced draws into a few large
// batches. Reused every pass; the vectors are emptied (not freed) at the top of Render(scene::basic_region*).
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<queued_instance>, scene::basic_cell::instance_bucket_key_hash> m_frame_instance_buckets;
// textures used by vehicle models, for the release of far textures
std::unordered_map<TModel3d const *, std::vector<texture_handle>> m_modeltextures;
// the read ahead of textures (Update_Texture_Prefetch()): the cell it goes on with
std::size_t m_prefetchcursor { 0 };
std::chrono::steady_clock::time_point m_prefetchvehicletime;
std::chrono::steady_clock::time_point m_prefetchdroptime;
// geometry of neighbouring tracks of a cell which share the material, gathered to be drawn together. reused by each Render(path_sequence)
std::vector<gfx::geometrybank_handle> m_pathbatch;
// instance records kept by the buckets of scene cells are good as long as they carry this number.
// it changes with each frame drawn with the scenery editor active, as the editor moves and resizes instances in place,
// and whenever scene::node_state_version does, as instances are shown, hidden or drawn on their own
unsigned int m_instancerecordsversion { 1 };
unsigned int m_nodestateversion { 0 }; // scene::node_state_version which m_instancerecordsversion was last checked against
// culling of instances in compute passes, run along the first color pass of the main viewport of a frame
// when Global.gfx_gpucull is set: the instances of the pass are drawn from its result (1), or the result is compared
@@ -683,7 +583,6 @@ class opengl46_renderer : public gfx_renderer {
int m_shadowcascade { -1 }; // the region is drawn for this cascade of the sun shadows
// Instances_Cast_Shadow() of the groups of the shadow pass under way
std::unordered_map<scene::basic_cell::instance_bucket_key, bool, scene::basic_cell::instance_bucket_key_hash> m_shadowcasters;
std::vector<std::uint32_t> m_instance_bandcounts; // reported by Render_Instanced() for the comparison
// the sleepers of tracks culled on the gpu with the instances of the main view (Global.gfx_gpucull_sleepers): records made
// from TTrack::m_sleeper_local_transforms when the track is queued first, and again when they or the instance records
// (editor, m_instancerecordsversion) went out of date. the records of tracks not queued for a while are let go
@@ -710,10 +609,6 @@ class opengl46_renderer : public gfx_renderer {
std::unordered_map<TAnimModel const *, model_records> m_modelrecords;
bool m_modelspass { false }; // the models drawn on their own of the pass under way go to its gpu culling
bool m_indirectcolourmodels { false }; // with m_indirectcolourpending: the depth pass culled and recorded them too
// comparison (Global.gfx_gpucull 2): the key of the model Render( TAnimModel * ) is called for, and the models it drew in
// the pass with their scaled squared distances, reported once the culling ran
scene::basic_cell::instance_bucket_key const *m_modelcomparekey { nullptr };
std::vector<std::pair<scene::basic_cell::instance_bucket_key, float>> m_modelcpudistances;
bool m_sleeperspass { false }; // the sleepers of the pass under way go to its gpu culling (the one of the main view)
bool m_indirectcoloursleepers { false }; // with m_indirectcolourpending: the depth pass culled and recorded the sleepers too
// counts per lod band of the sleepers Render_Sleepers() drew in the pass, by model and skin, for the comparison (they're
@@ -786,7 +681,6 @@ class opengl46_renderer : public gfx_renderer {
};
std::unordered_map<scene::basic_cell::instance_bucket_key, walk_record, scene::basic_cell::instance_bucket_key_hash> m_walkrecords;
int m_walkrankcutoff { 0 }; // Global.gfx_shadow_rank_cutoff the walks were made with
bool m_indirectchanging { false }; // the walk under way met a submodel which may look different in the next frame
// what the walks of the frame did, for the panel: walks made, of them not kept, walks taken from earlier frames, and the cpu time
// of the walks and of putting the draws in order with their blocks uploaded
struct walk_counts {
@@ -949,14 +843,10 @@ class opengl46_renderer : public gfx_renderer {
bool m_contactshadowslast { false };
// a vehicle drawn in the colour pass of the last frame moved, and one drawn in this frame did (motion blur, Render_pass())
bool m_vehiclesmoved { true };
bool m_vehiclesmoving { false };
gl::gpu_timer m_contactshadowstimer; // resolve of the depth
renderpass_config m_colorpass; // parametrs of most recent color pass
std::array<renderpass_config, 3> m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages
std::vector<TSubModel const *> m_pickcontrolsitems;
std::vector<TSubModel const *> m_picksurfaceitems;
TSubModel const *m_pickcontrolitem{nullptr};
std::vector<scene::basic_node *> m_picksceneryitems;
scene::basic_node *m_picksceneryitem{nullptr};
glm::vec3 m_worldmousecoordinates { 0.f };
bool m_mousehit { false };
@@ -997,21 +887,16 @@ class opengl46_renderer : public gfx_renderer {
uint32_t m_lights_lit = 0;
uint32_t m_lights_dropped = 0;
// lights of models and vehicles and the cab lamp, gathered once per frame for the light clusters
opengl46_local_lights m_locallights;
// the last Update_Lights() placed the cab lamp in the light buffer: the cab is lit by it, not by an addition to the ambient light
bool m_cablamplit = false;
gfx::local_lights m_locallights;
// the ambient light of the light buffer while the cab is lit (walk_ambient_save())
packed_vec3 m_cabambient { 0.f };
// cube shadow maps of the lamps
opengl46_local_shadows m_localshadows;
// the shadow pass draws to a cube shadow map, with the programs of m_localshadows
bool m_cubeshadows = false;
// the shadow pass draws the kept scenery part of the map of a lamp: no vehicles, and not the model of the lamp itself
bool m_cubescenery = false;
TAnimModel const *m_cubeskipinstance = nullptr;
// vehicles near the camera which cast the shadows of scenery lamps, and those of them by the lamp being drawn
std::vector<TDynamicObject *> m_lampshadowvehicles;
std::vector<TDynamicObject *> m_lampshadowslot;
// the shadow pass leaves this vehicle out (the one the headlights of the map being drawn belong to)
TDynamicObject const *m_shadowskipvehicle = nullptr;
// headlights which may get shadow maps in this frame, and their importance
std::vector<std::pair<float, light_array::light_record const *>> m_headlightshadowcandidates;
std::vector<light_array::light_record const *> m_headlightshadowpicked;
@@ -1041,44 +926,7 @@ class opengl46_renderer : public gfx_renderer {
// matrices of their instances past the ones placed earlier in the frame, and draw them from that base instance.
// persistently mapped with Global.gfx_bufferstreaming
std::unique_ptr<gl::frame_stream> m_instancestream;
// when > 0, the renderer is inside a Render_Instanced() call. The draw(handle)
// method routes to glDrawElementsInstanced* with this many instances from
// m_current_instance_base onwards rather than a single regular draw. The vertex shader reads
// per-instance modelview from instance_ssbo[gl_BaseInstance + gl_InstanceID]. Reset to 0 on exit.
std::size_t m_current_instance_count { 0 };
std::uint32_t m_current_instance_base { 0 };
// camera-space transforms of the sleepers and timbers drawn by Render_Timbers, by the model and its part or its level of
// detail; kept from frame to frame to reuse their allocations
struct timber_bucket {
track_sleepers::beam_model const *model { nullptr };
int part { -1 }; // of a model put together from parts; -1: the whole model
float band { 0.f }; // the squared distance the level of detail of the whole model is seen from
std::vector<glm::mat4> instances;
};
std::vector<timber_bucket> m_timberbuckets;
// persistent scratch buffer for Render_Instanced(): holds the per-instance
// camera-space root modelview matrices for the batch currently being built.
// Kept as a member rather than a function-local so its heap allocation is
// reused across calls -- clear() retains capacity, so once it has grown to
// the largest batch seen, steady-state frames perform no allocation here.
std::vector<glm::mat4> m_instance_modelviews;
// further persistent scratch buffers for Render_Instanced(): culled instances paired with their
// squared distance, and the lod bounds of the drawn model which split them into lod bands
struct instance_survivor {
glm::mat4 modelview;
float distancesquared;
std::uint32_t band;
};
std::vector<instance_survivor> m_instance_survivors;
std::vector<float> m_instance_lodbounds;
std::vector<TSubModel const *> m_instance_lodpending;
// per sort key (lod band and distance slot): first index in m_instance_modelviews (with end sentinel)
// and fill cursor; per lod band: nearest distance
std::vector<std::size_t> m_instance_bandstarts;
std::vector<std::size_t> m_instance_bandcursors;
std::vector<float> m_instance_banddistances;
gl::scene_ubs scene_ubs;
gl::model_ubs model_ubs;
gl::light_data light_data;
std::unordered_map<std::string, std::shared_ptr<gl::program>> m_shaders;
@@ -1167,8 +1015,6 @@ class opengl46_renderer : public gfx_renderer {
std::unique_ptr<opengl_texture> ldr_tex;
std::unique_ptr<gl::framebuffer> ldr_fb;
} m_preview;
// set while a preview is drawn: submodels faded out entirely are skipped, instead of being drawn invisible over the stencil
bool m_previewing { false };
static bool renderer_register;

View File

@@ -12,9 +12,9 @@ http://mozilla.org/MPL/2.0/.
// same values for the SPIR-V binaries, which glslc compiles without the renderer (the renderer checks they are the same).
//
// the descriptor sets (decision K6 of the plan): set 0 holds the resources of a pass, made when the renderer starts and bound
// with a dynamic offset in the ring of the frame; set 1 is pushed with the draws (vkCmdPushDescriptorSet2): the textures of
// the material and what changes between the draws. a layout can have only one set pushed, so the passes without a set of
// their own (the user interface, the compute passes) push set 0
// with dynamic offsets in the ring of the frame; set 1 is pushed with the draws (vkCmdPushDescriptorSet2): the textures of
// the material. a layout can have only one set pushed, so the passes without a set of their own (the user interface, the
// compute passes, the post-processing) push set 0
#pragma once
@@ -28,27 +28,63 @@ namespace vk14::bindings
constexpr std::uint32_t UI_SET = 0;
constexpr std::uint32_t UI_TEXTURE = 0;
// the sets of the passes of the scene (from the stage D1 of the plan)
// the sets of the passes of the scene
constexpr std::uint32_t PASS_SET = 0;
constexpr std::uint32_t MATERIAL_SET = 1;
// set 0 of the scene: the block of the scene (gl::scene_ubs, dynamic offset in the ring of the frame)
constexpr std::uint32_t SCENE_UBO = 0;
// the blocks of the draws of the frame (gl::model_ubs each), the one of a draw picked by the push constant draw
constexpr std::uint32_t DRAW_SSBO = 1;
// the modelview matrices of the instances of the frame, element 0 the identity (draws which aren't instanced)
constexpr std::uint32_t INSTANCE_SSBO = 2;
// the light buffer of the pass (gl::light_data, dynamic offset)
constexpr std::uint32_t LIGHT_SSBO = 3;
// the light clusters: the grid of the pass (gl::cluster_ubs, dynamic offset), the lists of the lights of the clusters
constexpr std::uint32_t CLUSTER_UBO = 4;
constexpr std::uint32_t CLUSTER_RANGE_SSBO = 5;
constexpr std::uint32_t CLUSTER_INDEX_SSBO = 6;
// the cascades of the sun shadows (depth compared), the environment map, the beams of the headlights
constexpr std::uint32_t SHADOW_TEX = 7;
constexpr std::uint32_t ENV_TEX = 8;
constexpr std::uint32_t HEADLIGHT_TEX = 9;
constexpr std::uint32_t PASS_BINDING_COUNT = 10;
// the textures of a material take the bindings of their units (#pragma texture(name, unit, ...)), 0 .. MAX_TEXTURES - 1
constexpr std::uint32_t MATERIAL_TEXTURE_FIRST = 0;
// the compute passes of the light clusters, set 0 pushed
constexpr std::uint32_t CL_CLUSTER_UBO = 0;
constexpr std::uint32_t CL_AABB_SSBO = 1;
constexpr std::uint32_t CL_RANGE_SSBO = 2;
constexpr std::uint32_t CL_INDEX_SSBO = 3;
constexpr std::uint32_t CL_COUNTER_SSBO = 4;
constexpr std::uint32_t CL_LIGHT_SSBO = 5;
// the post-processing and the copies of whole images: set 0 pushed, the images it reads from binding 0 on
constexpr std::uint32_t POST_SET = 0;
constexpr std::uint32_t POST_TEXTURE_FIRST = 0;
// 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)
constexpr std::uint32_t PUSH_CONSTANTS_SIZE = 112;
// the specialization constants of the shaders
// 1: the colour written to the window is encoded by the shader (a window without an srgb format, gfx.shadergamma yes)
constexpr std::uint32_t SPEC_OUTPUT_GAMMA = 0;
// the settings of the game (gfx.* in eu07.ini) the shaders of the scene depend on, as in shaders/ogl46/settings.glsl
constexpr std::uint32_t SPEC_SHADOWMAP_ENABLED = 1;
constexpr std::uint32_t SPEC_ENVMAP_ENABLED = 2;
constexpr std::uint32_t SPEC_MOTIONBLUR_ENABLED = 3;
constexpr std::uint32_t SPEC_POSTFX_ENABLED = 4;
constexpr std::uint32_t SPEC_EXTRAEFFECTS_ENABLED = 5;
constexpr std::uint32_t SPEC_COUNT = 6;
} // namespace vk14::bindings
namespace vk14
{
// the bindings above as #define lines, with MASZYNA_CONSTANTS, which tells shaders/vk14/common.glsl not to write its own
inline std::string binding_preamble()
{
auto const define = [](char const *Name, std::uint32_t const Value) { return std::string("#define ") + Name + " " + std::to_string(Value) + "\n"; };
return define("MASZYNA_CONSTANTS", 1) + define("UI_SET", bindings::UI_SET) + define("UI_TEXTURE", bindings::UI_TEXTURE) + define("PASS_SET", bindings::PASS_SET) + define("MATERIAL_SET", bindings::MATERIAL_SET) +
define("MATERIAL_TEXTURE_FIRST", bindings::MATERIAL_TEXTURE_FIRST) + define("SPEC_OUTPUT_GAMMA", bindings::SPEC_OUTPUT_GAMMA);
}
// the bindings above as #define lines, with the constants of the game the shaders share with the opengl renderer (the sizes of
// the blocks, the cluster grid), and MASZYNA_CONSTANTS, which tells shaders/vk14/common.glsl not to write its own
std::string binding_preamble();
} // namespace vk14

View File

@@ -0,0 +1,322 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the objects of the vulkan api of the vulkan renderer (vulkan14_renderer::context), for the files of the renderer only:
// the core (vulkan14renderer.cpp) and the scene (vulkan14scene.cpp)
#pragma once
#include "rendering/vulkan14/vulkan14renderer.h"
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/vulkan14/vulkan14geometry.h"
#include "model/material.h"
#include "gl/ubo.h"
#include <array>
#include <memory>
#include <unordered_map>
#include <vector>
namespace vk14
{
// how the vertices of a program of the scene come
enum class vertex_kind : std::uint8_t
{
none, // made by the vertex shader (the celestial bodies, the full screen quads)
scene, // gfx::basic_vertex and the user data, the geometry banks
colour, // position and colour, the sky dome
particle, // position, colour and texture coordinates in one stream, the smoke
position_uv // position and texture coordinates in two streams, the precipitation
};
// the push constants of the pipelines of the scene and of the post-processing (shaders/vk14/common.glsl, draw_constants_block)
struct draw_constants
{
std::uint32_t draw{0};
std::uint32_t flags{0};
std::uint32_t pad0{0};
std::uint32_t pad1{0};
glm::vec4 data[6]{};
};
static_assert(sizeof(draw_constants) == bindings::PUSH_CONSTANTS_SIZE, "the push constants don't match the shaders");
// a program of the scene: the shaders and the pipelines made of them for the targets and the states they were drawn with
struct scene_program : public gfx::shader_program
{
std::shared_ptr<shader_code const> vertex;
std::shared_ptr<shader_code const> fragment;
// the fragment shader of the passes without the velocity target (NO_MOTION_OUTPUT), made when it's first needed
std::string fragment_name;
std::shared_ptr<shader_code const> fragment_single;
vertex_kind vertices{vertex_kind::scene};
std::unordered_map<std::uint64_t, VkPipeline> pipelines;
};
// what a pass of the scene draws to; the pipelines are made for these
struct target_formats
{
std::array<VkFormat, 2> colour{VK_FORMAT_UNDEFINED, VK_FORMAT_UNDEFINED};
std::uint32_t colours{0};
VkFormat depth{VK_FORMAT_UNDEFINED};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
// the number the pipelines of the targets are known by
std::uint32_t id{0};
};
// the images a viewport draws its scene to, made for its size and the settings
struct viewport_targets
{
VkExtent2D extent{0, 0};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
bool motion{false}; // the velocity is drawn, for the motion blur
bool aberration{false};
// with more than one sample: the images drawn to, resolved to the ones below
std::shared_ptr<image> msaa_colour;
std::shared_ptr<image> msaa_motion;
std::shared_ptr<image> msaa_depth;
// the colour of the scene (hdr), the velocity and the depth: the resolved ones, or the ones drawn to with one sample
std::shared_ptr<image> colour;
std::shared_ptr<image> motion_image;
std::shared_ptr<image> depth;
// the colour after the motion blur (hdr)
std::shared_ptr<image> blurred;
// the tonemapped image, before the chromatic aberration, and the one which goes to the window
std::shared_ptr<image> tonemapped;
std::shared_ptr<image> output;
// the depth under the cursor of the editor: one sample of the depth of the ground resolved to it, and the buffer it's read back with
std::shared_ptr<image> cursor_depth;
target_formats formats;
target_formats output_formats;
bool srgb{true}; // the output encodes the colour (gfx.shadergamma no)
};
// the ring of a frame some data of the draws goes through, the descriptor of which reads a window of it: the window starts again
// further on when it's full, and the pass set is bound again with the new offset
struct window_ring
{
frame_ring ring;
VkDeviceSize window{0}; // the size of the window, bytes
VkDeviceSize stride{0};
VkDeviceSize base{0}; // where the current window starts
bool open{false};
};
// the light clusters (gl::cluster_grid), of the colour pass of each viewport in turn
struct cluster_state
{
buffer aabbs;
buffer ranges;
buffer indices;
buffer counters;
gl::cluster_ubs params{};
bool stale{true}; // the boxes have to be made anew
VkDeviceSize params_offset{0}; // in the ring of the frame, for the pass under way
std::uint32_t lights_total{0};
};
// a picking or a reading of a value of the depth, waiting for the gpu to be done with the frame it was made in
struct readback
{
std::shared_ptr<buffer> data;
std::uint64_t value{0}; // of the timeline
bool pending{false};
glm::ivec2 position{0};
};
// what the scene is drawn with
struct scene_state
{
// the layouts: of the pass set (set 0), the pushed set of the materials (set 1), of the pipelines of the scene, the
// post-processing and the compute passes of the clusters
VkDescriptorSetLayout pass_layout{VK_NULL_HANDLE};
push_set material_set;
VkPipelineLayout scene_layout{VK_NULL_HANDLE};
push_set post_set;
VkPipelineLayout post_layout{VK_NULL_HANDLE};
push_set cluster_set;
VkPipelineLayout cluster_layout{VK_NULL_HANDLE};
// the pass sets: made with the pool when the images they read are made anew
VkDescriptorPool pass_pool{VK_NULL_HANDLE};
enum pass_set_kind
{
set_colour, // the shadow map and the environment map
set_reflections, // the shadow map and the empty environment map
set_plain, // the empty maps (shadows, picking)
set_count
};
std::array<VkDescriptorSet, set_count> pass_sets{};
bool pass_sets_stale{true};
// the rings of the draws: the blocks of the models (gl::model_ubs) and the modelview matrices of the instances
window_ring draws;
window_ring instances;
// the images every pass may read: the stand-ins, the shadow maps, the environment map
std::shared_ptr<image> white; // a texture of a material which isn't there
std::shared_ptr<image> shadow_empty;
std::shared_ptr<image> shadow_map;
std::vector<VkImageView> shadow_layers; // a view of each cascade
std::uint32_t shadow_size{0};
std::shared_ptr<image> env_empty;
std::shared_ptr<image> env_map;
std::shared_ptr<image> env_depth;
std::array<VkImageView, 6> env_faces{};
std::shared_ptr<image> pick_colour;
std::shared_ptr<image> pick_depth;
VkImageView headlights_view{VK_NULL_HANDLE}; // the one the pass sets were made with
cluster_state clusters;
// the targets of the viewports, [0] the main window
std::vector<viewport_targets> targets;
// the formats of the passes which don't depend on the viewport
target_formats shadow_formats;
target_formats reflection_formats;
target_formats pick_formats;
std::uint32_t formats_count{0};
// the programs: of the materials (by the name Fetch_Shader() is given), and the ones of the renderer
std::unordered_map<std::string, std::shared_ptr<scene_program>> materials;
std::shared_ptr<scene_program> shadow, alpha_shadow, pick, pick_surface, line, billboard, freespot, celestial, skydome, smoke, precipitation;
// the post-processing, by the fragment shader
std::unordered_map<std::string, std::shared_ptr<shader_code const>> post_fragments;
std::shared_ptr<shader_code const> quad;
VkPipeline cluster_build{VK_NULL_HANDLE};
VkPipeline cluster_cull{VK_NULL_HANDLE};
// the reading of the colour under the cursor of the picking passes and of the depth under the cursor of the editor
readback pick_controls;
readback pick_scenery;
readback cursor;
// the screenshot asked for, copied with the frame
std::shared_ptr<buffer> screenshot;
std::uint64_t screenshot_value{0};
VkExtent2D screenshot_extent{0, 0};
bool screenshot_pending{false};
bool screenshot_wanted{false};
// the occlusion queries of the points of light of the lamps, a pool for each frame in flight with the submodels they were made
// for; the glare of a lamp is drawn where its point was seen in the frame the gpu was done with last
std::vector<VkQueryPool> occlusion_pools;
std::vector<std::vector<TSubModel const *>> occlusion_submodels;
std::unordered_map<TSubModel const *, bool> glare_visible;
static constexpr std::uint32_t occlusion_queries{1024};
// the images of the scene were put in their layouts (the stand-ins cleared)
bool images_ready{false};
};
} // namespace vk14
// the objects of the vulkan api. destroyed in the reverse order: the swapchains and the frames, then the device, then the instance
struct vulkan14_renderer::context
{
struct frame
{
VkCommandPool pool{VK_NULL_HANDLE};
// the uploads of the frame go to the queue first, then the commands of the frame
VkCommandBuffer uploads{VK_NULL_HANDLE};
VkCommandBuffer commands{VK_NULL_HANDLE};
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
std::uint64_t done{0};
// the number of the frame, for the report of a lost device
std::uint64_t number{0};
};
vk14::instance instance;
vk14::device device;
vk14::allocator memory;
vk14::deletion_queue deletions;
vk14::uploader uploads;
vk14::image_table images;
vk14::samplers sampling;
vk14::frame_ring ring;
vk14::shader_library shaders;
vk14::pipeline_library pipelines;
vk14::pass_list passes;
vk14::pending_textures pending;
vk14::resources resources;
vk14::imgui_backend imgui;
vk14::scene_state scene;
// the textures of the files; made after the objects above and gone before them
std::unique_ptr<texture_bank> textures;
// [0]: the main window, then the windows of the extra viewports
std::vector<std::unique_ptr<vk14::swapchain>> windows;
std::vector<frame> frames;
// one semaphore counts the frames done by the gpu; the binary ones are only between the swapchains and the queue
VkSemaphore timeline{VK_NULL_HANDLE};
std::uint64_t timelinevalue{0};
// frames recorded since the start
std::uint64_t framecount{0};
// the windows with an image acquired in the frame being made
std::vector<vk14::swapchain *> acquired;
bool devicelost{false};
// the frame being recorded: its index among the frames in flight, its commands
std::uint32_t slot{0};
VkCommandBuffer commands{VK_NULL_HANDLE};
~context()
{
destroy();
}
bool create_frames(std::uint32_t const Count, std::string &Reason);
// the parts of the renderer made once the device is there
bool create_core(std::uint32_t const Frames, std::string &Reason);
// the value of the timeline the gpu reached, 0 when it can't be read
std::uint64_t completed() const;
// waits until the gpu reached the value of the timeline; false: the device is lost
bool wait(std::uint64_t const Value) const;
void destroy();
};
// the state of the draws of the walk being recorded into a pass of the scene
struct vulkan14_renderer::draw_state
{
VkCommandBuffer commands{VK_NULL_HANDLE};
vk14::target_formats const *targets{nullptr};
VkExtent2D extent{0, 0};
vk14::scene_state::pass_set_kind set{vk14::scene_state::set_plain};
bool rendering{false}; // inside a rendering of a pass of the scene
bool shadows{false}; // a pass of the sun shadows: the back faces are drawn
// the offsets of the pass set in the rings of the frame
std::uint32_t scene_offset{0};
std::uint32_t light_offset{0};
std::uint32_t cluster_offset{0};
bool set_dirty{true};
// the program and the textures for the next draw (walk_material(), walk_program(), walk_texture())
vk14::scene_program *program{nullptr};
std::array<texture_handle, gl::MAX_TEXTURES> textures{};
bool textures_dirty{true};
// the block of the draw last pushed, for the chunks drawn together
std::uint32_t block{0};
// what is bound
VkPipeline pipeline{VK_NULL_HANDLE};
VkBuffer vertices{VK_NULL_HANDLE};
VkBuffer userdata{VK_NULL_HANDLE};
VkBuffer indices{VK_NULL_HANDLE};
VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_MAX_ENUM};
VkCullModeFlags cull{VK_CULL_MODE_FLAG_BITS_MAX_ENUM};
int depthtest{-1};
int depthwrite{-1};
std::uint32_t pushed_draw{~0u};
// the state of the walk (setup_drawing() and the glare of the opengl renderer)
bool blending{false};
bool cullface{true};
bool glare{false};
bool depthmask_off{false}; // the editor overlay: no depth written whatever the blending
// the occlusion queries of the glare of the lamps in the frame: the submodels, by the index of their query
std::vector<TSubModel const *> queries;
std::uint32_t query{~0u}; // the query under way
// the draws made in the pass, for the debug panel
std::uint32_t draws{0};
};

View File

@@ -37,9 +37,10 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
}
if (Compare)
{
// reverse z: the fragment is lit where it's nearer the light than the map, its depth greater
// as GL_LEQUAL of the opengl renderer: 1 (in the shadow) where the reference isn't nearer the light than the map (reverse
// z: its depth isn't greater), the shaders take the result as the amount of the shadow
info.compareEnable = VK_TRUE;
info.compareOp = VK_COMPARE_OP_GREATER_OR_EQUAL;
info.compareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
}
if (!VK14_VERIFY(vkCreateSampler(handle, &info, nullptr, &Sampler)))
@@ -53,7 +54,7 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
};
return make(repeat_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, true, Anisotropy, false, "repeat aniso") && make(clamp_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, Anisotropy, false, "clamp aniso") &&
make(clamp_linear, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, 1.f, false, "clamp linear") && make(clamp_nearest, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, false, "clamp nearest") &&
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, false, 1.f, true, "shadow");
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, true, "shadow");
}
void samplers::destroy(VkDevice Device)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,280 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14geometry.h"
#include "rendering/vulkan14/vulkan14texture.h"
namespace vk14
{
namespace
{
// what opengl reads for the user data of a vertex without any (the attribute isn't given)
gfx::vertex_userdata const no_userdata{glm::vec4{0.f, 0.f, 0.f, 1.f}};
} // namespace
VkPrimitiveTopology topology_of(unsigned int const Type)
{
switch (Type)
{
case GL_POINTS:
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
case GL_LINES:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
case GL_LINE_STRIP:
case GL_LINE_LOOP:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case GL_TRIANGLE_STRIP:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
case GL_TRIANGLE_FAN:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
case GL_TRIANGLES:
default:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
}
}
vulkan14_geometrybank::~vulkan14_geometrybank()
{
release_();
}
void vulkan14_geometrybank::create_(gfx::geometry_handle const &Geometry)
{
// a new chunk: the buffers are made anew with it
m_layoutdirty = true;
}
void vulkan14_geometrybank::replace_(gfx::geometry_handle const &Geometry)
{
if (m_layoutdirty)
{
return;
}
auto const index{Geometry.chunk - 1};
if (index >= m_places.size())
{
m_layoutdirty = true;
return;
}
auto const &chunk{gfx::geometry_bank::chunk(Geometry)};
auto &place{m_places[index]};
auto const loop{chunk.type == GL_LINE_LOOP};
auto const vertexcount{static_cast<std::uint32_t>(chunk.vertices.size()) + ((loop && chunk.indices.empty() && (false == chunk.vertices.empty())) ? 1u : 0u)};
auto const indexcount{static_cast<std::uint32_t>(chunk.indices.size()) + ((loop && (false == chunk.indices.empty())) ? 1u : 0u)};
if ((vertexcount != place.vertexcount) || (indexcount != place.indexcount))
{
// it doesn't fit in its place any more
m_layoutdirty = true;
return;
}
place.dirty = true;
m_chunksdirty = true;
}
void vulkan14_geometrybank::release_()
{
auto *deletions{m_resources.deletions};
if ((deletions != nullptr) && (m_resources.uploads != nullptr) && (m_vertices || m_userdata || m_indices))
{
deletions->push(m_resources.uploads->current_value(), [vertices = std::move(m_vertices), userdata = std::move(m_userdata), indices = std::move(m_indices)]() {});
}
m_vertices.reset();
m_userdata.reset();
m_indices.reset();
m_places.clear();
m_bytes = 0;
m_layoutdirty = true;
m_chunksdirty = false;
}
void vulkan14_geometrybank::gpu_vertices(geometry_chunk const &Chunk, std::vector<gfx::basic_vertex> &Vertices, std::vector<gfx::vertex_userdata> &Userdata) const
{
Vertices.insert(Vertices.end(), Chunk.vertices.begin(), Chunk.vertices.end());
if (Chunk.userdata.size() >= Chunk.vertices.size())
{
Userdata.insert(Userdata.end(), Chunk.userdata.begin(), Chunk.userdata.begin() + Chunk.vertices.size());
}
else
{
Userdata.insert(Userdata.end(), Chunk.userdata.begin(), Chunk.userdata.end());
Userdata.resize(Vertices.size(), no_userdata);
}
if ((Chunk.type == GL_LINE_LOOP) && Chunk.indices.empty() && (false == Chunk.vertices.empty()))
{
// back to the first vertex
Vertices.push_back(Chunk.vertices.front());
Userdata.push_back(Chunk.userdata.empty() ? no_userdata : Chunk.userdata.front());
}
}
bool vulkan14_geometrybank::build()
{
release_();
std::vector<gfx::basic_vertex> vertices;
std::vector<gfx::vertex_userdata> userdata;
std::vector<gfx::basic_index> indices;
m_places.resize(m_chunks.size());
for (std::size_t i = 0; i < m_chunks.size(); ++i)
{
auto const &chunk{m_chunks[i]};
auto &place{m_places[i]};
place.vertexfirst = static_cast<std::uint32_t>(vertices.size());
place.indexfirst = static_cast<std::uint32_t>(indices.size());
gpu_vertices(chunk, vertices, userdata);
place.vertexcount = static_cast<std::uint32_t>(vertices.size()) - place.vertexfirst;
indices.insert(indices.end(), chunk.indices.begin(), chunk.indices.end());
if ((chunk.type == GL_LINE_LOOP) && (false == chunk.indices.empty()))
{
indices.push_back(chunk.indices.front());
}
place.indexcount = static_cast<std::uint32_t>(indices.size()) - place.indexfirst;
place.dirty = false;
}
m_layoutdirty = false;
m_chunksdirty = false;
if (vertices.empty())
{
return false;
}
auto &memory{*m_resources.memory};
auto &uploads{*m_resources.uploads};
auto const make = [&](std::shared_ptr<buffer> &Buffer, void const *Data, VkDeviceSize const Size, VkBufferUsageFlags const Usage, char const *Name) {
Buffer = std::make_shared<buffer>();
if ((false == Buffer->create(memory, Size, Usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, memory_kind::device, Name)) || (false == uploads.upload_buffer(Buffer, Data, Size)))
{
Buffer.reset();
return false;
}
m_bytes += Size;
return true;
};
auto ok{make(m_vertices, vertices.data(), vertices.size() * sizeof(gfx::basic_vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, "geometry vertices") &&
make(m_userdata, userdata.data(), userdata.size() * sizeof(gfx::vertex_userdata), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, "geometry user data")};
if (ok && (false == indices.empty()))
{
ok = make(m_indices, indices.data(), indices.size() * sizeof(gfx::basic_index), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, "geometry indices");
}
if (false == ok)
{
log_error("no memory for the geometry of a bank of " + std::to_string(vertices.size()) + " vertices");
release_();
// tried again when it's drawn the next time
m_layoutdirty = true;
return false;
}
return true;
}
void vulkan14_geometrybank::update()
{
m_chunksdirty = false;
if ((false == bool(m_vertices)) || (false == bool(m_userdata)))
{
return;
}
std::vector<gfx::basic_vertex> vertices;
std::vector<gfx::vertex_userdata> userdata;
std::vector<gfx::basic_index> indices;
std::vector<std::pair<VkDeviceSize, VkDeviceSize>> vertexparts, userdataparts, indexparts;
for (std::size_t i = 0; i < m_places.size(); ++i)
{
auto &place{m_places[i]};
if (false == place.dirty)
{
continue;
}
place.dirty = false;
auto const &chunk{m_chunks[i]};
gpu_vertices(chunk, vertices, userdata);
vertexparts.emplace_back(place.vertexfirst * sizeof(gfx::basic_vertex), place.vertexcount * sizeof(gfx::basic_vertex));
userdataparts.emplace_back(place.vertexfirst * sizeof(gfx::vertex_userdata), place.vertexcount * sizeof(gfx::vertex_userdata));
if ((place.indexcount > 0) && m_indices)
{
indices.insert(indices.end(), chunk.indices.begin(), chunk.indices.end());
if (chunk.type == GL_LINE_LOOP)
{
indices.push_back(chunk.indices.front());
}
indexparts.emplace_back(place.indexfirst * sizeof(gfx::basic_index), place.indexcount * sizeof(gfx::basic_index));
}
}
auto &uploads{*m_resources.uploads};
if (false == vertices.empty())
{
uploads.upload_buffer(m_vertices, vertices.data(), vertices.size() * sizeof(gfx::basic_vertex), vertexparts);
uploads.upload_buffer(m_userdata, userdata.data(), userdata.size() * sizeof(gfx::vertex_userdata), userdataparts);
}
if (false == indices.empty())
{
uploads.upload_buffer(m_indices, indices.data(), indices.size() * sizeof(gfx::basic_index), indexparts);
}
}
bool vulkan14_geometrybank::source(gfx::geometry_handle const &Geometry, chunk_source &Source)
{
if ((Geometry.chunk == 0) || (Geometry.chunk > m_chunks.size()))
{
return false;
}
if (m_layoutdirty)
{
build();
}
else if (m_chunksdirty)
{
update();
}
auto const index{Geometry.chunk - 1};
if ((false == bool(m_vertices)) || (index >= m_places.size()))
{
return false;
}
auto const &place{m_places[index]};
Source.vertices = m_vertices->handle();
Source.userdata = m_userdata->handle();
Source.topology = topology_of(m_chunks[index].type);
if (place.indexcount > 0)
{
Source.indices = m_indices->handle();
Source.count = place.indexcount;
Source.first = place.indexfirst;
Source.basevertex = static_cast<std::int32_t>(place.vertexfirst);
}
else
{
Source.indices = VK_NULL_HANDLE;
Source.count = place.vertexcount;
Source.first = place.vertexfirst;
Source.basevertex = 0;
}
return Source.count > 0;
}
bool vulkan14_geometrybank::indirect_source_(gfx::geometry_handle const &Geometry, gfx::indirect_source &Source)
{
m_last = chunk_source{};
if (false == source(Geometry, m_last))
{
return false;
}
Source.count = m_last.count;
Source.first = m_last.first;
Source.basevertex = m_last.basevertex;
Source.indexed = (m_last.indices != VK_NULL_HANDLE);
Source.type = m_chunks[Geometry.chunk - 1].type;
return true;
}
} // namespace vk14

View File

@@ -0,0 +1,99 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the geometry banks of the vulkan renderer (stage D1 of the plan): the chunks of a bank in three device buffers of the bank
// (vertices, their user data, 32 bit indices), made the first time the bank is drawn after its chunks changed. a chunk replaced
// with the same size is copied in place, otherwise the buffers are made anew and the old ones go once the gpu is done with them.
// the data stays in memory as well (gfx::geometry_bank), the simulation reads it back. the primitives are the values of opengl
// the program gives them with; a line loop is drawn as a strip which goes back to its first vertex
#pragma once
#include "rendering/geometrybank.h"
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include <memory>
namespace vk14
{
struct resources;
// where the data of a chunk is in the buffers of its bank
struct chunk_source
{
VkBuffer vertices{VK_NULL_HANDLE};
VkBuffer userdata{VK_NULL_HANDLE};
VkBuffer indices{VK_NULL_HANDLE}; // null for a chunk without indices
std::uint32_t count{0}; // vertices, or indices
std::uint32_t first{0}; // first index, or vertex
std::int32_t basevertex{0};
VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
};
// the vulkan primitive of a primitive of opengl (GL_TRIANGLES ...)
VkPrimitiveTopology topology_of(unsigned int Type);
class vulkan14_geometrybank : public gfx::geometry_bank
{
public:
explicit vulkan14_geometrybank(resources const &Resources) : m_resources(Resources) {}
~vulkan14_geometrybank() override;
// the chunk with its data on the gpu: uploaded now when it changed since the last time. false: nothing to draw.
// gfx::geometrybank_manager::indirect_source() calls it too (which marks the bank as used), and keeps its answer in last()
bool source(gfx::geometry_handle const &Geometry, chunk_source &Source);
chunk_source const &last() const
{
return m_last;
}
// the bytes on the gpu, for the debug panel
VkDeviceSize gpu_bytes() const
{
return m_bytes;
}
private:
struct chunk_place
{
std::uint32_t vertexfirst{0};
std::uint32_t vertexcount{0}; // on the gpu: one more than the chunk for a line loop
std::uint32_t indexfirst{0};
std::uint32_t indexcount{0};
bool dirty{true};
};
void create_(gfx::geometry_handle const &Geometry) override;
void replace_(gfx::geometry_handle const &Geometry) override;
std::size_t draw_(gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams) override
{
return 0;
}
bool indirect_source_(gfx::geometry_handle const &Geometry, gfx::indirect_source &Source) override;
void release_() override;
// the buffers made anew for all the chunks
bool build();
// the chunks changed in place copied to the buffers
void update();
// the vertices of a chunk as they go to the gpu
void gpu_vertices(geometry_chunk const &Chunk, std::vector<gfx::basic_vertex> &Vertices, std::vector<gfx::vertex_userdata> &Userdata) const;
resources const &m_resources;
std::shared_ptr<buffer> m_vertices;
std::shared_ptr<buffer> m_userdata;
std::shared_ptr<buffer> m_indices;
std::vector<chunk_place> m_places;
bool m_layoutdirty{true}; // the buffers have to be made anew
bool m_chunksdirty{false}; // some chunks have to be copied
VkDeviceSize m_bytes{0};
chunk_source m_last;
};
} // namespace vk14

View File

@@ -177,6 +177,14 @@ void buffer::flush(VkDeviceSize Offset, VkDeviceSize Size) const
}
}
void buffer::invalidate(VkDeviceSize Offset, VkDeviceSize Size) const
{
if ((m_allocation != VK_NULL_HANDLE) && (Size > 0))
{
vmaInvalidateAllocation(m_allocator, m_allocation, Offset, Size);
}
}
// image
image::~image()
@@ -334,12 +342,12 @@ void deletion_queue::flush()
// frame ring
bool frame_ring::create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name)
bool frame_ring::create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name, VkDeviceSize Tail)
{
m_sizeperframe = SizePerFrame;
m_base = 0;
m_used = 0;
return m_buffer.create(Allocator, SizePerFrame * Frames, Usage, memory_kind::upload, Name) && (m_buffer.mapped() != nullptr);
return m_buffer.create(Allocator, SizePerFrame * Frames + Tail, Usage, memory_kind::upload, Name) && (m_buffer.mapped() != nullptr);
}
void frame_ring::destroy()

View File

@@ -91,6 +91,8 @@ class buffer
}
// makes the writes of the cpu seen by the gpu where the memory isn't coherent
void flush(VkDeviceSize Offset, VkDeviceSize Size) const;
// makes the writes of the gpu seen by the cpu where the memory isn't coherent (readback memory may be cached)
void invalidate(VkDeviceSize Offset, VkDeviceSize Size) const;
explicit operator bool() const
{
return m_buffer != VK_NULL_HANDLE;
@@ -214,7 +216,8 @@ struct ring_slice
class frame_ring
{
public:
bool create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name);
// Tail: bytes past the part of the last frame, so a descriptor of a fixed range can be given an offset anywhere in a part
bool create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name, VkDeviceSize Tail = 0);
void destroy();
// the part of the frame Frame is used from its start again
void begin_frame(std::uint32_t Frame);
@@ -226,10 +229,20 @@ class frame_ring
{
return m_used;
}
// the offset in the buffer the next allocation aligned to Alignment would get
VkDeviceSize next_offset(VkDeviceSize Alignment) const
{
auto const alignment{std::max<VkDeviceSize>(Alignment, 4)};
return m_base + (m_used + alignment - 1) / alignment * alignment;
}
VkDeviceSize capacity() const
{
return m_sizeperframe;
}
VkBuffer handle() const
{
return m_buffer.handle();
}
private:
buffer m_buffer;

View File

@@ -8,18 +8,7 @@ http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14renderer.h"
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "rendering/nullrenderer.h"
#include "application/application.h"
#include "simulation/simulation.h"
@@ -37,8 +26,11 @@ namespace
// background of the opengl renderer (srgb). before that the windows are black, as with the opengl renderer
glm::vec3 const clear_colour{51.f / 255.f, 102.f / 255.f, 85.f / 255.f};
// the ring each frame in flight streams its data through (the user interface for now)
VkDeviceSize const ring_size{8 * 1024 * 1024};
// the ring each frame in flight streams its data through: the user interface, the blocks of the scene and of the light clusters,
// the light buffers of the passes, the vertices of the particles and the sky; the descriptors read the light buffers with the range
// of the whole gl::light_data, which the tail past the last frame leaves room for
VkDeviceSize const ring_size{16 * 1024 * 1024};
VkDeviceSize const ring_tail{(sizeof(gl::light_data) + 65535) / 65536 * 65536};
float srgb_to_linear(float const Value)
{
@@ -47,192 +39,157 @@ float srgb_to_linear(float const Value)
} // namespace
// the objects of the vulkan api. destroyed in the reverse order: the swapchains and the frames, then the device, then the instance
struct vulkan14_renderer::context
bool vulkan14_renderer::context::create_frames(std::uint32_t const Count, std::string &Reason)
{
struct frame
{
VkCommandPool pool{VK_NULL_HANDLE};
// the uploads of the frame go to the queue first, then the commands of the frame
VkCommandBuffer uploads{VK_NULL_HANDLE};
VkCommandBuffer commands{VK_NULL_HANDLE};
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
std::uint64_t done{0};
// the number of the frame, for the report of a lost device
std::uint64_t number{0};
};
auto const handle{device.handle()};
vk14::instance instance;
vk14::device device;
vk14::allocator memory;
vk14::deletion_queue deletions;
vk14::uploader uploads;
vk14::image_table images;
vk14::samplers sampling;
vk14::frame_ring ring;
vk14::shader_library shaders;
vk14::pipeline_library pipelines;
vk14::pass_list passes;
vk14::pending_textures pending;
vk14::resources resources;
vk14::imgui_backend imgui;
// the textures of the files; made after the objects above and gone before them
std::unique_ptr<texture_bank> textures;
// [0]: the main window, then the windows of the extra viewports
std::vector<std::unique_ptr<vk14::swapchain>> windows;
std::vector<frame> frames;
// one semaphore counts the frames done by the gpu; the binary ones are only between the swapchains and the queue
VkSemaphore timeline{VK_NULL_HANDLE};
std::uint64_t timelinevalue{0};
// frames recorded since the start
std::uint64_t framecount{0};
// the windows with an image acquired in the frame being made
std::vector<vk14::swapchain *> acquired;
bool devicelost{false};
~context()
VkSemaphoreTypeCreateInfo timelineinfo{};
timelineinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO;
timelineinfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
timelineinfo.initialValue = 0;
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreinfo.pNext = &timelineinfo;
if (!VK14_VERIFY(vkCreateSemaphore(handle, &semaphoreinfo, nullptr, &timeline)))
{
destroy();
Reason = "no timeline semaphore";
return false;
}
instance.set_object_name(handle, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(timeline), "frame timeline");
bool create_frames(std::uint32_t const Count, std::string &Reason)
frames.resize(Count);
for (std::uint32_t i = 0; i < Count; ++i)
{
auto const handle{device.handle()};
VkSemaphoreTypeCreateInfo timelineinfo{};
timelineinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO;
timelineinfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
timelineinfo.initialValue = 0;
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreinfo.pNext = &timelineinfo;
if (!VK14_VERIFY(vkCreateSemaphore(handle, &semaphoreinfo, nullptr, &timeline)))
VkCommandPoolCreateInfo poolinfo{};
poolinfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolinfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
poolinfo.queueFamilyIndex = device.families().graphics;
if (!VK14_VERIFY(vkCreateCommandPool(handle, &poolinfo, nullptr, &frames[i].pool)))
{
Reason = "no timeline semaphore";
Reason = "no command pool";
return false;
}
instance.set_object_name(handle, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(timeline), "frame timeline");
frames.resize(Count);
for (std::uint32_t i = 0; i < Count; ++i)
VkCommandBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
VkCommandBufferAllocateInfo bufferinfo{};
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
bufferinfo.commandPool = frames[i].pool;
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
bufferinfo.commandBufferCount = 2;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, buffers)))
{
VkCommandPoolCreateInfo poolinfo{};
poolinfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolinfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
poolinfo.queueFamilyIndex = device.families().graphics;
if (!VK14_VERIFY(vkCreateCommandPool(handle, &poolinfo, nullptr, &frames[i].pool)))
{
Reason = "no command pool";
return false;
}
VkCommandBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
VkCommandBufferAllocateInfo bufferinfo{};
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
bufferinfo.commandPool = frames[i].pool;
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
bufferinfo.commandBufferCount = 2;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, buffers)))
{
Reason = "no command buffer";
return false;
}
frames[i].uploads = buffers[0];
frames[i].commands = buffers[1];
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].uploads), "uploads " + std::to_string(i));
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
Reason = "no command buffer";
return false;
}
frames[i].uploads = buffers[0];
frames[i].commands = buffers[1];
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].uploads), "uploads " + std::to_string(i));
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
}
return true;
}
// the parts of the renderer made once the device is there
bool vulkan14_renderer::context::create_core(std::uint32_t const Frames, std::string &Reason)
{
if (false == memory.create(instance, device, Reason))
{
return false;
}
uploads.init(memory, deletions, device.physical());
if (false == sampling.create(device, static_cast<float>(Global.AnisotropicFiltering), Reason))
{
return false;
}
auto const &limits{device.caps().limits};
(void)limits;
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "frame ring", ring_tail))
{
Reason = "no ring for the data of the frames";
return false;
}
if ((false == shaders.init(vk14::binding_preamble(), Reason)) || (false == pipelines.init(device, Reason)) || (false == passes.create(device, Frames, Reason)))
{
return false;
}
resources.gpu = &device;
resources.memory = &memory;
resources.uploads = &uploads;
resources.deletions = &deletions;
resources.sampling = &sampling;
resources.images = &images;
resources.thread = std::this_thread::get_id();
resources.pending = &pending;
textures = std::make_unique<texture_bank>([this]() -> texture_source * { return new vk14::vulkan14_texture(resources); });
return imgui.create(resources, shaders, pipelines, ring, Reason);
}
// the value of the timeline the gpu reached, 0 when it can't be read
std::uint64_t vulkan14_renderer::context::completed() const
{
std::uint64_t value{0};
if ((timeline == VK_NULL_HANDLE) || (vkGetSemaphoreCounterValue(device.handle(), timeline, &value) != VK_SUCCESS))
{
return 0;
}
return value;
}
bool vulkan14_renderer::context::wait(std::uint64_t const Value) const
{
if ((Value == 0) || (completed() >= Value))
{
return true;
}
VkSemaphoreWaitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO;
wait.semaphoreCount = 1;
wait.pSemaphores = &timeline;
wait.pValues = &Value;
return VK14_VERIFY(vkWaitSemaphores(device.handle(), &wait, UINT64_MAX));
}
// the parts of the renderer made once the device is there
bool create_core(std::uint32_t const Frames, std::string &Reason)
void vulkan14_renderer::context::destroy()
{
auto const handle{device.handle()};
if (handle != VK_NULL_HANDLE)
{
if (false == memory.create(instance, device, Reason))
{
return false;
}
uploads.init(memory, deletions, device.physical());
if (false == sampling.create(device, static_cast<float>(Global.AnisotropicFiltering), Reason))
{
return false;
}
auto const &limits{device.caps().limits};
(void)limits;
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "frame ring"))
{
Reason = "no ring for the data of the frames";
return false;
}
if ((false == shaders.init(vk14::binding_preamble(), Reason)) || (false == pipelines.init(device, Reason)) || (false == passes.create(device, Frames, Reason)))
{
return false;
}
resources.gpu = &device;
resources.memory = &memory;
resources.uploads = &uploads;
resources.deletions = &deletions;
resources.sampling = &sampling;
resources.images = &images;
resources.thread = std::this_thread::get_id();
resources.pending = &pending;
textures = std::make_unique<texture_bank>([this]() -> texture_source * { return new vk14::vulkan14_texture(resources); });
return imgui.create(resources, shaders, pipelines, ring, Reason);
// the end of the renderer, not a frame: everything on the gpu has to be done
vkDeviceWaitIdle(handle);
}
// the value of the timeline the gpu reached, 0 when it can't be read
std::uint64_t completed() const
acquired.clear();
windows.clear();
imgui.destroy();
// the textures give their images to the deletion queue, which goes now that the gpu is idle
textures.reset();
uploads.end_frame();
uploads = vk14::uploader{};
images.clear();
deletions.flush();
pipelines.shutdown();
shaders.shutdown();
passes.destroy();
ring.destroy();
if (handle != VK_NULL_HANDLE)
{
std::uint64_t value{0};
if ((timeline == VK_NULL_HANDLE) || (vkGetSemaphoreCounterValue(device.handle(), timeline, &value) != VK_SUCCESS))
sampling.destroy(handle);
for (auto &frame : frames)
{
return 0;
}
return value;
}
void destroy()
{
auto const handle{device.handle()};
if (handle != VK_NULL_HANDLE)
{
// the end of the renderer, not a frame: everything on the gpu has to be done
vkDeviceWaitIdle(handle);
}
acquired.clear();
windows.clear();
imgui.destroy();
// the textures give their images to the deletion queue, which goes now that the gpu is idle
textures.reset();
uploads.end_frame();
uploads = vk14::uploader{};
images.clear();
deletions.flush();
pipelines.shutdown();
shaders.shutdown();
passes.destroy();
ring.destroy();
if (handle != VK_NULL_HANDLE)
{
sampling.destroy(handle);
for (auto &frame : frames)
if (frame.pool != VK_NULL_HANDLE)
{
if (frame.pool != VK_NULL_HANDLE)
{
vkDestroyCommandPool(handle, frame.pool, nullptr);
}
}
if (timeline != VK_NULL_HANDLE)
{
vkDestroySemaphore(handle, timeline, nullptr);
vkDestroyCommandPool(handle, frame.pool, nullptr);
}
}
memory.destroy();
frames.clear();
timeline = VK_NULL_HANDLE;
device.destroy();
instance.destroy();
if (timeline != VK_NULL_HANDLE)
{
vkDestroySemaphore(handle, timeline, nullptr);
}
}
};
memory.destroy();
frames.clear();
timeline = VK_NULL_HANDLE;
device.destroy();
instance.destroy();
}
namespace
{
@@ -320,10 +277,20 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
return false;
}
if (false == init_scene(reason))
{
vk14::log_error("the renderer can't start: " + reason);
destroy_scene();
m_context.reset();
return false;
}
init_frame_data();
vk14::log_info(std::to_string(framesinflight) + " frames in flight");
// until the stages of the plan bring them, these parts of the simulator aren't there with vulkan
vk14::log_info("not available with vulkan yet: the scenery and the vehicles (D1-D7), picking, mouse position in the scene and screenshots (D6), the map panel, "
"python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
vk14::log_info("not available with vulkan yet: the atmosphere, the sky ambient light, automatic exposure and bloom, stars (P1), the weather fog and clouds (P2), "
"ambient occlusion and contact shadows (P3), shadows of lamps and headlights (P4), the culling on the gpu (P5), the heightmap terrain, grass and the particles "
"of the gpu (P6), the map panel, python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
WriteLog("Gfx Renderer: setup complete");
update_info();
return true;
@@ -331,38 +298,64 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
bool vulkan14_renderer::AddViewport(global_settings::extraviewport_config const &conf)
{
if (!m_context)
if (!m_context || m_viewports.empty())
{
return false;
}
viewport_config *viewport{nullptr};
if (conf.monitor == "MAIN")
{
// the projection of the main window is changed only, which the drawing of the scenery takes care of (D6)
vk14::log_info("extra viewport on the main window: drawn from the stage D6 of the plan");
return true;
// the main window drawn with the projection of the viewport
viewport = &m_viewports.front();
}
// a window of its own, without an opengl context
auto *window{Application.window(-1, true, conf.width, conf.height, Application.find_monitor(conf.monitor), true, false)};
if (window == nullptr)
else
{
vk14::log_error("the window of the extra viewport on " + conf.monitor + " couldn't be made");
return false;
// a window of its own, without an opengl context, with a swapchain of its own
auto *window{Application.window(-1, true, conf.width, conf.height, Application.find_monitor(conf.monitor), true, false)};
if (window == nullptr)
{
vk14::log_error("the window of the extra viewport on " + conf.monitor + " couldn't be made");
return false;
}
auto &ctx{*m_context};
auto swapchain{std::make_unique<vk14::swapchain>()};
std::string reason;
if (false == swapchain->create(ctx.instance, ctx.device, window, "viewport " + conf.monitor, static_cast<std::uint32_t>(ctx.frames.size()), reason))
{
vk14::log_error(reason);
return false;
}
ctx.windows.emplace_back(std::move(swapchain));
m_viewports.emplace_back();
viewport = &m_viewports.back();
viewport->window = ctx.windows.size() - 1;
}
auto &ctx{*m_context};
auto viewport{std::make_unique<vk14::swapchain>()};
std::string reason;
if (false == viewport->create(ctx.instance, ctx.device, window, "viewport " + conf.monitor, static_cast<std::uint32_t>(ctx.frames.size()), reason))
{
vk14::log_error(reason);
return false;
}
ctx.windows.emplace_back(std::move(viewport));
viewport->width = conf.width;
viewport->height = conf.height;
viewport->projection = conf.projection;
viewport->proj_type = viewport_config::custom;
viewport->draw_range = conf.draw_range;
vk14::log_info("viewport " + conf.monitor + ": " + std::to_string(conf.width) + "x" + std::to_string(conf.height));
return true;
}
void vulkan14_renderer::Shutdown()
{
if (m_context && (m_context->device.handle() != VK_NULL_HANDLE))
{
// the end of the renderer, not a frame: everything on the gpu has to be done
vkDeviceWaitIdle(m_context->device.handle());
}
// the geometry on the gpu goes with the context; the data of the banks stays for the simulation
for (auto *bank : m_banks)
{
if (bank != nullptr)
{
bank->release();
}
}
destroy_scene();
m_draw.reset();
m_context.reset();
}
@@ -387,184 +380,6 @@ void vulkan14_renderer::report_device_lost(char const *Where)
crashreport_add_info("vulkan_device_lost", report);
}
bool vulkan14_renderer::Render()
{
if (!m_context || m_context->devicelost)
{
return false;
}
auto &ctx{*m_context};
auto const device{ctx.device.handle()};
auto const slot{static_cast<std::uint32_t>(ctx.framecount % ctx.frames.size())};
auto &frame{ctx.frames[slot]};
Timer::subsystem.gfx_total.start();
// the gpu has to be done with the frame which used the slot before
if (frame.done > 0)
{
VkSemaphoreWaitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO;
wait.semaphoreCount = 1;
wait.pSemaphores = &ctx.timeline;
wait.pValues = &frame.done;
if (!VK14_VERIFY(vkWaitSemaphores(device, &wait, UINT64_MAX)))
{
report_device_lost("waiting for a frame");
return false;
}
ctx.passes.collect(slot);
}
// what the frames done used last goes
ctx.deletions.collect(std::max(ctx.completed(), frame.done));
VK14_VERIFY(vkResetCommandPool(device, frame.pool, 0));
ctx.ring.begin_frame(slot);
VkCommandBufferBeginInfo begin{};
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK14_VERIFY(vkBeginCommandBuffer(frame.uploads, &begin));
VK14_VERIFY(vkBeginCommandBuffer(frame.commands, &begin));
auto const value{ctx.timelinevalue + 1};
ctx.uploads.begin_frame(frame.uploads, value);
// the content other threads gave the textures (python)
{
std::vector<vk14::vulkan14_texture *> pending;
{
std::lock_guard<std::mutex> lock(ctx.pending.mutex);
pending.swap(ctx.pending.textures);
}
for (auto *texture : pending)
{
texture->apply_pending();
}
}
ctx.acquired.clear();
for (auto &window : ctx.windows)
{
switch (window->acquire(slot))
{
case vk14::swapchain::acquire_result::ready:
ctx.acquired.push_back(window.get());
break;
case vk14::swapchain::acquire_result::skipped:
break;
default:
report_device_lost("acquiring an image");
return false;
}
}
// the user interface: imgui makes its frame, the images it asks for are uploaded in this frame
Timer::subsystem.gfx_gui.start();
Application.render_ui();
Timer::subsystem.gfx_gui.stop();
frame.number = ctx.framecount;
ctx.passes.begin_frame(frame.commands, slot, ctx.framecount);
auto const background{simulation::is_ready ? clear_colour : glm::vec3{0.f}};
bool uidrawn{false};
for (auto *window : ctx.acquired)
{
auto const colour{window->srgb() ? glm::vec3{srgb_to_linear(background.r), srgb_to_linear(background.g), srgb_to_linear(background.b)} : background};
if (window != ctx.windows.front().get())
{
// the extra viewports: their colour only, until the scenery is drawn into them (D6)
window->record_clear(frame.commands, glm::vec4{colour, 1.f});
continue;
}
// the main window: cleared, the user interface over it, shown
ctx.passes.begin_pass("ui", {window->use(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT)});
VkRenderingAttachmentInfo attachment{};
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
attachment.imageView = window->view();
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.clearValue.color = {{colour.r, colour.g, colour.b, 1.f}};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, window->extent()};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
vkCmdBeginRendering(frame.commands, &rendering);
ctx.imgui.record(frame.commands, window->extent(), window->format(), window->srgb());
vkCmdEndRendering(frame.commands);
ctx.passes.end_pass();
vk14::barriers(frame.commands, {window->use(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE)});
uidrawn = true;
}
if (false == uidrawn)
{
// the main window is minimized: the frame of imgui has nowhere to go
ctx.imgui.skip();
}
ctx.passes.end_frame();
auto const uploaded{ctx.uploads.end_frame()};
VK14_VERIFY(vkEndCommandBuffer(frame.uploads));
VK14_VERIFY(vkEndCommandBuffer(frame.commands));
ctx.ring.flush();
std::vector<VkSemaphoreSubmitInfo> waits;
std::vector<VkSemaphoreSubmitInfo> signals;
for (auto *window : ctx.acquired)
{
VkSemaphoreSubmitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
wait.semaphore = window->acquire_semaphore(slot);
wait.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
waits.push_back(wait);
VkSemaphoreSubmitInfo signal{};
signal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
signal.semaphore = window->present_semaphore();
signal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
signals.push_back(signal);
}
VkSemaphoreSubmitInfo timelinesignal{};
timelinesignal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
timelinesignal.semaphore = ctx.timeline;
timelinesignal.value = value;
timelinesignal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
signals.push_back(timelinesignal);
// the uploads first, in the same submit: their barriers make them done before the frame samples the images
std::vector<VkCommandBufferSubmitInfo> commands;
if (uploaded)
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = frame.uploads;
commands.push_back(info);
}
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = frame.commands;
commands.push_back(info);
}
VkSubmitInfo2 submit{};
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
submit.waitSemaphoreInfoCount = static_cast<std::uint32_t>(waits.size());
submit.pWaitSemaphoreInfos = waits.data();
submit.commandBufferInfoCount = static_cast<std::uint32_t>(commands.size());
submit.pCommandBufferInfos = commands.data();
submit.signalSemaphoreInfoCount = static_cast<std::uint32_t>(signals.size());
submit.pSignalSemaphoreInfos = signals.data();
if (!VK14_VERIFY(vkQueueSubmit2(ctx.device.graphics_queue(), 1, &submit, VK_NULL_HANDLE)))
{
report_device_lost("submitting a frame");
return false;
}
frame.done = ctx.timelinevalue = value;
Timer::subsystem.gfx_total.stop();
return true;
}
void vulkan14_renderer::SwapBuffers()
{
if (!m_context)
@@ -616,143 +431,71 @@ float vulkan14_renderer::Framerate()
return m_framerate;
}
// geometry: kept in memory for the simulation, nothing on the gpu until D1
gfx::geometrybank_handle vulkan14_renderer::Create_Bank()
{
return m_geometry.register_bank(std::make_unique<null_geometrybank>());
}
gfx::geometry_handle vulkan14_renderer::Insert(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type)
{
// NOTE: we expect indexed geometry to come with calculated tangents
return m_geometry.create_chunk(Indices, Vertices, Userdata, Geometry, Type);
}
gfx::geometry_handle vulkan14_renderer::Insert(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type)
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
return m_geometry.create_chunk(Vertices, Userdata, Geometry, Type);
}
bool vulkan14_renderer::Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset)
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
return m_geometry.replace(Vertices, Userdata, Geometry, Offset);
}
bool vulkan14_renderer::Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type)
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
return m_geometry.append(Vertices, Userdata, Geometry);
}
gfx::index_array const &vulkan14_renderer::Indices(gfx::geometry_handle const &Geometry) const
{
return m_geometry.indices(Geometry);
}
gfx::vertex_array const &vulkan14_renderer::Vertices(gfx::geometry_handle const &Geometry) const
{
return m_geometry.vertices(Geometry);
}
gfx::userdata_array const &vulkan14_renderer::UserData(gfx::geometry_handle const &Geometry) const
{
return m_geometry.userdata(Geometry);
}
// materials and their shaders: none until D1
material_handle vulkan14_renderer::Fetch_Material(std::string const &Filename, bool const Loadnow)
{
return null_handle;
}
void vulkan14_renderer::Bind_Material(material_handle const Material, TSubModel const *sm, lighting_data const *lighting) {}
IMaterial const *vulkan14_renderer::Material(material_handle const Material) const
{
return IMaterial::null_material();
}
std::shared_ptr<gfx::shader_program> vulkan14_renderer::Fetch_Shader(std::string const &name)
{
return nullptr;
}
// textures: the files read and decoded as for the opengl renderer, made into images when they are first used (create(), e.g. by the
// launcher); the materials of the scene take them from D1
texture_handle vulkan14_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint)
{
if (!m_context || !m_context->textures)
{
return null_handle;
}
return m_context->textures->create(Filename, Loadnow, format_hint);
}
// the shaders take the textures by the pushed descriptors (D1), there's nothing to bind
void vulkan14_renderer::Bind_Texture(texture_handle const Texture) {}
void vulkan14_renderer::Bind_Texture(std::size_t const Unit, texture_handle const Texture) {}
ITexture &vulkan14_renderer::Texture(texture_handle const Texture)
{
if ((Texture == null_handle) || !m_context || !m_context->textures)
{
return *ITexture::null_texture();
}
return m_context->textures->mark_as_used(Texture);
}
ITexture const &vulkan14_renderer::Texture(texture_handle const Texture) const
{
if ((Texture == null_handle) || !m_context || !m_context->textures)
{
return *ITexture::null_texture();
}
return m_context->textures->texture(Texture);
}
// picking: D6
void vulkan14_renderer::Pick_Control_Callback(std::function<void(TSubModel const *, glm::vec2 const)> Callback) {}
void vulkan14_renderer::Pick_Node_Callback(std::function<void(scene::basic_node *)> Callback) {}
TSubModel const *vulkan14_renderer::Pick_Control() const
{
return nullptr;
}
scene::basic_node const *vulkan14_renderer::Pick_Node() const
{
return nullptr;
}
glm::dvec3 vulkan14_renderer::Mouse_Position() const
{
return glm::dvec3(0.0);
}
// maintenance
void vulkan14_renderer::Update(double const Deltatime)
{
if (m_context && m_context->textures && (true == Global.ResourceSweep) && (true == simulation::is_ready))
{
// textures nothing used for a while give their images back
m_context->textures->update();
}
Update_Pick_Control();
Update_Pick_Node();
update_screenshot();
m_updateaccumulator += Deltatime;
if (m_updateaccumulator < 1.0)
{
// too early for any work
return;
}
m_updateaccumulator = 0.0;
m_framerate = 1000.f / Timer::subsystem.mainloop_total.average();
// adjust draw ranges etc, based on recent performance (opengl46_renderer::Update())
if (Global.targetfps != 0.0f)
{
float fps_diff = Global.targetfps - m_framerate;
if (fps_diff > 0.0f)
Global.fDistanceFactor = std::max(0.5f, Global.fDistanceFactor - 0.05f);
else
Global.fDistanceFactor = std::min(Global.gfx_distance_factor_max, Global.fDistanceFactor + 0.05f);
}
// legacy framerate parameters
else if (Global.fFpsAverage != 0.f)
{
auto const fps_diff = m_framerate - Global.fFpsAverage;
if (fps_diff < -Global.fFpsDeviation)
{
Global.fDistanceFactor = std::min(std::max(1.0f, Global.fDistanceFactor - 0.05f), Global.gfx_distance_factor_max);
}
else if (fps_diff > Global.fFpsDeviation)
{
Global.fDistanceFactor = std::min(std::min(3.0f, Global.fDistanceFactor + 0.05f), Global.gfx_distance_factor_max);
}
}
if (Global.UpdateMaterials)
{
// update resources if there was environmental change
simulation_state simulationstate{Global.Weather, Global.Season};
std::swap(m_simulationstate, simulationstate);
if ((m_simulationstate.season != simulationstate.season) && (false == simulationstate.season.empty()))
{
m_materials.on_season_change();
}
if ((m_simulationstate.weather != simulationstate.weather) && (false == simulationstate.weather.empty()))
{
m_materials.on_weather_change();
}
}
if (m_context && m_context->textures && (true == Global.ResourceSweep) && (true == simulation::is_ready))
{
// garbage collection
m_geometry.update();
m_context->textures->update();
}
if ((Global.gfx_texture_releasedistance > 0.f) && (true == simulation::is_ready))
{
update_texture_release();
}
update_info();
}
@@ -781,15 +524,6 @@ void vulkan14_renderer::update_info()
}
}
void vulkan14_renderer::Update_Pick_Control() {}
void vulkan14_renderer::Update_Pick_Node() {}
glm::dvec3 vulkan14_renderer::Update_Mouse_Position()
{
return glm::dvec3(0.0);
}
bool vulkan14_renderer::Debug_Ui_State(std::optional<bool> State)
{
if (State)
@@ -813,8 +547,3 @@ imgui_renderer *vulkan14_renderer::GetImguiRenderer()
{
return (m_context ? &m_context->imgui : nullptr);
}
void vulkan14_renderer::MakeScreenshot()
{
vk14::log_warning("screenshots aren't made by the vulkan renderer yet (D6)");
}

View File

@@ -7,11 +7,12 @@ obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan. so far the core of the renderer (the track R of the plan,
// claude/vulkan14-renderer-plan.md): the gpu, the windows with their swapchains, the frames in flight, the memory and the
// uploads, the shaders and the pipelines, the pushed descriptors, the passes with their barriers and gpu times, the textures
// and the user interface (imgui) with the launcher. the scenery and the materials come in the later stages of the plan; until
// then those methods keep the data the simulation reads back and draw nothing.
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan (claude/vulkan14-renderer-plan.md): the gpu, the windows with their
// swapchains, the frames in flight, the memory and the uploads, the shaders and the pipelines, the pushed descriptors, the passes
// with their barriers and gpu times, the textures and the user interface (imgui) with the launcher (the track R of the plan), and
// the scene (the track D): the geometry, the materials, the passes of the colour of the scene with the sun shadows, the
// environment map and the post-processing, the picking, the screenshots, the extra viewports and the light clusters. the scene
// is walked by the walk shared with the opengl renderer (gfx::scene_walk), whose draws this renderer records.
//
// the vulkan headers stay in the files of rendering/vulkan14: the objects of the api live in vulkan14_renderer::context
@@ -19,8 +20,24 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/renderer.h"
#include "rendering/geometrybank.h"
#include "rendering/light.h"
#include "rendering/lightarray.h"
#include "rendering/common/scenewalk.h"
#include "rendering/common/locallights.h"
#include "rendering/particles.h"
#include "model/material.h"
#include "vehicle/Camera.h"
#include "gl/ubo.h"
class vulkan14_renderer : public gfx_renderer
class world_environment;
namespace vk14
{
struct scene_program;
struct target_formats;
enum class blend_mode;
} // namespace vk14
class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
{
public:
vulkan14_renderer();
@@ -38,16 +55,23 @@ class vulkan14_renderer : public gfx_renderer
gfx::geometry_handle Insert(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type) override;
gfx::geometry_handle Insert(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type) override;
bool Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset = 0) override;
bool Replace_Indexed(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry) override;
bool Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type) override;
gfx::index_array const &Indices(gfx::geometry_handle const &Geometry) const override;
gfx::vertex_array const &Vertices(gfx::geometry_handle const &Geometry) const override;
gfx::userdata_array const &UserData(gfx::geometry_handle const &Geometry) const override;
int Primitive(gfx::geometry_handle const &Geometry) const override;
// materials
material_handle Fetch_Material(std::string const &Filename, bool const Loadnow = true) override;
material_handle Matte_Material(material_handle const Material) override;
material_handle Tinted_Material(material_handle const Material, glm::vec3 const &Colour) override;
material_handle Blend_Material(material_handle const Material, std::array<material_handle, 3> const &Others) override;
bool Blendable_Material(material_handle const Material) override;
void Bind_Material(material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr) override;
IMaterial const *Material(material_handle const Material) const override;
// shaders
std::shared_ptr<gfx::shader_program> Fetch_Shader(std::string const &name) override;
std::string Shader_Name(std::shared_ptr<gfx::shader_program> const &Program) const override;
// textures
texture_handle Fetch_Texture(std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA) override;
void Bind_Texture(texture_handle const Texture) override;
@@ -60,6 +84,10 @@ class vulkan14_renderer : public gfx_renderer
TSubModel const *Pick_Control() const override;
scene::basic_node const *Pick_Node() const override;
glm::dvec3 Mouse_Position() const override;
bool Mouse_Hit() const override;
glm::mat4 Camera_View_Matrix() const override;
glm::mat4 Camera_Projection_Matrix() const override;
glm::dvec3 Camera_Position() const override;
// maintenance
void Update(double const Deltatime) override;
void Update_Pick_Control() override;
@@ -72,27 +100,203 @@ class vulkan14_renderer : public gfx_renderer
// ui
imgui_renderer *GetImguiRenderer() override;
void MakeScreenshot() override;
bool Render_Preview(TAnimModel *Instance, int const Size, int const Margin, bool const Shadows, std::vector<std::uint8_t> &Image) override;
static std::unique_ptr<gfx_renderer> create_func();
// how the application makes the windows of the renderer, and whether it can run on the machine
static gfx_backend_desc backend_desc();
private:
// the objects of the vulkan api (vulkan14renderer.cpp)
// the objects of the vulkan api (vulkan14context.h)
struct context;
void update_info();
// the device was lost: what was on the gpu goes to the log
void report_device_lost(char const *Where);
// a viewport the scene is drawn to: the main window, an extra viewport (a window of its own or the main window drawn with
// another projection)
struct viewport_config
{
int width{0};
int height{0};
float draw_range{1.f};
bool main{false};
bool shadow{false}; // its view is taken into the sun shadows
std::size_t window{0}; // the swapchain it goes to (vulkan14_renderer::context::windows)
enum vp_type
{
normal,
custom
} proj_type{normal};
viewport_proj_config projection{};
};
// the sun of the frame, with the factor of the place drawn (tunnels)
struct sun_light : public basic_light
{
float factor{1.f};
};
using gfx::scene_walk::Render;
using gfx::scene_walk::Render_Alpha;
// the draws of the shared walk of the scene, recorded
void walk_material(material_handle const Material, TSubModel const *Submodel = nullptr, lighting_data const *Lighting = nullptr) override;
void walk_shadow_material(material_handle const Material) override;
opengl_material const &walk_material_data(material_handle const Material) const override;
opengl_material const &walk_material_data(TSubModel const *Submodel) const override;
void walk_program(walk_shader const Program) override;
void walk_texture(std::size_t const Unit, texture_handle const Texture) override;
void walk_blending(bool const Alpha) override;
void walk_sunlight(float const Factor = 1.f) override;
void walk_environment(TEnvironmentType const Environment = e_flat) override;
void walk_ambient_save() override;
void walk_ambient_add(glm::vec3 const &Addition) override;
void walk_ambient_restore() override;
glm::vec4 walk_sun_ambient() const override;
void walk_draw(gfx::geometry_handle const &Geometry) override;
void walk_draw(std::vector<gfx::geometrybank_handle>::iterator First, std::vector<gfx::geometrybank_handle>::iterator Last) override;
std::uint32_t walk_instances(glm::mat4 const *Modelviews, std::size_t const Count) override;
void walk_cull_face(bool const Enabled) override;
void walk_glare(bool const Enabled) override;
void walk_glare_draw(TSubModel *Submodel, gfx::geometry_handle const &Geometry) override;
void walk_light_query(TSubModel *Submodel, bool const Begin) override;
void walk_editor_cursor() override;
// the scene (vulkan14scene.cpp)
bool init_scene(std::string &Reason);
// what the renderer prepares for the frames once it's there: the viewport of the main window, the common textures and geometry
void init_frame_data();
void destroy_scene();
// the scene of the frame: the passes of the viewports
void render_scene();
void setup_pass(viewport_config &Viewport, renderpass_config &Config, rendermode const Mode, float const Znear = 0.f, float const Zfar = 1.f, bool const Ignoredebug = false);
void setup_matrices();
// the colour of a viewport, with the passes it needs first: the sun shadows, the environment map, the light clusters
void render_viewport(std::size_t const Viewport);
void render_shadows(viewport_config &Viewport);
bool render_reflections(viewport_config &Viewport);
void render_reflection_face(viewport_config &Viewport);
void render_picking(rendermode const Mode);
// the sky dome and the sun and the moon
void render_environment(world_environment *Environment);
void render_particles();
void render_precipitation();
void render_editor_overlay();
// the post-processing of a viewport: the motion blur, the tonemapping, the chromatic aberration
void render_post(std::size_t const Viewport, bool const Motionblur);
// the output of a viewport to its window, with the user interface over it for the main window
void present_viewport(std::size_t const Viewport);
// the light buffer of the pass: the sun, the ambient light, the fog, the headlights, the lamps (the clustered lights)
void update_lights(light_array &Lights, bool const Headlights = true, bool const Clustered = false);
void write_local_light(gl::light_element &Light, gfx::local_lights::light const &Source, glm::dvec3 const &Camera, glm::mat4 const &View);
void headlight_projector(light_array::light_record const &Light, glm::dvec3 &Position, glm::mat3 &Rotation, glm::mat4 &Projection, float &Near, float &Fieldofview);
// the light buffer and its header written for the draws which follow
void upload_light_header();
void upload_lights();
void upload_scene_block();
void setup_sunlight_intensity(float const Factor = 1.f);
void setup_shadow_color(glm::vec4 const &Shadowcolor);
void setup_shadow_matrices();
// the light clusters of the pass: the grid and the lists of the lights
void update_clusters(glm::ivec2 const &Targetsize);
// the materials
material_handle invalid_material() const
{
return m_invalid_material;
}
// the draws: the pipeline of the program for the state of the walk, made when it's first needed
void draw_chunk(gfx::geometry_handle const &Geometry, bool const Newblock);
// a pass of the scene recorded with the walk: its targets, its size, its pass set (vk14::scene_state::pass_sets)
void start_draws(vk14::target_formats const &Targets, int const Width, int const Height, int const Set, bool const Shadows);
void end_draws();
// the rendering of the colour of a viewport, begun with the images cleared or again where it was
void begin_colour_rendering(std::size_t const Viewport, bool const Clear);
void push_constants(void const *Constants);
// a pipeline of the post-processing (a VkPipeline), nullptr when the shader isn't there. Encode: the colour is encoded by the
// shader (OUTPUT_GAMMA), -1: as gfx.shadergamma says
void *post_pipeline(char const *Fragment, int const Format, int const Encode = -1);
// the screenshot copied with a frame the gpu is done with, saved
void update_screenshot();
void bind_pass_set();
bool bind_scene_pipeline(vk14::scene_program &Program, vk14::target_formats const &Targets, vk14::blend_mode const Blend, int const Topology);
void apply_draw_state();
void push_textures();
std::uint32_t push_draw_block();
void open_instance_window();
void update_texture_release();
std::unique_ptr<context> m_context;
// the data of the geometry, which the simulation reads back; nothing on the gpu yet
// the data of the geometry, which the simulation reads back, and the banks with it on the gpu
gfx::geometrybank_manager m_geometry;
std::vector<gfx::geometry_bank *> m_banks;
material_manager m_materials;
material_handle m_invalid_material{null_handle};
std::vector<viewport_config> m_viewports;
viewport_config *m_current_viewport{nullptr};
renderpass_config m_colorpass; // of the latest colour pass
std::array<renderpass_config, 3> m_shadowpass; // of the latest cascades of the sun shadows
sun_light m_sunlight;
glm::vec3 m_fogcolor{0.f};
glm::vec4 m_shadowcolor{colors::shadow};
gl::scene_ubs scene_ubs{};
gl::light_data light_data{};
std::uint32_t m_lightcount{1}; // lights written to light_data
// the copies of the light buffer in the ring of the frame since the lights were last written, by the header and the sun
std::vector<std::pair<std::array<std::uint8_t, offsetof(gl::light_data, lights) + sizeof(gl::light_element)>, std::uint32_t>> m_lightheaders;
packed_vec3 m_cabambient{0.f};
gfx::local_lights m_locallights;
std::uint32_t m_lights_lit{0};
std::uint32_t m_lights_dropped{0};
// the reflections: the face drawn, and the place and the time of the last update
int m_environmentcubetextureface{0};
int m_environmentupdateface{6};
bool m_environmentready{false};
double m_environmentupdatetime{0.0};
glm::dvec3 m_environmentupdatelocation{0.0};
bool m_vehiclesmoved{true};
texture_handle m_suntexture{-1};
texture_handle m_moontexture{-1};
texture_handle m_smoketexture{-1};
texture_handle m_headlightstexture{-1};
texture_handle m_precipitationtexture{-1};
float m_precipitationovercast{-1.f};
double m_precipitationrotation{0.0};
// the editor overlay
gfx::geometrybank_handle m_editor_overlay_bank{};
std::vector<gfx::geometry_handle> m_editor_overlay_geometry;
unsigned int m_editor_overlay_revision{0};
unsigned int m_nodestateversion{0};
// picking
std::vector<std::function<void(TSubModel const *, glm::vec2)>> m_control_pick_requests;
std::vector<std::function<void(scene::basic_node *)>> m_node_pick_requests;
TSubModel const *m_pickcontrolitem{nullptr};
scene::basic_node *m_picksceneryitem{nullptr};
bool m_pickcontrolswanted{false};
bool m_picknodewanted{false};
glm::ivec2 m_pickposition{0};
glm::vec3 m_worldmousecoordinates{0.f};
bool m_mousehit{false};
// the state of the draws of the walk being recorded
struct draw_state;
std::unique_ptr<draw_state> m_draw;
// textures used by vehicle models, for the release of far textures
std::unordered_map<TModel3d const *, std::vector<texture_handle>> m_modeltextures;
std::vector<texture_handle> const &Model_Textures(TModel3d *Model);
// the season and the weather the materials were last given
struct simulation_state
{
std::string weather;
std::string season;
} m_simulationstate;
float m_framerate{0.f};
double m_updateaccumulator{0.0};
bool m_debug_ui_active{false};
std::string m_infotimes;
std::string m_infostats;
debug_stats m_laststats;
void update_info();
// the device was lost: what was on the gpu goes to the log
void report_device_lost(char const *Where);
static bool renderer_register;
};

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,8 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "gl/ubo.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
@@ -562,6 +564,24 @@ VkPipeline pipeline_library::get(pipeline_desc const &Desc)
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::scene_alpha:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::scene_additive:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::additive:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
@@ -703,3 +723,60 @@ void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent)
}
} // namespace vk14
namespace vk14
{
std::string binding_preamble()
{
std::string preamble{"#define MASZYNA_CONSTANTS 1\n"};
auto const define = [&preamble](char const *Name, auto const Value, char const *Suffix = "") { preamble += std::string("#define ") + Name + " " + std::to_string(Value) + Suffix + "\n"; };
using namespace vk14::bindings;
define("UI_SET", UI_SET);
define("UI_TEXTURE", UI_TEXTURE);
define("PASS_SET", PASS_SET);
define("MATERIAL_SET", MATERIAL_SET);
define("SCENE_UBO", SCENE_UBO);
define("DRAW_SSBO", DRAW_SSBO);
define("INSTANCE_SSBO", INSTANCE_SSBO);
define("LIGHT_SSBO", LIGHT_SSBO);
define("CLUSTER_UBO", CLUSTER_UBO);
define("CLUSTER_RANGE_SSBO", CLUSTER_RANGE_SSBO);
define("CLUSTER_INDEX_SSBO", CLUSTER_INDEX_SSBO);
define("SHADOW_TEX", SHADOW_TEX);
define("ENV_TEX", ENV_TEX);
define("HEADLIGHT_TEX", HEADLIGHT_TEX);
define("PASS_BINDING_COUNT", PASS_BINDING_COUNT);
define("MATERIAL_TEXTURE_FIRST", MATERIAL_TEXTURE_FIRST);
define("CL_CLUSTER_UBO", CL_CLUSTER_UBO);
define("CL_AABB_SSBO", CL_AABB_SSBO);
define("CL_RANGE_SSBO", CL_RANGE_SSBO);
define("CL_INDEX_SSBO", CL_INDEX_SSBO);
define("CL_COUNTER_SSBO", CL_COUNTER_SSBO);
define("CL_LIGHT_SSBO", CL_LIGHT_SSBO);
define("POST_SET", POST_SET);
define("POST_TEXTURE_FIRST", POST_TEXTURE_FIRST);
define("PUSH_CONSTANTS_SIZE", PUSH_CONSTANTS_SIZE);
define("SPEC_OUTPUT_GAMMA", SPEC_OUTPUT_GAMMA);
define("SPEC_SHADOWMAP_ENABLED", SPEC_SHADOWMAP_ENABLED);
define("SPEC_ENVMAP_ENABLED", SPEC_ENVMAP_ENABLED);
define("SPEC_MOTIONBLUR_ENABLED", SPEC_MOTIONBLUR_ENABLED);
define("SPEC_POSTFX_ENABLED", SPEC_POSTFX_ENABLED);
define("SPEC_EXTRAEFFECTS_ENABLED", SPEC_EXTRAEFFECTS_ENABLED);
define("SPEC_COUNT", SPEC_COUNT);
// the constants of the game the blocks of the shaders are sized by, the same as for the opengl renderer (gl/ubo.h)
define("MAX_LIGHTS", static_cast<unsigned int>(gl::MAX_LIGHTS), "U");
define("MAX_CASCADES", static_cast<unsigned int>(gl::MAX_CASCADES), "U");
define("MAX_PARAMS", static_cast<unsigned int>(gl::MAX_PARAMS), "U");
define("MAX_HEADLIGHT_SHADOWS", static_cast<unsigned int>(gl::MAX_HEADLIGHT_SHADOWS), "U");
define("CLUSTER_GRID_X", gl::CLUSTER_GRID_X);
define("CLUSTER_GRID_Y", gl::CLUSTER_GRID_Y);
define("CLUSTER_GRID_Z", gl::CLUSTER_GRID_Z);
define("CLUSTER_COUNT", gl::CLUSTER_COUNT);
define("MAX_LIGHTS_PER_CLUSTER", gl::MAX_LIGHTS_PER_CLUSTER);
define("CLUSTER_BUILD_GROUP_SIZE", gl::CLUSTER_BUILD_GROUP_SIZE);
define("CLUSTER_CULL_GROUP_SIZE", gl::CLUSTER_CULL_GROUP_SIZE);
return preamble;
}
} // namespace vk14

View File

@@ -76,7 +76,10 @@ enum class blend_mode
none,
alpha, // the colour over what is there by its alpha, the alpha kept for what's behind (the user interface)
premultiplied,
additive
additive,
// the blending of the scene, as glBlendFunc() of the opengl renderer gives it to the colour and the alpha alike
scene_alpha, // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
scene_additive // GL_SRC_ALPHA, GL_ONE (the glare of the lamps)
};
struct vertex_layout

View File

@@ -57,10 +57,59 @@ void uploader::begin_frame(VkCommandBuffer Commands, std::uint64_t Value)
bool uploader::end_frame()
{
if (m_buffercopies && (m_commands != VK_NULL_HANDLE))
{
// the draws of the frame read what was copied
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
barrier.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(m_commands, &dependency);
}
m_buffercopies = false;
m_commands = VK_NULL_HANDLE;
return m_recorded;
}
bool uploader::upload_buffer(std::shared_ptr<buffer> const &Target, void const *Data, VkDeviceSize Size, std::vector<std::pair<VkDeviceSize, VkDeviceSize>> const &Parts)
{
if ((Target == nullptr) || (Size == 0))
{
return true;
}
job upload;
upload.buffertarget = Target;
upload.parts = Parts;
if (upload.parts.empty())
{
upload.parts.emplace_back(0, Size);
}
upload.staging = std::make_shared<buffer>();
if ((false == upload.staging->create(*m_allocator, Size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, memory_kind::upload)) || (upload.staging->mapped() == nullptr))
{
log_error("no staging buffer of " + memory_size_string(Size) + " for an upload");
return false;
}
std::memcpy(upload.staging->mapped(), Data, static_cast<std::size_t>(Size));
upload.staging->flush(0, Size);
m_uploaded += Size;
if (m_commands != VK_NULL_HANDLE)
{
record(upload);
}
else
{
m_pending.emplace_back(std::move(upload));
}
return true;
}
bool uploader::upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps)
{
job upload;
@@ -89,6 +138,47 @@ bool uploader::upload(std::shared_ptr<image> const &Image, void const *Data, VkD
void uploader::record(job &Job)
{
if (Job.buffertarget != nullptr)
{
if (false == m_buffercopies)
{
// the frames sent before may still read the buffers the copies write to
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
barrier.srcAccessMask = VK_ACCESS_2_NONE;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(m_commands, &dependency);
m_buffercopies = true;
}
std::vector<VkBufferCopy2> regions;
VkDeviceSize source{0};
for (auto const &part : Job.parts)
{
VkBufferCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
region.srcOffset = source;
region.dstOffset = part.first;
region.size = part.second;
regions.push_back(region);
source += part.second;
}
VkCopyBufferInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
copy.srcBuffer = Job.staging->handle();
copy.dstBuffer = Job.buffertarget->handle();
copy.regionCount = static_cast<std::uint32_t>(regions.size());
copy.pRegions = regions.data();
vkCmdCopyBuffer2(m_commands, &copy);
m_recorded = true;
m_deletions->push(m_value, [staging = Job.staging, target = Job.buffertarget]() {});
return;
}
auto &target{*Job.target};
auto const commands{m_commands};
auto const aspect{target.aspect()};

View File

@@ -54,6 +54,10 @@ class uploader
// copies Data into Image (the levels given, the others made by blits of the first one when Mipmaps), the image left ready to be
// sampled. Data is copied away. false: no staging memory, nothing will be copied
bool upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps);
// copies Data into Target, done before the draws of the frame read it (and after the frames sent before did). Data holds the
// pieces of Parts (offset in Target, size) one after another; all of Data goes to the start of Target when there are none.
// false: no staging memory
bool upload_buffer(std::shared_ptr<buffer> const &Target, void const *Data, VkDeviceSize Size, std::vector<std::pair<VkDeviceSize, VkDeviceSize>> const &Parts = {});
// the bytes uploaded since the start, for the debug panel
std::uint64_t uploaded() const
{
@@ -69,6 +73,9 @@ class uploader
std::shared_ptr<buffer> staging;
std::vector<level> levels;
bool mipmaps{false};
// a copy to a buffer instead: the pieces copied
std::shared_ptr<buffer> buffertarget;
std::vector<std::pair<VkDeviceSize, VkDeviceSize>> parts;
};
void record(job &Job);
@@ -78,6 +85,8 @@ class uploader
VkCommandBuffer m_commands{VK_NULL_HANDLE};
std::uint64_t m_value{0};
bool m_recorded{false};
// buffers were copied to in the frame: the barriers before the first copy and after the last one
bool m_buffercopies{false};
std::vector<job> m_pending;
std::uint64_t m_uploaded{0};
mutable std::unordered_map<VkFormat, bool> m_blittable;
@@ -153,9 +162,10 @@ struct vulkan14_texture : public texture_source
bool create(bool const Static = false) override;
void release() override;
// as the opengl textures give it: one not made yet has no id of 0, which the materials take for a texture which is missing
size_t get_id() const override
{
return static_cast<size_t>(m_id);
return (m_made ? static_cast<size_t>(m_id) : std::numeric_limits<size_t>::max());
}
void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
bool resident() const override

Binary file not shown.

View File

@@ -41,3 +41,25 @@ source, compile the binaries again (or delete them): a binary older than its sou
* The constants of the game (bindings, sizes) are written in `ogl46/common.glsl` and `vk14/common.glsl` for the binaries; the
game puts its own values before the sources and checks at the start that the files have the same ones (the shader
validator in `ci_shadervalidator` checks them too).
## vk14
The shaders of `vk14/` are those of `ogl46/` made for the descriptor sets of the Vulkan renderer
(`rendering/vulkan14/vulkan14bindings.h`), drawn with the same matrices and the same conventions as the OpenGL renderer
(nothing is turned upside down but the picture shown in the window, `present.frag`).
* A stage file includes `common.glsl` right after its stage define. The pipeline layout it is drawn with is picked by a
define before that include: none for the passes of the scene, `LAYOUT_POST` for the post-processing (with `quad.vert`),
`LAYOUT_CLUSTER` for the compute passes of the light clusters, `LAYOUT_UI` for the user interface.
* The scene: set `PASS_SET` holds the blocks of the scene, of the draws (`model_draws[]`, the one of a draw picked by the push
constant `pc.draw`), of the instances and of the lights and clusters, and the shadow, environment and headlight maps;
set `MATERIAL_SET` the textures of the material at `MATERIAL_TEXTURE_FIRST + unit` of their `#pragma texture`. The names of
the model block (`modelview`, `param`, `opacity` ...) stand for the fields of the draw, as in the `MODEL_DRAWS` variant of
`ogl46/`.
* The push constants (`pc`, `PUSH_CONSTANTS_SIZE` bytes) carry the draw and the parameters of the special shaders in
`pc.data[]`; each of those says at the top what it takes from them. In the post-processing `param` stands for `pc.data`.
* The settings are specialization constants with the ids `SPEC_*` (`vk14/settings.glsl`).
* The features of the later stages of the renderer (the shadows of the lamps and the headlights, the contact shadows, the
weather and its fog, the sky ambient light, the prefiltered environment map, the automatic exposure, soft and gpu
particles) aren't in `vk14/` yet; the code paths are the ones `ogl46/` takes with them off, the fog is an analytic one
(`vk14/apply_fog.glsl`).

View File

@@ -23,9 +23,10 @@ import sys
STAGES = ('.vert', '.frag', '.geom', '.comp')
TREES = {
# source directory, binary directory, target environment, variants of the vertex and fragment shaders
'ogl46': ('ogl46', 'ogl46bin', 'opengl4.5', ['MODEL_DRAWS']),
'vk14': ('vk14', 'vk14bin', 'vulkan1.4', []),
# source directory, binary directory, target environment, variants of the vertex and fragment shaders, whether a variant is
# made only for the files which use its name (opengl links a program from the binaries of all of its stages or from none)
'ogl46': ('ogl46', 'ogl46bin', 'opengl4.5', ['MODEL_DRAWS'], False),
'vk14': ('vk14', 'vk14bin', 'vulkan1.4', ['NO_MOTION_OUTPUT'], True),
}
INCLUDE = re.compile(r'^[ \t]*#include[ \t]*[<"]([^>"]+)[>"]', re.M)
@@ -84,7 +85,7 @@ def main():
failed = []
count = 0
for key, (sourcedir, binarydir, env, variants) in TREES.items():
for key, (sourcedir, binarydir, env, variants, onlyused) in TREES.items():
if args.only and args.only != key:
continue
sources = os.path.join(args.shaders, sourcedir)
@@ -96,13 +97,17 @@ def main():
names = sorted(n for n in os.listdir(sources) if os.path.splitext(n)[1] in STAGES)
for name in names:
jobs = [('', [])]
if os.path.splitext(name)[1] in ('.vert', '.frag'):
jobs += [(v, ['-D%s=1' % v]) for v in variants]
try:
deps = dependencies(sources, name)
except OSError as e:
failed.append('%s/%s (%s)' % (sourcedir, name, e))
continue
if os.path.splitext(name)[1] in ('.vert', '.frag'):
text = ''
for dep in deps:
with open(os.path.join(sources, dep), 'r', encoding='utf-8', errors='replace') as f:
text += f.read()
jobs += [(v, ['-D%s=1' % v]) for v in variants if (not onlyused) or (v in text)]
for variant, defines in jobs:
binary = name + ('.' + variant if variant else '') + '.spv'
command = [glslc, '--target-env=' + env] + defines + [os.path.join(sources, name), '-o', os.path.join(binaries, binary)]

View File

@@ -0,0 +1,13 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 2) in vec2 f_coord;
#pragma texture(tex1, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D tex1;
void main()
{
if (texture(tex1, f_coord).a < 0.5f)
discard;
}

View File

@@ -0,0 +1,40 @@
// fog between the camera and a fragment, the api of shaders/ogl46/apply_fog.glsl. the fog volume and the weather of the opengl
// renderer come with the stage P2 of the plan; until then the fog is analytic: exponential with the distance, of the density of
// the draw (fog_density of the model block, a draw with 0 is not fogged), of the colour fog_color of the light buffer, turned
// towards the colour of the sun around its direction
// rgb: light the fog adds, a: what it lets through
vec4 fog_at_fragment()
{
if (fog_density <= 0.0)
return vec4(0.0, 0.0, 0.0, 1.0);
float amount = 1.0 - clamp(exp(-length(f_pos.xyz) * fog_density), 0.0, 1.0);
vec3 color = fog_color;
if ((lights_count >= 1U) && (lights[0].type == LIGHT_DIR))
{
float sunward = pow(max(dot(normalize(f_pos.xyz), normalize(-lights[0].dir)), 0.0), 30.0);
color = mix(color, lights[0].color, sunward);
}
return vec4(color * amount, 1.0 - amount);
}
vec3 get_fog_color()
{
vec4 fog = fog_at_fragment();
return fog.rgb / max(1.0 - fog.a, 1e-4);
}
float get_fog_amount()
{
return 1.0 - fog_at_fragment().a;
}
vec3 apply_fog(vec3 color)
{
vec4 fog = fog_at_fragment();
return color * fog.a + fog.rgb;
}
vec3 add_fog(vec3 color, float amount)
{
return color + fog_at_fragment().rgb * amount;
}

View File

@@ -0,0 +1,34 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 2) in vec2 f_coord;
#pragma texture(tex1, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D tex1;
#include "tonemapping.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
vec4 tex_color = texture(tex1, f_coord);
if (POSTFX_ENABLED)
{
out_color = tex_color * param[0];
}
else
{
out_color = tonemap(tex_color * param[0]);
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0f);
}
}

View File

@@ -0,0 +1,93 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// the sun, the moon and the textured sprite of the easter egg, drawn over the sky on a quad around the direction of the body
// (celestial.vert). param[0]: colour (rgb) and alpha, param[1]: position in view space and half the size of the quad,
// param[2].xy: corner (sprite) or centre (moon) of the picture in the texture, param[2].z: its size (sprite) or the radius of
// the disc in it (moon), in texture units, param[2].w: kind of body, 0: textured sprite, 1: sun, 2: moon.
// the colour goes out premultiplied, the alpha is how much of the sky behind the body it hides (blending ONE, ONE_MINUS_SRC_ALPHA)
layout(location = 2) in vec2 f_coord;
layout(location = 15) in vec2 f_local;
#pragma texture(tex1, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D tex1;
#include "tonemapping.glsl"
// shape and light of the sun and the moon, pc.data[0..2] of the push constants (a loose uniform vec4 body[3] in the opengl
// renderer, set by opengl46_renderer::Render(world_environment *)):
// [0].xy: direction to the zenith on the quad, [0].z: height over width of the disc (flattened by the refraction near the
// horizon), [0].w: radius of the disc in units of half the quad
// [1].xyz: direction to the sun in view space, [1].w: light of the earth on the dark part of the moon, against the light of
// the sun on the lit part
// [2].rgb: limb darkening of the sun per channel (linear law)
#define body pc.data
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
int kind = int(param[2].w + 0.5);
vec4 color = vec4(0.0);
if (kind == 0)
{
vec4 tex_color = texture(tex1, f_coord) * param[0];
color = vec4(tex_color.rgb * tex_color.a, 0.0);
}
else
{
// the point on the disc in its radii, x along the horizon, y to the zenith, with the flattening undone
vec2 zenith = body[0].xy;
vec2 across = vec2(zenith.y, -zenith.x);
vec2 p = vec2(dot(f_local, across), dot(f_local, zenith)) / body[0].w;
p.y /= body[0].z;
float r = length(p);
float coverage = clamp((1.0 - r) / max(fwidth(r), 1e-4) + 0.5, 0.0, 1.0);
// the horizon hides what is under it
vec3 direction = normalize(param[1].xyz + vec3(f_local * param[1].w, 0.0));
float height = (mat3(inv_view) * direction).y;
coverage *= clamp(height / max(fwidth(height), 1e-7) + 0.5, 0.0, 1.0);
// cosine of the angle between the line of sight and the surface of the sphere
float mu = sqrt(max(1.0 - r * r, 0.0));
if (kind == 1)
{
// limb darkening, kept to the average brightness of the disc
vec3 limb = (vec3(1.0) - body[2].rgb * (1.0 - mu)) / (vec3(1.0) - body[2].rgb / 3.0);
color = vec4(param[0].rgb * limb * coverage, 0.0);
}
else
{
// the moon as a sphere lit from the direction of the sun. the frame of the disc in view space, z to the viewer
vec3 z = -normalize(param[1].xyz);
vec3 x = vec3(across, 0.0);
x = normalize(x - z * dot(x, z));
vec3 y = cross(z, x);
vec2 q = p / max(r, 1.0);
vec3 normal = q.x * x + q.y * y + mu * z;
float mu0 = max(dot(normal, normalize(body[1].xyz)), 0.0);
// Lommel-Seeliger law: an even disc at the full moon, as the real one, no darkening towards the limb
float lit = 2.0 * mu0 / (mu0 + max(mu, 1e-3));
vec3 albedo = texture(tex1, param[2].xy + p * param[2].z).rgb;
color = vec4(albedo * param[0].rgb * ((lit + body[1].w) * coverage), coverage * param[0].a);
}
}
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = vec4(tonemap(vec4(color.rgb, 1.0)).rgb, color.a);
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0f);
}
}

View File

@@ -0,0 +1,34 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
const vec2 vert[4] = vec2[]
(
vec2(-1.0, 1.0),
vec2(-1.0, -1.0),
vec2( 1.0, 1.0),
vec2( 1.0, -1.0)
);
layout(location = 2) out vec2 f_coord;
// the corner of the quad, -1...1 across it
layout(location = 15) out vec2 f_local;
void main()
{
gl_Position = projection * (vec4(param[1].xyz, 1.0) + vec4(vert[gl_VertexIndex] * param[1].w, 0.0, 0.0));
f_local = vert[gl_VertexIndex];
vec2 coord = param[2].xy;
float size = param[2].z;
if (gl_VertexIndex == 0)
f_coord = vec2(coord.x, coord.y);
if (gl_VertexIndex == 1)
f_coord = vec2(coord.x, coord.y - size);
if (gl_VertexIndex == 2)
f_coord = vec2(coord.x + size, coord.y);
if (gl_VertexIndex == 3)
f_coord = vec2(coord.x + size, coord.y - size);
}

View File

@@ -0,0 +1,59 @@
#version 460 core
#define STAGE_COMPUTE 1
#define LAYOUT_CLUSTER 1
#include "common.glsl"
// bounding boxes of the light clusters in view space, see gl/cluster_grid.h
// one invocation per cluster, run when the projection or the depth range of the pass changes
layout(local_size_x = CLUSTER_BUILD_GROUP_SIZE) in;
// clip space w at the lower boundary of slice k: the inverse of the slice formula used when shading
float slice_depth(uint k)
{
return exp((float(k) - cluster_slice_bias) / cluster_slice_scale);
}
void main()
{
uint index = gl_GlobalInvocationID.x;
if (index >= uint(CLUSTER_COUNT))
return;
uvec3 cell = uvec3(
index % uint(CLUSTER_GRID_X),
(index / uint(CLUSTER_GRID_X)) % uint(CLUSTER_GRID_Y),
index / uint(CLUSTER_GRID_X * CLUSTER_GRID_Y));
// clip x, y and w are affine functions of the view space position, while clip z is constant (reverse z with
// the far plane at infinity), which makes the projection matrix singular. the position is recovered from
// those three alone, so off-axis and rotated screens of custom viewports and vr eyes work too
mat3 linear = mat3(
vec3(cluster_projection[0][0], cluster_projection[0][1], cluster_projection[0][3]),
vec3(cluster_projection[1][0], cluster_projection[1][1], cluster_projection[1][3]),
vec3(cluster_projection[2][0], cluster_projection[2][1], cluster_projection[2][3]));
vec3 translation = vec3(cluster_projection[3][0], cluster_projection[3][1], cluster_projection[3][3]);
mat3 unproject = inverse(linear);
// the tile in clip space xy, the slice in clip space w
vec2 tiles = vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y);
vec2 ndc_min = vec2(cell.xy) / tiles * 2.0 - 1.0;
vec2 ndc_max = vec2(cell.xy + 1U) / tiles * 2.0 - 1.0;
float w_near = slice_depth(cell.z);
float w_far = slice_depth(cell.z + 1U);
vec3 lower = vec3(3.402823e38);
vec3 upper = vec3(-3.402823e38);
for (int corner = 0; corner < 8; ++corner)
{
vec2 ndc = vec2(
(corner & 1) != 0 ? ndc_max.x : ndc_min.x,
(corner & 2) != 0 ? ndc_max.y : ndc_min.y);
float w = (corner & 4) != 0 ? w_far : w_near;
vec3 position = unproject * (vec3(ndc * w, w) - translation);
lower = min(lower, position);
upper = max(upper, position);
}
cluster_aabbs[index].lower = vec4(lower, 0.0);
cluster_aabbs[index].upper = vec4(upper, 0.0);
}

View File

@@ -0,0 +1,157 @@
#version 460 core
#define STAGE_COMPUTE 1
#define LAYOUT_CLUSTER 1
#include "common.glsl"
// assigns the lights to the light clusters, see gl/cluster_grid.h
// one invocation per cluster: tests every light of light_ssbo (the sun, slot 0, is handled apart from the clusters)
// against the bounding box of the cluster in view space, and writes the indices of the lights which reach it.
// run every frame after the lights were updated, for the grid of the pass about to be drawn
layout(local_size_x = CLUSTER_CULL_GROUP_SIZE) in;
// a light is taken to end where its brightest possible contribution drops under this, a fraction of
// what the display can tell apart. there is no other limit: the 1 km filter on the cpu side is about the distance of a light
// from the camera, not about how far it reaches, and the shading lights a surface at any distance
const float MIN_CONTRIBUTION = 1.0 / 1024.0;
// reach of a light whose contribution does not fall off with the distance; its square still fits in a float
const float UNLIMITED_RANGE = 1.0e18;
// the boxes and the light data are floats: planes of the headlight projection let this much (in meters) of a box through
const float PLANE_MARGIN = 0.05;
// brightest value the light adds to a surface it hits, as the shading computes it
float light_peak(light_s light)
{
float peak = max(light.color.r, max(light.color.g, light.color.b)) * light.intensity;
if (light.type == LIGHT_HEADLIGHTS)
// weights are the state of the individual lamps and the beam texture, which is not brighter than 1, picks from them
return peak * max(light.headlight_weights.r, max(light.headlight_weights.g, light.headlight_weights.b));
return peak * (1.0 + light.ambient);
}
// distance from the light at which its contribution falls under MIN_CONTRIBUTION, 0 for a light which has none
float light_range(light_s light)
{
float peak = light_peak(light);
if (!(peak > MIN_CONTRIBUTION))
return 0.0;
// attenuation which the contribution can drop to without being lost: 1 / k
float k = peak / MIN_CONTRIBUTION;
if (light.type == LIGHT_HEADLIGHTS)
{
// 1 + linear * d + quadratic * d^2 <= k, the positive root written the way which has no cancellation for a small quadratic
float a = light.quadratic;
float b = light.linear;
float c = 1.0 - k;
float denominator = b + sqrt(b * b - 4.0 * a * c);
return (denominator > 1e-12) ? min(-2.0 * c / denominator, UNLIMITED_RANGE) : UNLIMITED_RANGE;
}
// point and spot lights fall off with the square of the distance; the shading ends them at 1 / sqrt(quadratic) where it is set
float range = sqrt(k);
return (light.quadratic > 0.0) ? min(range, inversesqrt(light.quadratic)) : range;
}
bool sphere_touches_box(vec3 center, float radius, vec3 lower, vec3 upper)
{
vec3 offset = center - clamp(center, lower, upper);
return dot(offset, offset) <= radius * radius;
}
// whether any part of the box, given by its center and half extents, is on the positive side of the plane
bool box_reaches_plane(vec4 plane, vec3 center, vec3 extent)
{
return dot(plane.xyz, center) + plane.w + dot(abs(plane.xyz), extent) + PLANE_MARGIN * length(plane.xyz) >= 0.0;
}
// planes bounding the beam of a headlight: the projection takes a view space position to the texture coordinates
// of the beam, and the beam is where those fall in 0..1 and the position is in front of the projector. clip z of that
// projection is a constant (reverse z, no far plane), so only the four sides and the near plane are real planes
bool box_in_headlight_beam(mat4 projection, vec3 center, vec3 extent)
{
vec4 row_x = vec4(projection[0][0], projection[1][0], projection[2][0], projection[3][0]);
vec4 row_y = vec4(projection[0][1], projection[1][1], projection[2][1], projection[3][1]);
vec4 row_z = vec4(projection[0][2], projection[1][2], projection[2][2], projection[3][2]);
vec4 row_w = vec4(projection[0][3], projection[1][3], projection[2][3], projection[3][3]);
return box_reaches_plane(row_x, center, extent)
&& box_reaches_plane(row_w - row_x, center, extent)
&& box_reaches_plane(row_y, center, extent)
&& box_reaches_plane(row_w - row_y, center, extent)
&& box_reaches_plane(row_z, center, extent)
&& box_reaches_plane(row_w - row_z, center, extent);
}
// whether the sphere touches the infinite cone with the apex at the origin, the unit axis and the cosine of the half angle
bool sphere_touches_cone(vec3 center, float radius, vec3 axis, float cosine)
{
float sine = max(sqrt(max(1.0 - cosine * cosine, 0.0)), 1e-4);
float along = dot(center, axis);
float across = sqrt(max(dot(center, center) - along * along, 0.0));
// distance to the boundary ray if the closest point of the cone is on it, and to the apex otherwise
float distance_to_cone = (along * cosine + across * sine > 0.0) ? across * cosine - along * sine : length(center);
return distance_to_cone <= radius;
}
bool light_reaches_box(light_s light, float range, vec3 lower, vec3 upper)
{
vec3 center = 0.5 * (lower + upper);
vec3 extent = 0.5 * (upper - lower);
if (light.type == LIGHT_DIR)
return true;
if (!sphere_touches_box(light.pos, range, lower, upper))
return false;
if (light.type == LIGHT_HEADLIGHTS)
return box_in_headlight_beam(light.headlight_projection, center, extent);
if ((light.type == LIGHT_SPOT) && (light.out_cutoff > 0.0))
// the cone is tested against the sphere around the box, which lets some of the corners through. a cone wider than
// a half of the space is taken as the whole sphere
return sphere_touches_cone(center - light.pos, length(extent), normalize(light.dir), light.out_cutoff);
return true;
}
void main()
{
uint index = gl_GlobalInvocationID.x;
if (index >= uint(CLUSTER_COUNT))
return;
vec3 lower = cluster_aabbs[index].lower.xyz;
vec3 upper = cluster_aabbs[index].upper.xyz;
// the lights are in the order of their importance, so when a cluster is full the least important are the ones left out
uint found[MAX_LIGHTS_PER_CLUSTER];
uint found_count = 0U;
uint dropped = 0U;
uint lights_end = min(lights_total, uint(lights.length()));
for (uint i = 1U; i < lights_end; ++i)
{
float range = light_range(lights[i]);
if (range <= 0.0 && lights[i].type != LIGHT_DIR)
continue;
if (!light_reaches_box(lights[i], range, lower, upper))
continue;
if (found_count < uint(MAX_LIGHTS_PER_CLUSTER))
found[found_count++] = i;
else
++dropped;
}
uint offset = 0U;
if (found_count > 0U)
{
offset = atomicAdd(cluster_index_count, found_count);
for (uint k = 0U; k < found_count; ++k)
cluster_light_indices[offset + k] = found[k];
}
if (dropped > 0U)
atomicAdd(cluster_overflow_count, dropped);
cluster_ranges[index] = cluster_range_s(offset, found_count);
}

35
shaders/vk14/color.frag Normal file
View File

@@ -0,0 +1,35 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 13) in vec3 f_color;
layout(location = 3) in vec4 f_pos;
#include "apply_fog.glsl"
#include "tonemapping.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#include "sky_shoulder.glsl"
void main()
{
vec3 col = sky_shoulder(pow(max(f_color.rgb, vec3(0.0)), vec3(2.2)));
if (POSTFX_ENABLED)
{
out_color = vec4(apply_fog(col), 1.0f);
}
else
{
out_color = tonemap(vec4(apply_fog(col), 1.0f));
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0f);
}
}

View File

@@ -1,7 +1,22 @@
// what the shaders of the vulkan 1.4 renderer share: the bindings of rendering/vulkan14/vulkan14bindings.h and the settings.
// what the shaders of the vulkan 1.4 renderer share: the bindings of rendering/vulkan14/vulkan14bindings.h, the constants of the
// game, the settings, the push constants and the resources of the pipeline layout a shader is drawn with.
// compiled by the game, the bindings come from the renderer (vk14::binding_preamble()) and are taken instead of these; the
// SPIR-V binaries (shaders/vk14bin) are compiled with the values written here, so they have to stay the same as in the game,
// which checks them when it starts (and doesn't load the binaries when they differ)
//
// the pipeline layout is picked by a define before the include of this file:
// (none) the passes of the scene (materials, shadows, picking, lines, sprites, sky, particles): set PASS_SET with
// the blocks of the scene, of the draws, of the instances, the lights, the clusters and the shadow,
// environment and headlight maps; set MATERIAL_SET with the textures of the material
// LAYOUT_POST the post-processing and the copies of whole images: set POST_SET pushed with the images read, the
// parameters in the push constants (param stands for pc.data)
// LAYOUT_CLUSTER the compute passes of the light clusters: set 0 pushed at the CL_* bindings
// LAYOUT_UI the user interface: none of the above, its own push constants
//
// the locations of the inputs and outputs between the stages are the table of shaders/ogl46/common.glsl, the same for a name
// in every shader: 0 f_normal, 1 f_normal_raw, 2 f_coord, 3 f_pos, 4 f_clip_pos, 5 f_clip_future_pos, 6 TangentFragPos,
// 7 UserData, 8-10 f_tbn, 12 f_albedo, 13 f_color, 14 f_coords, 15 f_local, 16 f_soft, 17 f_streak (11 f_model_draw is not
// used: the draw is in the push constants)
#ifndef COMMON_GLSL
#define COMMON_GLSL
@@ -15,18 +30,276 @@
#define UI_TEXTURE 0
#define PASS_SET 0
#define MATERIAL_SET 1
#define SCENE_UBO 0
#define DRAW_SSBO 1
#define INSTANCE_SSBO 2
#define LIGHT_SSBO 3
#define CLUSTER_UBO 4
#define CLUSTER_RANGE_SSBO 5
#define CLUSTER_INDEX_SSBO 6
#define SHADOW_TEX 7
#define ENV_TEX 8
#define HEADLIGHT_TEX 9
#define PASS_BINDING_COUNT 10
#define MATERIAL_TEXTURE_FIRST 0
#define CL_CLUSTER_UBO 0
#define CL_AABB_SSBO 1
#define CL_RANGE_SSBO 2
#define CL_INDEX_SSBO 3
#define CL_COUNTER_SSBO 4
#define CL_LIGHT_SSBO 5
#define POST_SET 0
#define POST_TEXTURE_FIRST 0
#define PUSH_CONSTANTS_SIZE 112
#define SPEC_OUTPUT_GAMMA 0
#define SPEC_SHADOWMAP_ENABLED 1
#define SPEC_ENVMAP_ENABLED 2
#define SPEC_MOTIONBLUR_ENABLED 3
#define SPEC_POSTFX_ENABLED 4
#define SPEC_EXTRAEFFECTS_ENABLED 5
#define SPEC_COUNT 6
#define MAX_LIGHTS 1024U
#define MAX_CASCADES 3U
#define MAX_PARAMS 3U
#define MAX_HEADLIGHT_SHADOWS 4U
#define CLUSTER_GRID_X 16
#define CLUSTER_GRID_Y 9
#define CLUSTER_GRID_Z 24
#define CLUSTER_COUNT 3456
#define MAX_LIGHTS_PER_CLUSTER 64
#define CLUSTER_BUILD_GROUP_SIZE 64
#define CLUSTER_CULL_GROUP_SIZE 64
#endif
// whether the colour written to the window is encoded by the shader (FBOUT): the window has no srgb format (gfx.shadergamma yes).
// a specialization constant, set by the renderer when it makes the pipeline
layout(constant_id = SPEC_OUTPUT_GAMMA) const bool OUTPUT_GAMMA = false;
#include "settings.glsl"
// the colour written to the window
vec4 FBOUT(vec4 x)
#ifndef LAYOUT_UI
// vk14::draw_constants, PUSH_CONSTANTS_SIZE bytes, visible in every stage. draw: the element of model_draws[] the draw reads
// (the passes of the scene); flags and data: the parameters of the special shaders, each says what it takes from them
layout(push_constant) uniform draw_constants_block
{
return (OUTPUT_GAMMA ? vec4(pow(x.rgb, vec3(1.0 / 2.2)), x.a) : x);
uint draw;
uint flags;
uint pad0;
uint pad1;
vec4 data[6];
} pc;
#endif
const uint LIGHT_SPOT = 0U;
const uint LIGHT_POINT = 1U;
const uint LIGHT_DIR = 2U;
const uint LIGHT_HEADLIGHTS = 3U;
// gl::light_element
struct light_s
{
vec3 pos;
uint type;
vec3 dir;
float in_cutoff;
vec3 color;
float out_cutoff;
float linear;
float quadratic;
float intensity;
float ambient;
mat4 headlight_projection;
vec4 headlight_weights;
};
// light clusters, see gl/cluster_grid.h
struct cluster_aabb_s
{
vec4 lower;
vec4 upper;
};
struct cluster_range_s
{
uint offset;
uint count;
};
#if defined(LAYOUT_CLUSTER)
layout(std140, set = 0, binding = CL_CLUSTER_UBO) uniform cluster_ubo
{
mat4 cluster_projection;
float cluster_znear;
float cluster_zfar;
float cluster_slice_scale;
float cluster_slice_bias;
vec2 cluster_viewport_size;
uint cluster_valid;
};
layout(std430, set = 0, binding = CL_AABB_SSBO) buffer cluster_aabb_ssbo
{
cluster_aabb_s cluster_aabbs[];
};
layout(std430, set = 0, binding = CL_RANGE_SSBO) buffer cluster_range_ssbo
{
cluster_range_s cluster_ranges[];
};
layout(std430, set = 0, binding = CL_INDEX_SSBO) buffer cluster_index_ssbo
{
uint cluster_light_indices[];
};
layout(std430, set = 0, binding = CL_COUNTER_SSBO) buffer cluster_counter_ssbo
{
uint cluster_index_count;
uint cluster_overflow_count;
};
layout(std430, set = 0, binding = CL_LIGHT_SSBO) readonly buffer light_ssbo
{
vec3 ambient;
// lights stored in lights[], what the cluster culling goes through
uint lights_total;
vec3 fog_color;
// lights the shading loop goes through when the lights are not clustered
uint lights_count;
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
uint lights_clustered;
// not used by the shaders of this renderer yet (the sky ambient light and the prefiltered environment map come later)
float sky_ambient_factor;
uint env_ggx;
light_s lights[];
};
#elif defined(LAYOUT_POST)
// the post-processing reads the parameters the opengl renderer gives it in the model block from the push constants
#define param pc.data
#elif !defined(LAYOUT_UI)
// the block of the scene, gl::scene_ubs (every member kept, the layout is the one of the c++ struct)
layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo
{
mat4 projection;
mat4 inv_view;
mat4 lightview[MAX_CASCADES];
vec3 cascade_end;
float time;
vec4 rain_params;
vec4 wiper_pos;
vec4 wiper_timer_out;
vec4 wiper_timer_return;
mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS];
vec4 headlight_shadow_params[MAX_HEADLIGHT_SHADOWS];
vec4 contact_shadow_params;
vec4 contact_shadow_lamps;
vec4 shadow_fit;
};
// the model block of a draw, gl::model_ubs (256 bytes), the one of the draw picked by pc.draw. the names below stand for its
// fields, so the shaders read them as the uniform block of the opengl renderer
struct model_draw_s
{
mat4 modelview;
mat3 modelviewnormal;
vec4 param[MAX_PARAMS];
mat4 future;
float opacity;
float emission;
float fog_density;
float alpha_mult;
float shadow_tone;
};
layout(std430, set = PASS_SET, binding = DRAW_SSBO) readonly buffer model_draw_ssbo
{
model_draw_s model_draws[];
};
#define modelview model_draws[pc.draw].modelview
#define modelviewnormal model_draws[pc.draw].modelviewnormal
#define param model_draws[pc.draw].param
#define future model_draws[pc.draw].future
#define opacity model_draws[pc.draw].opacity
#define emission model_draws[pc.draw].emission
#define fog_density model_draws[pc.draw].fog_density
#define alpha_mult model_draws[pc.draw].alpha_mult
#define shadow_tone model_draws[pc.draw].shadow_tone
// per-instance modelview matrices of the instanced draws, element 0 the identity. a draw which isn't instanced is drawn with
// firstInstance 0, an instanced one from its base instance on, with modelview set to the submodel-local chain (computed from
// the identity, not from the camera or the instance), so the product is the final transform per instance per submodel
layout(std430, set = PASS_SET, binding = INSTANCE_SSBO) readonly buffer instance_ssbo
{
mat4 instance_modelview[];
};
// the light buffer of the pass, gl::light_data
layout(std430, set = PASS_SET, binding = LIGHT_SSBO) readonly buffer light_ssbo
{
vec3 ambient;
// lights stored in lights[], what the cluster culling goes through
uint lights_total;
vec3 fog_color;
// lights the shading loop goes through when the lights are not clustered
uint lights_count;
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
uint lights_clustered;
// not used by the shaders of this renderer yet (the sky ambient light and the prefiltered environment map come later)
float sky_ambient_factor;
uint env_ggx;
light_s lights[];
};
// the grid of the light clusters of the pass, gl::cluster_ubs; the compute passes fill the lists, shading only reads them
layout(std140, set = PASS_SET, binding = CLUSTER_UBO) uniform cluster_ubo
{
mat4 cluster_projection;
float cluster_znear;
float cluster_zfar;
float cluster_slice_scale;
float cluster_slice_bias;
vec2 cluster_viewport_size;
uint cluster_valid;
};
layout(std430, set = PASS_SET, binding = CLUSTER_RANGE_SSBO) readonly buffer cluster_range_ssbo
{
cluster_range_s cluster_ranges[];
};
layout(std430, set = PASS_SET, binding = CLUSTER_INDEX_SSBO) readonly buffer cluster_index_ssbo
{
uint cluster_light_indices[];
};
#ifdef STAGE_VERTEX
mat4 effective_modelview()
{
return instance_modelview[gl_InstanceIndex] * modelview;
}
mat3 effective_modelviewnormal()
{
// instance_modelview can include per-instance scale (uniform or per-axis), so its upper 3x3 is not its own inverse
// transpose in the non-uniform case
mat3 instance_mv3 = mat3(instance_modelview[gl_InstanceIndex]);
mat3 instance_normal = transpose(inverse(instance_mv3));
return instance_normal * modelviewnormal;
}
#endif
#endif
#endif

View File

@@ -0,0 +1,108 @@
vec3 rgb2yuv(vec3 val)
{
return
clamp(
vec3(
(0.257 * val.r) + (0.504 * val.g) + (0.098 * val.b) + 0.0625,
-(0.148 * val.r) - (0.291 * val.g) + (0.439 * val.b) + 0.5,
(0.439 * val.r) - (0.368 * val.g) - (0.071 * val.b) + 0.5 ),
0.0, 1.0);
}
vec3 yuv2rgb(vec3 val)
{
val -= vec3(0.0625, 0.5, 0.5);
return
clamp(
vec3(
(1.164 * val.r) + (1.596 * val.b),
(1.164 * val.r) - (0.391 * val.g) - (0.813 * val.b),
(1.164 * val.r) + (2.018 * val.g) ),
0.0, 1.0);
}
vec3 rgb2hsl( vec3 col )
{
float red = col.r;
float green = col.g;
float blue = col.b;
float minc = min( col.r, min( col.g, col.b ));
float maxc = max( col.r, max( col.g, col.b ));
float delta = maxc - minc;
float lum = (minc + maxc) * 0.5;
float sat = 0.0;
float hue = 0.0;
if (lum > 0.0 && lum < 1.0) {
float mul = (lum < 0.5) ? (lum) : (1.0-lum);
sat = delta / (mul * 2.0);
}
vec3 masks = vec3(
(maxc == red && maxc != green) ? 1.0 : 0.0,
(maxc == green && maxc != blue) ? 1.0 : 0.0,
(maxc == blue && maxc != red) ? 1.0 : 0.0
);
vec3 adds = vec3(
((green - blue ) / delta),
2.0 + ((blue - red ) / delta),
4.0 + ((red - green) / delta)
);
float deltaGtz = (delta > 0.0) ? 1.0 : 0.0;
hue += dot( adds, masks );
hue *= deltaGtz;
hue /= 6.0;
if (hue < 0.0)
hue += 1.0;
return vec3( hue, sat, lum );
}
vec3 hsl2rgb( vec3 col )
{
const float onethird = 1.0 / 3.0;
const float twothird = 2.0 / 3.0;
const float rcpsixth = 6.0;
float hue = col.x;
float sat = col.y;
float lum = col.z;
vec3 xt = vec3(
rcpsixth * (hue - twothird),
0.0,
rcpsixth * (1.0 - hue)
);
if (hue < twothird) {
xt.r = 0.0;
xt.g = rcpsixth * (twothird - hue);
xt.b = rcpsixth * (hue - onethird);
}
if (hue < onethird) {
xt.r = rcpsixth * (onethird - hue);
xt.g = rcpsixth * hue;
xt.b = 0.0;
}
xt = min( xt, 1.0 );
float sat2 = 2.0 * sat;
float satinv = 1.0 - sat;
float luminv = 1.0 - lum;
float lum2m1 = (2.0 * lum) - 1.0;
vec3 ct = (sat2 * xt) + satinv;
vec3 rgb;
if (lum >= 0.5) { rgb = (luminv * ct) + lum2m1; }
else { rgb = lum * ct; }
return rgb;
}

15
shaders/vk14/copy.frag Normal file
View File

@@ -0,0 +1,15 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
layout(location = 14) in vec2 f_coords;
#pragma texture(tex1, 0, sRGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D tex1;
layout(location = 0) out vec4 out_color;
void main()
{
out_color = texture(tex1, f_coords);
}

26
shaders/vk14/dither.glsl Normal file
View File

@@ -0,0 +1,26 @@
// noise of half a step of the 8-bit output, added to the colour about to be written to the window: smooth dark gradients (the
// fog at night) show no bands, which otherwise crawl over the image with the smallest change of the light. interleaved gradient
// noise (Jimenez 2014) of the pixel, the same in every frame. the colour is the one written to the window: encoded already with
// OUTPUT_GAMMA, linear otherwise (the sRGB framebuffer encodes it, so the step is taken in the encoded values)
#ifndef DITHER_GLSL
#define DITHER_GLSL
#include "common.glsl"
vec3 dither_output(vec3 color)
{
float noise = (fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))) - 0.5) / 255.0;
if (OUTPUT_GAMMA)
{
return max(color + noise, vec3(0.0));
}
else
{
vec3 linear = max(color, vec3(0.0));
vec3 encoded = mix(linear * 12.92, 1.055 * pow(linear, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), linear));
encoded = max(encoded + noise, vec3(0.0));
return mix(encoded / 12.92, pow((encoded + 0.055) / 1.055, vec3(2.4)), step(vec3(0.04045), encoded));
}
}
#endif

View File

@@ -0,0 +1,54 @@
layout(set = PASS_SET, binding = ENV_TEX) uniform samplerCube envmap;
vec3 envmap_color( vec3 normal )
{
vec3 envcolor;
if (ENVMAP_ENABLED)
{
vec3 refvec = reflect(f_pos.xyz, normal); // view space
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space
envcolor = texture(envmap, refvec).rgb;
// Sanitize. The env cubemap can briefly contain NaN/Inf after a
// reflection-pass regeneration (m_empty_cubemap was bound during the
// face render and on NVIDIA can sample as undefined). Without this
// guard, a single bad texel propagates through env_reflection in
// apply_lights() and produces one-frame black flashes on glossy
// (esp. specgloss) surfaces.
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
envcolor = max(envcolor, vec3(0.0));
}
else
{
envcolor = vec3(0.5);
}
return envcolor;
}
// Roughness-aware env map lookup — uses mip levels for blurry reflections.
// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur.
vec3 envmap_color_lod(vec3 fragnormal, float lod)
{
if (ENVMAP_ENABLED)
{
vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing
vec3 envcolor = textureLod(envmap, refvec, lod).rgb;
// See envmap_color() above — same NaN/Inf sanitize, also needed here
// because mipmap generation propagates a single NaN texel across the
// whole mip chain.
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
return max(envcolor, vec3(0.0));
}
else
{
return vec3(0.5); // was vec3(0.0), match the non-LOD fallback
}
}
// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte): the plain map, its mipmaps
// (box filtered) standing for the roughness. the prefiltered GGX map of the opengl renderer (env_ggx) comes in a later stage
vec3 envmap_color_rough(vec3 fragnormal, float roughness)
{
const float MAX_REFLECTION_LOD = 8.0;
return envmap_color_lod(fragnormal, roughness * MAX_REFLECTION_LOD);
}

View File

@@ -0,0 +1,38 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 3) in vec4 f_pos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#include "tonemapping.glsl"
#include "apply_fog.glsl"
void main()
{
float x = (gl_PointCoord.x - 0.5f) * 2.0f;
float y = (gl_PointCoord.y - 0.5f) * 2.0f;
float dist = sqrt(x * x + y * y);
if (dist > 0.5f)
discard;
// vec4 color = vec4(param[0].rgb * emission, mix(param[0].a, 0.0f, dist * 2.0f));
vec4 color = vec4(apply_fog(pow(param[0].rgb, vec3(2.2)) * emission), mix(param[0].a, 0.0f, dist * 2.0f));
// vec4 color = vec4(add_fog(param[0].rgb * emission * (1.0 - get_fog_amount()), 1.0), mix(param[0].a, 0.0f, dist * 2.0f));
out_color = FBOUT(color);
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,21 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
layout(location = 0) in vec3 v_vert;
layout(location = 1) in vec3 v_normal;
layout(location = 2) in vec2 v_coord;
layout(location = 3) out vec4 f_pos;
layout(location = 4) out vec4 f_clip_pos;
layout(location = 5) out vec4 f_clip_future_pos;
void main()
{
f_pos = modelview * vec4(v_vert, 1.0);
f_clip_pos = (projection * modelview) * vec4(v_vert, 1.0f);
f_clip_future_pos = (projection * future * modelview) * vec4(v_vert, 1.0f);
gl_Position = f_clip_pos;
gl_PointSize = param[1].x;
}

View File

@@ -1,5 +1,6 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_UI 1
// the user interface (imgui): the colour of the vertices times the texture of the draw (the font, an image, a texture), pushed
// with the draw

View File

@@ -1,5 +1,6 @@
#version 460 core
#define STAGE_VERTEX 1
#define LAYOUT_UI 1
// the user interface (imgui), drawn by vk14::imgui_backend over the image of the window
#include "common.glsl"

View File

@@ -0,0 +1,594 @@
// stage D of the vulkan renderer: the sun with its cascaded shadows, the environment map, the headlights and the lamps (in the
// clusters or not). the cube shadow maps of the lamps, the shadow maps of the headlights, the contact shadows, the shadows of
// the clouds, the directional sky ambient light and the prefiltered environment map of shaders/ogl46/light_common.glsl come
// with the later stages; the code below is the one of the opengl renderer with those left out, it shades as that one does
// with them off
layout(set = PASS_SET, binding = SHADOW_TEX) uniform sampler2DArrayShadow shadowmap;
layout(set = PASS_SET, binding = HEADLIGHT_TEX) uniform sampler2D headlightmap;
#include "envmapping.glsl"
#include "conversion.glsl"
float glossiness = 1.0;
float metalic = 0.0;
layout(location = 7) in vec4 UserData;
#pragma param(weathering, 2, 3, 1, zero)
// ---------------------------------------------------------------------
// Lighting balance tunables - tweak these to control overall scene
// exposure without touching tonemapping.glsl.
//
// AMBIENT_SCALE: brightness of SHADED faces (indirect/sky term).
// Lower -> deeper shadows, less burn under bright
// textures. Higher -> flatter / brighter shading.
//
// SUN_DIFFUSE_SCALE: brightness of UNSHADED (sun-lit) faces. Lower
// this to dim hot surfaces in direct sunlight
// without affecting shaded areas. Was 3.5; 2.5
// calmly fits the ACES tonemap shoulder.
//
// SUN_NDOTL_SHARPNESS: N.L curve on the sun. 1.0 = pure Lambert, higher
// = sharper terminator (more contrast between
// lit and shaded faces of the same surface).
// ---------------------------------------------------------------------
const float AMBIENT_SCALE = 0.65;
const float SUN_DIFFUSE_SCALE = 1.5;
const float SUN_NDOTL_SHARPNESS = 1.25;
float length2(vec3 v)
{
return dot(v, v);
}
// share of the sun (the moon) held back before the fragment by the shadow map, 0..1
float calc_shadow_map();
float calc_shadow()
{
return calc_shadow_map();
}
float calc_shadow_map()
{
if (SHADOWMAP_ENABLED)
{
float distance = dot(f_pos.xyz, f_pos.xyz);
uint cascade;
for (cascade = 0U; cascade < MAX_CASCADES; cascade++)
if (distance <= cascade_end[cascade])
break;
// cascades fitted to the depth of the view of an earlier frame (gfx.shadow.sdsm) cover only the area of the points seen
// then: a point at the edge of the view or one which came closer since may be outside the map of its cascade. it goes to
// the first further cascade whose map holds it with the reach of the filter, and is lit past the last one
if (shadow_fit.x > 0.0)
{
float border = 4.0 / float(textureSize(shadowmap, 0).x);
for (; cascade < MAX_CASCADES; cascade++)
{
vec2 fitcoords = (lightview[cascade] * vec4(f_pos.xyz, 1.0)).xy;
if (all(greaterThanEqual(fitcoords, vec2(border))) && all(lessThanEqual(fitcoords, vec2(1.0 - border))))
break;
}
if (cascade >= MAX_CASCADES)
return 0.0;
}
// Normal offset: sample the shadow map from a point pushed along the surface normal
// by a fraction of the cascade's texel footprint. A constant depth bias can't keep up
// with large texels (far cascades, small shadow maps) on surfaces at a grazing angle
// to the sun, which then show shadow acne stripes. lightview maps view space metres
// to [0,1] shadow map coordinates, so its x/y row lengths give texels per metre.
vec3 shadownormal = normalize(f_normal);
float shadowNdotL = clamp(dot(shadownormal, normalize(-lights[0].dir)), 0.0, 1.0);
mat4 shadowview = lightview[cascade];
float texelspermetre = min(length(vec3(shadowview[0][0], shadowview[1][0], shadowview[2][0])),
length(vec3(shadowview[0][1], shadowview[1][1], shadowview[2][1]))) * float(textureSize(shadowmap, 0).x);
float texelsize = 1.0 / texelspermetre;
vec3 shadowpos = f_pos.xyz + shadownormal * (texelsize * 1.5 * (1.0 - shadowNdotL));
vec4 lightpos = shadowview * vec4(shadowpos, 1.0);
vec3 coords = lightpos.xyz / lightpos.w;
if (coords.z < 0.0)
return 0.0f;
float bias = 0.00005f * float(cascade + 1U);
// Fast path: 4 textureGather() calls cover a 4x4 texel block. Raw gathered
// comparisons must be weighted by the sub-texel position (bilinear weights),
// otherwise the result is constant across each shadow map texel and shadow
// edges turn into texel-sized stair steps. With per-axis weights
// (1-f, 1, 1, f) this equals a 3x3 grid of hardware bilinear PCF taps.
vec2 size = vec2(textureSize(shadowmap, 0).xy);
vec2 uv = coords.xy * size - 0.5;
vec2 base = floor(uv);
vec2 f = uv - base;
float refz = coords.z + bias;
float layer = float(cascade);
// sampling at texel corners selects blocks with lower-left texel base-1 and base+1
vec4 g00 = textureGather(shadowmap, vec3((base + vec2(0.0, 0.0)) / size, layer), refz);
vec4 g10 = textureGather(shadowmap, vec3((base + vec2(2.0, 0.0)) / size, layer), refz);
vec4 g01 = textureGather(shadowmap, vec3((base + vec2(0.0, 2.0)) / size, layer), refz);
vec4 g11 = textureGather(shadowmap, vec3((base + vec2(2.0, 2.0)) / size, layer), refz);
// gather component order: x=(0,1) y=(1,1) z=(1,0) w=(0,0)
float shadow = dot(g00, vec4(1.0 - f.x, 1.0, 1.0 - f.y, (1.0 - f.x) * (1.0 - f.y)))
+ dot(g10, vec4(1.0, f.x, f.x * (1.0 - f.y), 1.0 - f.y))
+ dot(g01, vec4((1.0 - f.x) * f.y, f.y, 1.0, 1.0 - f.x))
+ dot(g11, vec4(f.y, f.x * f.y, f.x, 1.0));
return shadow / 9.0;
}
else
{
return 0.0;
}
}
// -----------------------------------------------------------------------
// GGX Microfacet BRDF helpers (Cook-Torrance)
// -----------------------------------------------------------------------
// Trowbridge-Reitz (GGX) Normal Distribution Function
// D(N,H,α) = α⁴ / (π · ((NdotH)²·(α⁴−1)+1)²)
// α = roughness² (perceptual remapping so the slider feels linear)
float D_GGX(float NdotH, float roughness)
{
float a = roughness * roughness; // perceptual -> linear roughness
float a2 = a * a;
float d = (NdotH * NdotH) * (a2 - 1.0) + 1.0;
return a2 / (3.14159265359 * d * d);
}
// Schlick-GGX single-term masking/shadowing (k remapped for direct lighting)
float G_SchlickGGX(float NdotX, float roughness)
{
float r = roughness + 1.0;
float k = (r * r) * (1.0 / 8.0); // k_direct = (roughness+1)²/8
return NdotX / (NdotX * (1.0 - k) + k);
}
// Separable Smith geometry term (not height-correlated)
// G(N,V,L) = G_SchlickGGX(NdotV) · G_SchlickGGX(NdotL)
float G_Smith(float NdotV, float NdotL, float roughness)
{
return G_SchlickGGX(NdotV, roughness) * G_SchlickGGX(NdotL, roughness);
}
const float BRDF_PI = 3.14159265359;
// the smoothest surface the lights are reflected by: a mirror would make a highlight smaller than a pixel
const float MIN_ROUGHNESS = 0.045;
// cap of the brdf for the lamps and headlights (see specular_light()): a tiny light next to a polished surface
const float LOCAL_LIGHT_BRDF_LIMIT = 16.0;
// no cap for the sun
const float SUN_BRDF_LIMIT = 1.0e6;
// the specular reflectance at normal incidence and the perceptual roughness the lights are reflected with, worked out by
// apply_lights() for the fragment before it adds the lamps (add_local_lights())
vec3 spec_f0 = vec3(0.04);
float spec_roughness = 0.5;
// a matte material (mat_default) reflects nothing: neither the surroundings nor the sun and the lamps, only the diffuse light
bool matte = false;
// light of a light from Light_dir reflected by the surface, in the units of the diffuse shading (a white lambertian surface
// facing the light reflects 1 of it): pi times the cook-torrance brdf with schlick's fresnel of F0, times n.l. Limit caps the
// brdf without the n.l
vec3 specular_light(vec3 light_dir, vec3 fragnormal, vec3 f0, float roughness, float limit)
{
float NdotL = dot(fragnormal, light_dir);
if (NdotL <= 0.0)
return vec3(0.0);
vec3 V = normalize(-f_pos.xyz);
vec3 H = normalize(light_dir + V);
float NdotV = max(dot(fragnormal, V), 1e-4);
float NdotH = max(dot(fragnormal, H), 0.0);
float VdotH = max(dot(V, H), 0.0);
float brdf = D_GGX(NdotH, roughness) * G_Smith(NdotV, NdotL, roughness) / (4.0 * NdotL * NdotV);
vec3 fresnel = f0 + (1.0 - f0) * pow(1.0 - VdotH, 5.0);
return fresnel * (min(brdf, limit) * NdotL * BRDF_PI);
}
// perceptual roughness raised where the normal changes much from a pixel to the next (normal maps, tight curves), so the
// highlight of a detail smaller than a pixel spreads over the pixel instead of flickering. Kaplanyan, Tokuyoshi, "Improved
// geometric specular antialiasing", 2019
float specular_antialiasing(vec3 fragnormal, float roughness)
{
vec3 dndx = dFdx(fragnormal);
vec3 dndy = dFdy(fragnormal);
float variance = 0.25 * (dot(dndx, dndx) + dot(dndy, dndy));
float kernel = min(2.0 * variance, 0.18);
float a2 = roughness * roughness * roughness * roughness;
return sqrt(sqrt(clamp(a2 + kernel, 0.0, 1.0)));
}
// the materials without a specgloss map give glossiness: (param[1].w, 10 by default) as the exponent of the blinn-phong
// highlight of the old renderer. a lobe of exponent n is about a ggx one of alpha sqrt(2 / (n + 2))
float phong_roughness(float exponent)
{
return sqrt(sqrt(2.0 / (max(exponent, 0.0) + 2.0)));
}
// glossiness which apply_lights() turns into the roughness of a blinn-phong exponent (it takes 1 - glossiness / param[1].w)
float phong_glossiness(float exponent)
{
return (1.0 - phong_roughness(exponent)) * max(abs(param[1].w), 1.0);
}
// the reflection of the surroundings of a material without a specgloss map is a smooth varnish over the rough surface: the
// old renderer showed it as a sharp reflection, as much of it as reflection: (and the masks of the material) asked for
bool legacy_coat = false;
// set up the gloss of a material without a specgloss map, before apply_lights()
void legacy_gloss()
{
glossiness = phong_glossiness(abs(param[1].w));
legacy_coat = true;
}
// Returns vec2(diffuse, specular) for a single punctual light.
//
// diffuse – Lambert N·L (Fresnel-weighted diffuse is handled per-material
// in apply_lights, so we return raw N·L here).
// specular – Cook-Torrance GGX: D·G / (4·NdotL·NdotV).
// The Fresnel factor (F) is intentionally omitted here;
// apply_lights already carries a per-material Fresnel term
// that is applied to env reflections and can be routed to
// direct specular there.
//
// Roughness is derived identically to env_roughness in apply_lights so
// that direct and indirect specular highlights read consistently.
vec2 calc_light(vec3 light_dir, vec3 fragnormal)
{
vec3 N = fragnormal;
vec3 L = light_dir;
vec3 V = normalize(-f_pos.xyz);
vec3 H = normalize(L + V);
float NdotL = max(dot(N, L), 0.0);
float NdotV = max(dot(N, V), 1e-4);
float NdotH = max(dot(N, H), 0.0);
float diffuse_v = NdotL;
// Mirror the env-map roughness derivation so direct and indirect lobes match.
// glossiness == param[1].w → roughness == 0.04 (near-mirror)
// glossiness == 0 → roughness == 1.0 (fully diffuse)
float roughness = clamp(1.0 - glossiness / max(abs(param[1].w), 1.0), 0.04, 1.0);
// Cook-Torrance specular (no Fresnel — see above):
// f_spec = D(N,H,α) · G(N,V,L,α) / (4 · NdotL · NdotV)
float D = D_GGX(NdotH, roughness);
float G = G_Smith(NdotV, NdotL, roughness);
float specular_v = (NdotL > 0.0)
? (D * G) / max(4.0 * NdotL * NdotV, 1e-4)
: 0.0;
return vec2(diffuse_v, specular_v);
}
vec2 calc_point_light(light_s light, vec3 fragnormal)
{
vec3 light_dir = normalize(light.pos - f_pos.xyz);
vec2 val = calc_light(light_dir, fragnormal);
val.x += light.ambient;
val *= light.intensity;
float distance = length(light.pos - f_pos.xyz);
float atten = 1.0f / (distance * distance);
//float atten = 1.0f / (1.0f + light.linear * distance + light.quadratic * (distance * distance));
return val * atten;
}
vec2 calc_spot_light(light_s light, vec3 fragnormal)
{
vec3 light_dir = normalize(light.pos - f_pos.xyz);
float theta = dot(light_dir, normalize(-light.dir));
float epsilon = light.in_cutoff - light.out_cutoff;
float intensity = clamp((theta - light.out_cutoff) / epsilon, 0.0, 1.0);
vec2 point = calc_point_light(light, fragnormal);
return point * intensity;
}
vec2 calc_dir_light(light_s light, vec3 fragnormal)
{
vec3 light_dir = normalize(-light.dir);
return calc_light(light_dir, fragnormal);
}
// dpdx and dpdy are the change of the view space position of the fragment from a pixel to the next one. the beam texture is
// sampled with gradients worked out from them instead of the ones of the hardware: those are undefined where the fragments of
// a quad do not go through the same lights, which is what happens at the edges of the clusters
vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
{
vec4 headlightpos = light.headlight_projection * f_pos;
vec3 coords = headlightpos.xyz / headlightpos.w;
if (coords.z > 1.0)
return vec2(0.0);
if (coords.z < 0.0)
return vec2(0.0);
// the beam is what the projection takes in. beyond its sides the texture would be clamped to its edge, and near the projector
// (where a pixel spans many texels) its coarsest levels spill the average of the whole beam over the surroundings
if (coords.x < 0.0 || coords.x > 1.0 || coords.y < 0.0 || coords.y > 1.0)
return vec2(0.0);
vec3 light_dir = normalize(light.pos - f_pos.xyz);
// Tighter wrap (was +0.25): faces angled away from the headlight cone
// fall off to dark much faster, so cab/exterior surfaces read with a
// clear directional shape instead of a flat half-lit wash.
vec2 part = vec2(1.0) * clamp(dot(fragnormal, light_dir) + 0.10, 0.0, 1.0);
float distance = length(light.pos - f_pos.xyz);
float atten = 1.0f / (1.0f + light.linear * distance + light.quadratic * (distance * distance));
atten *= mix(1.0, 0.0, clamp((coords.z - 0.998) * 500.0, 0.0, 1.0));
vec2 beamcoords = headlightpos.xy / headlightpos.w;
vec4 dheadlightposdx = light.headlight_projection * vec4(dpdx, 0.0);
vec4 dheadlightposdy = light.headlight_projection * vec4(dpdy, 0.0);
vec2 dbeamdx = (dheadlightposdx.xy - beamcoords * dheadlightposdx.w) / headlightpos.w;
vec2 dbeamdy = (dheadlightposdy.xy - beamcoords * dheadlightposdy.w) / headlightpos.w;
vec3 lights = textureGrad(headlightmap, beamcoords, dbeamdx, dbeamdy).rgb * light.headlight_weights.rgb;
float lightintensity = max(max(lights.r, lights.g), lights.b);
// x the diffuse light, y the light the specular reflection takes (specular_light() brings its own n.l)
return vec2(part.x, 1.0) * (atten * lightintensity);
}
// closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit
const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01;
// light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse
// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the distance, and smoothly to nothing at the range of the light,
// 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the
// outer one
vec2 calc_local_light(light_s light, vec3 fragnormal)
{
vec3 tolight = light.pos - f_pos.xyz;
float distance2 = dot(tolight, tolight);
vec3 light_dir = tolight * inversesqrt(max(distance2, 1e-8));
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2);
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir)));
if (atten <= 0.0)
return vec2(0.0);
return vec2(max(dot(fragnormal, light_dir), 0.0), 1.0) * atten;
}
// index of the light cluster the fragment is in, see gl/cluster_grid.h: its screen tile and slice of depth. the depth is the clip
// space w, which is 1 / gl_FragCoord.w. a tile is kept in the grid, as the far edge of the render target and rounding
// right below it give one past the last
uint fragment_cluster()
{
ivec2 tile = ivec2(gl_FragCoord.xy * (vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y) / cluster_viewport_size));
tile = clamp(tile, ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
int slice = clamp(int(floor(cluster_slice_bias - log(gl_FragCoord.w) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1);
return uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice));
}
// adds the light of a headlight or lamp to the diffuse and the specular light of the fragment
void add_local_light(light_s light, vec2 part, vec3 fragnormal, inout vec3 diffuse, inout vec3 specular)
{
if (part.y <= 0.0)
return;
vec3 color = light.color * light.intensity;
diffuse += color * part.x;
if (matte)
return;
specular += color * (part.y * param[1].y) * specular_light(normalize(light.pos - f_pos.xyz), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT);
}
// adds the light of the headlights and lamps which reach the fragment to its diffuse and specular light. these are the ones in
// the list of its cluster when the lights are clustered (the sun is not on the list, the callers take care of it), and the
// first lights_count - 1 of lights[] otherwise, which are headlights only. the specular light is reflected with spec_f0 and
// spec_roughness, and is already coloured by the fresnel term
void add_local_lights(vec3 fragnormal, inout vec3 diffuse, inout vec3 specular)
{
// worked out before the loops, which the fragments of a quad do not leave together
vec3 dpdx = dFdx(f_pos.xyz);
vec3 dpdy = dFdy(f_pos.xyz);
if (lights_clustered != 0U)
{
cluster_range_s range = cluster_ranges[fragment_cluster()];
for (uint i = 0U; i < range.count; i++)
{
light_s light = lights[cluster_light_indices[range.offset + i]];
vec2 part = (light.type == LIGHT_HEADLIGHTS) ? calc_headlights(light, fragnormal, dpdx, dpdy) : calc_local_light(light, fragnormal);
add_local_light(light, part, fragnormal, diffuse, specular);
}
}
else
{
for (uint i = 1U; i < lights_count; i++)
{
light_s light = lights[i];
add_local_light(light, calc_headlights(light, fragnormal, dpdx, dpdy), fragnormal, diffuse, specular);
}
}
}
// the light of the headlights and lamps added to Fragcolor, diffuse and specular together (for shaders which don't tell
// the albedo from the reflection)
vec3 add_headlights(vec3 fragcolor, vec3 fragnormal)
{
vec3 diffuse = vec3(0.0);
vec3 specular = vec3(0.0);
add_local_lights(fragnormal, diffuse, specular);
return fragcolor + diffuse + specular;
}
// -----------------------------------------------------------------------
// Split-sum environment BRDF (Karis / UE4 analytic approximation).
//
// This is the missing piece that made matte specgloss surfaces "shine like
// crazy": previously the env reflection was added at full strength
// (envcolor * fresnel * reflectivity) and roughness only blurred the mip,
// never dimmed the energy. A rough surface therefore mirrored the bright
// sky just as strongly as a polished one.
//
// EnvBRDFApprox returns the pre-integrated specular scale (the "DFG" term)
// for a given F0, roughness and view angle. For rough surfaces it collapses
// toward ~0, so low-glossiness materials reflect almost nothing — matching
// what Substance 3D Painter shows. Polished surfaces keep their full
// reflection, and the grazing-angle Fresnel edge is preserved.
// roughness 1.0 (matte) -> scale ~0.015 (virtually no reflection)
// roughness 0.0 (mirror) -> scale ~F0..1 (full reflection + Fresnel rim)
vec3 EnvBRDFApprox(vec3 F0, float roughness, float NoV)
{
const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
const vec4 c1 = vec4( 1.0, 0.0425, 1.040, -0.040);
vec4 r = roughness * c0 + c1;
float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
return F0 * AB.x + AB.y;
}
// Diffuse colour of the submodel (t3d/e3d) or shape, used as the albedo multiplier:
// untextured materials take it as their colour, textured ones multiply the base texture by it
// (same as the legacy renderer's GL_MODULATE). param[0].rgb holds the authored sRGB colour,
// param[1].x its intensity (HSV value unless a material overrides it).
vec3 material_diffuse()
{
vec3 diffusecolor = pow(max(param[0].rgb, vec3(0.0)), vec3(2.2));
float value = max(param[0].r, max(param[0].g, param[0].b));
return (value > 0.0 ? diffusecolor * (param[1].x / value) : vec3(0.0));
}
// lights the surface: the ambient light, the sun (the moon), the headlights and lamps and the reflection of the surroundings.
// the units are the ones of the ambient light, the irradiance over pi: a white lambertian surface lit by 1 of it shows 1.
// Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the
// reflectance of 4% at normal incidence), Reflectivity how much of the reflected surroundings is the cubemap rather than the
// ambient light, Shadowtone the share of the sun left in its shadow (1 for the materials which take no shadows).
// a Sunless surface (the interior of a cab) gets no light of the sun, its shadow only darkens the ambient light
vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone, bool sunless)
{
// Diffuse colour is applied once, to the albedo; the light terms below are not scaled by it again
texturecolor *= material_diffuse();
float polished = 0.0;
if (sunless)
{
// Cab interior: dim ambient less than the exterior path - ambient
// is the dominant indoor illumination, but it was still too bright.
fragcolor *= 0.80;
}
else
{
#ifndef NO_WEATHERING
float weathered = (param[2].w > 0.0 ? clamp(param[2].w * UserData.x, 0.0, 1.0) : 0.0);
float tinted = (param[2].w < 0.0 ? clamp(-param[2].w * UserData.x, 0.0, 1.0) : 0.0);
// a tint only scales the colour of the texture, the steel keeps its own polish
polished = weathered * smoothstep(0.03, 0.12, dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)));
float weatheredluma = min(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 0.05);
vec3 unused = texturecolor * (weatheredluma / max(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 1e-4));
unused = mix(unused, vec3(1.0, 0.82, 0.74) * (weatheredluma / 0.8525), 0.5);
texturecolor = mix(texturecolor, unused, smoothstep(0.0, 0.4, weathered));
texturecolor = (tinted > 0.0 ? texturecolor * mix(vec3(1.0), param[2].rgb, tinted) : texturecolor);
specularity = mix(specularity, min(specularity, 0.03), smoothstep(0.0, 0.4, polished));
metalic = mix(metalic, 0.0, polished);
glossiness = mix(glossiness, min(glossiness, 0.06 * abs(param[1].w)), polished);
reflectivity = mix(reflectivity, min(reflectivity, 0.08 * param[1].z), smoothstep(0.0, 0.4, polished));
#endif
// Scale ambient before it gets tinted by the texture.
// Sun, headlights and emission are added afterwards so they are NOT
// attenuated by AMBIENT_SCALE - this only dims the indirect term.
fragcolor *= AMBIENT_SCALE;
}
vec3 emissioncolor = vec3(emission);
vec3 view_dir = normalize(-f_pos.xyz);
float NdotV = max(dot(fragnormal, view_dir), 1e-4);
// glossiness == param[1].w -> mirror, glossiness == 0 -> fully rough
float roughness = clamp(1.0 - glossiness / max(abs(param[1].w), 1.0), 0.0, 1.0);
roughness = max(specular_antialiasing(fragnormal, roughness), MIN_ROUGHNESS);
// metals reflect with their base colour, dielectrics with the specular level (0.5 -> 0.04)
vec3 f0 = mix(vec3(0.08 * specularity), texturecolor, metalic);
spec_f0 = f0;
spec_roughness = roughness;
// Pre-integrated env BRDF: how much of the surroundings the surface reflects, for its F0, roughness and the view angle.
// the surroundings are the cubemap where the material asks for it (reflectivity) and the ambient light elsewhere, so a
// surface reflects them always, the rough one barely
vec3 env_spec = EnvBRDFApprox(f0, roughness, NdotV);
float env_roughness = roughness;
vec3 cubemap_spec = env_spec;
float cubemap_share = max(reflectivity, 0.0);
float ambient_share = clamp(1.0 - reflectivity, 0.0, 1.0);
if (matte)
{
env_spec = vec3(0.0);
cubemap_share = 0.0;
}
// what the diffuse layer is left with
vec3 diffuse_albedo = texturecolor * (1.0 - metalic) * (1.0 - env_spec);
if (legacy_coat)
{
// the varnish: a smooth dielectric layer reflecting the cubemap, worn off where the material is weathered
env_roughness = specular_antialiasing(fragnormal, roughness * smoothstep(0.0, 0.4, polished));
cubemap_spec = EnvBRDFApprox(mix(vec3(0.04), texturecolor, metalic), env_roughness, NdotV);
ambient_share = 1.0;
diffuse_albedo *= 1.0 - cubemap_spec * min(cubemap_share, 1.0);
}
vec3 envcolor = (matte ? vec3(0.0) : envmap_color_rough(fragnormal, env_roughness));
if (lights_count == 0U)
return fragcolor * diffuse_albedo
+ emissioncolor * texturecolor
+ fragcolor * env_spec * ambient_share
+ envcolor * cubemap_spec * cubemap_share;
vec3 light_dir = normalize(-lights[0].dir);
float NdotL = max(dot(fragnormal, light_dir), 0.0);
// Sharpen sun N.L falloff so the lit-to-shaded terminator on cab
// panels, vehicle bodies and terrain reads as a clear edge rather
// than a soft Lambertian ramp. Tunable via SUN_NDOTL_SHARPNESS.
float sun_NdotL = pow(NdotL, SUN_NDOTL_SHARPNESS);
// the shadows of the clouds come with the weather (stage P2): nothing holds the sun back yet
float cloudsun = 1.0;
// Sun HDR scale -> SUN_DIFFUSE_SCALE, the same for the diffuse and the specular light
vec3 sunlight = lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * cloudsun);
float shadow = 0.0;
if (shadowtone < 1.0)
shadow = clamp(calc_shadow(), 0.0, 1.0);
// the cubemap knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun
float cubemap_shadow = 1.0;
if (sunless)
{
// the sun adds no light in the cab, where it would fall its shadow darkens the ambient light
float diffuseamount = sun_NdotL * lights[0].intensity;
float darkness = (shadowtone < 1.0 ? max(shadow, 1.0 - cloudsun) : 0.0);
fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * darkness, 0.0, 1.0));
}
else
cubemap_shadow = 1.0 - (1.0 - shadowtone) * shadow * 0.5;
// the reflection of the surroundings, before the light of the sun and the lamps joins the ambient light
vec3 env_reflection = fragcolor * env_spec * ambient_share
+ envcolor * cubemap_spec * (cubemap_share * cubemap_shadow);
// the shadow leaves the share of the sun Shadowtone tells (the old shadow colour of the scenery) to the diffuse light,
// nothing to the highlight
if (!sunless)
fragcolor += sunlight * (sun_NdotL * (1.0 - (1.0 - shadowtone) * shadow));
vec3 specular = matte ? vec3(0.0) : sunlight * specular_light(light_dir, fragnormal, f0, roughness, SUN_BRDF_LIMIT) * (1.0 - shadow);
vec3 lampspecular = vec3(0.0);
add_local_lights(fragnormal, fragcolor, lampspecular);
return fragcolor * diffuse_albedo // ambient, sun and lamps on the diffuse layer
+ specular * param[1].y // highlight of the sun, fresnel coloured (metals take their base colour)
+ lampspecular // highlights of the lamps, param[1].y already applied
+ emissioncolor * texturecolor
+ env_reflection;
}
vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone)
{
return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, false);
}
// the interior of a cab: no light of the sun, its shadow darkens the ambient light
vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone)
{
return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, true);
}

View File

@@ -0,0 +1,4 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
#include "mat_colored.glsl"

View File

@@ -0,0 +1,60 @@
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#include "common.glsl"
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, zero)
#pragma param(glossiness, 1, 3, 1, glossiness)
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
// no texture: the diffuse colour alone makes the albedo, apply_lights() applies it to the white base
vec4 tex_color = vec4(1.0, 1.0, 1.0, param[0].a);
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 fragnormal = normalize(f_normal);
float reflectivity = param[1].z;
float specularity = dot(pow(param[0].rgb, vec3(2.2)), vec3(0.5));
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,4 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
#include "mat_default.glsl"

View File

@@ -0,0 +1,69 @@
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#include "common.glsl"
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, zero)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 fragnormal = normalize(f_normal);
matte = true;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, 0.0, 0.0, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
/*
float distance = dot(f_pos.xyz, f_pos.xyz);
if( distance <= cascade_end.x ) { color.r += 0.25; }
else if( distance <= cascade_end.y ) { color.g += 0.25; }
else if( distance <= cascade_end.z ) { color.b += 0.25; }
*/
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
}
}

View File

@@ -0,0 +1,4 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
#include "mat_colored.glsl"

View File

@@ -0,0 +1,4 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
#include "mat_default.glsl"

View File

@@ -0,0 +1,4 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
#include "mat_reflmap.glsl"

View File

@@ -0,0 +1,77 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, zero)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(detail_scale, 2, 0, 1, one)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma texture(detailnormalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D detailnormalmap;
#define NORMALMAP
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(detailnormalmap, f_coord * param[2].x).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
}
}

View File

@@ -0,0 +1,77 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, zero)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(specgloss, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D specgloss;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 fragnormal = normalize(f_normal);
float reflectivity = param[1].z;
float specularity = texture(specgloss, f_coord).r;
glossiness = texture(specgloss, f_coord).g * abs(param[1].w);
metalic = texture(specgloss, f_coord).b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
/*
float distance = dot(f_pos.xyz, f_pos.xyz);
if( distance <= cascade_end.x ) { color.r += 0.25; }
else if( distance <= cascade_end.y ) { color.g += 0.25; }
else if( distance <= cascade_end.z ) { color.b += 0.25; }
*/
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
}
}

View File

@@ -0,0 +1,89 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(detail_scale, 2, 0, 1, one)
#pragma param(detail_height_scale, 2, 1, 1, one)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#pragma texture(detailnormalmap, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D detailnormalmap;
#define NORMALMAP
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
vec3 fragcolor = ambient;
vec4 normal_map = texture(normalmap, f_coord);
vec4 detailnormal_map = texture(detailnormalmap, f_coord * param[2].x);
vec3 normal;
vec3 normaldetail;
normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0;
normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0));
normaldetail.xyz = normaldetail.xyz * param[2].y;
normal.xy = normal_map.rg* 2.0 - 1.0;
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z)));
float reflblend = (normal_map.a + detailnormal_map.a);
float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,96 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(detail_scale, 2, 0, 1, one)
#pragma param(detail_height_scale, 2, 1, 1, one)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#pragma texture(detailnormalmap, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D detailnormalmap;
#pragma texture(specgloss, 3, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 3) uniform sampler2D specgloss;
#define NORMALMAP
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec4 normal_map = texture(normalmap, f_coord);
vec4 detailnormal_map = texture(detailnormalmap, f_coord * param[2].x);
vec4 specgloss_map = texture(specgloss, f_coord);
vec3 normal;
vec3 normaldetail;
normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0;
normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0));
normaldetail.xyz = normaldetail.xyz * param[2].y;
normal.xy = normal_map.rg* 2.0 - 1.0;
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z)));
float reflblend = (normal_map.a + detailnormal_map.a);
float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend;
float specularity = specgloss_map.r;
glossiness = specgloss_map.g * abs(param[1].w);
metalic = specgloss_map.b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,152 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 6) in vec3 TangentFragPos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(detail_scale, 2, 0, 1, one)
#pragma param(detail_height_scale, 2, 1, 1, one)
#pragma param(height_scale, 2, 2, 1, zero)
#pragma param(height_offset, 2, 3, 1, zero)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#pragma texture(detailnormalmap, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D detailnormalmap;
#define PARALLAX
// param[2].w is the height offset, not the weathering
#define NO_WEATHERING
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
void main()
{
//parallax mapping
vec3 viewDir = normalize(-TangentFragPos); //tangent view pos - tangent frag pos
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
vec4 normal_map = texture(normalmap, f_coord_p);
vec4 detailnormal_map = texture(detailnormalmap, f_coord_p * param[2].x);
vec4 tex_color = texture(diffuse, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
vec3 normaldetail;
normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0;
normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0));
normaldetail.xyz = normaldetail.xyz * param[2].y;
normal.xy = normal_map.rg* 2.0 - 1.0;
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z)));
float reflectivity = param[1].z * normal_map.a;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
{
float pos_len = length(f_pos.xyz);
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
vec2 dcoorddx = dFdx(f_coord);
vec2 dcoorddy = dFdy(f_coord);
if (pos_len > 100.0) {
return f_coord;
}
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
if (EXTRAEFFECTS_ENABLED)
{
const float minLayers = 8.0;
const float maxLayers = 32.0;
float LayersWeight = pos_len / 20.0;
vec2 currentTexCoords = f_coord;
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
float currentLayerDepth = 0.0; // depth of current layer
vec2 P = viewDir.xy * param[2].z * fade; // the amount to shift the texture coordinates per layer (from vector P)
vec2 deltaTexCoords = P / numLayers;
while(currentLayerDepth < currentDepthMapValue)
{
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
currentLayerDepth += layerDepth; // get depth of next layer
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
}
else
{
float height = texture(normalmap, f_coord).b;
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].z - param[2].w) * 0.2) * fade;
return f_coord - p;
}
}

View File

@@ -0,0 +1,158 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 6) in vec3 TangentFragPos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(detail_scale, 2, 0, 1, one)
#pragma param(detail_height_scale, 2, 1, 1, one)
#pragma param(height_scale, 2, 2, 1, zero)
#pragma param(height_offset, 2, 3, 1, zero)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#pragma texture(specgloss, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D specgloss;
#pragma texture(detailnormalmap, 3, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 3) uniform sampler2D detailnormalmap;
#define PARALLAX
// param[2].w is the height offset, not the weathering
#define NO_WEATHERING
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
void main()
{
//parallax mapping
vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
vec4 normal_map = texture(normalmap, f_coord_p);
vec4 detailnormal_map = texture(detailnormalmap, f_coord_p * param[2].x);
vec4 tex_color = texture(diffuse, f_coord_p);
vec4 specgloss_map = texture(specgloss, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
vec3 fragcolor = ambient;
vec3 normal;
vec3 normaldetail;
normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0;
normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0));
normaldetail.xyz = normaldetail.xyz * param[2].y;
normal.xy = normal_map.rg* 2.0 - 1.0;
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z)));
float reflectivity = param[1].z * normal_map.a;
float specularity = specgloss_map.r;
glossiness = specgloss_map.g * abs(param[1].w);
metalic = specgloss_map.b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
{
float pos_len = length(f_pos.xyz);
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
vec2 dcoorddx = dFdx(f_coord);
vec2 dcoorddy = dFdy(f_coord);
if (pos_len > 100.0) {
return f_coord;
}
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
if (EXTRAEFFECTS_ENABLED)
{
const float minLayers = 8.0;
const float maxLayers = 32.0;
float LayersWeight = pos_len / 20.0;
vec2 currentTexCoords = f_coord;
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
float currentLayerDepth = 0.0; // depth of current layer
vec2 P = viewDir.xy * param[2].z * fade; // the amount to shift the texture coordinates per layer (from vector P)
vec2 deltaTexCoords = P / numLayers;
while(currentLayerDepth < currentDepthMapValue)
{
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
currentLayerDepth += layerDepth; // get depth of next layer
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
}
else
{
float height = texture(normalmap, f_coord).b;
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].z - param[2].w) * 0.2) * fade;
return f_coord - p;
}
}

View File

@@ -0,0 +1,29 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
#include "tonemapping.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
vec4 color = vec4(1.0, 0.0, 1.0, 1.0);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0f);
}
}

View File

@@ -0,0 +1,76 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#define NORMALMAP
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord).a;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,81 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#pragma texture(specgloss, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D specgloss;
#define NORMALMAP
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord).a;
float specularity = texture(specgloss, f_coord).r;
glossiness = texture(specgloss, f_coord).g * abs(param[1].w);
metalic = texture(specgloss, f_coord).b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,137 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 6) in vec3 TangentFragPos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(height_scale, 2, 1, 1, zero)
#pragma param(height_offset, 2, 2, 1, zero)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#define PARALLAX
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
void main()
{
//parallax mapping
vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
vec4 tex_color = texture(diffuse, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord_p).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord_p).a;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
{
float pos_len = length(f_pos.xyz);
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
vec2 dcoorddx = dFdx(f_coord);
vec2 dcoorddy = dFdy(f_coord);
if (pos_len > 100.0) {
return f_coord;
}
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
if (EXTRAEFFECTS_ENABLED)
{
const float minLayers = 8.0;
const float maxLayers = 32.0;
float LayersWeight = pos_len / 20.0;
vec2 currentTexCoords = f_coord;
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
float currentLayerDepth = 0.0; // depth of current layer
vec2 P = viewDir.xy * param[2].y * fade; // the amount to shift the texture coordinates per layer (from vector P)
vec2 deltaTexCoords = P / numLayers;
while(currentLayerDepth < currentDepthMapValue)
{
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
currentLayerDepth += layerDepth; // get depth of next layer
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
}
else
{
float height = texture(normalmap, f_coord).b;
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].y - param[2].z) * 0.2) * fade;
return f_coord - p;
}
}

View File

@@ -0,0 +1,142 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 6) in vec3 TangentFragPos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(height_scale, 2, 1, 1, zero)
#pragma param(height_offset, 2, 2, 1, zero)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#pragma texture(specgloss, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D specgloss;
#define PARALLAX
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
void main()
{
//parallax mapping
vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
vec4 tex_color = texture(diffuse, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord_p).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord_p).a;
float specularity = texture(specgloss, f_coord_p).r;
glossiness = texture(specgloss, f_coord_p).g * abs(param[1].w);
metalic = texture(specgloss, f_coord_p).b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
{
float pos_len = length(f_pos.xyz);
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
vec2 dcoorddx = dFdx(f_coord);
vec2 dcoorddy = dFdy(f_coord);
if (pos_len > 100.0) {
return f_coord;
}
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
if (EXTRAEFFECTS_ENABLED)
{
const float minLayers = 8.0;
const float maxLayers = 32.0;
float LayersWeight = pos_len / 20.0;
vec2 currentTexCoords = f_coord;
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
float currentLayerDepth = 0.0; // depth of current layer
vec2 P = viewDir.xy * param[2].y * fade; // the amount to shift the texture coordinates per layer (from vector P)
vec2 deltaTexCoords = P / numLayers;
while(currentLayerDepth < currentDepthMapValue)
{
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
currentLayerDepth += layerDepth; // get depth of next layer
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
}
else
{
float height = texture(normalmap, f_coord).b;
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].y - param[2].z) * 0.2) * fade;
return f_coord - p;
}
}

View File

@@ -0,0 +1,216 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// Windshield rain effects
// by @lcddisplay; wiper movement @MichauSto
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma texture(diffuse, 0, sRGB_A)
#pragma texture(raindropsatlas, 1, sRGB_A)
#pragma texture(wipermask, 2, sRGB_A)
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, zero)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(raindrop_grid_size, 2, 0, 1, one)
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
#include "random.glsl"
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D raindropsatlas;
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D wipermask;
const float specular_intensity = 1.0;
const float wobble_strength = 0.002;
const float wobble_speed = 30.0;
vec4 getDropTex(float choice, vec2 uv) {
vec2 offset;
if (choice < 0.25) offset = vec2(0.0, 0.0);
else if (choice < 0.5) offset = vec2(0.5, 0.0);
else if (choice < 0.75) offset = vec2(0.0, 0.5);
else offset = vec2(0.5, 0.5);
return texture(raindropsatlas, offset + uv * 0.5);
}
float GetMixFactor(in vec2 co, out float side);
void main() {
vec4 tex_color = texture(diffuse, f_coord);
if (tex_color.a < 0.01) discard;
vec2 rainCoord = f_coord;
float gridSize = ceil(param[2].x);
const float numDrops = 20000.0;
const float cycleDuration = 4.0;
float squareMin = 0.5 / gridSize;
float squareMax = 1.2 / gridSize;
vec2 cell = floor(rainCoord * gridSize);
vec3 dropLayer = vec3(0.0);
float dropMaskSum = 0.0;
// Grid of 9 droplets in immediate neighbourhood
for (int oy = -1; oy <= 1; oy++) {
for (int ox = -1; ox <= 1; ox++) {
vec2 neighborCell = cell + vec2(ox, oy);
vec2 neighborCenter = (neighborCell + .5) / gridSize;
float side;
float mixFactor = GetMixFactor(neighborCenter, side);
uint seed = Hash(uvec3(neighborCell, side));
if(mixFactor < RandF(seed)) {
continue;
}
// Show a percentage of droplets given by rain intensity param
float activationSeed = RandF(seed);
if (activationSeed > rain_params.x)
continue; // kropla nieaktywna
// Randomly modulate droplet center & size
vec2 dropCenter = (neighborCell + vec2(RandF(seed), RandF(seed))) / gridSize;
float squareSize = mix(squareMin, squareMax, RandF(seed));
float lifeTime = time + RandF(seed) * cycleDuration;
float phase = fract(lifeTime / cycleDuration);
float actiwe = clamp(1.0 - phase, 0.0, 1.0);
// Gravity influence (TODO add vehicle speed & wind here!)
float gravityStart = 0.5;
float gravityPhase = smoothstep(gravityStart, 1.0, phase);
float dropMass = mix(0.3, 1.2, RandF(seed));
float gravitySpeed = 0.15 * dropMass;
vec2 gravityOffset = vec2(0.0, gravityPhase * gravitySpeed * phase);
// Random wobble
bool hasWobble = (RandF(seed) < 0.10);
vec2 wobbleOffset = vec2(0.0);
if (hasWobble && gravityPhase > 0.0) {
float intensity = sin(time * wobble_speed + RandF(seed) * 100.) * wobble_strength * gravityPhase;
wobbleOffset = vec2(intensity, 0.0);
}
vec2 slideOffset = gravityOffset + wobbleOffset;
// Flatten droplets influenced by gravity
float flattenAmount = smoothstep(0.1, 0.5, gravityPhase);
float flattenX = mix(1.0, 0.4, flattenAmount);
float stretchY = mix(1.0, 1.6, flattenAmount);
// Droplet local position & mask
vec2 diff = (rainCoord + slideOffset) - dropCenter;
diff.x *= 1.0 / flattenX;
diff.y *= 1.0 / stretchY;
float mask = smoothstep(squareSize * 0.5, squareSize * 0.45, max(abs(diff.x), abs(diff.y)));
if (mask > 0.001) {
vec2 localUV = (diff + squareSize * 0.5) / squareSize;
float choice = RandF(seed);
vec4 dropTex = getDropTex(choice, localUV);
float sharpAlpha = smoothstep(0.3, 0.9, dropTex.a);
float ambLum = clamp(dot(ambient, vec3(0.2126, 0.7152, 0.0722)), 0.0, 1.0);
float sunFactor = pow(clamp(ambLum * 6.0, 0.0, 1.0), 0.5);
float dynBright = mix(0.0, 1.0, sunFactor);
float colorLuma = length(dropTex.rgb);
float alphaRange = smoothstep(0.1, 0.3, colorLuma);
float blackAlpha = mix(0.25, 0.85, alphaRange);
float sparkle = 0.0;
if (hasWobble) {
sparkle = pow(abs(sin(f_coord.x * 8.0 + time * 2.0 + RandF(seed) * 6.2831)), 40.0)
* mix(0.2, 1.0, sunFactor);
}
vec3 specularColor = vec3(1.0) * specular_intensity * sparkle;
vec3 dropLit = dropTex.rgb * dynBright + specularColor * sharpAlpha;
dropLayer += dropLit * sharpAlpha * actiwe * blackAlpha * mask;
dropMaskSum += sharpAlpha * actiwe * blackAlpha * mask;
}
}
}
vec3 finalMix = dropLayer;
float alphaOut = clamp(dropMaskSum, 0.0, 1.0);
out_color = vec4(finalMix, alphaOut);
{ // Overlay windshield texture with alpha
vec3 fragcolor = ambient;
vec3 fragnormal = normalize(f_normal);
float reflectivity = param[1].z;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(fragcolor, tex_color.a * alpha_mult);
out_color.xyz = apply_fog(mix(out_color.xyz, color.xyz, color.a));
out_color.a = mix(out_color.a, 1., color.a);
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0);
}
}
float GetMixFactor(in vec2 co, out float side) {
vec4 movePhase = wiper_pos;
bvec4 is_out = lessThanEqual(movePhase, vec4(1.));
movePhase = mix(vec4(2.) - movePhase, movePhase, is_out);
vec4 mask = texture(wipermask, co);
vec4 areaMask = step(.001, mask);
vec4 maskVal = mix(1. - mask, mask, is_out);
vec4 wipeWidth = smoothstep(1., .9, movePhase) * .25;
vec4 cleaned = smoothstep(movePhase - wipeWidth, movePhase, maskVal) * areaMask;
vec4 side_v = step(maskVal, movePhase);
cleaned *= side_v;
side_v = mix(side_v, vec4(1.) - side_v, is_out);
// "regeneration", raindrops gradually returning after wiper pass:
vec4 regenPhase = clamp((vec4(time) - mix(wiper_timer_out, wiper_timer_return, side_v) - vec4(.2)) / vec4(rain_params.y), vec4(0.), vec4(1.));
side_v = mix(vec4(0.), vec4(1.) - side_v, areaMask);
vec4 factor_v = mix(vec4(1.), regenPhase * (vec4(1.) - cleaned), areaMask);
side = 0.;
float out_factor = 1.;
// Find out the wiper blade that influences given grid cell the most
for(int i = 0; i < 4; ++i)
{
bool is_candidate = factor_v[i] < out_factor;
out_factor = mix(out_factor, factor_v[i], is_candidate);
side = mix(side, side_v[i], is_candidate);
}
return out_factor;
}

View File

@@ -0,0 +1,4 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
#include "mat_reflmap.glsl"

View File

@@ -0,0 +1,72 @@
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#include "common.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(reflmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D reflmap;
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 fragnormal = normalize(f_normal);
float reflectivity = param[1].z * texture(reflmap, f_coord).a;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, 0.0, shadow_tone);
if(alphatestfail)
fragcolor.r = 1.0;
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,79 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(reflmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D reflmap;
#pragma texture(specgloss, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D specgloss;
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 fragnormal = normalize(f_normal);
float reflectivity = param[1].z * texture(reflmap, f_coord).a;
float specularity = texture(specgloss, f_coord).r;
glossiness = texture(specgloss, f_coord).g * abs(param[1].w);
metalic = texture(specgloss, f_coord).b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
if(alphatestfail)
fragcolor.r = 1.0;
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,75 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, zero)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#define NORMALMAP
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord).a;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, 1.0);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,80 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, zero)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#pragma texture(specgloss, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D specgloss;
#define NORMALMAP
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord).a;
float specularity = texture(specgloss, f_coord).r;
glossiness = texture(specgloss, f_coord).g * abs(param[1].w);
metalic = texture(specgloss, f_coord).b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, 1.0);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,38 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
#include "tonemapping.glsl"
layout(location = 1) flat in vec3 f_normal_raw;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
float x = (gl_PointCoord.x - 0.5f) * 2.0f;
float y = (gl_PointCoord.y - 0.5f) * 2.0f;
float dist2 = abs(x * x + y * y);
if (dist2 > 0.5f * 0.5f)
discard;
// color data space is shared with normals, ugh
vec4 color = vec4(pow(f_normal_raw.rgb, vec3(2.2)), 1.0f);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0f);
}
}

View File

@@ -0,0 +1,75 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, zero)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#define NORMALMAP
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord).a;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights_sunless(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,80 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, zero)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(normalmap, 1, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D normalmap;
#pragma texture(specgloss, 2, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D specgloss;
#define NORMALMAP
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord).a;
float specularity = texture(specgloss, f_coord).r;
glossiness = texture(specgloss, f_coord).g * abs(param[1].w);
metalic = texture(specgloss, f_coord).b;
fragcolor = apply_lights_sunless(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,13 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// the rails and the ballast of a track faded into those of the tracks joined at its ends, see trackblend.glsl
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(diffuse1, 1, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D diffuse1;
#pragma texture(diffuse2, 2, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D diffuse2;
#pragma texture(diffuse3, 3, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 3) uniform sampler2D diffuse3;
#include "trackblend.glsl"

View File

@@ -0,0 +1,22 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// the rails and the ballast of a track of a normal mapped material faded into those of the tracks joined at its ends, see trackblend.glsl
#define TRACKBLEND_NORMALMAP 1
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(diffuse1, 1, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D diffuse1;
#pragma texture(diffuse2, 2, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D diffuse2;
#pragma texture(diffuse3, 3, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 3) uniform sampler2D diffuse3;
#pragma texture(normalmap, 4, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 4) uniform sampler2D normalmap;
#pragma texture(normalmap1, 5, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 5) uniform sampler2D normalmap1;
#pragma texture(normalmap2, 6, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 6) uniform sampler2D normalmap2;
#pragma texture(normalmap3, 7, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 7) uniform sampler2D normalmap3;
#include "trackblend.glsl"

View File

@@ -0,0 +1,25 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// the rails and the ballast of a track of a parallax material faded into those of the tracks joined at its ends, see trackblend.glsl
#define TRACKBLEND_NORMALMAP 1
#define TRACKBLEND_PARALLAX 1
#pragma param(height_scale, 2, 1, 1, zero)
#pragma param(height_offset, 2, 2, 1, zero)
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(diffuse1, 1, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D diffuse1;
#pragma texture(diffuse2, 2, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D diffuse2;
#pragma texture(diffuse3, 3, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 3) uniform sampler2D diffuse3;
#pragma texture(normalmap, 4, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 4) uniform sampler2D normalmap;
#pragma texture(normalmap1, 5, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 5) uniform sampler2D normalmap1;
#pragma texture(normalmap2, 6, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 6) uniform sampler2D normalmap2;
#pragma texture(normalmap3, 7, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 7) uniform sampler2D normalmap3;
#include "trackblend.glsl"

View File

@@ -0,0 +1,21 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// the rails and the ballast of a track of a material with normal and specgloss maps faded into those of the tracks joined at its ends, see trackblend.glsl
#define TRACKBLEND_NORMALMAP 1
#define TRACKBLEND_SPECGLOSS 1
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
#pragma texture(diffuse1, 1, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D diffuse1;
#pragma texture(diffuse2, 2, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D diffuse2;
#pragma texture(normalmap, 3, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 3) uniform sampler2D normalmap;
#pragma texture(normalmap1, 4, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 4) uniform sampler2D normalmap1;
#pragma texture(normalmap2, 5, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 5) uniform sampler2D normalmap2;
#pragma texture(specgloss, 6, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 6) uniform sampler2D specgloss;
#include "trackblend.glsl"

View File

@@ -0,0 +1,98 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(wave_strength, 2, 1, 1, one)
#pragma param(wave_speed, 2, 2, 1, one)
#pragma texture(normalmap, 0, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D normalmap;
#pragma texture(dudvmap, 1, RGB)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D dudvmap;
#pragma texture(diffuse, 2, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D diffuse;
//wave distortion variables
float move_factor = 0.0;
#define WATER
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
//wave distortion
move_factor += (param[2].z * time);
move_factor = mod(move_factor, 1.0);
vec2 texture_coords = f_coord;
vec2 distorted_tex_coord = texture(dudvmap, vec2(texture_coords.x + move_factor, texture_coords.y)).rg * 0.1;
distorted_tex_coord = texture_coords + vec2(distorted_tex_coord.x , distorted_tex_coord.y + move_factor);
vec2 total_distorted_tex_coord = (texture(dudvmap, distorted_tex_coord).rg * 2.0 - 1.0 ) * param[2].y;
texture_coords += total_distorted_tex_coord;
vec4 tex_color = texture(diffuse, texture_coords);
vec3 fragcolor = ambient * 0.25;
vec3 normal;
normal.xy = (texture(normalmap, texture_coords).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, texture_coords ).a;
// water reflects 2% at normal incidence
float specularity = 0.25;
glossiness = abs(param[1].w);
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
//fragcolor = mix(fragcolor, param[0].rgb, param[1].z);
// fragcolor = (fragcolor * tex_color.rgb * param[1].z) + (fragcolor * (1.0 - param[1].z)); //multiply
//fragcolor = ( max(fragcolor + param[0].rgb -1.0,0.0) * param[1].z + fragcolor * (1.0 - param[1].z)); //linear burn
//fragcolor = ( min(param[0].rgb,fragcolor) * param[1].z + fragcolor * (1.0 - param[1].z)); //darken
// fresnel effect
vec3 view_dir = normalize(vec3(0.0f, 0.0f, 0.0f) - f_pos.xyz);
float fresnel = pow(max(dot(normalize(f_normal), view_dir), 0.0), 0.2);
float fresnel_inv = ((fresnel - 1.0 ) * -1.0 );
vec4 color = vec4(apply_fog(fragcolor), clamp((fresnel_inv + param[0].a), 0.0, 1.0));
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

View File

@@ -0,0 +1,103 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) in vec3 f_normal;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 8) in mat3 f_tbn;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
#pragma param(color, 0, 0, 4, diffuse)
#pragma param(diffuse, 1, 0, 1, diffuse)
#pragma param(specular, 1, 1, 1, specular)
#pragma param(reflection, 1, 2, 1, one)
#pragma param(glossiness, 1, 3, 1, glossiness)
#pragma param(wave_strength, 2, 1, 1, one)
#pragma param(wave_speed, 2, 2, 1, one)
#pragma texture(normalmap, 0, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D normalmap;
#pragma texture(dudvmap, 1, RGB)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D dudvmap;
#pragma texture(diffuse, 2, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D diffuse;
#pragma texture(specgloss, 3, RGBA)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 3) uniform sampler2D specgloss;
//wave distortion variables
float move_factor = 0.0;
#define WATER
#include "light_common.glsl"
#include "apply_fog.glsl"
#include "tonemapping.glsl"
void main()
{
//wave distortion
move_factor += (param[2].z * time);
move_factor = mod(move_factor, 1.0);
vec2 texture_coords = f_coord;
vec2 distorted_tex_coord = texture(dudvmap, vec2(texture_coords.x + move_factor, texture_coords.y)).rg * 0.1;
distorted_tex_coord = texture_coords + vec2(distorted_tex_coord.x , distorted_tex_coord.y + move_factor);
vec2 total_distorted_tex_coord = (texture(dudvmap, distorted_tex_coord).rg * 2.0 - 1.0 ) * param[2].y;
texture_coords += total_distorted_tex_coord;
vec4 tex_color = texture(diffuse, texture_coords);
vec3 fragcolor = ambient * 0.25;
vec3 normal;
normal.xy = (texture(normalmap, texture_coords).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, texture_coords ).a;
vec4 specgloss_map = texture(specgloss, texture_coords);
float specularity = specgloss_map.r;
glossiness = specgloss_map.g * abs(param[1].w);
metalic = specgloss_map.b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
//fragcolor = mix(fragcolor, param[0].rgb, param[1].z);
// fragcolor = (fragcolor * tex_color.rgb * param[1].z) + (fragcolor * (1.0 - param[1].z)); //multiply
//fragcolor = ( max(fragcolor + param[0].rgb -1.0,0.0) * param[1].z + fragcolor * (1.0 - param[1].z)); //linear burn
//fragcolor = ( min(param[0].rgb,fragcolor) * param[1].z + fragcolor * (1.0 - param[1].z)); //darken
// fresnel effect
vec3 view_dir = normalize(vec3(0.0f, 0.0f, 0.0f) - f_pos.xyz);
float fresnel = pow(max(dot(normalize(f_normal), view_dir), 0.0), 0.2);
float fresnel_inv = ((fresnel - 1.0 ) * -1.0 );
vec4 color = vec4(apply_fog(fragcolor), clamp((fresnel_inv + param[0].a), 0.0, 1.0));
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
}

10
shaders/vk14/pick.frag Normal file
View File

@@ -0,0 +1,10 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) out vec4 out_color;
void main()
{
out_color = vec4(param[0].rgb, 1.0);
}

View File

@@ -0,0 +1,11 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 0) out vec4 out_color;
layout(location = 2) in vec2 f_coord;
void main()
{
out_color = vec4(param[0].r, f_coord, 1.0);
}

View File

@@ -0,0 +1,64 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#pragma texture(color_tex, 0, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D iChannel0;
#include "dither.glsl"
vec2 barrelDistortion(vec2 f_coords, float amt) {
vec2 cc = f_coords - 0.5;
float dist = dot(cc, cc);
return f_coords + cc * dist * amt;
}
float sat( float t )
{
return clamp( t, 0.0, 1.0 );
}
float linterp( float t ) {
return sat( 1.0 - abs( 1.0*t - 1.0 ) );
}
float remap( float t, float a, float b ) {
return sat( (t - a) / (b - a) );
}
vec4 spectrum_offset( float t ) {
vec4 ret;
float lo = step(t,0.5);
float hi = 1.0-lo;
float w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );
ret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);
return pow( ret, vec4(1.0/2.2) );
}
const float max_distort = 0.05;
const int num_iter = 10;
const float reci_num_iter_f = 1.0 / float(num_iter);
void main()
{
vec2 uv=f_coords.xy;
vec4 sumcol = vec4(0.0);
vec4 sumw = vec4(0.0);
for ( int i=0; i<num_iter;++i )
{
float t = float(i) * reci_num_iter_f;
vec4 w = spectrum_offset( t );
sumw += w;
sumcol += w * texture( iChannel0, barrelDistortion(uv, .6 * max_distort*t ) );
}
out_color = sumcol / sumw;
out_color.rgb = dither_output(out_color.rgb);
}

View File

@@ -0,0 +1,46 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// param is pc.data of the push constants (the model block in the opengl renderer): param[0].x the scale of the velocities
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#pragma texture(color_tex, 0, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D color_tex;
#pragma texture(velocity_tex, 1, RG)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 1) uniform sampler2D velocity_tex;
#pragma texture(depth_tex, 2, R)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 2) uniform sampler2D depth_tex;
void main()
{
float ori_depth = texture(depth_tex, f_coords).x;
vec2 texelSize = 1.0 / vec2(textureSize(color_tex, 0));
vec2 velocity = texture(velocity_tex, f_coords).xy * param[0].x;
float speed = length(velocity / texelSize);
int nSamples = clamp(int(speed), 1, 32);
float depth_diff = 0.001 * 8.0;
vec4 result = vec4(0.0);
result += vec4(texture(color_tex, f_coords).rgb, 1.0);
for (int i = 1; i < nSamples; ++i)
{
vec2 offset = velocity * (float(i) / float(nSamples - 1) - 0.5);
vec2 coord = f_coords + offset;
float depth = texture(depth_tex, coord).x;
if (abs(depth - ori_depth) < depth_diff)
result += vec4(texture(color_tex, coord).rgb, 1.0);
}
out_color = vec4((result.rgb / result.w), 1.0);
}

View File

@@ -0,0 +1,78 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// the projection of the view (projection of the scene block in the opengl renderer) is pc.data[0..3] of the push constants,
// its columns
#define projection mat4(pc.data[0], pc.data[1], pc.data[2], pc.data[3])
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#pragma texture(depth_tex, 0, R)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D depth_tex;
#pragma texture(noise_tex, 1, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 1) uniform sampler2D noise_tex;
// the depth is reversed (0..1, 1 at the near plane, 0 at infinity), so it is the clip z itself
vec3 view_pos_from_depth(vec2 uv, float depth) {
vec4 clip = vec4(uv * 2.0 - 1.0, depth, 1.0);
vec4 view = inverse(projection) * clip;
return view.xyz / view.w;
}
// Deterministic hemisphere sample (z > 0), mimics the C++ kernel generator
vec3 kernel_sample(int i) {
float fi = float(i);
vec3 dir = normalize(vec3(
fract(sin(fi * 12.9898) * 43758.5453) * 2.0 - 1.0,
fract(sin(fi * 78.2330) * 43758.5453) * 2.0 - 1.0,
fract(sin(fi * 37.7190) * 43758.5453) // z >= 0 -> hemisphere
));
float len = fract(sin(fi * 94.6720) * 43758.5453);
float scale = fi / 32.0;
scale = mix(0.1, 1.0, scale * scale); // bias samples inward
return dir * len * scale;
}
void main() {
float d = texture(depth_tex, f_coords).r;
if (d <= 0.0) { out_color = vec4(1.0); return; } // skybox -> no occlusion
vec3 pos = view_pos_from_depth(f_coords, d);
vec3 ddx = dFdx(pos);
vec3 ddy = dFdy(pos);
vec3 n = normalize(cross(ddx, ddy));
// derive screen size from the depth texture instead of a UBO field
vec2 screen_size = vec2(textureSize(depth_tex, 0));
vec2 noise_uv = f_coords * (screen_size / 4.0);
vec3 rvec = texture(noise_tex, noise_uv).xyz;
vec3 t = normalize(rvec - n * dot(rvec, n));
vec3 b = cross(n, t);
mat3 TBN = mat3(t, b, n);
const int KERNEL = 32;
const float RADIUS = 0.5;
const float BIAS = 0.025;
float occ = 0.0;
for (int i = 0; i < KERNEL; ++i) {
vec3 sp = TBN * kernel_sample(i);
sp = pos + sp * RADIUS;
vec4 clip = projection * vec4(sp, 1.0);
vec3 ndc = clip.xyz / clip.w;
vec2 suv = ndc.xy * 0.5 + 0.5;
float sd = texture(depth_tex, suv).r;
float szv = view_pos_from_depth(suv, sd).z;
float range = smoothstep(0.0, 1.0, RADIUS / abs(pos.z - szv));
occ += (szv >= sp.z + BIAS ? 1.0 : 0.0) * range;
}
out_color = vec4(1.0 - occ / float(KERNEL));
}

View File

@@ -0,0 +1,99 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// applies the ambient occlusion of the half size target to the image at full size, as a multiplier: drawn with the
// blending dst * src over the image (opengl46_ao::apply). the four nearest pixels of the occlusion are weighed by how
// close they are and by how close their distance is to that of the pixel, so the occlusion does not spill over the
// edges of objects. each pixel of the occlusion stands for the top left one of the four full size pixels it covers
// params.x: 0 ground truth ambient occlusion, 1 the older ssao (its own fixed curve)
// params.y: strength, a power of the occlusion (1 = as computed); params.z: albedo assumed by the multiple bounce term.
// the fog in front of a surface hides its occlusion as it hides the surface: the analytic fog of apply_fog.glsl, the fog
// volume comes with the stage P2.
// the parameters are in the push constants (loose uniforms in the opengl renderer): pc.data[0..3] the columns of the inverse
// of the projection (inv_projection), pc.data[4] params, pc.data[5].x the density of the fog of the scene (fog_density of the
// regular draws, 0: no fog), pc.data[5].yzw unused
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D depth_tex;
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 1) uniform sampler2D ao_tex;
#define inv_projection mat4(pc.data[0], pc.data[1], pc.data[2], pc.data[3])
#define params pc.data[4]
#define scene_fog_density pc.data[5].x
vec3 view_position(ivec2 pixel, float depth)
{
ivec2 full_size = textureSize(depth_tex, 0);
vec2 uv = (vec2(pixel) + 0.5) / vec2(full_size);
vec4 position = inv_projection * vec4(uv * 2.0 - 1.0, depth, 1.0);
return position.xyz / position.w;
}
float view_distance(ivec2 pixel, float depth)
{
return length(view_position(pixel, depth));
}
// light which comes back after more than one bounce off surfaces of the given albedo, fit of Jimenez et al. 2016
float multi_bounce(float visibility, float albedo)
{
float a = 2.0404 * albedo - 0.3324;
float b = -4.7951 * albedo + 0.6417;
float c = 2.7552 * albedo + 0.6903;
return max(visibility, ((visibility * a + b) * visibility + c) * visibility);
}
void main()
{
ivec2 full_size = textureSize(depth_tex, 0);
ivec2 half_size = textureSize(ao_tex, 0);
ivec2 pixel = ivec2(gl_FragCoord.xy);
float depth = texelFetch(depth_tex, pixel, 0).r;
if (depth <= 0.0)
{
// the sky
out_color = vec4(1.0);
return;
}
float distance = view_distance(pixel, depth);
vec2 position = vec2(pixel) * 0.5;
ivec2 base = ivec2(floor(position));
vec2 fraction = position - vec2(base);
float sum = 0.0;
float weights = 0.0;
for (int y = 0; y < 2; ++y)
{
for (int x = 0; x < 2; ++x)
{
ivec2 texel = clamp(base + ivec2(x, y), ivec2(0), half_size - 1);
ivec2 texelpixel = min(texel * 2, full_size - 1);
float texeldepth = texelFetch(depth_tex, texelpixel, 0).r;
float texeldistance = (texeldepth > 0.0) ? view_distance(texelpixel, texeldepth) : 1e30;
float bilinear = (x == 0 ? 1.0 - fraction.x : fraction.x) * (y == 0 ? 1.0 - fraction.y : fraction.y);
float weight = (bilinear + 1e-3) / (1e-3 + abs(texeldistance - distance) / distance);
sum += texelFetch(ao_tex, texel, 0).r * weight;
weights += weight;
}
}
float ao = clamp(sum / max(weights, 1e-6), 0.0, 1.0);
if (params.x > 0.5)
{
// the curve the older ssao was applied with, a floor so the shadows do not go black
ao = mix(0.3, 1.0, pow(ao, 0.8));
}
else
{
ao = pow(ao, params.y);
ao = multi_bounce(ao, params.z);
}
// what the fog lets through of the surface is what is left to occlude
float transmittance = (scene_fog_density > 0.0) ? clamp(exp(-distance * scene_fog_density), 0.0, 1.0) : 1.0;
ao = mix(1.0, ao, transmittance);
out_color = vec4(vec3(ao), 1.0);
}

View File

@@ -0,0 +1,18 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#pragma texture(ssao_tex, 0, R)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D ssao_tex;
void main() {
vec2 texel = 1.0 / vec2(textureSize(ssao_tex, 0));
float sum = 0.0;
for (int x = -2; x < 2; ++x)
for (int y = -2; y < 2; ++y)
sum += texture(ssao_tex, f_coords + vec2(x, y) * texel).r;
out_color = vec4(sum / 16.0);
}

View File

@@ -0,0 +1,40 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#pragma texture(tex1, 0, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D tex1;
// glow of the bright parts of the image (postfx_bloom_*.frag)
#pragma texture(bloom_tex, 1, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 1) uniform sampler2D bloom_tex;
// the automatic exposure (exposure_ssbo of the opengl renderer, exposure_average.comp) comes in a later stage: the fixed
// exposure multiplier alone
const float exposure = 0.0;
#include "tonemapping.glsl"
#include "dither.glsl"
// param is pc.data of the push constants (the model block in the opengl renderer).
// param[0].x: share of the bloom in the image (0: no bloom, the texture is not read), param[0].y: 1 if the automatic
// exposure is applied (there is none yet, so it has no effect), param[0].z: fixed exposure multiplier, param[0].w: scale of
// the bloom texture (it holds the sum of the levels of the chain), param[1].x: 1 when this is the last pass before the
// window, which takes the dither
void main()
{
vec2 texcoord = f_coords;
vec3 hdr_color = texture(tex1, texcoord).xyz;
if (param[0].x > 0.0)
hdr_color = mix(hdr_color, texture(bloom_tex, texcoord).rgb * param[0].w, param[0].x);
float exposurescale = param[0].z;
if (param[0].y > 0.5 && exposure > 0.0)
exposurescale *= exposure;
out_color = tonemap(vec4(hdr_color * exposurescale, 1.0));
if (param[1].x > 0.5)
out_color.rgb = dither_output(out_color.rgb);
}

View File

@@ -0,0 +1,42 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 2) in vec2 f_coord;
#pragma texture(tex1, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D tex1;
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#include "tonemapping.glsl"
void main()
{
vec4 tex_color = texture(tex1, vec2(f_coord.x, f_coord.y + param[1].x));
vec4 color = tex_color * param[0];
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = tonemap(color);
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
}
}

View File

@@ -0,0 +1,20 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
layout(location = 0) in vec3 v_vert;
layout(location = 1) in vec2 v_coord;
layout(location = 2) out vec2 f_coord;
layout(location = 4) out vec4 f_clip_pos;
layout(location = 5) out vec4 f_clip_future_pos;
void main()
{
f_clip_pos = (projection * modelview) * vec4(v_vert, 1.0f);
f_clip_future_pos = (projection * future * modelview) * vec4(v_vert, 1.0f);
gl_Position = f_clip_pos;
f_coord = v_coord;
}

17
shaders/vk14/present.frag Normal file
View File

@@ -0,0 +1,17 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// the finished image to the window (with quad.vert). the renderer draws with the matrices of the opengl one, so its images
// have the rows in the memory order of opengl, the bottom of the picture first; the window shows the first row at the top.
// this is the only place the picture is turned upside down. no push constants
layout(location = 14) in vec2 f_coords;
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D tex1;
layout(location = 0) out vec4 out_color;
void main()
{
out_color = FBOUT(texture(tex1, vec2(f_coords.x, 1.0 - f_coords.y)));
}

27
shaders/vk14/quad.vert Normal file
View File

@@ -0,0 +1,27 @@
#version 460 core
#define STAGE_VERTEX 1
#define LAYOUT_POST 1
#include "common.glsl"
const vec2 vert[4] = vec2[]
(
vec2(-1.0, 1.0),
vec2(-1.0, -1.0),
vec2( 1.0, 1.0),
vec2( 1.0, -1.0)
);
const vec2 uv[4] = vec2[]
(
vec2(0.0, 1.0),
vec2(0.0, 0.0),
vec2(1.0, 1.0),
vec2(1.0, 0.0)
);
layout(location = 14) out vec2 f_coords;
void main()
{
gl_Position = vec4(vert[gl_VertexIndex], 0.0, 1.0);
f_coords = uv[gl_VertexIndex];
}

39
shaders/vk14/random.glsl Normal file
View File

@@ -0,0 +1,39 @@
uint Hash(uint s);
uint Hash(uvec2 s);
uint Hash(uvec3 s);
uint Hash(float s);
uint HashCombine(uint seed, uint value);
// Evolving Sub-Grid Turbulence for Smoke Animation
// H. Schechter and R. Bridson
uint Hash(uint s) {
s ^= 2747636419u;
s *= 2654435769u;
s ^= s >> 16u;
s *= 2654435769u;
s ^= s >> 16u;
s *= 2654435769u;
return s;
}
uint Hash(uvec2 s) {
return HashCombine(Hash(s.x), s.y);
}
uint Hash(uvec3 s) {
return HashCombine(Hash(s.xy), s.z);
}
uint HashCombine(uint seed, uint value) {
return seed ^ (Hash(value) + 0x9e3779b9u + (seed<<6u) + (seed>>2u));
}
uint Rand(inout uint seed) {
seed = Hash(seed);
return seed;
}
float RandF(inout uint seed) {
return float(Rand(seed)) * 2.3283064365386963e-10;
}

View File

@@ -0,0 +1,25 @@
// the settings of the game the shaders depend on (gfx.* in eu07.ini): specialization constants the renderer sets when it makes a
// pipeline, the ids are the SPEC_* of rendering/vulkan14/vulkan14bindings.h. included by common.glsl after the constants
#ifndef SETTINGS_GLSL
#define SETTINGS_GLSL
#ifndef SPEC_OUTPUT_GAMMA
#error "settings.glsl takes the ids of the specialization constants from common.glsl: include common.glsl instead"
#endif
layout(constant_id = SPEC_SHADOWMAP_ENABLED) const bool SHADOWMAP_ENABLED = true;
layout(constant_id = SPEC_ENVMAP_ENABLED) const bool ENVMAP_ENABLED = true;
layout(constant_id = SPEC_MOTIONBLUR_ENABLED) const bool MOTIONBLUR_ENABLED = true;
// the colour goes on to the post-processing; tonemapped by the shaders of the scene otherwise
layout(constant_id = SPEC_POSTFX_ENABLED) const bool POSTFX_ENABLED = true;
layout(constant_id = SPEC_EXTRAEFFECTS_ENABLED) const bool EXTRAEFFECTS_ENABLED = true;
// whether the colour written to the window is encoded by the shader (FBOUT): the window has no srgb format (gfx.shadergamma yes)
layout(constant_id = SPEC_OUTPUT_GAMMA) const bool OUTPUT_GAMMA = false;
// the colour written to the window
vec4 FBOUT(vec4 x)
{
return (OUTPUT_GAMMA ? vec4(pow(x.rgb, vec3(1.0 / 2.2)), x.a) : x);
}
#endif

View File

@@ -0,0 +1,8 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 2) in vec2 f_coord;
void main()
{
}

View File

@@ -0,0 +1,14 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
layout(location = 0) in vec3 v_vert;
layout(location = 2) in vec2 v_coord;
layout(location = 2) out vec2 f_coord;
void main()
{
gl_Position = (projection * effective_modelview()) * vec4(v_vert, 1.0f);
f_coord = v_coord;
}

View File

@@ -0,0 +1,16 @@
// the skydome vertex colours carry Perez luminance scaled by a fixed exposure
// (see CSkyDome::SetExposure), so the circumsolar region and the horizon band
// routinely land well above 1.0. hard-clipping them, as this shader used to do,
// flattened everything within ~20 degrees of the sun - and most of the lower sky -
// into one solid white patch. compress the top end instead: below the knee the
// response is unchanged, above it it rolls off towards SKY_SHOULDER_WHITE, so the
// brightness gradient around the sun survives and the patch keeps a soft edge.
const float SKY_SHOULDER_KNEE = 0.25;
const float SKY_SHOULDER_WHITE = 0.95;
vec3 sky_shoulder(vec3 color)
{
const float range = SKY_SHOULDER_WHITE - SKY_SHOULDER_KNEE;
vec3 excess = max(color - vec3(SKY_SHOULDER_KNEE), vec3(0.0));
return min(color, vec3(SKY_SHOULDER_KNEE)) + range * (vec3(1.0) - exp(-excess / range));
}

43
shaders/vk14/smoke.frag Normal file
View File

@@ -0,0 +1,43 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
layout(location = 13) in vec4 f_color;
layout(location = 2) in vec2 f_coord;
layout(location = 3) in vec4 f_pos;
layout(location = 4) in vec4 f_clip_pos;
layout(location = 5) in vec4 f_clip_future_pos;
#pragma texture(tex1, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D tex1;
#include "apply_fog.glsl"
#include "tonemapping.glsl"
layout(location = 0) out vec4 out_color;
#ifdef NO_MOTION_OUTPUT
// a pass without the velocity target: the writes go nowhere
vec4 out_motion;
#else
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
vec4 tex_color = texture(tex1, f_coord) * f_color;
if (POSTFX_ENABLED)
{
out_color = vec4(apply_fog(tex_color.rgb), tex_color.a);
}
else
{
out_color = tonemap(vec4(apply_fog(tex_color.rgb), tex_color.a));
}
if (MOTIONBLUR_ENABLED)
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
}
}

25
shaders/vk14/smoke.vert Normal file
View File

@@ -0,0 +1,25 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
layout(location = 0) in vec3 v_vert;
layout(location = 1) in vec4 v_color;
layout(location = 2) in vec2 v_coord;
layout(location = 3) out vec4 f_pos;
layout(location = 13) out vec4 f_color;
layout(location = 2) out vec2 f_coord;
layout(location = 4) out vec4 f_clip_pos;
layout(location = 5) out vec4 f_clip_future_pos;
void main()
{
f_pos = modelview * vec4(v_vert, 1.0f);
f_clip_pos = (projection * modelview) * vec4(v_vert, 1.0f);
f_clip_future_pos = (projection * future * modelview) * vec4(v_vert, 1.0f);
gl_Position = f_clip_pos;
f_coord = v_coord;
f_color = v_color;
}

View File

@@ -0,0 +1,154 @@
#include "common.glsl"
const float pureWhite = 1.0;
vec3 reinhard(vec3 x)
{
// return x / (x + vec3(1.0));
// Reinhard tonemapping operator.
// see: "Photographic Tone Reproduction for Digital Images", eq. 4
float luminance = dot(x, vec3(0.2126, 0.7152, 0.0722));
float mappedLuminance = (luminance * (1.0 + luminance/(pureWhite*pureWhite))) / (1.0 + luminance);
// Scale color by ratio of average luminances.
return (mappedLuminance / luminance) * x;
}
// https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
vec3 ACESFilm(vec3 x)
{
float a = 2.51f;
float b = 0.03f;
float c = 2.43f;
float d = 0.59f;
float e = 0.14f;
return (x*(a*x+b))/(x*(c*x+d)+e);
}
// https://www.slideshare.net/ozlael/hable-john-uncharted2-hdr-lighting
vec3 filmicF(vec3 x)
{
float A = 0.22f;
float B = 0.30f;
float C = 0.10f;
float D = 0.20f;
float E = 0.01f;
float F = 0.30f;
return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F)) - E/F;
}
vec3 filmic(vec3 x)
{
return filmicF(x) / filmicF(vec3(11.2f));
}
// AgX tonemapping based on nxrighthere / Missing Deadlines implementation.
// 0: Default, 1: Golden, 2: Punchy
#ifndef AGX_LOOK
#define AGX_LOOK 0
#endif
vec3 AgxDefaultContrastApprox(vec3 x)
{
vec3 x2 = x * x;
vec3 x4 = x2 * x2;
return 15.5 * x4 * x2
- 40.14 * x4 * x
+ 31.96 * x4
- 6.868 * x2 * x
+ 0.4298 * x2
+ 0.1191 * x
- 0.00232;
}
vec3 Agx(vec3 val)
{
mat3 agx_mat = mat3(
0.842479062253094, 0.0784335999999992, 0.0792237451477643,
0.0423282422610123, 0.878468636469772, 0.0791661274605434,
0.0423756549057051, 0.0784336, 0.879142973793104
);
// DEFAULT_LOG2_MIN = -6.0
// DEFAULT_LOG2_MAX = +4.5
// MIDDLE_GRAY = 0.18
// log2(pow(2, VALUE) * MIDDLE_GRAY)
const float min_ev = -8.47393;
const float max_ev = 2.026069;
const float agx_eps = 1e-6;
// Input transform (inset)
val = agx_mat * val;
// Log2 space encoding. max() avoids -INF/NaN for zero/negative inputs.
val = clamp(log2(max(val, vec3(agx_eps))), min_ev, max_ev);
val = (val - min_ev) / (max_ev - min_ev);
// Apply sigmoid function approximation.
return AgxDefaultContrastApprox(val);
}
vec3 AgxEotf(vec3 val)
{
mat3 agx_mat_inv = mat3(
1.19687900512017, -0.0980208811401368, -0.0990297440797205,
-0.0528968517574562, 1.15190312990417, -0.0989611768448433,
-0.0529716355144438, -0.0980434501171241, 1.15107367264116
);
// Inverse input transform (outset)
val = agx_mat_inv * val;
return max(val, vec3(0.0));
}
vec3 AgxLook(vec3 val)
{
vec3 lw = vec3(0.2126, 0.7152, 0.0722);
float luma = dot(val, lw);
vec3 offset = vec3(0.0);
vec3 slope = vec3(1.0);
vec3 power = vec3(1.6);
float sat = 0.9;
#if AGX_LOOK == 1
// Golden
slope = vec3(1.0, 0.9, 0.5);
power = vec3(0.8);
sat = 0.8;
#elif AGX_LOOK == 2
// Punchy
slope = vec3(1.0);
power = vec3(1.35);
sat = 1.1;
#endif
// ASC CDL
val = pow(max(val * slope + offset, vec3(0.0)), power);
return vec3(luma) + sat * (val - vec3(luma));
}
vec3 ApplyAgX(vec3 linearColorRec709)
{
linearColorRec709 = Agx(linearColorRec709);
linearColorRec709 = AgxLook(linearColorRec709);
linearColorRec709 = AgxEotf(linearColorRec709);
return linearColorRec709;
}
vec4 tonemap(vec4 x)
{
// Use AgX by default. Reinhard and ACES above are kept for reference.
// Last-line-of-defense sanitize so NaN/Inf/negative HDR values do not
// escape into log2()/pow() and produce black flashes or invalid output.
vec3 hdr = x.rgb;
hdr = mix(hdr, vec3(0.0), vec3(any(isnan(hdr)) || any(isinf(hdr))));
hdr = max(hdr, vec3(0.0));
return FBOUT(vec4(ApplyAgX(hdr), x.a));
//return FBOUT(vec4(ACESFilm(hdr), x.a));
//return FBOUT(vec4(reinhard(x.rgb), x.a));
}

Some files were not shown because too many files have changed in this diff Show More