All three of our color channels are filled up by our three light types. We can go further with this idea and introduce a new Viewport as another data texture to give a bright specular spot.
We already have a ViewportContainer and a Viewport, so we can save time by duplicating it.
Ctrl/Cmd + D
, or right-click on it and select Duplicate.The specular highlight should only follow the Key light, so you can delete the Specular Fill and Kick light. Duplicate each of the other RemoteTransforms and make them point to the new Specular proxies.
To only receive specular light, make the specular key light’s color white, and replace the material on the Specular Sphere’s mesh with a new SpatialMaterial
.
We have a new data texture, so we need a new uniform for it. We can also introduce three new uniforms to control the specular highlight: its softness, its size, and its color.
The softness ranges from 0 to 1, with 0 being fully sharp and 1 being fully soft. The size is an arbitrary range; I went with [0..4], and it’s already more than enough for any specular highlight spot.
uniform sampler2D specular_data : hint_black; uniform float specular_softness : hint_range(0, 1) = 0.5; uniform float specular_size : hint_range(0, 4) = 0.5; uniform vec4 specular_color : hint_color = vec4(1);
The specular highlight comes after the fill light color’s addition. The first step, just like with diffuse data, is to sample the specular highlight. Since it’s a pure black and white color, we can use the first channel to get a single value.
float specular = texture(specular_data, SCREEN_UV).r;
We could introduce more gradient ramps to control the sharpness of the specular highlight, but that introduces more parameters, and the final shader is already complicated. Instead, we can use constants to set a min and a max and use the smoothstep
function. The result is the same as if we used a ramp without introducing a new texture sample, but is part of the code and does not change.
The choice for a constant is up to you, the shader designer, but I’m providing some defaults. Introduce these values as const
variables at the top of the shader.
shader_type spatial; render_mode unshaded; const float SPECULAR_SOFT_MIN = 0.0; const float SPECULAR_SOFT_MAX = 0.64; const float SPECULAR_HARD_MIN = 0.17; const float SPECULAR_HARD_MAX = 0.18;
Why not use step
instead of smoothstep
for hard? You could, but sometimes the crispness of the highlight causes aliasing issues, and the pixels start showing. smoothstep
provides a more gentle transition.
With those constants established, we can go back down to where we have the specular sampled and use them to get the specular highlight’s soft and hard versions. We can use the specular_size
variable as a multiplier on the specular value to adjust its size.
float soft_specular = smoothstep(SPECULAR_SOFT_MIN, SPECULAR_HARD_MAX, specular * specular_size); float hard_specular = smoothstep(SPECULAR_HARD_MIN, SPECULAR_HARD_MAX, specular * specular_size);
We use the specular_softness
uniform to blend the hard and the soft using the mix
function, which linearly interpolates between the two based on the factor. Multiply that result by the specular_color
and we have the specular highlight we can add to the out_color
.
vec3 specular_out = mix(hard_specular, soft_specular, specular_softness) * specular_color.rgb; out_color += specular_out;
Connect the new specular viewport texture and play around with the values until you get a pleasing effect.
shader_type spatial; render_mode unshaded; //Specular constants const float SPECULAR_SOFT_MIN = 0.0; const float SPECULAR_SOFT_MAX = 0.64; const float SPECULAR_HARD_MIN = 0.17; const float SPECULAR_HARD_MAX = 0.18; //Data textures uniform sampler2D light_data : hint_black; uniform sampler2D specular_data : hint_black; uniform sampler2D key_light_ramp : hint_black; uniform sampler2D fill_light_ramp : hint_black; uniform sampler2D kick_light_ramp : hint_black; //Specular uniform float specular_softness : hint_range(0, 1); uniform float specular_size : hint_range(0, 4); uniform vec4 specular_color : hint_color; //Light colors uniform vec4 key_light_color : hint_color; uniform vec4 shadow_color : hint_color; uniform vec4 fill_light_color : hint_color; uniform vec4 kick_light_color : hint_color; void fragment() { //Data vec3 diffuse = texture(light_data, SCREEN_UV).rgb; //Key light float key_light_value = texture(key_light_ramp, vec2(diffuse.r, 0)).r; vec3 out_color = key_light_value * key_light_color.rgb; out_color = max(out_color, shadow_color.rgb); //Fill light float fill_light_value = texture(fill_light_ramp, vec2(diffuse.g, 0)).r; out_color += fill_light_value * fill_light_color.rgb; //Kick light float kick_light_value = texture(kick_light_ramp, vec2(diffuse.b, 0)).r; out_color += kick_light_value * kick_light_color.rgb; //Specular float specular = texture(specular_data, SCREEN_UV).r; float soft_specular = smoothstep(SPECULAR_SOFT_MIN, SPECULAR_SOFT_MAX, specular * specular_size); float hard_specular = smoothstep(SPECULAR_HARD_MIN, SPECULAR_HARD_MAX, specular * specular_size); vec3 specular_out = mix(hard_specular, soft_specular, specular_softness) * specular_color.rgb; out_color += specular_out; ALBEDO = out_color; }