mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
Close up no longer falls apart: how near the nearest thing is goes by each object's box (not a ball round it: inside a big or long flat one's ball it came to nothing, the near plane with it, and surfaces fought each other in blocks, jittering, the sky and grid gone), the near plane a quarter of the way to it; reference pictures count too. Grid lines meet exactly where their pieces join (no crawling dots): a streak's ends each face the eye, each its own width; pieces a quarter further from the eye each. The depth pre-pass waited on before the scene uses it; the same depth in both (invariant)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d313a9452a
commit
9d9557117a
9 changed files with 102 additions and 51 deletions
|
|
@ -211,14 +211,14 @@ void Renderer::drawSprite(const glm::vec3& at, float size, const glm::vec4& colo
|
||||||
list.push_back({glm::vec4(at, size * 0.5f), color, glm::vec4(0, 0, 0, rotation), glm::vec4(0, 0, 0, tex)});
|
list.push_back({glm::vec4(at, size * 0.5f), color, glm::vec4(0, 0, 0, rotation), glm::vec4(0, 0, 0, tex)});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::drawStreak(const glm::vec3& from, const glm::vec3& to, float width, const glm::vec4& color, TextureId texture, bool additive) {
|
void Renderer::drawStreak(const glm::vec3& from, const glm::vec3& to, float width, const glm::vec4& color, TextureId texture, bool additive, float widthTo) {
|
||||||
auto& list = additive ? packet_.glowSprites : packet_.sprites;
|
auto& list = additive ? packet_.glowSprites : packet_.sprites;
|
||||||
if (list.size() >= 8192) return;
|
if (list.size() >= 8192) return;
|
||||||
uint32_t bits = texture;
|
uint32_t bits = texture;
|
||||||
float tex;
|
float tex;
|
||||||
std::memcpy(&tex, &bits, 4);
|
std::memcpy(&tex, &bits, 4);
|
||||||
// up.xyz: half its length along it (non-zero marks a streak; its side is found per frame).
|
// up.xyz: half its length along it (non-zero marks a streak); right.w: half its width at `to`.
|
||||||
list.push_back({glm::vec4((from + to) * 0.5f, width * 0.5f), color, glm::vec4(0.0f), glm::vec4((to - from) * 0.5f, tex)});
|
list.push_back({glm::vec4((from + to) * 0.5f, width * 0.5f), color, glm::vec4(0.0f, 0.0f, 0.0f, (widthTo < 0.0f ? width : widthTo) * 0.5f), glm::vec4((to - from) * 0.5f, tex)});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::drawModel(ModelId model, const glm::mat4& transform, const glm::vec4& tint, const std::vector<glm::mat4>* skin,
|
void Renderer::drawModel(ModelId model, const glm::mat4& transform, const glm::vec4& tint, const std::vector<glm::mat4>* skin,
|
||||||
|
|
@ -757,11 +757,9 @@ void Renderer::buildPacket(uint32_t width, uint32_t height) {
|
||||||
up = glm::cross(right, f);
|
up = glm::cross(right, f);
|
||||||
for (auto* list : {&p.sprites, &p.glowSprites})
|
for (auto* list : {&p.sprites, &p.glowSprites})
|
||||||
for (gpu::Sprite& s : *list) {
|
for (gpu::Sprite& s : *list) {
|
||||||
if (const glm::vec3 half(s.up); glm::dot(half, half) > 0.0f) { // a streak: its side faces the eye
|
if (const glm::vec3 half(s.up); glm::dot(half, half) > 0.0f) { // a streak: each end's side faces the eye (sprite.vert)
|
||||||
glm::vec3 side = glm::cross(half, camera_.origin - glm::vec3(s.posSize));
|
s.right = glm::vec4(camera_.origin, s.right.w);
|
||||||
side = glm::length(side) > 1e-6f ? glm::normalize(side) : right;
|
s.posSize.w = -std::max(s.posSize.w, 1e-9f); // (below 0: a streak; its half width at `from`)
|
||||||
s.right = glm::vec4(side * s.posSize.w, 0.0f);
|
|
||||||
s.posSize.w = 1.0f;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
s.right = glm::vec4(right, s.right.w), s.up = glm::vec4(up, s.up.w);
|
s.right = glm::vec4(right, s.right.w), s.up = glm::vec4(up, s.up.w);
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,9 @@ public:
|
||||||
// A particle: a camera-facing square `size` across, unlit (color can be HDR-bright).
|
// A particle: a camera-facing square `size` across, unlit (color can be HDR-bright).
|
||||||
// additive: light added on (fire, sparks, flashes); else blended (smoke, dust).
|
// additive: light added on (fire, sparks, flashes); else blended (smoke, dust).
|
||||||
void drawSprite(const glm::vec3& at, float size, const glm::vec4& color, TextureId texture, float rotation = 0.0f, bool additive = true);
|
void drawSprite(const glm::vec3& at, float size, const glm::vec4& color, TextureId texture, float rotation = 0.0f, bool additive = true);
|
||||||
// A sprite stretched from `from` to `to`, `width` across, turned to face the eye as much as
|
// A sprite stretched from `from` to `to`, `width` across (`widthTo` at `to`: < 0, the same),
|
||||||
// it can (rain).
|
// each end turned to face the eye as much as it can (rain; lines drawn in pieces meet exactly).
|
||||||
void drawStreak(const glm::vec3& from, const glm::vec3& to, float width, const glm::vec4& color, TextureId texture, bool additive = false);
|
void drawStreak(const glm::vec3& from, const glm::vec3& to, float width, const glm::vec4& color, TextureId texture, bool additive = false, float widthTo = -1.0f);
|
||||||
// skin: one matrix per joint (anim::skinMatrices) for skinned models; ignored otherwise.
|
// skin: one matrix per joint (anim::skinMatrices) for skinned models; ignored otherwise.
|
||||||
// lighting: probe light for moving things (nullptr = lightmap or simple sun shading).
|
// lighting: probe light for moving things (nullptr = lightmap or simple sun shading).
|
||||||
void drawModel(ModelId model, const glm::mat4& transform, const glm::vec4& tint = glm::vec4(1.0f),
|
void drawModel(ModelId model, const glm::mat4& transform, const glm::vec4& tint = glm::vec4(1.0f),
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,10 @@ void main() {
|
||||||
float c = cos(u_right.w), s = sin(u_right.w);
|
float c = cos(u_right.w), s = sin(u_right.w);
|
||||||
vec2 r = vec2(c * q.x - s * q.y, s * q.x + c * q.y);
|
vec2 r = vec2(c * q.x - s * q.y, s * q.x + c * q.y);
|
||||||
vec3 world = u_posSize.xyz + (u_right.xyz * r.x + u_up.xyz * r.y) * u_posSize.w;
|
vec3 world = u_posSize.xyz + (u_right.xyz * r.x + u_up.xyz * r.y) * u_posSize.w;
|
||||||
|
if (u_posSize.w < 0.0) { // a streak: right.xyz the eye, w its half width at the far end; each end's side faces the eye
|
||||||
|
vec3 end = u_posSize.xyz + u_up.xyz * q.y, side = cross(u_up.xyz, u_right.xyz - end);
|
||||||
|
world = end + (length(side) > 1e-9 ? normalize(side) : vec3(0.0, 0.0, 1.0)) * q.x * (q.y < 0.0 ? -u_posSize.w : u_right.w);
|
||||||
|
}
|
||||||
v_uv = vec2(q.x * 0.5 + 0.5, 0.5 - q.y * 0.5);
|
v_uv = vec2(q.x * 0.5 + 0.5, 0.5 - q.y * 0.5);
|
||||||
gl_Position = u_viewProj * vec4(world, 1.0);
|
gl_Position = u_viewProj * vec4(world, 1.0);
|
||||||
gl_Position.z = gl_Position.z * 2.0 - gl_Position.w;
|
gl_Position.z = gl_Position.z * 2.0 - gl_Position.w;
|
||||||
|
|
|
||||||
|
|
@ -1413,6 +1413,17 @@ void VulkanBackend::render(FramePacket& p) {
|
||||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||||
drawMeshes(cmd, p.opaque, p.viewProj, false, depthMeshPipeline_, depthSkinnedPipeline_, cullFor_, true);
|
drawMeshes(cmd, p.opaque, p.viewProj, false, depthMeshPipeline_, depthSkinnedPipeline_, cullFor_, true);
|
||||||
vkCmdEndRendering(cmd);
|
vkCmdEndRendering(cmd);
|
||||||
|
// The scene pass tests against that depth: wait for it to be written (two render passes
|
||||||
|
// aren't ordered on their own; without this, tiles of it could still be the old depth).
|
||||||
|
VkMemoryBarrier2 mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER_2};
|
||||||
|
mb.srcStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
|
||||||
|
mb.srcAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
|
mb.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
|
||||||
|
mb.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
|
VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
|
||||||
|
dep.memoryBarrierCount = 1;
|
||||||
|
dep.pMemoryBarriers = &mb;
|
||||||
|
vkCmdPipelineBarrier2(cmd, &dep);
|
||||||
mark(cmd, "depth pre-pass");
|
mark(cmd, "depth pre-pass");
|
||||||
depth.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
depth.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ layout(location = 0) out vec3 v_normal;
|
||||||
layout(location = 1) out vec2 v_uv;
|
layout(location = 1) out vec2 v_uv;
|
||||||
layout(location = 2) out vec2 v_lightmapUv;
|
layout(location = 2) out vec2 v_lightmapUv;
|
||||||
layout(location = 3) out vec3 v_world;
|
layout(location = 3) out vec3 v_world;
|
||||||
|
// The same depth in the depth pre-pass and the scene pass (else the compiler, dropping what the pre-pass
|
||||||
|
// doesn't use, can round it differently and the scene pass fails its depth test in patches).
|
||||||
|
invariant gl_Position;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Vertex vx = VERTEX(VERTEX_ID);
|
Vertex vx = VERTEX(VERTEX_ID);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ layout(location = 0) out vec3 v_normal;
|
||||||
layout(location = 1) out vec2 v_uv;
|
layout(location = 1) out vec2 v_uv;
|
||||||
layout(location = 2) out vec2 v_lightmapUv;
|
layout(location = 2) out vec2 v_lightmapUv;
|
||||||
layout(location = 3) out vec3 v_world;
|
layout(location = 3) out vec3 v_world;
|
||||||
|
// The same depth in the depth pre-pass and the scene pass (else the compiler, dropping what the pre-pass
|
||||||
|
// doesn't use, can round it differently and the scene pass fails its depth test in patches).
|
||||||
|
invariant gl_Position;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Vertex vx = VERTEX(VERTEX_ID);
|
Vertex vx = VERTEX(VERTEX_ID);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ void main() {
|
||||||
float c = cos(pc.right.w), s = sin(pc.right.w);
|
float c = cos(pc.right.w), s = sin(pc.right.w);
|
||||||
vec2 r = vec2(c * q.x - s * q.y, s * q.x + c * q.y);
|
vec2 r = vec2(c * q.x - s * q.y, s * q.x + c * q.y);
|
||||||
vec3 world = pc.posSize.xyz + (pc.right.xyz * r.x + pc.up.xyz * r.y) * pc.posSize.w;
|
vec3 world = pc.posSize.xyz + (pc.right.xyz * r.x + pc.up.xyz * r.y) * pc.posSize.w;
|
||||||
|
if (pc.posSize.w < 0.0) { // a streak: right.xyz the eye, w its half width at the far end; each end's side faces the eye
|
||||||
|
vec3 end = pc.posSize.xyz + pc.up.xyz * q.y, side = cross(pc.up.xyz, pc.right.xyz - end);
|
||||||
|
world = end + (length(side) > 1e-9 ? normalize(side) : vec3(0.0, 0.0, 1.0)) * q.x * (q.y < 0.0 ? -pc.posSize.w : pc.right.w);
|
||||||
|
}
|
||||||
v_uv = q * 0.5 + 0.5;
|
v_uv = q * 0.5 + 0.5;
|
||||||
v_uv.y = 1.0 - v_uv.y;
|
v_uv.y = 1.0 - v_uv.y;
|
||||||
gl_Position = pc.viewProj * vec4(world, 1.0);
|
gl_Position = pc.viewProj * vec4(world, 1.0);
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,12 @@ private:
|
||||||
glm::vec3 target_{0, 0, 0.4f};
|
glm::vec3 target_{0, 0, 0.4f};
|
||||||
float yaw_ = -135.0f, pitch_ = 25.0f, dist_ = 4.0f, fov_ = 50.0f;
|
float yaw_ = -135.0f, pitch_ = 25.0f, dist_ = 4.0f, fov_ = 50.0f;
|
||||||
glm::vec2 nearFar_{2.0f, 16384.0f};
|
glm::vec2 nearFar_{2.0f, 16384.0f};
|
||||||
std::map<uint32_t, std::pair<uint64_t, float>> radii_; // (each object's mesh: how far it reaches from its place)
|
struct Reach {
|
||||||
|
uint64_t version = 0;
|
||||||
|
float radius = 0.0f; // (how far its mesh reaches from its place)
|
||||||
|
glm::vec3 lo{0.0f}, hi{0.0f}; // (the box round its mesh, its mirrored copies too)
|
||||||
|
};
|
||||||
|
std::map<uint32_t, Reach> radii_; // (each object's)
|
||||||
ImVec2 viewMin_{0, 0}, viewMax_{1, 1};
|
ImVec2 viewMin_{0, 0}, viewMax_{1, 1};
|
||||||
glm::mat4 vp_{1.0f};
|
glm::mat4 vp_{1.0f};
|
||||||
glm::vec3 eye_{0.0f};
|
glm::vec3 eye_{0.0f};
|
||||||
|
|
@ -1471,6 +1476,7 @@ private:
|
||||||
void lookBy(ImVec2 d);
|
void lookBy(ImVec2 d);
|
||||||
void refocus();
|
void refocus();
|
||||||
float nearestThing();
|
float nearestThing();
|
||||||
|
auto reachOf(const Object& o) -> const Reach&;
|
||||||
float viewScale() const { return std::max(std::min(dist_, nearest_), 0.001f); } // (how big the view is where it matters: what's ahead, or nearer)
|
float viewScale() const { return std::max(std::min(dist_, nearest_), 0.001f); } // (how big the view is where it matters: what's ahead, or nearer)
|
||||||
void benchStep();
|
void benchStep();
|
||||||
void benchEnd(std::chrono::steady_clock::time_point began);
|
void benchEnd(std::chrono::steady_clock::time_point began);
|
||||||
|
|
|
||||||
100
src/app_view.cpp
100
src/app_view.cpp
|
|
@ -191,18 +191,38 @@ auto App::refocus() -> void {
|
||||||
if (t > 0.0f) dist_ = std::clamp(t, 0.01f, 5000.0f), target_ = eye + f * dist_;
|
if (t > 0.0f) dist_ = std::clamp(t, 0.01f, 5000.0f), target_ = eye + f * dist_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// How far an object's mesh reaches (worked out again when its shape changes).
|
||||||
|
auto App::reachOf(const Object& o) -> const Reach& {
|
||||||
|
Reach& r = radii_[o.id];
|
||||||
|
if (r.version == shapeKey(o) && r.version) return r;
|
||||||
|
r = {shapeKey(o)};
|
||||||
|
if (!o.mesh.verts.empty()) r.lo = boundsMin(o.mesh), r.hi = boundsMax(o.mesh);
|
||||||
|
for (int a = 0; a < 3; ++a)
|
||||||
|
if (o.mirror & (1 << a)) r.lo[a] = std::min(r.lo[a], -r.hi[a]), r.hi[a] = std::max(r.hi[a], -r.lo[a]);
|
||||||
|
for (const glm::vec3& v : o.mesh.verts) r.radius = std::max(r.radius, glm::length(v));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
// How far the nearest thing (an object, the ground) is from the eye: the near plane and the
|
// How far the nearest thing (an object, the ground) is from the eye: the near plane and the
|
||||||
// shade in corners go by it, so looking past something close doesn't break them.
|
// shade in corners go by it, so looking past something close doesn't break them. To each
|
||||||
|
// object's box (not a ball round it: inside a long flat one's ball, it would come to nothing).
|
||||||
auto App::nearestThing() -> float {
|
auto App::nearestThing() -> float {
|
||||||
float best = std::abs(eye_.z); // (the ground)
|
float best = std::abs(eye_.z); // (the ground)
|
||||||
for (const Object& o : scene_.objects) {
|
for (const Object& o : scene_.objects) {
|
||||||
if (!o.visible) continue;
|
if (!o.visible) continue;
|
||||||
auto& [version, radius] = radii_[o.id];
|
const Reach& r = reachOf(o);
|
||||||
if (version != shapeKey(o) || !version) {
|
const glm::mat4 m = objectMatrix(o);
|
||||||
version = shapeKey(o), radius = 0;
|
const glm::vec3 local = glm::inverse(m) * glm::vec4(eye_, 1.0f);
|
||||||
for (const glm::vec3& v : o.mesh.verts) radius = std::max(radius, glm::length(v));
|
best = std::min(best, glm::distance(eye_, glm::vec3(m * glm::vec4(glm::clamp(local, r.lo, r.hi), 1.0f))));
|
||||||
}
|
}
|
||||||
best = std::min(best, std::max(glm::distance(eye_, o.position) - radius * std::max({std::abs(o.scale.x), std::abs(o.scale.y), std::abs(o.scale.z)}), 0.0f));
|
for (const Reference& ref : scene_.references) {
|
||||||
|
if (!ref.visible || (ref.ownViewOnly && !lookingAt(ref.axis))) continue;
|
||||||
|
const Picture& pic = picture(ref);
|
||||||
|
if (!pic.ok) continue;
|
||||||
|
const auto c = referenceCorners(ref, pic.aspect);
|
||||||
|
const glm::vec3 u = c[1] - c[0], v = c[3] - c[0], d = eye_ - c[0];
|
||||||
|
const float s = std::clamp(glm::dot(d, u) / std::max(glm::dot(u, u), 1e-12f), 0.0f, 1.0f), t = std::clamp(glm::dot(d, v) / std::max(glm::dot(v, v), 1e-12f), 0.0f, 1.0f);
|
||||||
|
best = std::min(best, glm::distance(eye_, c[0] + u * s + v * t));
|
||||||
}
|
}
|
||||||
return std::max(best, 0.001f);
|
return std::max(best, 0.001f);
|
||||||
}
|
}
|
||||||
|
|
@ -264,7 +284,7 @@ auto App::updateCamera(ImGuiIO& io) -> void {
|
||||||
else {
|
else {
|
||||||
float size = 0.0f; // (the biggest object's size: the least step is a little of it)
|
float size = 0.0f; // (the biggest object's size: the least step is a little of it)
|
||||||
for (const Object& o : scene_.objects)
|
for (const Object& o : scene_.objects)
|
||||||
if (auto r = radii_.find(o.id); r != radii_.end()) size = std::max(size, r->second.second * std::max({std::abs(o.scale.x), std::abs(o.scale.y), std::abs(o.scale.z)}));
|
size = std::max(size, reachOf(o).radius * std::max({std::abs(o.scale.x), std::abs(o.scale.y), std::abs(o.scale.z)}));
|
||||||
const float step = std::clamp(std::max(dist_ * 0.2f, size * 0.05f), 0.001f, 500.0f) * fry_movespeed.f();
|
const float step = std::clamp(std::max(dist_ * 0.2f, size * 0.05f), 0.001f, 500.0f) * fry_movespeed.f();
|
||||||
target_ += viewDir() * step * io.MouseWheel;
|
target_ += viewDir() * step * io.MouseWheel;
|
||||||
refocus();
|
refocus();
|
||||||
|
|
@ -308,16 +328,12 @@ auto App::settleView() -> void {
|
||||||
auto App::nearFar() -> glm::vec2 {
|
auto App::nearFar() -> glm::vec2 {
|
||||||
float reach = 1.0f; // (how far from the middle the furthest object reaches, m)
|
float reach = 1.0f; // (how far from the middle the furthest object reaches, m)
|
||||||
for (const Object& o : scene_.objects) {
|
for (const Object& o : scene_.objects) {
|
||||||
auto& [version, radius] = radii_[o.id];
|
reach = std::max(reach, glm::length(o.position) + reachOf(o).radius * std::max({std::abs(o.scale.x), std::abs(o.scale.y), std::abs(o.scale.z)}));
|
||||||
if (version != shapeKey(o) || !version) {
|
|
||||||
version = shapeKey(o), radius = 0;
|
|
||||||
for (const glm::vec3& v : o.mesh.verts) radius = std::max(radius, glm::length(v));
|
|
||||||
}
|
|
||||||
reach = std::max(reach, glm::length(o.position) + radius * std::max({std::abs(o.scale.x), std::abs(o.scale.y), std::abs(o.scale.z)}));
|
|
||||||
}
|
}
|
||||||
// (the near plane as far out as it can be: the depth's precision is spread between them, so
|
// (the near plane as far out as it can be: the depth's precision is spread between them, so
|
||||||
// the nearer it is, the more surfaces far off flicker through each other)
|
// the nearer it is, the more surfaces flicker through each other. Nothing is nearer than the
|
||||||
const float nearM = std::clamp(viewScale() * 0.005f, 1e-5f, 50.0f);
|
// nearest thing, and a quarter of the way to it keeps even the screen's corners clear of it)
|
||||||
|
const float nearM = std::clamp(std::max(viewScale() * 0.005f, nearest_ * 0.25f), 1e-5f, 50.0f);
|
||||||
const float farM = std::max({dist_ * 60.0f, (glm::length(eye_) + reach) * 2.0f, 100.0f});
|
const float farM = std::max({dist_ * 60.0f, (glm::length(eye_) + reach) * 2.0f, 100.0f});
|
||||||
return glm::vec2(nearM, farM) * kUnits;
|
return glm::vec2(nearM, farM) * kUnits;
|
||||||
}
|
}
|
||||||
|
|
@ -1388,7 +1404,7 @@ auto App::draw3D(keel::Renderer& r) -> void {
|
||||||
const glm::vec3 u = n.z > 0.5f ? glm::vec3(1, 0, 0) : n.x > 0.5f ? glm::vec3(0, 1, 0) : glm::vec3(1, 0, 0);
|
const glm::vec3 u = n.z > 0.5f ? glm::vec3(1, 0, 0) : n.x > 0.5f ? glm::vec3(0, 1, 0) : glm::vec3(1, 0, 0);
|
||||||
const glm::vec3 v = glm::cross(n, u);
|
const glm::vec3 v = glm::cross(n, u);
|
||||||
const float first = gridStep() * 0.1f;
|
const float first = gridStep() * 0.1f;
|
||||||
const int count = 20, pieces = 12, levels = 4;
|
const int count = 20, levels = 4;
|
||||||
const glm::vec3 centre = target_ - n * glm::dot(target_, n);
|
const glm::vec3 centre = target_ - n * glm::dot(target_, n);
|
||||||
const float cu = glm::dot(centre, u), cv = glm::dot(centre, v);
|
const float cu = glm::dot(centre, u), cv = glm::dot(centre, v);
|
||||||
const float shade = rendered ? 0.0f : 1.0f; // (dark lines on the sunny ground, light ones in the grey room)
|
const float shade = rendered ? 0.0f : 1.0f; // (dark lines on the sunny ground, light ones in the grey room)
|
||||||
|
|
@ -1400,31 +1416,37 @@ auto App::draw3D(keel::Renderer& r) -> void {
|
||||||
const glm::vec3 along = dir ? u : v, across = dir ? v : u;
|
const glm::vec3 along = dir ? u : v, across = dir ? v : u;
|
||||||
const float mid = dir ? cu : cv, side = std::abs(at - (dir ? cv : cu));
|
const float mid = dir ? cu : cv, side = std::abs(at - (dir ? cv : cu));
|
||||||
// (short pieces near the eye, longer further off: each one's width and fading is
|
// (short pieces near the eye, longer further off: each one's width and fading is
|
||||||
// worked out at its middle, so it has to be about the same all along it)
|
// worked out at its middle, so it has to be about the same all along it. Each piece
|
||||||
|
// reaches a quarter further from the eye than the last: from the eye's nearest
|
||||||
|
// point on the line, steps growing with how far the eye is from the line)
|
||||||
const float near = std::clamp(glm::dot(eye_, along), from, to);
|
const float near = std::clamp(glm::dot(eye_, along), from, to);
|
||||||
for (int k = 0; k < pieces * 2; ++k) {
|
const glm::vec3 off = eye_ - across * at;
|
||||||
const int sideOf = k < pieces ? -1 : 1, j = k % pieces;
|
const float gap = std::max(glm::length(off - along * glm::dot(off, along)), 1e-4f); // (from the eye to the line)
|
||||||
|
for (int sideOf : {-1, 1}) {
|
||||||
const float len = sideOf < 0 ? near - from : to - near;
|
const float len = sideOf < 0 ? near - from : to - near;
|
||||||
if (len <= 0.0f) continue;
|
if (len <= 0.0f) continue;
|
||||||
const float f0 = float(j) / float(pieces), f1 = float(j + 1) / float(pieces);
|
const int pieces = std::clamp(int(std::ceil(std::log1p(len / gap) / std::log(1.25f))), 2, 64);
|
||||||
const float t0 = near + float(sideOf) * len * f0 * f0 * f0, t1 = near + float(sideOf) * len * f1 * f1 * f1;
|
for (int j = 0; j < pieces; ++j) {
|
||||||
const glm::vec3 a = across * at + along * t0, b = across * at + along * t1, c = (a + b) * 0.5f;
|
const float t0 = near + float(sideOf) * gap * std::expm1(std::log1p(len / gap) * float(j) / float(pieces));
|
||||||
// (pixels to the next line like it, across this one as it lies on the screen)
|
const float t1 = near + float(sideOf) * gap * std::expm1(std::log1p(len / gap) * float(j + 1) / float(pieces));
|
||||||
const glm::vec4 c0 = vp_ * glm::vec4(c, 1.0f), c1 = vp_ * glm::vec4(c + across * s, 1.0f), c2 = vp_ * glm::vec4(c + along * s, 1.0f);
|
const glm::vec3 a = across * at + along * t0, b = across * at + along * t1, c = (a + b) * 0.5f;
|
||||||
if (c0.w <= 1e-6f || c1.w <= 1e-6f || c2.w <= 1e-6f) continue; // (behind the eye)
|
// (pixels to the next line like it, across this one as it lies on the screen)
|
||||||
const glm::vec2 half = glm::vec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y) * 0.5f;
|
const glm::vec4 c0 = vp_ * glm::vec4(c, 1.0f), c1 = vp_ * glm::vec4(c + across * s, 1.0f), c2 = vp_ * glm::vec4(c + along * s, 1.0f);
|
||||||
const glm::vec2 s0 = glm::vec2(c0) / c0.w * half, s1 = glm::vec2(c1) / c1.w * half, s2 = glm::vec2(c2) / c2.w * half;
|
if (c0.w <= 1e-6f || c1.w <= 1e-6f || c2.w <= 1e-6f) continue; // (behind the eye)
|
||||||
const glm::vec2 way = s2 - s0;
|
const glm::vec2 half = glm::vec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y) * 0.5f;
|
||||||
const float wayLen = glm::length(way), d = s1.x - s0.x, e = s1.y - s0.y;
|
const glm::vec2 s0 = glm::vec2(c0) / c0.w * half, s1 = glm::vec2(c1) / c1.w * half, s2 = glm::vec2(c2) / c2.w * half;
|
||||||
const float apart = wayLen > 1e-6f ? std::abs(d * way.y - e * way.x) / wayLen : glm::length(s1 - s0);
|
const glm::vec2 way = s2 - s0;
|
||||||
const float px = pixelSize(c);
|
const float wayLen = glm::length(way), d = s1.x - s0.x, e = s1.y - s0.y;
|
||||||
float alpha = axis ? std::max(glm::smoothstep(1.5f, 6.0f, apart), 0.35f) : glm::smoothstep(5.0f, 20.0f, apart);
|
const float apart = wayLen > 1e-6f ? std::abs(d * way.y - e * way.x) / wayLen : glm::length(s1 - s0);
|
||||||
if (ring > 0.0f) alpha *= glm::smoothstep(ring, ring * 0.6f, std::max(side, std::abs((t0 + t1) * 0.5f - mid)));
|
float alpha = axis ? std::max(glm::smoothstep(1.5f, 6.0f, apart), 0.35f) : glm::smoothstep(5.0f, 20.0f, apart);
|
||||||
const float strong = axis ? 1.0f : glm::smoothstep(20.0f, 160.0f, apart);
|
if (ring > 0.0f) alpha *= glm::smoothstep(ring, ring * 0.6f, std::max(side, std::abs((t0 + t1) * 0.5f - mid)));
|
||||||
glm::vec4 pc = axis ? col : glm::vec4(glm::vec3(shade), glm::mix(dim, bright, strong));
|
const float strong = axis ? 1.0f : glm::smoothstep(20.0f, 160.0f, apart);
|
||||||
pc.a *= alpha;
|
glm::vec4 pc = axis ? col : glm::vec4(glm::vec3(shade), glm::mix(dim, bright, strong));
|
||||||
if (pc.a < 0.004f) continue;
|
pc.a *= alpha;
|
||||||
r.drawStreak(lift(a) * kUnits, lift(b) * kUnits, px * (1.0f + 0.6f * strong) * kUnits, pc, keel::kWhiteTexture);
|
if (pc.a < 0.004f) continue;
|
||||||
|
const float thick = (1.0f + 0.6f * strong) * kUnits; // (a pixel or so at each end: the pieces meet exactly)
|
||||||
|
r.drawStreak(lift(a) * kUnits, lift(b) * kUnits, pixelSize(a) * thick, pc, keel::kWhiteTexture, false, pixelSize(b) * thick);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (int level = 0; level < levels; ++level) {
|
for (int level = 0; level < levels; ++level) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue