百人计划(图形部分)Bloom算法 游戏中的辉光效果实现

 【技术美术百人计划】图形 4.1 Bloom算法 游戏中的辉光效果实现_哔哩哔哩_bilibili

 冯乐乐大佬:《unity shader入门精要》

什么是Bloom(算法)

  • Bloom,也称辉光,是一种常见的屏幕效果
  • 模拟摄像机的一种图像效果,让画面中较亮的区域“扩散”到周围的区域中,造成一种朦胧的效果
  • 可以让物体具有真实的明亮效果
  • 可以实现光晕效果

 Bloom的实现原理

  • 1.首先根据一个阈值提出图像中的较亮区域,并将它们存储在一张渲染纹理中
  • 2.利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散的效果
  • 3.与原图混合

 

https://www.yuque.com/6527chen/ldyt32/yknxgb#833c2646

这里引用了下苦咖啡好喝的笔记

前置知识1:HDR和LDR 

 LDR(Low Dynamic Range低动态范围)

  • JPG\PNG格式图片
  • RBG范围在[0,1]之间
  • 用0-1表达真实世界的色彩是不够的

HDR(High Dynamic Range,高动态范围)

  • HDR\EXR格式图片
  • RGB范围可在[0,1]之外

如果不用HDR,只能提取亮度小于1的区域,可能路灯与水洼的亮度一样,都会产生bloom效果,但是使用HDR,就可以提取更亮的地方,让更亮的区域产生bloom效果。超过1的地方

前置知识2:高斯模糊 

  • 实现图像模糊的一种方式
  • 利用高斯核进行卷积运算,得到模糊的图像

image.png

高斯核

高斯核:通过高斯函数定义的卷积核

  • 核中心:(0,0)
  • 核大小:3x3
  • 标准方差σ:1.5

计算步骤:

  • 将(x,y)带入公式中,计算出权重值,(权重值代表当前处理像素的影响程度,离中心越近权重越大)
  • 为了保证卷积后图像不变暗,需要对高斯核进行归一化处理(每个权重除以所有权重和)

二维高斯核的特点

一维高斯核中包括了很多重复的权重(下例中0.0545,0.02442)

下例中大小为5的高斯核,实际上只需要记录三个权重值即可

归一化的作用是:为了保证卷积后的图像不会变暗 

image.png

卷积 

课程内容

  • 是一种图像操作
  • 利用“卷积核”对图像的每个像素进行一系列操作

卷积核

  • 通常是由四方形网格结构,该区域内每个放个都有一个权重值
  • 当我们对图像中的像素进行卷积时:
  • 会把卷积核的中心放置在该像素上
  • 翻转核之后再依次计算核中每个元素和其覆盖的图像像素值的乘积并求和
  • 得到的结果就是该位置的新像素值

image.png

 算法实现

C#代码


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//继承基类(检查是否满足条件)
public class Gaussian : PostEffectsBase
{
    //定义需要传给Shader的参数
    public Shader GaussianShader;//指定的Shader
    private Material GaussianMat = null;//创建的Material
    [Range(0,4)]
    public int iterations = 1;    // 高斯模糊迭代次数
    [Range(1,8)]
    public int downSample = 2;    //降采样的次数(控制渲染纹理的大小)
    [Range(0.0f, 4.0f)]
    public float luminanceThreshold = 0.6f;    //阈值
    [Range(0.2f, 3.0f)]
    public float blurSpread = 0.6f;    //高斯模糊范围
    public Material material
    {
        get
        {
            GaussianMat = CheckShaderAndCreateMaterial(GaussianShader, GaussianMat);
            //创建的Material=函数(指定的Shader,创建的Material(get函数调用基类的函数来得到对应的材质))
            return GaussianMat;
        }
    }
    //使用OnRenderImage获取当前屏幕图像,检查材质是否可用,
    protected void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        //null:直接让结果显示在屏幕上,不做任何处理
        if (material != null) {
            //向Shader传递亮度阈值参数
            material.SetFloat("_LuminanceThreshold", luminanceThreshold);
            //定义长宽高
            int rtW = source.width / downSample;
            int rtH = source.height / downSample;
            //定义buffer0缓冲区的大小
            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW,rtH,0);
            //设置buffer0为双线性滤波
            buffer0.filterMode = FilterMode.Bilinear;
            //调用Shader中第一个Pass来提取图像,并存储在buffer0中
            Graphics.Blit(source, buffer0, material, 0);
            //for循环,iteration次数
            for (int i = 0; i < iterations; i++)
            {
                //将高斯模糊范围传入Shdaer(等下Shader需定义),乘以范围使每次迭代模糊范围不同
                material.SetFloat("_BlurSize",1.0f + blurSpread*i);
                //定义buffer1,
                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                //对图像进行竖直方向的高斯模糊。buffer0(Scr),buffer1(dest),material(使用的材质),1(Shader中第二个pass)
                Graphics.Blit(buffer0,buffer1,material,1);
                //释放buffer0
                RenderTexture.ReleaseTemporary(buffer0);
                //重新赋值
                buffer0 = buffer1;
                //重新分配buffer1,准备接受第二次图像数据
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                //对图像进行垂直方向的高斯模糊。
                Graphics.Blit(buffer0, buffer1, material, 2);
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;
            }
            //material中将buffer0赋予给_Bloom
            material.SetTexture("_Bloom",buffer0);
            //渲染图像到dest上
            Graphics.Blit(source,destination,material,3);
            //释放buffer0
            RenderTexture.ReleaseTemporary(buffer0);
        }
        else
        {
            Graphics.Blit(source,destination);//材质为空直接输入原图
        }
    }
}

