Why not add a color material parameter to your material? Then all of your materials could just share that instance and you only have to update the one color object.
I did this with the PBR shaders for SpaceBugs because I wanted to control the ambient brightness. It bugged me that it 100% came from the light probe (I use a generic light probe)… so I essentially use a color as an “ambient filter” which lets me control the brightness and color of the ambient light. (Direct light is already covered by the regular light color.)
I’m just going to paste the whole material so you can compare it to regular PBR… I didn’t add much but it was a while ago and I don’t know if PBR has changed since.
This is the FilteredPBR.j3md:
MaterialDef Filtered PBR Lighting {
MaterialParameters {
// Alpha threshold for fragment discarding
Float AlphaDiscardThreshold (AlphaTestFallOff)
//metalness of the material
Float Metallic : 1.0
//Roughness of the material
Float Roughness : 1.0
// Base material color
Color BaseColor : 1.0 1.0 1.0 1.0
// The emissive color of the object
Color Emissive
// the emissive power
Float EmissivePower : 3.0
// the emissive intensity
Float EmissiveIntensity : 2.0
// BaseColor map
Texture2D BaseColorMap
// Metallic map
Texture2D MetallicMap -LINEAR
// Roughness Map
Texture2D RoughnessMap -LINEAR
//Metallic and Roughness are packed respectively in the b and g channel of a single map
// r: unspecified
// g: Roughness
// b: Metallic
Texture2D MetallicRoughnessMap -LINEAR
// Texture of the emissive parts of the material
Texture2D EmissiveMap
// Normal map
Texture2D NormalMap -LINEAR
//The type of normal map: -1.0 (DirectX), 1.0 (OpenGl)
Float NormalType : -1.0
// For Spec gloss pipeline
Boolean UseSpecGloss
Texture2D SpecularMap
Texture2D GlossinessMap
Texture2D SpecularGlossinessMap
Color Specular : 1.0 1.0 1.0 1.0
Float Glossiness : 1.0
// Parallax/height map
Texture2D ParallaxMap -LINEAR
//Set to true is parallax map is stored in the alpha channel of the normal map
Boolean PackedNormalParallax
//Sets the relief height for parallax mapping
Float ParallaxHeight : 0.05
//Set to true to activate Steep Parallax mapping
Boolean SteepParallax
//Horizon fade
Boolean HorizonFade
// Set to Use Lightmap
Texture2D LightMap
// Set to use TexCoord2 for the lightmap sampling
Boolean SeparateTexCoord
// the light map is a gray scale ao map, on ly the r channel will be read.
Boolean LightMapAsAOMap
//shadows
Int FilterMode
Boolean HardwareShadows
Texture2D ShadowMap0
Texture2D ShadowMap1
Texture2D ShadowMap2
Texture2D ShadowMap3
//pointLights
Texture2D ShadowMap4
Texture2D ShadowMap5
Float ShadowIntensity
Vector4 Splits
Vector2 FadeInfo
Matrix4 LightViewProjectionMatrix0
Matrix4 LightViewProjectionMatrix1
Matrix4 LightViewProjectionMatrix2
Matrix4 LightViewProjectionMatrix3
//pointLight
Matrix4 LightViewProjectionMatrix4
Matrix4 LightViewProjectionMatrix5
Vector3 LightPos
Vector3 LightDir
Float PCFEdge
Float ShadowMapSize
// For hardware skinning
Int NumberOfBones
Matrix4Array BoneMatrices
// For Morph animation
FloatArray MorphWeights
Int NumberOfMorphTargets
Int NumberOfTargetsBuffers
//For instancing
Boolean UseInstancing
//For Vertex Color
Boolean UseVertexColor
Boolean BackfaceShadows : false
Vector4 FilterColor
}
Technique {
LightMode SinglePassAndImageBased
VertexShader GLSL110 GLSL150: Common/MatDefs/Light/PBRLighting.vert
FragmentShader GLSL110 GLSL150: MatDefs/FilteredPBR.frag
WorldParameters {
WorldViewProjectionMatrix
CameraPosition
WorldMatrix
WorldNormalMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
BASECOLORMAP : BaseColorMap
NORMALMAP : NormalMap
METALLICMAP : MetallicMap
ROUGHNESSMAP : RoughnessMap
EMISSIVEMAP : EmissiveMap
EMISSIVE : Emissive
SPECGLOSSPIPELINE : UseSpecGloss
PARALLAXMAP : ParallaxMap
NORMALMAP_PARALLAX : PackedNormalParallax
STEEP_PARALLAX : SteepParallax
LIGHTMAP : LightMap
SEPARATE_TEXCOORD : SeparateTexCoord
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
USE_PACKED_MR: MetallicRoughnessMap
USE_PACKED_SG: SpecularGlossinessMap
SPECULARMAP : SpecularMap
GLOSSINESSMAP : GlossinessMap
NORMAL_TYPE: NormalType
VERTEX_COLOR : UseVertexColor
AO_MAP: LightMapAsAOMap
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
HORIZON_FADE: HorizonFade
FILTER_COLOR : FilterColor
}
}
Technique PreShadow {
VertexShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.vert
FragmentShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
FaceCull Off
DepthTest On
DepthWrite On
PolyOffset 5 3
ColorWrite Off
}
}
Technique PostShadow{
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
DISCARD_ALPHA : AlphaDiscardThreshold
SHADOWMAP_SIZE : ShadowMapSize
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
Technique PostShadow{
VertexShader GLSL100: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
DISCARD_ALPHA : AlphaDiscardThreshold
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
Technique PreNormalPass {
VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert
FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
Technique Glow {
VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert
FragmentShader GLSL100 GLSL150: Common/MatDefs/Light/Glow.frag
WorldParameters {
WorldViewProjectionMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
NEED_TEXCOORD1
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
}
FilteredPBR.frag:
#import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/PBR.glsllib"
#import "Common/ShaderLib/Parallax.glsllib"
#import "Common/ShaderLib/Lighting.glsllib"
varying vec2 texCoord;
#ifdef SEPARATE_TEXCOORD
varying vec2 texCoord2;
#endif
varying vec4 Color;
uniform vec4 g_LightData[NB_LIGHTS];
uniform vec3 g_CameraPosition;
uniform float m_Roughness;
uniform float m_Metallic;
varying vec3 wPosition;
#if NB_PROBES >= 1
uniform samplerCube g_PrefEnvMap;
uniform vec3 g_ShCoeffs[9];
uniform mat4 g_LightProbeData;
#endif
#if NB_PROBES >= 2
uniform samplerCube g_PrefEnvMap2;
uniform vec3 g_ShCoeffs2[9];
uniform mat4 g_LightProbeData2;
#endif
#if NB_PROBES == 3
uniform samplerCube g_PrefEnvMap3;
uniform vec3 g_ShCoeffs3[9];
uniform mat4 g_LightProbeData3;
#endif
#ifdef BASECOLORMAP
uniform sampler2D m_BaseColorMap;
#endif
#ifdef USE_PACKED_MR
uniform sampler2D m_MetallicRoughnessMap;
#else
#ifdef METALLICMAP
uniform sampler2D m_MetallicMap;
#endif
#ifdef ROUGHNESSMAP
uniform sampler2D m_RoughnessMap;
#endif
#endif
#ifdef EMISSIVE
uniform vec4 m_Emissive;
#endif
#ifdef EMISSIVEMAP
uniform sampler2D m_EmissiveMap;
#endif
#if defined(EMISSIVE) || defined(EMISSIVEMAP)
uniform float m_EmissivePower;
uniform float m_EmissiveIntensity;
#endif
#ifdef SPECGLOSSPIPELINE
uniform vec4 m_Specular;
uniform float m_Glossiness;
#ifdef USE_PACKED_SG
uniform sampler2D m_SpecularGlossinessMap;
#else
uniform sampler2D m_SpecularMap;
uniform sampler2D m_GlossinessMap;
#endif
#endif
#ifdef PARALLAXMAP
uniform sampler2D m_ParallaxMap;
#endif
#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
uniform float m_ParallaxHeight;
#endif
#ifdef LIGHTMAP
uniform sampler2D m_LightMap;
#endif
#if defined(NORMALMAP) || defined(PARALLAXMAP)
uniform sampler2D m_NormalMap;
varying vec4 wTangent;
#endif
varying vec3 wNormal;
#ifdef DISCARD_ALPHA
uniform float m_AlphaDiscardThreshold;
#endif
#ifdef FILTER_COLOR
uniform vec4 m_FilterColor;
#endif
void main(){
vec2 newTexCoord;
vec3 viewDir = normalize(g_CameraPosition - wPosition);
vec3 norm = normalize(wNormal);
#if defined(NORMALMAP) || defined(PARALLAXMAP)
vec3 tan = normalize(wTangent.xyz);
mat3 tbnMat = mat3(tan, wTangent.w * cross( (norm), (tan)), norm);
#endif
#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
vec3 vViewDir = viewDir * tbnMat;
#ifdef STEEP_PARALLAX
#ifdef NORMALMAP_PARALLAX
//parallax map is stored in the alpha channel of the normal map
newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
#else
//parallax map is a texture
newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);
#endif
#else
#ifdef NORMALMAP_PARALLAX
//parallax map is stored in the alpha channel of the normal map
newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
#else
//parallax map is a texture
newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);
#endif
#endif
#else
newTexCoord = texCoord;
#endif
#ifdef BASECOLORMAP
vec4 albedo = texture2D(m_BaseColorMap, newTexCoord) * Color;
#else
vec4 albedo = Color;
#endif
#ifdef USE_PACKED_MR
vec2 rm = texture2D(m_MetallicRoughnessMap, newTexCoord).gb;
float Roughness = rm.x * max(m_Roughness, 1e-4);
float Metallic = rm.y * max(m_Metallic, 0.0);
#else
#ifdef ROUGHNESSMAP
float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-4);
#else
float Roughness = max(m_Roughness, 1e-4);
#endif
#ifdef METALLICMAP
float Metallic = texture2D(m_MetallicMap, newTexCoord).r * max(m_Metallic, 0.0);
#else
float Metallic = max(m_Metallic, 0.0);
#endif
#endif
float alpha = albedo.a;
#ifdef DISCARD_ALPHA
if(alpha < m_AlphaDiscardThreshold){
discard;
}
#endif
// ***********************
// Read from textures
// ***********************
#if defined(NORMALMAP)
vec4 normalHeight = texture2D(m_NormalMap, newTexCoord);
//Note the -2.0 and -1.0. We invert the green channel of the normal map,
//as it's complient with normal maps generated with blender.
//see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898
//for more explanation.
vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)));
normal = normalize(tbnMat * normal);
//normal = normalize(normal * inverse(tbnMat));
#else
vec3 normal = norm;
#endif
#ifdef SPECGLOSSPIPELINE
#ifdef USE_PACKED_SG
vec4 specularColor = texture2D(m_SpecularGlossinessMap, newTexCoord);
float glossiness = specularColor.a * m_Glossiness;
specularColor *= m_Specular;
#else
#ifdef SPECULARMAP
vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);
#else
vec4 specularColor = vec4(1.0);
#endif
#ifdef GLOSSINESSMAP
float glossiness = texture2D(m_GlossinessMap, newTexCoord).r * m_Glossiness;
#else
float glossiness = m_Glossiness;
#endif
specularColor *= m_Specular;
#endif
vec4 diffuseColor = albedo;// * (1.0 - max(max(specularColor.r, specularColor.g), specularColor.b));
Roughness = 1.0 - glossiness;
vec3 fZero = specularColor.xyz;
#else
float specular = 0.5;
float nonMetalSpec = 0.08 * specular;
vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic;
vec4 diffuseColor = albedo - albedo * Metallic;
vec3 fZero = vec3(specular);
#endif
gl_FragColor.rgb = vec3(0.0);
vec3 ao = vec3(1.0);
#ifdef LIGHTMAP
vec3 lightMapColor;
#ifdef SEPARATE_TEXCOORD
lightMapColor = texture2D(m_LightMap, texCoord2).rgb;
#else
lightMapColor = texture2D(m_LightMap, texCoord).rgb;
#endif
#ifdef AO_MAP
lightMapColor.gb = lightMapColor.rr;
ao = lightMapColor;
#else
gl_FragColor.rgb += diffuseColor.rgb * lightMapColor;
#endif
specularColor.rgb *= lightMapColor;
#endif
float ndotv = max( dot( normal, viewDir ),0.0);
for( int i = 0;i < NB_LIGHTS; i+=3){
vec4 lightColor = g_LightData[i];
vec4 lightData1 = g_LightData[i+1];
vec4 lightDir;
vec3 lightVec;
lightComputeDir(wPosition, lightColor.w, lightData1, lightDir, lightVec);
float fallOff = 1.0;
#if __VERSION__ >= 110
// allow use of control flow
if(lightColor.w > 1.0){
#endif
fallOff = computeSpotFalloff(g_LightData[i+2], lightVec);
#if __VERSION__ >= 110
}
#endif
//point light attenuation
fallOff *= lightDir.w;
lightDir.xyz = normalize(lightDir.xyz);
vec3 directDiffuse;
vec3 directSpecular;
float hdotv = PBR_ComputeDirectLight(normal, lightDir.xyz, viewDir,
lightColor.rgb, fZero, Roughness, ndotv,
directDiffuse, directSpecular);
//#ifdef FILTER_COLOR
// directSpecular.rgb *= m_FilterColor.rgb;
//#endif
vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular;
gl_FragColor.rgb += directLighting * fallOff;
}
#if NB_PROBES >= 1
vec3 color1 = vec3(0.0);
vec3 color2 = vec3(0.0);
vec3 color3 = vec3(0.0);
float weight1 = 1.0;
float weight2 = 0.0;
float weight3 = 0.0;
float ndf = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData, g_ShCoeffs, g_PrefEnvMap, color1);
#if NB_PROBES >= 2
float ndf2 = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData2, g_ShCoeffs2, g_PrefEnvMap2, color2);
#endif
#if NB_PROBES == 3
float ndf3 = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData3, g_ShCoeffs3, g_PrefEnvMap3, color3);
#endif
#if NB_PROBES >= 2
float invNdf = max(1.0 - ndf,0.0);
float invNdf2 = max(1.0 - ndf2,0.0);
float sumNdf = ndf + ndf2;
float sumInvNdf = invNdf + invNdf2;
#if NB_PROBES == 3
float invNdf3 = max(1.0 - ndf3,0.0);
sumNdf += ndf3;
sumInvNdf += invNdf3;
weight3 = ((1.0 - (ndf3 / sumNdf)) / (NB_PROBES - 1)) * (invNdf3 / sumInvNdf);
#endif
weight1 = ((1.0 - (ndf / sumNdf)) / (NB_PROBES - 1)) * (invNdf / sumInvNdf);
weight2 = ((1.0 - (ndf2 / sumNdf)) / (NB_PROBES - 1)) * (invNdf2 / sumInvNdf);
float weightSum = weight1 + weight2 + weight3;
weight1 /= weightSum;
weight2 /= weightSum;
weight3 /= weightSum;
#endif
#ifdef FILTER_COLOR
color1.rgb *= m_FilterColor.rgb;
color2.rgb *= m_FilterColor.rgb;
color3.rgb *= m_FilterColor.rgb;
#endif
gl_FragColor.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0);
#endif
#if defined(EMISSIVE) || defined (EMISSIVEMAP)
#ifdef EMISSIVEMAP
vec4 emissive = texture2D(m_EmissiveMap, newTexCoord);
#else
vec4 emissive = m_Emissive;
#endif
gl_FragColor += emissive * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity;
#endif
gl_FragColor.a = alpha;
}
The important parts are related to the added FilterColor.
With this I was able to have red overhead lighting and set a low red ambient color or pretty much tweak the ambient lighting any way I wanted.
And for the future, if you have ever to have a ‘constantly modifying’ float on a bunch of shaders, just use a Vec3 or Color to hold the values and then you can share instances. (Or setting a single material parameter override on the scene root works also.)