WebGL - 片元着色器 1.Bloom 特效实现

2,400 阅读6分钟
原文链接: www.talkingcoder.com

第一件事先上demo吧     Bloom特效

demo下载地址

这篇文章耽误了很久。

首先说一下目的。今天是为了实现一个模糊特效的算法,这个算法常用与游戏里面post processing实现Bloom效果,也叫做全屏发光,本质上是高斯模糊。有时游戏在游戏里面看见类似于仙境的效果,或者在ios桌面下拉后的模糊效果。这些效果之间稍有差别,但是不是很大。今天我实现高斯模糊的基础是实现卷积运算。然后大家在这个基础上可以实现各种其他的模糊算法。


首先我们看看bloom效果,talking coder ”我的主页“ 就实现了高斯模糊。点击右上角自己的昵称进入“我的主页”后即可查看。


滚动鼠标就会发现随着鼠标的滚动图片越来越模糊。这个就是高斯模糊,Bloom特效的关键算法实现。




Bloom实现方法简介

首先我们介绍下实现辉光的几种经典的实现。


1.第一种方式:一个很简单的方式就是在一个公告牌(billboard)材质上面贴上一张发光效果的贴图。这种方法可谓简单粗暴,所谓效果不够图片来凑。虽然很简单,但是很多游戏里面就是这么实现的。


2.第二种:对图片实现卷积运算。所谓的图片的卷积运算,就是将元素附近的其他元素叠加到自身。为什么这样会实现模糊的效果?有兴趣的同学可以试着将任意一张图片复制很多份,一起置于ps当中。每次置于一张图片的时候都比前一张向下偏移一个像素,然后让他们叠加。实际上这就是一个模糊滤镜。


第一种方式很简单,我们就当作是设计师线下用ps帮我们实现了,我们用查表法检索出结果,就像法线贴图一样。我们在这里重点介绍第二种方式的实现方式。


1.传递贴图到着色器(temple.html文件已实现)

2.获取贴图的纹理像素(temple.html文件已实现)

3.实现卷积运算

为了降低不必要的难度我们已经实现了模板文件。虽然在文件里面有,注释也写的很清楚,我还是再这里贴出来着色器的主要代码。大家后续直接使用GLSL实现算法就可以了。 以下的代码直接实现了第一步和第二步。


<script id="vertexShaderprocess" type="x-shader/x-vertex">
        //传递到片元着色器的变量,后续使用
	uniform vec2 uvScale;

	//从纹理着色器传递到片元着色器的纹理坐标
	varying vec2 vUv;

	//片元着色器的入口函数
	void main()
	{
        //纹理坐标乘以的倍数,在初始化环境我们是1,这是为后续其他demo准备的,暂时无视
	vUv = uvScale * uv;

        //模型视图矩阵传递给webgl系统,可以暂时无视。
	vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );

	gl_Position = projectionMatrix * mvPosition;

	}
</script>
<script id="fragmentShaderprocess" type="x-shader/x-fragment">
        //传递到片元着色器的时间参数,准备给后续demo的,暂时无视
	uniform float time;

	//屏幕的坐标分别表示xy轴
	uniform vec2 resolution;
            
        //以下两个是传递到片元着色器的雾参数
	uniform float fogDensity;

	uniform vec3 fogColor;
            
        //传递到片元着色器的噪声纹理
	uniform sampler2D texturenoise;

	//传递到片元着色器的纹理
	uniform sampler2D texture2;

        //从顶点着色器传递到片元着色器的纹理坐标
	varying vec2 vUv;

	void main( void ) 
	{

         // 获取噪声纹理
	 vec4 noise = texture2D( texturenoise, vUv );
			  
	 // 获取纹理的像素,这里直接实现了第二步
	 vec4 color = texture2D( texture2, vUv * 1.0 );

         //将颜色传递给webgl着色器		
	 gl_FragColor = color;

	 gl_FragColor = color/(length(1.0-vUv*2.0));
	}