Shader

 

Shader "Unlit/Guassian"
{
  	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}//Src原图
		_Bloom ("Bloom (RGB)", 2D) = "black" {}//提亮图像的模糊图
		_LuminanceThreshold ("Luminance Threshold", Float) = 0.5//亮度阈值
		_BlurSize ("Blur Size", Float) = 1.0//模糊范围
	}
	SubShader {
		CGINCLUDE
		
		#include "UnityCG.cginc"
		
		sampler2D _MainTex;
		half4 _MainTex_TexelSize;
		sampler2D _Bloom;
		float _LuminanceThreshold;
		float _BlurSize;
		
		//定义v2f
		///pos
		///uv
		struct v2f {
			float4 pos : SV_POSITION; 
			half2 uv : TEXCOORD0;
		};	
		//提取亮度的顶点着色器,appdata_img
		///顶点转换,uv赋值
		v2f vertExtractBright(appdata_img v) {
			v2f o;		
			o.pos = UnityObjectToClipPos(v.vertex);		
			o.uv = v.texcoord;				 
			return o;
		}
		
		fixed luminance(fixed4 color) {
			return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 
		}
		//提取亮度的片元着色器
		///先进行纹理采样
		///进行亮度提取,采样得到的亮度值(_LuminanceThreshold)减去阙值,并截取到0-1中
		///得到的值和原像素值相乘,得到提取后的亮部区域
		fixed4 fragExtractBright(v2f i) : SV_Target {
			fixed4 c = tex2D(_MainTex, i.uv);
			fixed val = clamp(luminance(c) - _LuminanceThreshold, 0.0, 1.0);
			
			return c * val;
		}
        
		//-----------高斯模糊----------------//
		struct v2fGaussian {
			float4 pos : SV_POSITION;
			half2 uv[5]: TEXCOORD0;//一维高斯核的UV
		};
		  
		v2fGaussian vertBlurVertical(appdata_img v) {
			v2fGaussian o;
			o.pos = UnityObjectToClipPos(v.vertex);
			
			half2 uv = v.texcoord;
			
			o.uv[0] = uv;
			o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
			o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
			o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
			o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
					 
			return o;
		}
		
		v2fGaussian vertBlurHorizontal(appdata_img v) {
			v2fGaussian o;
			o.pos = UnityObjectToClipPos(v.vertex);
			
			half2 uv = v.texcoord;
			
			o.uv[0] = uv;
			o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
			o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
			o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
			o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;					 
			return o;
		}
		
		fixed4 fragBlur(v2fGaussian i) : SV_Target {
			//pass1 pass2片元着色器
			//卷积计算
			float weight[3] = {0.4026, 0.2442, 0.0545};			
			fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];			
			for (int it = 1; it < 3; it++) {
				sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
				sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
			}
			
			return fixed4(sum, 1.0);
		}
		///--------混合亮部图像和原图像-----------
		///pos和uv,这里的uv是四维xy:_MainTex zw:_Bloom
		struct v2fBloom {
			float4 pos : SV_POSITION; 
			half4 uv : TEXCOORD0;
		};
		//顶点着色器
		v2fBloom vertBloom(appdata_img v) {
			v2fBloom o;		
			o.pos = UnityObjectToClipPos (v.vertex);
			o.uv.xy = v.texcoord;		
			o.uv.zw = v.texcoord;  //zw是_Bloom,即模糊后的较亮区域的纹理坐标
			//平台差异化处理
			#if UNITY_UV_STARTS_AT_TOP			
			if (_MainTex_TexelSize.y < 0.0)
				o.uv.w = 1.0 - o.uv.w;
			#endif			        	
			return o; 
		}
		
		fixed4 fragBloom(v2fBloom i) : SV_Target {
			return tex2D(_MainTex, i.uv.xy) + tex2D(_Bloom, i.uv.zw);//返回合并的颜色
		} 
		
		ENDCG
		//关闭ZTest ,剔除,深度写入		
		ZTest Always Cull Off ZWrite Off
		
		Pass {  //去除较亮的图像
			CGPROGRAM  
			#pragma vertex vertExtractBright  
			#pragma fragment fragExtractBright  
			
			ENDCG  
		}
		//use pass 的名字会转换成大写字母,使用时要用大写
		//usepass in GAUSSIAN_BLUR_VERTICAL
		//usepass in GAUSSIAN_BLUR_HORIZONTAL
		Pass {
			NAME "GAUSSIAN_BLUR_VERTICAL"//对亮部水平模糊
			
			CGPROGRAM
			  
			#pragma vertex vertBlurVertical  
			#pragma fragment fragBlur
			  
			ENDCG  
		}
		
		Pass {  
			NAME "GAUSSIAN_BLUR_HORIZONTAL"//垂直模糊·
			
			CGPROGRAM  
			
			#pragma vertex vertBlurHorizontal  
			#pragma fragment fragBlur
			
			ENDCG
		}
		
		Pass {  
			CGPROGRAM  //合并原始图像和模糊的亮部图像
			#pragma vertex vertBloom  
			#pragma fragment fragBloom  
			
			ENDCG  
		}
	}
	FallBack Off
}

Bloom算法的应用 

配合自发光贴图使用

配合特效

GodRay效果(基于径向模糊的后处理)

模拟光线往某个方向扩散的效果

配合Tonemapping(色调映射)

从HDR到LDR的过程叫做tone mapping

可以保留暗处和亮处的细节

GoodRay效果

使用径向模糊代替高斯模糊,模拟光线往某个方向扩散的效果

GoodRay配合ToneMapping

可以看到ToneMapping解决了过曝的问题

作业


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值