diff --git a/locales/en/simulator.json b/locales/en/simulator.json index bb3c277a..1191a5f7 100644 --- a/locales/en/simulator.json +++ b/locales/en/simulator.json @@ -83,6 +83,14 @@ "title": "Linear Value", "description": "The value V in the HSV color space equals the total brightness of the rays when \"Simulate Colors\" is off and for monochromatic light between 420 to 620 nm. V is capped to 1 while preserving H and S. The color is gamma-corrected." }, + "linearRGB": { + "title": "Linear RGB", + "description": "Similar to \"Linear Value\", but each component of the RGB color space is capped to 1 independently." + }, + "reinhard": { + "title": "Reinhard", + "description": "The Reinhard tone mapping applied to the total relative luminance. At the low brightness limit, the color is consistent with \"Linear Value\"." + }, "colorizedIntensity": { "title": "Colorized Intensity", "description": "The total brightness of 100, 10, …, 10⁻⁵ are represented by red, orange, yellow, green, cyan, blue, purple, and black, respectively, and interpolate smoothly in log scale." diff --git a/locales/zh-CN/simulator.json b/locales/zh-CN/simulator.json index 81a500d3..fcfaf366 100644 --- a/locales/zh-CN/simulator.json +++ b/locales/zh-CN/simulator.json @@ -364,6 +364,14 @@ "linear": { "description": "HSV 色彩空间中的明度 V 值等于「模拟色彩」关闭时光线的总亮度,或光线为 420 至 620 nm 之间的单色光时的总亮度。 V 超过 1 时将调降为 1 并保持 H 和 S 不变。颜色经过伽马修正。", "title": "线性明度" + }, + "linearRGB": { + "title": "线性 RGB", + "description": "与「线性明度」类似,但将 RGB 色彩空间的每个分量分别调降至 1。" + }, + "reinhard": { + "title": "Reinhard", + "description": "应用于相对光亮度总和的 Reinhard 色调映射。在低亮度极限下,颜色与「线性明度」一致。" } } } diff --git a/locales/zh-TW/simulator.json b/locales/zh-TW/simulator.json index 0fcdb297..c652a88c 100644 --- a/locales/zh-TW/simulator.json +++ b/locales/zh-TW/simulator.json @@ -364,6 +364,14 @@ "linear": { "description": "HSV 色彩空間中的明度 V 值等於「模擬色彩」關閉時光線的總亮度,或光線為 420 至 620 nm 之間的單色光時的總亮度。V 超過 1 時將調降為 1 並保持 H 和 S 不變。顏色經過伽馬修正。", "title": "線性明度" + }, + "linearRGB": { + "title": "線性 RGB", + "description": "與「線性明度」類似,但將 RGB 色彩空間的每個分量分別調降至 1。" + }, + "reinhard": { + "title": "Reinhard", + "description": "套用於相對光亮度總和的 Reinhard 色調映射。在低亮度極限下,顏色與「線性明度」一致。" } } } diff --git a/src/simulator/index.html b/src/simulator/index.html index fed5dd3c..73e1fa0b 100644 --- a/src/simulator/index.html +++ b/src/simulator/index.html @@ -1203,6 +1203,16 @@
+
+ + +
+
+
+ + +
+
diff --git a/src/simulator/js/FloatColorRenderer.js b/src/simulator/js/FloatColorRenderer.js index 5dac8740..4fd736b8 100644 --- a/src/simulator/js/FloatColorRenderer.js +++ b/src/simulator/js/FloatColorRenderer.js @@ -251,6 +251,54 @@ class FloatColorRenderer { } `; break; + case 'linearRGB': + quadFragmentSource = ` + precision highp float; + uniform sampler2D u_texture; + varying vec2 v_texCoord; + + void main() { + vec4 color = texture2D(u_texture, v_texCoord); + float maxComponent = max(max(color.r, color.g), color.b); + gl_FragColor = vec4(pow(color.rgb, vec3(1.0 / 2.2)), maxComponent); + } + `; + break; + case 'reinhard': + quadFragmentSource = ` + precision highp float; + + uniform sampler2D u_texture; + varying vec2 v_texCoord; + + // Convert RGB to luminance using perceptual weights + float getLuminance(vec3 color) { + return dot(color, vec3(0.2126, 0.7152, 0.0722)); + } + + void main() { + // Sample the texture + vec4 color = texture2D(u_texture, v_texCoord); + + // Calculate luminance + float Lold = getLuminance(color.rgb); + + // Apply Reinhard tone mapping to luminance + float Lnew = Lold / (1.0 + Lold); + + // Scale RGB by ratio of new to old luminance + vec3 toneMapped = color.rgb * (Lnew / Lold); + + // Apply gamma correction + vec3 gammaCorrected = pow(toneMapped, vec3(1.0 / 2.2)); + + // Store the maximum component of the original color + float maxComponent = max(max(color.r, color.g), color.b); + + gl_FragColor = vec4(gammaCorrected, maxComponent); + } + `; + break; case 'colorizedIntensity': quadFragmentSource = ` precision highp float; @@ -911,8 +959,9 @@ class FloatColorRenderer { return [r * factor, g * factor, b * factor, 1.0]; case 'legacy_color': return [r, g, b, 1.0]; - case 'linear': - + case 'colorizedIntensity': + return [m, m, m, 1.0]; + default: // Correct gamma const rr = Math.pow(r, 2.2); const gg = Math.pow(g, 2.2); @@ -921,8 +970,6 @@ class FloatColorRenderer { const ratio = m / Math.max(rr, gg, bb); return [rr * ratio, gg * ratio, bb * ratio, 1.0]; - case 'colorizedIntensity': - return [m, m, m, 1.0]; } } diff --git a/src/simulator/js/app.js b/src/simulator/js/app.js index 228829c7..21b3e3f9 100644 --- a/src/simulator/js/app.js +++ b/src/simulator/js/app.js @@ -161,6 +161,16 @@ async function startApp() { editor.onActionComplete(); simulator.updateSimulation(); }); + document.getElementById('colorMode_linearRGB').addEventListener('click', function () { + colorModebtn_clicked('linearRGB'); + editor.onActionComplete(); + simulator.updateSimulation(); + }); + document.getElementById('colorMode_reinhard').addEventListener('click', function () { + colorModebtn_clicked('reinhard'); + editor.onActionComplete(); + simulator.updateSimulation(); + }); document.getElementById('colorMode_colorizedIntensity').addEventListener('click', function () { colorModebtn_clicked('colorizedIntensity'); editor.onActionComplete(); @@ -1374,6 +1384,10 @@ function initUIText() { setText('colorMode_legacy_description', i18next.t('simulator:colorModeModal.legacy.description')); setText('colorMode_linear_text', i18next.t('simulator:colorModeModal.linear.title')); setText('colorMode_linear_description', i18next.t('simulator:colorModeModal.linear.description')); + setText('colorMode_linearRGB_text', i18next.t('simulator:colorModeModal.linearRGB.title')); + setText('colorMode_linearRGB_description', i18next.t('simulator:colorModeModal.linearRGB.description')); + setText('colorMode_reinhard_text', i18next.t('simulator:colorModeModal.reinhard.title')); + setText('colorMode_reinhard_description', i18next.t('simulator:colorModeModal.reinhard.description')); setText('colorMode_colorizedIntensity_text', i18next.t('simulator:colorModeModal.colorizedIntensity.title')); setText('colorMode_colorizedIntensity_description', i18next.t('simulator:colorModeModal.colorizedIntensity.description')); setText('close_colorMode', i18next.t('simulator:common.closeButton'));