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'));