HDR The Bungie Way featuring Bloom, Flares and Lighting

Hey guys, back again with some more basic stuff. I need help getting a bloom curve, from the presentation HDR The Bungie Way by Chris Tchou. I dont like the way my bloom looks in its current state. I may look at some different techniques for bloom because currently there aren’t many that looks good in my opinion besides the few listed below. I’ve already implemented Kawase’s Bloom Filter, now i’m trying out this one. But the key component I don’t understand how to achieve is the bloom curve. I’ve uploaded just the slides, however some of the slides are missing pictures which hinder slightly my ability to understand. But a few of the pictures can also be found below, along with the current code I have. So what I ask is that someone help me with implementing a bloom curve to the HDR Lighting from [Bungie06], that will allow me to have bloom that looks gorgeous :). (Bloom Curve starts at slide 64).

I’ve extended this post to ask for help with other post processing matters on how to achieve better bloom/glare/ghosting effects with any tips or slides that you guys may have found. I wish to improve the quality of my lighting in games as well by making the lighting more “physically accurate”.

Bloom Curve (Taken from Bungie’s slides, but missing in the powerpoint, only found on the web):

Bloom Curve:

No Bloom Curve:

Examples of bloom/flares that I like and want to acheive something like:

I know I need some ghost samples for my bloom to also make it look “prettier”:

And I need streaking, I already have streaking but Its not as good as these other implementations like Yebis 2 or iCEnhancer the GTAIV ENB Series mod. Here is my implementation of streaking:

Lastly I want to improve my lighting , like Cody has done here, with Physically accurate sunlight and ambient colors:

Current Code:
Shader:

Shader "Custom/HDRLighting2" 
{
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BloomTex ("Base (RGB)", 2D) = "white" {}
	}
		
		CGINCLUDE
		#include "UnityCG.cginc"

		uniform sampler2D _MainTex;
		uniform sampler2D _OrignMaterial;
		uniform sampler2D _BloomTex;
		uniform sampler2D _BlurTex;
		uniform sampler2D _Curve;
		uniform float4 _BloomColor;
		uniform float _BlurSize;
		uniform float _RangeScale;
		uniform float _BloomIntensity;
		uniform float _BloomThreshold;
		
		float4 offsets;
		
		struct v2f 
		{
			float4 pos : POSITION;
			float2 uv : TEXCOORD0;
	
			float4 uv01 : TEXCOORD1;
			float4 uv23 : TEXCOORD2;
			float4 uv45 : TEXCOORD3;
		};
		
		v2f vert (appdata_img v) 
		{
			v2f o;
			o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
	
			o.uv.xy = v.texcoord.xy;
	
			o.uv01 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1);
			o.uv23 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 2.0;
			o.uv45 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 3.0;
	
			return o;  
	}
		
		float BloomCurve(float fBright)
		{
			fBright = max(tex2D(_Curve, float2(_RangeScale, 0.5)).x, 2.00f);
			return fBright;
		}
		
		float4 Tonemap(v2f_img i)  : COLOR
		{
			float4 vColor = tex2D(_MainTex, i.uv);
			vColor.rgb *= 5.199f;
			vColor.rgb = vColor.rgb / (1.0f + vColor.rgb);

			vColor.rgb = pow(vColor.rgb, float3(1.0f / 0.550f, 1.0f / 0.552f, 1.0f / 0.549f));
			vColor.rgb *= 1.10f;
			
			return float4(vColor.rgb, 1.0f);
		}
		
		float4 Threshold(v2f_img i) : COLOR
		{
			float4 vSample = tex2D(_MainTex, i.uv); 
			
			return saturate((vSample - _BloomThreshold) / (1 - _BloomThreshold));
		}
		
		float4 Downsample(v2f_img i) : COLOR
		{
			float2 InputSize = float2( 1.0f / 1280.0f, 1.0f / 720.0f);
		
			float4 vSample = tex2D(_MainTex, i.uv);
			
			vSample += tex2D(_MainTex, i.uv+InputSize * float2(1.0f, 1.0f));
			vSample += tex2D(_MainTex, i.uv+InputSize * float2(-1.0f, 1.0f));
			vSample += tex2D(_MainTex, i.uv+InputSize * float2(1.0f, -1.0f));
			vSample += tex2D(_MainTex, i.uv+InputSize * float2(-1.0f, -1.0f));
			
			vSample /= 4.0f;
			
			return vSample;
		
		}
		
		float4 Bloom(v2f_img i) : COLOR
		{
			float4 vSample = tex2D(_MainTex, i.uv); 
			float fIntensity = max(dot(vSample.rgb, float3(0.3f, 0.3f, 0.3f)), 0.0001f);
			
			//_BloomIntensity = BloomCurve(fIntensity);
			float3 fBloomColor = vSample.rgb * float3(_BloomIntensity, _BloomIntensity, _BloomIntensity) / float3(fIntensity, fIntensity, fIntensity);
			
			return float4(fBloomColor, 1.0f);
			
		}
		
		float4 AccumBlur(v2f_img i)  : COLOR
		{
			float4 vBloom = tex2D(_BloomTex, i.uv);
			float4 vBlur = tex2D(_BlurTex, i.uv);
		
			float4 vFinal = vBloom + vBlur;
			
			return vFinal;
		}
		
		float4 Blur(v2f i)  : COLOR
		{
			float4 color = float4 (0,0,0,0);

			color += 0.40 * tex2D (_MainTex, i.uv);
			color += 0.15 * tex2D (_MainTex, i.uv01.xy);
			color += 0.15 * tex2D (_MainTex, i.uv01.zw);
			color += 0.10 * tex2D (_MainTex, i.uv23.xy);
			color += 0.10 * tex2D (_MainTex, i.uv23.zw);
			color += 0.05 * tex2D (_MainTex, i.uv45.xy);
			color += 0.05 * tex2D (_MainTex, i.uv45.zw);	
			
			return color;
		}
		
		float4 Composite(v2f_img i)  : COLOR
		{
			float4 vSample = tex2D(_OrignMaterial, i.uv);
			float4 vBloom = tex2D(_BloomTex, i.uv);
			vBloom *= _BloomColor;
		
		
			float4 vFinal = vBloom + vSample;
		
			return vFinal;
		}

		ENDCG

