diff --git a/engine/render/renderer.cpp b/engine/render/renderer.cpp index bfc166a..3a41607 100644 --- a/engine/render/renderer.cpp +++ b/engine/render/renderer.cpp @@ -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); }); } diff --git a/engine/render/renderer.h b/engine/render/renderer.h index 873304c..f827d7a 100644 --- a/engine/render/renderer.h +++ b/engine/render/renderer.h @@ -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), diff --git a/engine/rhi/gl/gl_legacy.cpp b/engine/rhi/gl/gl_legacy.cpp index b1f45d5..3ddde8b 100644 --- a/engine/rhi/gl/gl_legacy.cpp +++ b/engine/rhi/gl/gl_legacy.cpp @@ -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); } diff --git a/engine/shaders/sprite.frag b/engine/shaders/sprite.frag index a55454b..6973b06 100644 --- a/engine/shaders/sprite.frag +++ b/engine/shaders/sprite.frag @@ -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. diff --git a/engine/shaders/sprite.vert b/engine/shaders/sprite.vert index 3ee2c40..bc96d4d 100644 --- a/engine/shaders/sprite.vert +++ b/engine/shaders/sprite.vert @@ -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); } diff --git a/src/app_view.cpp b/src/app_view.cpp index 27fecda..5eb0c5d 100644 --- a/src/app_view.cpp +++ b/src/app_view.cpp @@ -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); }