</script>

下面我们将实现第三步,用卷积运算达到实现模糊的作用。

vec4 color11 = texture2D( texture2, vec2( vUv.x - 1.0 * h, vUv.y + 1.0 * h) );
vec4 color12 = texture2D( texture2, vec2( vUv.x - 0.0 * h, vUv.y + 1.0 * h) );  
vec4 color13 = texture2D( texture2, vec2( vUv.x + 1.0 * h, vUv.y + 1.0 * h) );

vec4 color21 = texture2D( texture2, vec2( vUv.x - 1.0 * h, vUv.y) );
vec4 color22 = texture2D( texture2, vec2( vUv.x , vUv.y) );
vec4 color23 = texture2D( texture2, vec2( vUv.x + 1.0 * h, vUv.y) );

vec4 color31 = texture2D( texture2, vec2( vUv.x - 1.0 * h, vUv.y-1.0*h) );
vec4 color32 = texture2D( texture2, vec2( vUv.x - 0.0 * h, vUv.y-1.0*h) );
vec4 color33 = texture2D( texture2, vec2( vUv.x + 1.0 * h, vUv.y-1.0*h) );

gl_FragColor = 

(
	 1.0*color11+1.0*color12+1.0*color13
	+1.0*color21+2.0*color22+1.0*color23
	+1.0*color31+1.0*color32+1.0*color33

)/10.0;


 我们可以发现运行test1.html的代码的时候的确模糊了,但是不那么明显,因为我们的卷积核不够大,或者换句话说我们的偏移不够。后续再讲如何改变卷积积核。先来讲讲如何实现卷积。


1.首先计算偏移值

也就是这一句 float h = 1.0/512.0;这里我选用的图片是512*512,我的图片偏移是1像素,所以计算了一个偏移值为1/512。


2.获取没有经过偏移的图片

也就是这一句 vec4 color22 = texture2D( texture2, vec2( vUv.x , vUv.y) );


3.获取经过偏移的图片。

比如想右上的是color13,获取左下的是vec4 color32 = texture2D( texture2, vec2( vUv.x - 0.0 * h, vUv.y-1.0*h) );获取正上的是color12,,,,,等等


4.图片叠加

直接color13加上color22 即可,但是要除以2,因为两张图片叠加所以所有的颜色值都是原来的大约两倍,所以需要除以叠加图片的张数,这里是2 为什么这里会用叠加来实现模糊? 大家可以使用PS来模拟原理

1.打开ps,新建图层置入一张图片

2.新建另外一个图层,再次置入上一张图片

3.第二张图片向下偏移少许

4.点击ps右下角的fx,有个颜色混合


可以看见两张图片由于偏移加上叠加的原因,实现了类似运动模糊的效果。这个就是模糊的原理,之所以后来又有各种什么高斯模糊,什么加权平均模糊,,,,等等,都是因为在不同的方向叠加了指定数量的图片。


5.乘以合适的高斯模糊系数


这个就是我们的高斯模糊的卷积模板。

gl_FragColor = 

(
     1.0*color11+1.0*color12+1.0*color13
    +1.0*color21+2.0*color22+1.0*color23
    +1.0*color31+1.0*color32+1.0*color33

)/10.0;

这样我们的高斯模糊就正式实现了,但是大家运行demo的时候可能已经发现了,好像并不是很模糊。目前的代码里面影响模糊的因素有两个,卷积核大小(我们现在的是3*3的卷积核,这里暂时不讲怎么改变卷积核),还有偏移系数。也就是float h = 1.0/512.0。目前的偏移是一个像素,我们可以试试看便宜10个像素,保证很模糊。


文章现在处于试发布阶段,由于自己很久没写文章,思维逻辑难免出现难以贴近读者的情况,而且也很难以初学者思考的角度去写文章,有难以理解的地方,欢迎大家直接加群151269709反馈问题,thanks。

著作权归作者Chaos所有 文章发表于2016年11月06日