Subshader {

	ZTest Off
	Cull Off
	ZWrite Off
	Fog { Mode off }
	
//Pass 0: Threshold
 Pass 
 {
 Name "Threshold"

      CGPROGRAM
      #pragma fragmentoption ARB_precision_hint_fastest 
      #pragma vertex vert_img
      #pragma fragment Threshold
      ENDCG
  }
  
//Pass 1: Downsample
 Pass 
 {
 Name "Downsample"

      CGPROGRAM
      #pragma fragmentoption ARB_precision_hint_fastest 
      #pragma vertex vert_img
      #pragma fragment Downsample
      ENDCG
  }
  
  //Pass 2: Bloom
 Pass 
 {
 Name "Bloom"

      CGPROGRAM
      #pragma vertex vert_img
      #pragma fragment Bloom
      ENDCG
  }
    //Pass 3: AccumBlur
 Pass 
 {
 Name "AccumBlur"

      CGPROGRAM
      #pragma vertex vert_img
      #pragma fragment AccumBlur
      ENDCG
  }
  
    //Pass 4: Blur
 Pass 
 {
 Name "Blur"

      CGPROGRAM
      #pragma vertex vert
      #pragma fragment Blur
       #pragma fragmentoption ARB_precision_hint_fastest 
      ENDCG
  } 
    
   //Pass 5: Tonemap
  Pass 
 {
 Name "Tonemap"

      CGPROGRAM
      #pragma vertex vert_img
      #pragma fragment Tonemap
      ENDCG
  }
  
   //Pass 6: Composite
 Pass 
 {
 Name "Composite"

      CGPROGRAM
      #pragma vertex vert_img
      #pragma fragment Composite
      ENDCG
  }
}

Fallback off
	
}

