uniform_float uGamma { default = 2.2; min = 0.1; max = 4.0; step = 0.01; display_name = "#{OMWShaders:GammaLevelName}"; description = "#{OMWShaders:GammaLevelDescription}"; } uniform_float uThreshold { default = 0.35; min = 0.0; max = 1.0; step = 0.01; display_name = "#{OMWShaders:BloomThresholdLevelName}"; description = "#{OMWShaders:BloomThresholdLevelDescription}"; } uniform_float uClamp { default = 1.0; min = 0.0; max = 1.0; step = 0.01; display_name = "#{OMWShaders:BloomClampLevelName}"; description = "#{OMWShaders:BloomClampLevelDescription}"; } uniform_float uSkyFactor { default = 0.5; min = 0.0; max = 2.0; step = 0.01; display_name = "#{OMWShaders:SkyFactorLevelName}"; description = "#{OMWShaders:SkyFactorLevelDescription}"; } uniform_float uRadius { default = 0.5; min = 0.0; max = 1.0; step = 0.01; display_name = "#{OMWShaders:RadiusLevelName}"; description = "#{OMWShaders:RadiusLevelDescription}"; } uniform_float uStrength { default = 0.25; min = 0.0; max = 1.0; step = 0.01; display_name = "#{OMWShaders:StrengthLevelName}"; description = "#{OMWShaders:StrengthLevelDescription}"; } shared { float scramblify(float x) { x = fract(x); x = x + 4.0; x = x*x; x = x*x; return fract(x); } float scramblev2_inner(vec2 v, float z) { return scramblify(v.x*0.6491 + v.y*0.029 + z); } float time = fract(omw.simulationTime); vec4 scramblev2(vec2 v) { v *= 61.12; vec2 fwup = vec2(scramblev2_inner(v, fract(time)), scramblev2_inner(v, fract(time*fract(v.x)) + 0.18943)); return vec4(0.5) - vec4(fwup, fwup); } float gauss(float x) { return exp(-x*x); } float calculate_radius(vec2 texcoord) { float radius = uRadius * 0.2; radius *= omw.resolution.y; radius = max(radius, 0.1); // hack: make the radius wider on the screen edges // (makes things in the corner of the screen look less "wrong" with not-extremely-low FOVs) radius *= pow(texcoord.x*2.0-1.0, 2)+1.0; radius *= pow(texcoord.y*2.0-1.0, 2)+1.0; return radius; } vec3 powv(vec3 a, float x) { return pow(a, vec3(x)); } } render_target RT_NoMipmap { width_ratio = 0.25; height_ratio = 0.25; internal_format = rgb16f; source_type = float; source_format = rgb; mipmaps = false; min_filter = nearest; mag_filter = nearest; } render_target RT_Horizontal { width_ratio = 0.25; height_ratio = 0.25; internal_format = rgb16f; source_type = float; source_format = rgb; mipmaps = false; min_filter = nearest; mag_filter = nearest; } render_target RT_Vertical { width_ratio = 0.25; height_ratio = 0.25; internal_format = rgb16f; source_type = float; source_format = rgb; mipmaps = false; min_filter = linear; mag_filter = linear; } fragment nomipmap(target=RT_NoMipmap) { omw_In vec2 omw_TexCoord; void main() { // downsample into a smaller buffer with mipmapping disabled (no need for it + might interfere with the blurring process) // do the gamma compression step while we're at it vec3 sample = omw_GetLastShader(omw_TexCoord).rgb; bool is_sky = (omw_GetLinearDepth(omw_TexCoord) > omw.far*0.999); float factor = is_sky ? uSkyFactor : 1.0; vec3 ret_sample = powv(sample, uGamma); factor *= (!is_sky && (dot(sample, vec3(1.0/3.0)) < uThreshold)) ? 0.0 : 1.0; ret_sample *= factor; omw_FragColor = vec4(ret_sample, 1.0); } } fragment horizontal(target=RT_Horizontal, rt1=RT_NoMipmap) { omw_In vec2 omw_TexCoord; void main() { // gaussian blur, horizontal step float radius = calculate_radius(omw_TexCoord); int radius_i = int(ceil(radius)); vec3 sum = vec3(0.0); float normalize = 0.0; for(int x = -radius_i; x <= radius_i; x += 1) { float strength = gauss(float(x)/radius*2.0); normalize += strength; vec2 coord = omw_TexCoord + vec2(float(x), 0.0) / omw.resolution.xy; vec3 sample = omw_Texture2D(RT_NoMipmap, coord).rgb; sum += strength * sample; } sum /= normalize; omw_FragColor = vec4(sum, 1.0); } } fragment vertical(target=RT_Vertical, rt1=RT_Horizontal) { omw_In vec2 omw_TexCoord; void main() { // gaussian blur, vertical step float radius = calculate_radius(omw_TexCoord); int radius_i = int(ceil(radius)); vec3 sum = vec3(0.0); float normalize = 0.0; for(int y = -radius_i; y <= radius_i; y += 1) { float strength = gauss(float(y)/radius*2.0); normalize += strength; vec2 coord = omw_TexCoord + vec2(0.0, float(y)) / omw.resolution.xy; vec3 sample = omw_Texture2D(RT_Horizontal, coord).rgb; sum += strength * sample; } sum /= normalize; omw_FragColor = vec4(sum, 1.0); } } fragment final(rt1=RT_Vertical) { omw_In vec2 omw_TexCoord; void main() { vec3 color = omw_Texture2D(RT_Vertical, omw_TexCoord).rgb; // add dithering (in gamma-compressed light, because monitors are sRGB) color = powv(color, 1.0/uGamma); color += (scramblev2(omw_TexCoord).rgb/255.0 - vec3(0.5/255.0))*2.0; color = max(vec3(0.0), color); // also do clamping in gamma-compressed light float color_luma = dot(color, vec3(1.0/3.0)); if(uClamp == 0.0) color *= 0.0; else if(color_luma > uClamp) color /= color_luma/uClamp; color = powv(color, uGamma); // add bloom to base color in linear light vec3 base_color = powv(omw_GetLastShader(omw_TexCoord).rgb, uGamma); vec3 add_color = base_color + color * uStrength * 0.5; omw_FragColor = vec4(powv(add_color, 1.0/uGamma), 1.0); } } technique { passes = nomipmap, horizontal, vertical, final; description = "#{OMWShaders:BloomDescription}"; author = "OpenMW"; version = "1.0"; }