mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
The grid worked out for each pixel (as in Blender): one square on its plane, every set of lines showing everywhere their lines are far enough apart on the screen (no patch round the middle, no pieces smearing or fading as you move), a pixel or so wide and smooth, the axes in the colour of the world's axis they run along; in every renderer (drawGrid: a sprite marked by its colour's alpha)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9d9557117a
commit
b2fc0437bc
6 changed files with 101 additions and 70 deletions
|
|
@ -211,6 +211,12 @@ 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)});
|
||||
}
|
||||
|
||||
void Renderer::drawGrid(const glm::vec3& centre, const glm::vec3& u, const glm::vec3& v, float first, const glm::vec3& look, float reach) {
|
||||
if (packet_.sprites.size() >= 8192) return;
|
||||
// (colour.a < 0 marks it; sprite.vert / sprite.frag)
|
||||
packet_.sprites.push_back({glm::vec4(centre, reach), glm::vec4(look, -1.0f), glm::vec4(u, first), glm::vec4(v, 0.0f)});
|
||||
}
|
||||
|
||||
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;
|
||||
if (list.size() >= 8192) return;
|
||||
|
|
@ -757,6 +763,7 @@ void Renderer::buildPacket(uint32_t width, uint32_t height) {
|
|||
up = glm::cross(right, f);
|
||||
for (auto* list : {&p.sprites, &p.glowSprites})
|
||||
for (gpu::Sprite& s : *list) {
|
||||
if (s.color.a < 0.0f) continue; // (a grid: on its own plane)
|
||||
if (const glm::vec3 half(s.up); glm::dot(half, half) > 0.0f) { // a streak: each end's side faces the eye (sprite.vert)
|
||||
s.right = glm::vec4(camera_.origin, s.right.w);
|
||||
s.posSize.w = -std::max(s.posSize.w, 1e-9f); // (below 0: a streak; its half width at `from`)
|
||||
|
|
@ -765,6 +772,7 @@ void Renderer::buildPacket(uint32_t width, uint32_t height) {
|
|||
s.right = glm::vec4(right, s.right.w), s.up = glm::vec4(up, s.up.w);
|
||||
}
|
||||
std::sort(p.sprites.begin(), p.sprites.end(), [&](const gpu::Sprite& a, const gpu::Sprite& b) {
|
||||
if ((a.color.a < 0.0f) != (b.color.a < 0.0f)) return a.color.a < 0.0f; // (grids first: they're under everything)
|
||||
return glm::dot(glm::vec3(a.posSize) - camera_.origin, f) > glm::dot(glm::vec3(b.posSize) - camera_.origin, f);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,11 @@ public:
|
|||
// A sprite stretched from `from` to `to`, `width` across (`widthTo` at `to`: < 0, the same),
|
||||
// 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, float widthTo = -1.0f);
|
||||
// A grid of lines on the plane through `centre` along u and v (unit), drawn for each pixel:
|
||||
// four sets `first` × 10 apart, each showing by how far apart its lines are on the screen,
|
||||
// the axes coloured; look: the lines' shade (0 dark, 1 light), how faint, how strong; fading
|
||||
// out `reach` from the centre.
|
||||
void drawGrid(const glm::vec3& centre, const glm::vec3& u, const glm::vec3& v, float first, const glm::vec3& look, float reach);
|
||||
// skin: one matrix per joint (anim::skinMatrices) for skinned models; ignored otherwise.
|
||||
// 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),
|
||||
|
|
|
|||
|
|
@ -366,28 +366,67 @@ void main() { o_color = vec4(u_color.rgb * (0.5 + 0.5 * max(dot(v_normal, normal
|
|||
const char* kSpriteVert = R"(
|
||||
uniform mat4 u_viewProj;
|
||||
uniform vec4 u_posSize, u_right, u_up; // right.w: turn (radians)
|
||||
uniform vec4 u_color;
|
||||
out vec2 v_uv;
|
||||
out vec3 v_world;
|
||||
const vec2 kQuad[6] = vec2[](vec2(-1,-1), vec2(1,-1), vec2(1,1), vec2(-1,-1), vec2(1,1), vec2(-1,1));
|
||||
void main() {
|
||||
vec2 q = kQuad[gl_VertexID];
|
||||
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);
|
||||
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
|
||||
if (u_color.a < 0.0) world = u_posSize.xyz + (u_right.xyz * q.x + u_up.xyz * q.y) * u_posSize.w; // a grid: a square on its plane
|
||||
else 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_world = world;
|
||||
gl_Position = u_viewProj * vec4(world, 1.0);
|
||||
gl_Position.z = gl_Position.z * 2.0 - gl_Position.w;
|
||||
}
|
||||
)";
|
||||
const char* kSpriteFrag = R"(
|
||||
in vec2 v_uv;
|
||||
in vec3 v_world;
|
||||
uniform sampler2D u_albedo;
|
||||
uniform vec4 u_color;
|
||||
uniform vec4 u_color, u_posSize, u_right, u_up;
|
||||
out vec4 o_color;
|
||||
// A grid (a sprite with colour.a < 0), worked out for each pixel where it lies on its plane:
|
||||
// four sets of lines each `first` × 10 apart, each set showing by how far apart its lines are on
|
||||
// the screen (none where they crowd together, stronger where they're far apart), lines about a
|
||||
// pixel wide, smooth; the axes through the middle in the colour of the world's axis they run along.
|
||||
// look: x the lines' shade (0 dark, 1 light), y how faint, z how strong; reach: it fades out there.
|
||||
vec3 gridAxisColor(vec3 d) {
|
||||
d = abs(d);
|
||||
return d.x > 0.5 ? vec3(0.9, 0.25, 0.25) : d.y > 0.5 ? vec3(0.3, 0.75, 0.25) : vec3(0.25, 0.45, 0.95);
|
||||
}
|
||||
vec4 gridColor(vec3 w, vec3 centre, vec3 u, vec3 v, float first, vec3 look, float reach) {
|
||||
vec2 c = vec2(dot(w, u), dot(w, v));
|
||||
vec2 fw = max(fwidth(c), vec2(1e-9));
|
||||
float lines = 0.0;
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
float s = first * pow(10.0, float(k));
|
||||
vec2 apart = s / fw; // (pixels between this set's lines, each way)
|
||||
vec2 off = abs(fract(c / s + 0.5) - 0.5) * apart; // (pixels to the nearest of them)
|
||||
vec2 strong = smoothstep(20.0, 160.0, apart);
|
||||
vec2 cover = clamp(0.5 * (1.0 + 0.6 * strong) + 0.5 - off, 0.0, 1.0);
|
||||
vec2 a = cover * smoothstep(5.0, 20.0, apart) * mix(vec2(look.y), vec2(look.z), strong);
|
||||
lines = max(lines, max(a.x, a.y));
|
||||
}
|
||||
vec2 axis = clamp(1.25 - abs(c) / fw, 0.0, 1.0) * 0.9; // (x: the line along v, y: along u)
|
||||
float fade = smoothstep(reach, reach * 0.6, length(w - centre));
|
||||
vec3 col = vec3(look.x) * lines;
|
||||
float a = lines;
|
||||
col = mix(col, gridAxisColor(v), axis.x), a = mix(a, 1.0, axis.x);
|
||||
col = mix(col, gridAxisColor(u), axis.y), a = mix(a, 1.0, axis.y);
|
||||
return vec4(col / max(a, 1e-6), a * fade);
|
||||
}
|
||||
void main() {
|
||||
if (u_color.a < 0.0) {
|
||||
o_color = gridColor(v_world, u_posSize.xyz, normalize(u_right.xyz), normalize(u_up.xyz), u_right.w, u_color.rgb, u_posSize.w);
|
||||
return;
|
||||
}
|
||||
vec4 t = texture(u_albedo, v_uv);
|
||||
o_color = vec4(t.rgb * u_color.rgb, clamp((t.a - 0.03) / 0.97, 0.0, 1.0) * u_color.a);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,44 @@ PUSH_BLOCK {
|
|||
vec4 up;
|
||||
} pc;
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec3 v_world;
|
||||
// A grid (a sprite with colour.a < 0), worked out for each pixel where it lies on its plane:
|
||||
// four sets of lines each `first` × 10 apart, each set showing by how far apart its lines are on
|
||||
// the screen (none where they crowd together, stronger where they're far apart), lines about a
|
||||
// pixel wide, smooth; the axes through the middle in the colour of the world's axis they run along.
|
||||
// look: x the lines' shade (0 dark, 1 light), y how faint, z how strong; reach: it fades out there.
|
||||
vec3 gridAxisColor(vec3 d) {
|
||||
d = abs(d);
|
||||
return d.x > 0.5 ? vec3(0.9, 0.25, 0.25) : d.y > 0.5 ? vec3(0.3, 0.75, 0.25) : vec3(0.25, 0.45, 0.95);
|
||||
}
|
||||
vec4 gridColor(vec3 w, vec3 centre, vec3 u, vec3 v, float first, vec3 look, float reach) {
|
||||
vec2 c = vec2(dot(w, u), dot(w, v));
|
||||
vec2 fw = max(fwidth(c), vec2(1e-9));
|
||||
float lines = 0.0;
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
float s = first * pow(10.0, float(k));
|
||||
vec2 apart = s / fw; // (pixels between this set's lines, each way)
|
||||
vec2 off = abs(fract(c / s + 0.5) - 0.5) * apart; // (pixels to the nearest of them)
|
||||
vec2 strong = smoothstep(20.0, 160.0, apart);
|
||||
vec2 cover = clamp(0.5 * (1.0 + 0.6 * strong) + 0.5 - off, 0.0, 1.0);
|
||||
vec2 a = cover * smoothstep(5.0, 20.0, apart) * mix(vec2(look.y), vec2(look.z), strong);
|
||||
lines = max(lines, max(a.x, a.y));
|
||||
}
|
||||
vec2 axis = clamp(1.25 - abs(c) / fw, 0.0, 1.0) * 0.9; // (x: the line along v, y: along u)
|
||||
float fade = smoothstep(reach, reach * 0.6, length(w - centre));
|
||||
vec3 col = vec3(look.x) * lines;
|
||||
float a = lines;
|
||||
col = mix(col, gridAxisColor(v), axis.x), a = mix(a, 1.0, axis.x);
|
||||
col = mix(col, gridAxisColor(u), axis.y), a = mix(a, 1.0, axis.y);
|
||||
return vec4(col / max(a, 1e-6), a * fade);
|
||||
}
|
||||
|
||||
layout(location = 0) out vec4 o_color;
|
||||
void main() {
|
||||
if (pc.color.a < 0.0) { // a grid: right.w its finest step, color.rgb its look, posSize.w how far it reaches
|
||||
o_color = gridColor(v_world, pc.posSize.xyz, normalize(pc.right.xyz), normalize(pc.up.xyz), pc.right.w, pc.color.rgb, pc.posSize.w);
|
||||
return;
|
||||
}
|
||||
vec4 t = sampleTex(floatBitsToUint(pc.up.w), v_uv);
|
||||
// Block compression leaves a little alpha where there should be none; bright sprites
|
||||
// would show it as a square: cut it.
|
||||
|
|
|
|||
|
|
@ -9,17 +9,20 @@ PUSH_BLOCK {
|
|||
vec4 up; // camera up, w = texture index (bits)
|
||||
} pc;
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec3 v_world;
|
||||
const vec2 kQuad[6] = vec2[](vec2(-1,-1), vec2(1,-1), vec2(1,1), vec2(-1,-1), vec2(1,1), vec2(-1,1));
|
||||
void main() {
|
||||
vec2 q = kQuad[VERTEX_ID];
|
||||
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);
|
||||
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
|
||||
if (pc.color.a < 0.0) world = pc.posSize.xyz + (pc.right.xyz * q.x + pc.up.xyz * q.y) * pc.posSize.w; // a grid: a square on its plane
|
||||
else 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.y = 1.0 - v_uv.y;
|
||||
v_world = world;
|
||||
gl_Position = pc.viewProj * vec4(world, 1.0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1399,77 +1399,17 @@ auto App::draw3D(keel::Renderer& r) -> void {
|
|||
// lines come in too close together to see and fade in. Each line is drawn by the widest set
|
||||
// it belongs to (once), in pieces, each a pixel wide where it is.
|
||||
if (tab_ == Tab::Render ? renderGrid_ : showGrid_) {
|
||||
// Worked out for each pixel (as in Blender): four sets of lines from a tenth of the
|
||||
// grid step, each showing by how far apart its lines are on the screen, the axes
|
||||
// coloured; round the point under the eye, out to where the widest set ends.
|
||||
glm::vec3 p, n;
|
||||
workPlane(p, n);
|
||||
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 float first = gridStep() * 0.1f;
|
||||
const int count = 20, levels = 4;
|
||||
const glm::vec3 centre = target_ - n * glm::dot(target_, n);
|
||||
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 dim = rendered ? 0.14f : 0.05f, bright = rendered ? 0.4f : 0.16f;
|
||||
auto lift = [&](const glm::vec3& q) { return q + (eye_ - q) * 0.002f; }; // (towards the eye along its line of sight: the same place on the screen, but in front of the ground)
|
||||
// One line, at `at` across, from `from` to `to` along; s: how far apart it and its like
|
||||
// are; ring: how far its set reaches from the middle (0: no end, the axes).
|
||||
auto line = [&](int dir, float at, float from, float to, float s, float ring, glm::vec4 col, bool axis) {
|
||||
const glm::vec3 along = dir ? u : v, across = dir ? v : u;
|
||||
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
|
||||
// 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 glm::vec3 off = eye_ - across * at;
|
||||
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;
|
||||
if (len <= 0.0f) continue;
|
||||
const int pieces = std::clamp(int(std::ceil(std::log1p(len / gap) / std::log(1.25f))), 2, 64);
|
||||
for (int j = 0; j < pieces; ++j) {
|
||||
const float t0 = near + float(sideOf) * gap * std::expm1(std::log1p(len / gap) * float(j) / float(pieces));
|
||||
const float t1 = near + float(sideOf) * gap * std::expm1(std::log1p(len / gap) * float(j + 1) / float(pieces));
|
||||
const glm::vec3 a = across * at + along * t0, b = across * at + along * t1, c = (a + b) * 0.5f;
|
||||
// (pixels to the next line like it, across this one as it lies on the screen)
|
||||
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);
|
||||
if (c0.w <= 1e-6f || c1.w <= 1e-6f || c2.w <= 1e-6f) continue; // (behind the eye)
|
||||
const glm::vec2 half = glm::vec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y) * 0.5f;
|
||||
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 glm::vec2 way = s2 - s0;
|
||||
const float wayLen = glm::length(way), d = s1.x - s0.x, e = s1.y - s0.y;
|
||||
const float apart = wayLen > 1e-6f ? std::abs(d * way.y - e * way.x) / wayLen : glm::length(s1 - s0);
|
||||
float alpha = axis ? std::max(glm::smoothstep(1.5f, 6.0f, apart), 0.35f) : glm::smoothstep(5.0f, 20.0f, apart);
|
||||
if (ring > 0.0f) alpha *= glm::smoothstep(ring, ring * 0.6f, std::max(side, std::abs((t0 + t1) * 0.5f - mid)));
|
||||
const float strong = axis ? 1.0f : glm::smoothstep(20.0f, 160.0f, apart);
|
||||
glm::vec4 pc = axis ? col : glm::vec4(glm::vec3(shade), glm::mix(dim, bright, strong));
|
||||
pc.a *= alpha;
|
||||
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) {
|
||||
const float s = first * std::pow(10.0f, float(level)), ring = count * s;
|
||||
const bool top = level == levels - 1;
|
||||
for (int dir = 0; dir < 2; ++dir) {
|
||||
const float c0 = snapTo(dir ? cv : cu, s), mid = dir ? cu : cv;
|
||||
for (int i = -count; i <= count; ++i) {
|
||||
const float at = c0 + float(i) * s;
|
||||
if (std::abs(at) < s * 0.01f) continue; // (the axis: drawn on its own)
|
||||
if (!top && std::abs(snapTo(at, s * 10.0f) - at) < s * 0.01f) continue; // (the next set's)
|
||||
line(dir, at, mid - ring, mid + ring, s, ring, {}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The axes: right through, as far as the widest set reaches.
|
||||
const float reach = count * first * std::pow(10.0f, float(levels - 1));
|
||||
for (int dir = 0; dir < 2; ++dir) {
|
||||
const glm::vec4 col = dir ? (n.x > 0.5f ? glm::vec4(0.3f, 0.75f, 0.25f, 0.9f) : glm::vec4(0.9f, 0.25f, 0.25f, 0.9f))
|
||||
: (n.z > 0.5f ? glm::vec4(0.3f, 0.75f, 0.25f, 0.9f) : glm::vec4(0.25f, 0.45f, 0.95f, 0.9f));
|
||||
const float mid = dir ? cu : cv;
|
||||
line(dir, 0.0f, mid - reach, mid + reach, first, 0.0f, col, true);
|
||||
}
|
||||
const float first = gridStep() * 0.1f, reach = std::max(first * 20000.0f, nearFar_.y / kUnits * 0.5f);
|
||||
const glm::vec3 under = eye_ - n * glm::dot(eye_, n); // (the grid's plane goes through the origin)
|
||||
const glm::vec3 look = rendered ? glm::vec3(0.0f, 0.14f, 0.4f) : glm::vec3(1.0f, 0.05f, 0.16f); // (dark lines on the sunny ground, light ones in the grey room)
|
||||
r.drawGrid(under * kUnits, u, v, first * kUnits, look, reach * kUnits);
|
||||
}
|
||||
if (tab_ == Tab::Print) drawPrintBed(r, light);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue