import { Shader } from './Shader.js' /** * ShaderHDR provides enhanced HDR tone mapping capabilities. * It extends the base Shader class to include tone mapping operations * and additional uniforms for HDR rendering. * * Features: * - Multiple tone mapping operators: Reinhard, ACES, and Exposure * - Configurable parameters for each operator * - Linear space processing * * @extends Shader */ class ShaderHDR extends Shader { /** * Creates a new enhanced ShaderHDR instance. * * @param {Object} options - Shader configuration options * @param {boolean} [options.isLinear=true] - Whether the shader operates in linear space * @param {string[]} [options.modes=['reinhard', 'aces', 'exposure']] - Available tone mapping modes * @param {string} [options.mode='reinhard'] - Default tone mapping mode * @param {Object[]} [options.samplers] - Texture samplers for the shader */ constructor(options) { // Set default options options = Object.assign({ isLinear: true, // Important: we work in linear space! format: 'rgba16f', }, options); super(options); this.modes = ['reinhard', 'aces', 'exposure', 'balanced']; this.mode = options.mode || 'reinhard'; this.uniforms = { 'whitePoint': { type: 'float', needsUpdate: true, value: 1.0 }, 'shadowLift': { type: 'float', needsUpdate: true, value: 0.0 }, 'acesContrast': { type: 'float', needsUpdate: true, value: 1.6 }, 'exposure': { type: 'float', needsUpdate: true, value: 1.0 }, 'highlightCompression': { type: 'float', needsUpdate: true, value: 1.0 }, } this.samplers.push({ id: 0, name: 'source', type: this.format }); /** * Tone mapping operations available in the shader. * @type {Object.<string, string>} */ this.toneMapOperations = { // Enhanced Reinhard operator with highlight preservation 'reinhard': ` // Enhanced Reinhard with both whitePoint and shadowLift parameters // Apply shadowLift pre-tone mapping to brighten shadows color.rgb = mix(color.rgb, pow(color.rgb, vec3(0.4)), shadowLift); // Reinhard tone mapping with more sensitive whitePoint float wp = max(0.1, whitePoint); color.rgb = (color.rgb * (1.0 + color.rgb/(wp))) / (1.0 + color.rgb); // Apply shadowLift post-tone mapping for more pronounced effect color.rgb = mix(color.rgb, pow(color.rgb, vec3(0.6)), shadowLift * 0.5); `, // ACES filmic tone mapping operator 'aces': ` // ACES filmic tone mapping approximation // Based on formula from Krzysztof Narkowicz // https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ // Allows for contrast adjustment with acesContrast parameter // Apply contrast parameter color.rgb *= acesContrast; // ACES tone mapping formula const vec3 a = vec3(2.51); const vec3 b = vec3(0.03); const vec3 c = vec3(2.43); const vec3 d = vec3(0.59); const vec3 e = vec3(0.14); color.rgb = (color.rgb * (a * color.rgb + b)) / (color.rgb * (c * color.rgb + d) + e); // Clamp to prevent artifacts color.rgb = clamp(color.rgb, 0.0, 1.0); `, // Simple exposure-based tone mapping 'exposure': ` // Apply exposure adjustment // exposure = 1.0 means no change, > 1.0 brightens, < 1.0 darkens color.rgb = vec3(1.0) - exp(-color.rgb * exposure); `, // New balanced operator 'balanced': ` // Lift shadows slightly to enhance details in dark areas color.rgb = mix(color.rgb, pow(color.rgb, vec3(0.5)), 0.05); // Adaptive scaling for highlights based on highlightCompression float hc = max(0.1, highlightCompression); // Avoid division by zero color.rgb = color.rgb / (color.rgb + vec3(hc)); // Apply logarithmic compression for highlights color.rgb = log(1.0 + color.rgb) / log(1.0 + hc); // Clamp to prevent overexposure color.rgb = clamp(color.rgb, 0.0, 1.0); ` }; // Set default samplers if not provided if (!this.samplers || this.samplers.length === 0) { this.samplers = [ { id: 0, name: 'source', type: 'rgba16f' } ]; } } /** * Generates the fragment shader source code with enhanced HDR tone mapping. * * @returns {string} GLSL source code for the fragment shader * @override */ fragShaderSrc() { // Get the selected tone mapping operation const toneMapOperation = this.toneMapOperations[this.mode] || this.toneMapOperations['reinhard']; console.log(this.mode); console.log(toneMapOperation); return ` in vec2 v_texcoord; // Uniforms for tone mapping and enhancements uniform float whitePoint; uniform float shadowLift; uniform float acesContrast; uniform float exposure; uniform float highlightCompression; vec4 data() { // Sample the HDR texture (already in linear space) vec4 color = texture(source, v_texcoord); // Apply selected tone mapping operation to compress HDR values ${toneMapOperation} // Return the tone-mapped color in linear space // The final gamma correction will be applied by Canvas.js return vec4(color.rgb, color.a); } `; } /** * Sets the white point uniform for the shader. * * @param {number} whitePoint - The new value for the white point */ setWhitePoint(whitePoint) { this.setUniform('whitePoint', whitePoint); } /** * Sets the shadow lift parameter for the shader. * * @param {number} shadowLift - The new value for shadow lift */ setShadowLift(shadowLift) { this.setUniform('shadowLift', shadowLift); } /** * Sets the ACES contrast parameter for the shader. * * @param {number} acesContrast - The new value for ACES contrast */ setAcesContrast(acesContrast) { this.setUniform('acesContrast', acesContrast); } /** * Sets the exposure parameter for the shader. * * @param {number} exposure - The new value for the exposure */ setExposure(exposure) { this.setUniform('exposure', exposure); } /** * Sets the highlight compression parameter for the shader. * * @param {number} highlightCompression - The new value for highlight compression */ setHighlightCompression(highlightCompression) { this.setUniform('highlightCompression', highlightCompression); } } export { ShaderHDR };