C# (CPU code)

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class HDRLighting2 : MonoBehaviour {

	#region Variables
	public Shader HDRLightingShader;
	public float BloomAmount = 1.0f;
	public float BloomThreshold = 1.0f;
	public Color BloomColor;
	public float Blur = 1.0f;
	private Material HDRLightingMaterial;
	private RenderTexture BrightPass;
	private RenderTexture DownSample_0;
	private RenderTexture DownSample_1;
	private RenderTexture DownSample_2;
	private RenderTexture DownSample_3;
	private RenderTexture HDRBloom;
	private RenderTexture HDRBlur;
	private RenderTexture LDRTonemap;
	private RenderTexture UpSample_0;
	private RenderTexture UpSample_1;
	private RenderTexture UpSample_2;
	private RenderTexture UpSample_3;
	private RenderTexture Compose;
	private RenderTextureFormat HDR = RenderTextureFormat.ARGBHalf;
	private RenderTextureFormat LDR = RenderTextureFormat.ARGB32;
	#endregion
	
	#region Properties
	Material material
	{
		get
		{
			if(HDRLightingMaterial == null)
			{
				HDRLightingMaterial = new Material(HDRLightingShader);
				HDRLightingMaterial.hideFlags = HideFlags.HideAndDontSave;	
			}
			return HDRLightingMaterial;
		}
	}
	#endregion
	// Use this for initialization
	void Start () 
	{
		if(!SystemInfo.supportsImageEffects)
		{
			enabled = false;
			return;
		}
		
		//HDRLightingShader = Shader.Find("HDRLighting2");
		
	}
	
	void SetConstants()
	{
		material.SetFloat("_BloomIntensity", BloomAmount);
		material.SetFloat("_BloomThreshold", BloomThreshold);
		material.SetColor("_BloomColor", BloomColor);
	}
	
	void BrightPassFilter(RenderTexture source)
	{
		BrightPass = RenderTexture.GetTemporary(source.width, source.height, 0, HDR);
		
		Graphics.Blit(source, BrightPass, material, 0);
	}
	
	void Downsample(RenderTexture source)
	{
			DownSample_0 = RenderTexture.GetTemporary(source.width / 2, source.height / 2, 0, HDR);
			DownSample_1 = RenderTexture.GetTemporary(source.width / 4, source.height / 4, 0, HDR);
			DownSample_2 = RenderTexture.GetTemporary(source.width / 8, source.height / 8, 0, HDR);
			DownSample_3 = RenderTexture.GetTemporary(source.width / 16, source.height / 16, 0, HDR);
		
			Graphics.Blit(BrightPass, DownSample_0, material, 1);
			RenderTexture.ReleaseTemporary(BrightPass);
			Graphics.Blit(DownSample_0, DownSample_1, material, 1);
			RenderTexture.ReleaseTemporary(DownSample_0);
			Graphics.Blit(DownSample_1, DownSample_2, material, 1);
			RenderTexture.ReleaseTemporary(DownSample_1);
			Graphics.Blit(DownSample_2, DownSample_3, material, 1);
			RenderTexture.ReleaseTemporary(DownSample_2);
		
	}
	
	void Bloom(RenderTexture source)
	{
		HDRBloom = RenderTexture.GetTemporary(source.width / 16, source.height / 16, 0, HDR);
		Graphics.Blit(DownSample_3, HDRBloom, material, 2);
		RenderTexture.ReleaseTemporary(DownSample_3);
	}
	
	void AccumulateBlur(RenderTexture source)
	{
		HDRBlur = RenderTexture.GetTemporary(source.width / 16, source.height / 16, 0, HDR);
		
		float WidthOverHeight = (source.width) / (source.height);
		float OneOverBase = 1.0f / 512.0f;
		
		//Seperable Blur (Unitys approach)
		for(int i = 0; i < 2; i++) 
		{
			material.SetVector ("offsets", new Vector4 (0.0f, Blur * OneOverBase, 0.0f, 0.0f));
			Graphics.Blit (HDRBloom, HDRBlur, material, 4); 
			material.SetVector ("offsets", new Vector4 (Blur * OneOverBase / WidthOverHeight, 0.0f, 0.0f, 0.0f));
			Graphics.Blit (HDRBlur, HDRBloom, material, 4);
		}
		
		UpSample_0 = RenderTexture.GetTemporary(source.width / 16, source.height / 16, 0, HDR);
		UpSample_1 = RenderTexture.GetTemporary(source.width / 8, source.height / 8, 0, HDR);
		UpSample_2 = RenderTexture.GetTemporary(source.width / 4, source.height / 4, 0, HDR);
		UpSample_3 = RenderTexture.GetTemporary(source.width / 2, source.height / 2, 0, HDR);
		
		Graphics.Blit(HDRBloom, UpSample_0, material, 3);
		RenderTexture.ReleaseTemporary(HDRBloom);
		
		material.SetTexture("_BlurTex", HDRBlur);
		
		for(int i = 0; i < 2; i++) 
		{
			material.SetVector ("offsets", new Vector4 (0.0f, Blur * OneOverBase, 0.0f, 0.0f));
			Graphics.Blit (UpSample_0, HDRBlur, material, 4); 
			material.SetVector ("offsets", new Vector4 (Blur * OneOverBase / WidthOverHeight, 0.0f, 0.0f, 0.0f));
			Graphics.Blit (HDRBlur, UpSample_0, material, 4);
		}
		
		Graphics.Blit(UpSample_0, UpSample_1, material, 3);
		RenderTexture.ReleaseTemporary(UpSample_0);
		
		for(int i = 0; i < 2; i++) 
		{
			material.SetVector ("offsets", new Vector4 (0.0f, Blur * OneOverBase, 0.0f, 0.0f));
			Graphics.Blit (UpSample_1, HDRBlur, material, 4); 
			material.SetVector ("offsets", new Vector4 (Blur * OneOverBase / WidthOverHeight, 0.0f, 0.0f, 0.0f));
			Graphics.Blit (HDRBlur, UpSample_1, material, 4);
		}
		
		Graphics.Blit(UpSample_1, UpSample_2, material, 3);
		RenderTexture.ReleaseTemporary(UpSample_1);
		
		for(int i = 0; i < 2; i++) 
		{
			material.SetVector ("offsets", new Vector4 (0.0f, Blur * OneOverBase, 0.0f, 0.0f));
			Graphics.Blit (UpSample_2, HDRBlur, material, 4); 
			material.SetVector ("offsets", new Vector4 (Blur * OneOverBase / WidthOverHeight, 0.0f, 0.0f, 0.0f));
			Graphics.Blit (HDRBlur, UpSample_2, material, 4);
		}
		
		RenderTexture.ReleaseTemporary(HDRBlur);
		
		Graphics.Blit(UpSample_2, UpSample_3, material, 3);
		RenderTexture.ReleaseTemporary(UpSample_2);
		
		
	}
	void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)
	{
		if(HDRLightingShader != null)
		{
			SetConstants();
			
			BrightPassFilter(sourceTexture);	
			Downsample(sourceTexture);
			Bloom(sourceTexture);
			AccumulateBlur(sourceTexture);
			
			Compose = RenderTexture.GetTemporary(sourceTexture.width, sourceTexture.height, 0, LDR);
			
			material.SetTexture("_BloomTex", UpSample_3);
			material.SetTexture("_OrignMaterial", sourceTexture);
			Graphics.Blit(UpSample_3, Compose, material, 6);
			RenderTexture.ReleaseTemporary(UpSample_3);
			Graphics.Blit(Compose, destTexture, material, 5);
			
			RenderTexture.ReleaseTemporary(Compose);
			
		}		
	}
	
	// Update is called once per frame
	void Update () 
	{
		BloomAmount = Mathf.Clamp(BloomAmount, 0.0f, 5.0f);
		BloomThreshold = Mathf.Clamp(BloomThreshold, 0.0f, 1.0f); 
	}
	
	void OnDisable ()
	{
		if(HDRLightingMaterial)
		{
			DestroyImmediate(HDRLightingMaterial);	
		}
		
	}
	
	
}

