diff --git a/src/framework/parsers/glb-parser.js b/src/framework/parsers/glb-parser.js index 1c725948e22..48f8ffdf40f 100644 --- a/src/framework/parsers/glb-parser.js +++ b/src/framework/parsers/glb-parser.js @@ -1140,6 +1140,9 @@ const createMaterial = (gltfMaterial, textures) => { // glTF doesn't define how to occlude specular material.occludeSpecular = SPECOCC_AO; + // gLTF stores vertex colors in linear space + material.vertexColorGamma = false; + material.diffuseVertexColor = true; material.specularTint = true; material.specularVertexColor = true; diff --git a/src/scene/materials/standard-material-options-builder.js b/src/scene/materials/standard-material-options-builder.js index cad1503cb3d..09c220195e9 100644 --- a/src/scene/materials/standard-material-options-builder.js +++ b/src/scene/materials/standard-material-options-builder.js @@ -289,6 +289,8 @@ class StandardMaterialOptionsBuilder { options.litOptions.useDynamicRefraction = stdMat.useDynamicRefraction; options.litOptions.dispersion = stdMat.dispersion > 0; options.litOptions.shadowCatcher = stdMat.shadowCatcher; + + options.litOptions.useVertexColorGamma = stdMat.vertexColorGamma; } _updateEnvOptions(options, stdMat, scene, cameraShaderParams) { diff --git a/src/scene/materials/standard-material-options.js b/src/scene/materials/standard-material-options.js index c3cc50909cb..e2d225195d3 100644 --- a/src/scene/materials/standard-material-options.js +++ b/src/scene/materials/standard-material-options.js @@ -47,6 +47,8 @@ class StandardMaterialOptions { lightMapEncoding = 'linear'; + vertexColorGamma = true; + /** * If normal map contains X in RGB, Y in Alpha, and Z must be reconstructed. * diff --git a/src/scene/materials/standard-material-parameters.js b/src/scene/materials/standard-material-parameters.js index fb8f059fb6b..9c41de2247e 100644 --- a/src/scene/materials/standard-material-parameters.js +++ b/src/scene/materials/standard-material-parameters.js @@ -33,6 +33,8 @@ const standardMaterialParameterTypes = { ..._textureParameter('diffuseDetail', true, false), diffuseDetailMode: 'string', + vertexColorGamma: 'boolean', + specular: 'rgb', specularTint: 'boolean', ..._textureParameter('specular'), diff --git a/src/scene/materials/standard-material.js b/src/scene/materials/standard-material.js index d563a3415d9..5cead247f32 100644 --- a/src/scene/materials/standard-material.js +++ b/src/scene/materials/standard-material.js @@ -523,6 +523,10 @@ const _tempColor = new Color(); * backfaces. * @property {boolean} shadowCatcher When enabled, the material will output accumulated directional * shadow value in linear space as the color. + * @property {boolean} vertexColorGamma If true, shader will convert vertex colors to gamma space. + * True by default, e.g. for procedural or FBX models. gLTF stores vertex colors in linear space, + * so the parser will set this flag to false for imported GLB models. If you use vertex colors to + * store other data, set this to false to avoid unwanted color space conversions. * * @category Graphics */ @@ -1194,6 +1198,7 @@ function _defineMaterialProps() { _defineFlag('opacityDither', DITHER_NONE); _defineFlag('opacityShadowDither', DITHER_NONE); _defineFlag('shadowCatcher', false); + _defineFlag('vertexColorGamma', true); _defineTex2D('diffuse'); _defineTex2D('specular'); diff --git a/src/scene/shader-lib/glsl/chunks/lit/vert/litMain.js b/src/scene/shader-lib/glsl/chunks/lit/vert/litMain.js index 6b6220f824e..06aced88854 100644 --- a/src/scene/shader-lib/glsl/chunks/lit/vert/litMain.js +++ b/src/scene/shader-lib/glsl/chunks/lit/vert/litMain.js @@ -63,6 +63,15 @@ mat4 dModelMatrix; #include "litUserCodeVS" +#ifdef VERTEX_COLOR + vec3 decodeGamma(vec3 raw) { + return pow(raw, vec3(2.2)); + } + vec4 gammaCorrectInput(vec4 color) { + return vec4(decodeGamma(color.xyz), color.w); + } +#endif + void main(void) { #include "litUserMainStartVS" @@ -102,7 +111,11 @@ void main(void) { #include "uvTransformVS, UV_TRANSFORMS_COUNT" #ifdef VERTEX_COLOR - vVertexColor = vertex_color; + #ifdef STD_VERTEX_COLOR_GAMMA + vVertexColor = gammaCorrectInput(vertex_color); + #else + vVertexColor = vertex_color; + #endif #endif #ifdef LINEAR_DEPTH diff --git a/src/scene/shader-lib/glsl/chunks/standard/frag/diffuse.js b/src/scene/shader-lib/glsl/chunks/standard/frag/diffuse.js index 0cbf75eb71b..46252da5809 100644 --- a/src/scene/shader-lib/glsl/chunks/standard/frag/diffuse.js +++ b/src/scene/shader-lib/glsl/chunks/standard/frag/diffuse.js @@ -20,7 +20,7 @@ void getAlbedo() { #endif #ifdef STD_DIFFUSE_VERTEX - dAlbedo *= gammaCorrectInput(saturate(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL})); + dAlbedo *= saturate(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL}); #endif } `; diff --git a/src/scene/shader-lib/glsl/chunks/standard/frag/emissive.js b/src/scene/shader-lib/glsl/chunks/standard/frag/emissive.js index bf1fb5e8e96..09c8afa2112 100644 --- a/src/scene/shader-lib/glsl/chunks/standard/frag/emissive.js +++ b/src/scene/shader-lib/glsl/chunks/standard/frag/emissive.js @@ -10,7 +10,7 @@ void getEmission() { #endif #ifdef STD_EMISSIVE_VERTEX - dEmission *= gammaCorrectInput(saturate(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL})); + dEmission *= saturate(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL}); #endif } `; diff --git a/src/scene/shader-lib/programs/lit-shader-options.js b/src/scene/shader-lib/programs/lit-shader-options.js index 0e9ce905f65..0b9a98119ae 100644 --- a/src/scene/shader-lib/programs/lit-shader-options.js +++ b/src/scene/shader-lib/programs/lit-shader-options.js @@ -83,6 +83,8 @@ class LitShaderOptions { vertexColors = false; + useVertexColorGamma = true; + lightMapEnabled = false; dirLightMapEnabled = false; diff --git a/src/scene/shader-lib/programs/lit-shader.js b/src/scene/shader-lib/programs/lit-shader.js index cf0e07259c1..0c8f46de1ac 100644 --- a/src/scene/shader-lib/programs/lit-shader.js +++ b/src/scene/shader-lib/programs/lit-shader.js @@ -284,6 +284,9 @@ class LitShader { attributes.vertex_color = SEMANTIC_COLOR; vDefines.set('VERTEX_COLOR', true); varyings.set('vVertexColor', 'vec4'); + if (options.useVertexColorGamma) { + vDefines.set('STD_VERTEX_COLOR_GAMMA', ''); + } } if (options.useMsdf && options.msdfTextAttribute) { diff --git a/src/scene/shader-lib/programs/standard.js b/src/scene/shader-lib/programs/standard.js index 745b2f0a07d..d06ae8721c1 100644 --- a/src/scene/shader-lib/programs/standard.js +++ b/src/scene/shader-lib/programs/standard.js @@ -88,11 +88,8 @@ class ShaderGeneratorStandard extends ShaderGenerator { return expression; } - _validateMapChunk(propName, chunkName, chunks) { + _validateMapChunk(code, propName, chunkName, chunks) { Debug.call(() => { - // strip comments from the chunk - const code = chunks.get(chunkName).replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1'); - const requiredChangeStrings = []; // Helper function to add a formatted change string if the old syntax is found @@ -170,7 +167,16 @@ class ShaderGeneratorStandard extends ShaderGenerator { // log errors if the chunk format is deprecated (format changed in engine 2.7) Debug.call(() => { if (chunkCode) { - this._validateMapChunk(propNameCaps, chunkName, chunks); + // strip comments from the chunk + const code = chunks.get(chunkName).replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1'); + + this._validateMapChunk(code, propNameCaps, chunkName, chunks); + + if (vertexColorOption && !options.vertexColorGamma) { + if (code.includes('gammaCorrectInput')) { + Debug.errorOnce(`Shader chunk ${chunkName} contains gamma correction code which is incompatible with vertexColorGamma=false. Please remove gamma correction calls from the chunk.`, { code: code }); + } + } } }); diff --git a/src/scene/shader-lib/wgsl/chunks/lit/vert/litMain.js b/src/scene/shader-lib/wgsl/chunks/lit/vert/litMain.js index 0b9d8c30e33..96d209e0c71 100644 --- a/src/scene/shader-lib/wgsl/chunks/lit/vert/litMain.js +++ b/src/scene/shader-lib/wgsl/chunks/lit/vert/litMain.js @@ -66,6 +66,15 @@ var dModelMatrix: mat4x4f; #include "litUserCodeVS" +#ifdef VERTEX_COLOR + fn decodeGamma3(raw: vec3f) -> vec3f { + return pow(raw, vec3f(2.2)); + } + fn gammaCorrectInputVec4(color: vec4f) -> vec4f { + return vec4f(decodeGamma3(color.xyz), color.w); + } +#endif + @vertex fn vertexMain(input : VertexInput) -> VertexOutput { @@ -104,7 +113,11 @@ fn vertexMain(input : VertexInput) -> VertexOutput { #include "uvTransformVS, UV_TRANSFORMS_COUNT" #ifdef VERTEX_COLOR - output.vVertexColor = vertex_color; + #ifdef STD_VERTEX_COLOR_GAMMA + output.vVertexColor = gammaCorrectInputVec4(vertex_color); + #else + output.vVertexColor = vertex_color; + #endif #endif #ifdef LINEAR_DEPTH diff --git a/src/scene/shader-lib/wgsl/chunks/standard/frag/diffuse.js b/src/scene/shader-lib/wgsl/chunks/standard/frag/diffuse.js index 95174110c36..efe697aacf2 100644 --- a/src/scene/shader-lib/wgsl/chunks/standard/frag/diffuse.js +++ b/src/scene/shader-lib/wgsl/chunks/standard/frag/diffuse.js @@ -20,7 +20,7 @@ fn getAlbedo() { #endif #ifdef STD_DIFFUSE_VERTEX - dAlbedo = dAlbedo * gammaCorrectInputVec3(saturate3(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL})); + dAlbedo = dAlbedo * saturate3(vVertexColor.{STD_DIFFUSE_VERTEX_CHANNEL}); #endif } `; diff --git a/src/scene/shader-lib/wgsl/chunks/standard/frag/emissive.js b/src/scene/shader-lib/wgsl/chunks/standard/frag/emissive.js index c6237aac4dc..461e8f8d663 100644 --- a/src/scene/shader-lib/wgsl/chunks/standard/frag/emissive.js +++ b/src/scene/shader-lib/wgsl/chunks/standard/frag/emissive.js @@ -10,7 +10,7 @@ fn getEmission() { #endif #ifdef STD_EMISSIVE_VERTEX - dEmission = dEmission * gammaCorrectInputVec3(saturate3(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL})); + dEmission = dEmission * saturate3(vVertexColor.{STD_EMISSIVE_VERTEX_CHANNEL}); #endif } `;