From 448c0805d26c80c45bc6ab43a00cb4f82c014c31 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 16:04:29 +0000 Subject: [PATCH 1/2] =?UTF-8?q?Mobile:=20=D1=86=D0=B2=D0=B5=D1=82=D0=B0=20?= =?UTF-8?q?sRGB=E2=86=92linear,=20=D1=81=D0=B2=D0=B5=D1=82=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D1=80=D0=B0=D0=BD=20=D0=B7=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=80=D0=BE=D0=BC,=20=D1=82=D0=B5=D0=BD=D0=B8=20=D0=BF=D0=BB?= =?UTF-8?q?=D0=BE=D1=82=D0=BD=D0=B5=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - color-management.js: шестнадцатеричные и CSS-цвета считаются sRGB и переводятся в линейные (как ColorManagement в новых three), getHex отдаёт sRGB. Цвета образцов больше не выходят светлее на рендере. - Солнце 5, экспозиция 0.70, полусферическая подсветка только без HDRI: серый образец на солнце — чуть светлее себя, тень — 0.43 от освещённого. - Подложка и рама забора — задуманные цвета вместо подобранных под старое высветление; разметка редактора без тональной кривой. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013c9N76tZZnqcQLUUSc5okD --- ARCHITECTURE.md | 37 ++++++++++++++++++++++++++++++----- TODO.md | 3 +++ color-management.js | 43 +++++++++++++++++++++++++++++++++++++++++ editor3d.js | 14 ++++++++++++-- index.html | 9 +++++---- shared/house-builder.js | 9 +++++---- viewer3d-builders.js | 8 ++++---- viewer3d-core.js | 38 +++++++++++++++++++++++------------- 8 files changed, 129 insertions(+), 32 deletions(-) create mode 100644 color-management.js diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 0375257..aeed60d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -182,11 +182,12 @@ viewer3d-core/builders/railing — classic scripts с общей глобаль версию поднимать обязательно. Актуальный срез (совпадает с `index.html`): ``` -styles-desktop.css?v=53 styles-mobile.css?v=5 state.js?v=78 -canvas.js?v=66 shared/house-builder.js?v=89 ResourceManager.js?v=14 -viewer3d-core.js?v=161 viewer3d-builders.js?v=67 viewer3d-railing.js?v=18 -viewer3d-entourage.js?v=14 editor3d.js?v=21 nav-desktop.js?v=143 -nav-mobile.js?v=6 project-io.js?v=7 backend_API/Calculator.js?v=3 +color-management.js?v=1 styles-desktop.css?v=53 styles-mobile.css?v=5 +state.js?v=78 canvas.js?v=66 shared/house-builder.js?v=90 +ResourceManager.js?v=14 viewer3d-core.js?v=162 viewer3d-builders.js?v=68 +viewer3d-railing.js?v=18 viewer3d-entourage.js?v=14 editor3d.js?v=22 +nav-desktop.js?v=143 nav-mobile.js?v=6 project-io.js?v=7 +backend_API/Calculator.js?v=3 ``` `viewer3d-entourage.js` автоматически детектит `IS_MOBILE` (UA + `innerWidth<768`) @@ -949,6 +950,32 @@ JSON-контракта `POST /api/calculate` и схемы БД лежит в g - В «Порядок подключения скриптов» добавлен актуальный срез `?v=N`, чтобы версии не искать по журналу; в решения — две строки про снап ограждения. +Ветка **Mobile**: цвета и свет сцены. + +- **Цвета — sRGB на входе, линейные внутри** (`color-management.js`, подключается + сразу после three.min.js). Рендер выводит sRGB, а three r128 принимал + `color: 0x…` / `color.set('#…')` как линейные — цвета образцов выходили заметно + светлее (серый #7e7e7e → #b8bec6 в кадре, тёмно-серый #1d2630 → #596f87). Модуль + делает то же, что ColorManagement новых three: `setHex`/`setStyle` переводят + sRGB → linear, `getHex` отдаёт sRGB (прочитанный и записанный заново цвет не + переводится дважды). `setRGB`, конструктор из трёх чисел и цвета GLB (glTF + хранит их линейными) не трогаются. +- **Свет подобран замером** на стенде (серый куб на серой площадке): + `SUN_INTENSITY = 5`, `SCENE_EXPOSURE = 0.70`; полусферическая подсветка только без + HDRI (1.8), с HDRI — 0. Было: солнце 1.5 при physicallyCorrectLights, экспозиция + 0.72 (её занизили, компенсируя высветление цветов, — отсюда тёмные текстуры) и + подсветка 0.3. Итог: #7e7e7e на солнце — #9a9897 (нейтрально, чуть светлее + образца), тень на земле — 0.43 от освещённого места (было 0.67). +- Цвета, подобранные под старое высветление, приведены к задуманным: подложка + `PAD_COLOR` 0x3c3c3c → 0x6a6a6a (≈130 в кадре, как и было задумано), рама забора + `FENCE_FRAME_COLOR` 0x2a2a2a → 0x4a4a4a. `_texAverageColor` отдаёт sRGB-hex. +- **Разметка редактора без тональной кривой** (`toneMapped: false` у линий, + маркеров, сетки, рамок фасада): оранжевый выбора теперь ровно акцент интерфейса + (раньше выходил #dab05e), белые ручки — белые. Зелёный «прочих объектов» и сетка + заданы цветами, которые раньше были видны на экране (0x73a782, 0xb6b6b6/0xc7c7c7). +- Cache-bust: `color-management.js?v=1`, `shared/house-builder.js?v=90`, + `viewer3d-core.js?v=162`, `viewer3d-builders.js?v=68`, `editor3d.js?v=22`. + Ветка **Mobile**: заглушки каталога убраны совсем. - Несуществующих товаров интерфейс больше не показывает: `STUB_RESULTS` diff --git a/TODO.md b/TODO.md index b7faff5..5d62ca0 100644 --- a/TODO.md +++ b/TODO.md @@ -258,6 +258,9 @@ «По выбранным фильтрам товаров нет» и «Сбросить фильтры» вместо заглушек. - [x] Заглушки каталога убраны совсем: пустой раздел — «В этом разделе каталога пока нет товаров», недоступный каталог — «Каталог временно недоступен». +- [x] Цвета и свет: цвета образцов переводятся из sRGB в линейные (раньше на + рендере выходили светлее образцов), экспозиция и солнце подобраны замером — + текстуры не темнее, чем надо, тени плотнее (0.43 от освещённого вместо 0.67). - [x] Дорожки: замкнутое кольцо сшивается на шве (миттер, как на внутренних углах); сшитые точки (шов кольца, стык линий) перетаскиваются вместе. - [x] Смета: остался только расчёт по спецификации; описание проекта и diff --git a/color-management.js b/color-management.js new file mode 100644 index 0000000..9585bfd --- /dev/null +++ b/color-management.js @@ -0,0 +1,43 @@ +// ══════════════════════════════════════════════ +// ЦВЕТА: sRGB на входе, линейные внутри (правка 2026-10-08) +// +// Рендер выводит картинку в sRGB (renderer.outputEncoding = sRGBEncoding), а +// освещение считается в линейном пространстве. Three r128 цвета не переводит: +// `color: 0x7e7e7e` или `color.set('#7e7e7e')` он принимал как ЛИНЕЙНЫЕ, и на +// выходе цвет выходил заметно светлее образца (серый #7e7e7e — #b8bec6 в кадре, +// тёмно-серый #1d2630 — #596f87). Текстуры при этом кодировались правильно, и +// экспозицию когда-то занизили, чтобы «не разбеливало» — так потемнели они. +// +// Здесь — то, что новые версии three делают сами (ColorManagement): цвет, +// заданный шестнадцатеричным числом или CSS-строкой, считается sRGB и +// переводится в линейный; getHex() отдаёт обратно sRGB, поэтому цвет, прочитанный +// и записанный заново (средний цвет текстуры, цвет карниза), не переводится +// дважды. setRGB, конструктор с тремя числами и цвета из GLB (glTF хранит их +// линейными) не трогаются. +// +// Подключать СРАЗУ после three.min.js, до любого кода, создающего цвета. +// ══════════════════════════════════════════════ +(function () { + if (typeof THREE === 'undefined' || !THREE.Color || THREE.Color.prototype._srgbPatched) return; + const P = THREE.Color.prototype; + const setHex = P.setHex, setStyle = P.setStyle, getHex = P.getHex; + + P.setHex = function (hex) { + setHex.call(this, hex); + return this.convertSRGBToLinear(); + }; + + // Имена цветов ('white') setStyle разбирает через setHex — они уже переведены. + // Остальные формы (#hex, rgb(), hsl()) он пишет в r, g, b напрямую. + P.setStyle = function (style) { + setStyle.call(this, style); + if (/^\s*(#|rgb|hsl)/i.test(String(style))) this.convertSRGBToLinear(); + return this; + }; + + P.getHex = function () { + return getHex.call(this.clone().convertLinearToSRGB()); + }; + + P._srgbPatched = true; +})(); diff --git a/editor3d.js b/editor3d.js index 7d3da2e..a2dc756 100644 --- a/editor3d.js +++ b/editor3d.js @@ -419,9 +419,13 @@ function _e3dClearGroup() { return g; } +// Разметка редактора — интерфейс, а не предмет в сцене: тональная кривая рендера +// (ACES) её не трогает, цвета на экране — ровно цвета интерфейса (правка 2026-10-08; +// раньше оранжевый выбора выходил грязно-жёлтым #dab05e, белые ручки — серыми). function _e3dLineMat(color, width) { return new THREE.LineBasicMaterial({ color, linewidth: width || 1, depthTest: false, transparent: true, opacity: 0.95, + toneMapped: false, }); } @@ -445,6 +449,7 @@ function _e3dMarker(np, color, radius) { const geo = new THREE.CircleGeometry(radius || 0.18, 20); const mesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({ color, depthTest: false, transparent: true, opacity: 0.9, side: THREE.DoubleSide, + toneMapped: false, })); mesh.rotation.x = -Math.PI / 2; mesh.position.set(w.x, y, w.z); @@ -473,7 +478,9 @@ function _e3dRectPts(r) { } const E3D_COL_SEL = 0xff7a1a; // выбранный объект — оранжевый, как акцент интерфейса -const E3D_COL_IDLE = 0x2f6f3f; // прочие объекты раздела — приглушённый зелёный +// Прочие объекты раздела — приглушённый зелёный. Значение — то, что раньше было +// видно на экране (0x2f6f3f проходил через высветление и ACES и выглядел так). +const E3D_COL_IDLE = 0x73a782; // Сетка 1 м: видна, пока открыт раздел с разметкой на земле. function _e3dSyncGrid(show) { @@ -482,7 +489,9 @@ function _e3dSyncGrid(show) { if (!show) { if (E3D.grid) E3D.grid.visible = false; return; } if (!E3D.grid) { const div = Math.round(GRID / E3D_GRID_STEP); - E3D.grid = new THREE.GridHelper(GRID, div, 0x8a8a8a, 0xb9b9b9); + // Цвета — видимые раньше (0x8a8a8a / 0xb9b9b9 после высветления и ACES). + E3D.grid = new THREE.GridHelper(GRID, div, 0xb6b6b6, 0xc7c7c7); + E3D.grid.material.toneMapped = false; E3D.grid.material.transparent = true; E3D.grid.material.opacity = 0.35; E3D.grid.material.depthWrite = false; @@ -563,6 +572,7 @@ function e3dSync() { const box = new THREE.Box3().setFromObject(seg); const h = new THREE.Box3Helper(box, E3D_COL_SEL); h.material.depthTest = false; + h.material.toneMapped = false; h.renderOrder = 999; g.add(h); } diff --git a/index.html b/index.html index 71353b6..017c156 100644 --- a/index.html +++ b/index.html @@ -279,6 +279,7 @@ + @@ -287,7 +288,7 @@ - + - - + + - + - + diff --git a/viewer3d-core.js b/viewer3d-core.js index c0c7d74..a06f825 100644 --- a/viewer3d-core.js +++ b/viewer3d-core.js @@ -147,8 +147,8 @@ function _setupControls(camera, domElement) { // ИНИЦИАЛИЗАЦИЯ СЦЕНЫ // ══════════════════════════════════════════════ // Свет сцены (правка 2026-10-08): см. комментарий у «Освещение» в init3dCanvas. -const SUN_INTENSITY = 5.0; // солнце; physicallyCorrectLights — альбедо × I·cos / π -const SCENE_EXPOSURE = 0.70; // экспозиция ACES при солнце 5 и небе из HDRI +const SUN_INTENSITY = 2.6; // солнце; physicallyCorrectLights — альбедо × I·cos / π +const SCENE_EXPOSURE = 0.95; // экспозиция ACES при солнце 2.6 и небе из HDRI function init3dCanvas(targetSlotId) { const targetSlot = document.getElementById(targetSlotId || 'three-container'); @@ -207,8 +207,9 @@ function init3dCanvas(targetSlotId) { // ── Освещение ───────────────────────────────── // Баланс подобран замером (правка 2026-10-08, после перевода цветов в линейные — - // color-management.js): серый образец #7e7e7e на солнце выходит #9a9897, тень на - // земле — 0.43 от освещённого места. Было: солнце 1.5 при physicallyCorrectLights + // color-management.js): серый образец #7e7e7e на солнце выходит #8e8f93, тень на + // земле — 0.57 от освещённого места (0.43 при солнце 5 вышло слишком плотно — + // ответ продукта: «тени нужно прозрачнее»). Было: солнце 1.5 при physicallyCorrectLights // давало едва половину альбедо, освещение от неба (HDRI) засвечивало тени почти // до уровня солнца (0.67), а заниженная экспозиция 0.72 темнила всё, что без // прямого солнца, — отсюда «тёмные текстуры» на стенах.