[Bungie06] Dropbox - Error - Simplify your life

Thanks for your help guys, I will be back soon with help on auto-exposure without reinhard’s tonemap operator, just a pure custom auto-exposure.

Also forgot to mention, here is what I currently have for the ambient lighting but i’m sure lots of things can be improved:

For the bloom curve, the idea is to use the overall intensity of the pixel to determine how much to apply the effect instead of using each color channel individually, which would result in any differences in color becoming exaggerated.

Bungie is using dot(color, float3(0.3,0.3,0.3)) to get the intensity, but you may want to try dot(color, float3(0.212671, 0.71516, 0.072169)) instead. That will get you the luminance, which is essentially the intensity weighted by the sensitivity of our eyes to particular wavelengths of light. In theory it should give a more realistic result, but to be honest I don’t know enough about photometry to be sure that it’s correct to use in this situation. It might just make everything look worse. It’s also incorrect for use in low-light scenes, but I don’t have the matrix for those to hand at the moment.

The function for the bloom curve graph appears to be something along the lines of x*0.4+max(0,x-1.25)*0.6, but it’s hard to know for sure (label your axes, Bungie!) You could use pretty much anything here, it just takes the image intensity and outputs the bloom intensity. x*x/3.2 will give you something similar but with a smoother distribution, or max(0,x-1)^2 will only cause intensities over 1.0 to bloom. It all depends on what sort of look you’re going for.

After that, the bloom_color = color * bloom_intensity / intensity line divides out the old intensity and multiplies in the new one, then you can do the usual resampling, blurring, and additive blending to get the final image.

Your post is extremely helpful :). Each of the samples worked flawlessly. I was going to use those “common” luminance coefficients, but I was afraid it would affect the color of the bloom somehow, the slides kept mentioning the key is to reserve the color, so i tried not to veer off too far from their code.

I guess another issue is preserving color, I’m seeing the horrible reddish bloom from orange glowing objects, and the darker blues in the bloom than the objects actually are. I assume this has something to deal with the thresholding, when I threshold the image I get colors that aren’t the same as the original image. I read some where that you can use a tone map operator to threshold the image so it preserves color and only extracts the bright parts. the D3D Book Im pretty sure talks about this. But It has reinhards tonemap written all over it.

I did notice something though, I see some bloom techniques where they don’t just use a thresholding main image but using patterns and code that makes shapes and they use those to draw the bloom I think :face_with_spiral_eyes:. I’m not entirely sure how to get that effect working but it seems like an interesting approach.

But I’ll figure something out for that soon. I plan on adding blue shifting after I figure an auto exposure without reinhard’s tonemap! Then the rest of the glare/flare, streaking etc.

Any more tips or tricks guys? Feedback is welcome! :slight_smile:

WGermany

Please - Finish This!!! And I’m your first customer. I tried and looks beautify, but needs auto exposure. (As an opportunity to introduce support dirty texture - is add realistics).

I’ve made a bunch more changes to this, I’ve added dirty texture support a long time ago, fixed some bugs and I’m adding auto exposure now. This wasn’t a release though, it was a thread asking for help. :slight_smile: