unity,荧光效果(bloom)实现过程
两个月前,刚接触unity的时候费了半天劲儿做了个荧光效果(见:http://www.cnblogs.com/wantnon/p/4430749.html),今天终于抽空整理了一下,把过程写下来。
荧光效果如图:
一,接口:
整理完以后得到三个东西:AE_GroupBloom.cs,Camera_renderBloomRT.prefab,bloomEmitter.shader。
使用方法:
1,将AE_GroupBloom.cs添加到Main Camera上。
2,将Camera_renderBloomRT.prefab添加到场景中。并将Main Camera赋给其中的Main Camera Ref。
3,将发光物体的shader选为Custom/bloomEmitter。
这样就可以实现上面图中的效果。下面说具体代码。
二,实现原理:
(注:我用的是unity5)
AE_GroupBloom.cs代码如下:
using UnityEngine;
using System.Collections;
public class AE_GroupBloom : MonoBehaviour {
public Material m_groupBloomMaterial;
void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture){
////Debug.Log (destTexture);
//Copies source texture into destination render texture with a shader.
Graphics.Blit (sourceTexture, destTexture, m_groupBloomMaterial);
}
}
Graphics.Blit的官方文档中写道:
Copies source texture into destination render texture with a shader.
This is mostly used for implementing image effects.
Blit sets dest as the render target, sets source _MainTex property on the material, and draws a full-screen quad.
在此处由于脚本加在Main Camera上,所以即sourceTexture就是Main Camera渲染的画面,destTexture就是Inspector中显示的Main Camera的Camera组件中的Target Texture属性,其实默认为None,表示直接渲染到屏幕(否则可以赋一个renderTexture,使此相机渲染到renderTexture上)。Graphics.Blit(sourceTexture, destTexture, m_groupBloomMaterail)的意思就是将Main Camera渲染的画面经过m_groupBloomMaterial中的shader过滤后传送到屏幕(而且sourceTexture会用作m_groupBloomMaterial的shader的_MainTex)。可见,这便是典型的后处理过程,m_groupBloomMaterial中的shder便是后处理shader。
m_groupBloomMaterial是在脚本的setting中事先赋值好的(AE_GroupBloom.mat),如图:
AE_GroupBloom.mat中的shader用的是Custom/AE_GroupBloom.shader,代码如下:
Shader "Custom/AE_GroupBloom" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_BloomTex ("BloomTex (RGB)", 2D) = "white" {}
_BloomFactor("Bloom Factor",Range(0,10)) =2.0
}
SubShader {
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform sampler2D _BloomTex;
float _BloomFactor;
v2f_img vert(appdata_img v) : POSITION {
v2f_img o;
o.pos=mul(UNITY_MATRIX_MVP, v.vertex);
o.uv=v.texcoord.xy;
return o;
}
fixed4 frag(v2f_img i):COLOR
{
//Get the colors from the RenderTexture and the uv‘s
//from the v2f_img struct
fixed4 mainColor = tex2D(_MainTex, i.uv);
fixed4 bloomColor= tex2D(_BloomTex, i.uv);
fixed4 finalColor =bloomColor*_BloomFactor+mainColor;
return finalColor;
}
ENDCG
}
}
FallBack "Diffuse"
}
前面说了_MainTex就是Main Camera渲染的画面,在shader中按如下公式进行合成:
FinalTex=MainTex+BloomTex*BloomFactor
在本例中,我取的是BloomFactor=2.
即:
= + *2
于是,现在剩下的问题就是BloomTex如何得到。
1,错误的方法:
最容易想到的办法就是把这些发光物体单独渲染到一个renderTexture上,然后作blur,即得到BloomTex。
只可惜这个方法是错误的,原因在于这种方法没考虑深度遮挡问题,如下图所示:
当发光物体被其它物体遮挡时,BloomTex上应该有相应的“缺口”。所以上面所述单独只将发光物体渲染一遍是错误的。
2,正确的方法:
知道了前面方法错在哪里,正确的方法自然也就有了。正确的方法是应该将所有物体都渲染一遍,并且对于所有非发光物体使用“纯黑”shader渲染,对于发光物体使用UnlitColor(颜色无光照)shader渲染,最后再blur,这样就能得到带“缺口”的BloomTex了。
具体实现如下:
新建一个RenderTexture命名为bloomRT,新建一个Camera命名为Camera_renderBloomRT。
在Inspector中将Camera_renderBloomRT的Camera组件的Target Texture赋为bloomRT。这样Camera_renderBloomRT就会把图像渲染到bloomRT上了。
为Camera_renderBloomRT添加脚本renderBloomRT(并在Inspector中将Main Camera赋给其中的m_mainCameraRef变量):
using UnityEngine;
using System.Collections;
public class renderBloomRT : MonoBehaviour {
public Camera m_mainCameraRef;
public Shader m_renderBloomTexShader;
void Start () {
GetComponent<Camera> ().enabled = false;//it is equals to uncheck Camera component in Inspector
synchronizePosAndRotWithMainCamera ();
}
void LateUpdate () {
synchronizePosAndRotWithMainCamera ();
//Rendering with Replaced Shaders: http://www.cnblogs.com/wantnon/p/4528677.html
GetComponent<Camera>().RenderWithShader(m_renderBloomTexShader, "RenderType");
}
void synchronizePosAndRotWithMainCamera(){
transform.position=m_mainCameraRef.transform.position;
transform.rotation = m_mainCameraRef.transform.rotation;
}
}
此脚本做了三件事儿:
1,将Camera_renderBloomRT的camera组件disable掉(相当于在inspector中将camera组件前面的勾去掉)。
2,让Camera_renderBloomRT的position和rotation与Main Camera始终保持一致。
3,调用GetComponent<Camera>().RenderWithShader(m_renderBloomTexShader, "RenderType")进行渲染(渲染到bloomRT上)。
m_renderBloomTexShader是在脚本的setting中事先赋好的(Custom/renderBloomTex.shader),如图:
renderBloomTex.shader代码如下:
Shader "Custom/renderBloomTex" {//modified from "Unlit/Color"
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
}
SubShader { //this subShader is same with "Unlit/Color" shader, except the RenderType change to "GroupBloom"
Tags { "RenderType"="GroupBloom" }
LOD 100
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
};
struct v2f {
float4 vertex : SV_POSITION;
UNITY_FOG_COORDS(0)
};
fixed4 _Color;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : COLOR
{
fixed4 col = _Color;
UNITY_APPLY_FOG(i.fogCoord, col);
UNITY_OPAQUE_ALPHA(col.a);
return col;
}
ENDCG
}
}
SubShader { //because this subShader renders pure black, so we need not support fog
Tags { "RenderType"="Opaque" }
LOD 100
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
};
struct v2f {
float4 vertex : SV_POSITION;
};
fixed4 _Color;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
fixed4 frag (v2f i) : COLOR
{
fixed4 col = float4(0,0,0,1);
return col;
}
ENDCG
}
}
}
此shader的第一个subShader与unity5内置的"Unlit/Color"shader完全相同,只是将"RenderType"="Opaque"改成了"RenderType"="GroupBloom"。第二个subShader则是将所渲染的像素都弄成纯黑色,其标签仍使用"RenderType"="Opaque"。
当调用GetComponent<Camera>().RenderWithShader(m_renderBloomTexShader, "RenderType"),系统将会对所有含有"RenderType"="Opaque"标签的物体使用第二个subShader进行渲染(渲染成纯黒色),而对所有含有"RenderType"="GroupBloom"标签的物体使用第一个subShader进行渲染(Unlit+Color)。
(注:关于RenderWithShader的详细解释见:http://www.cnblogs.com/wantnon/p/4528677.html)
于是接下来我们就清楚该怎么做了,就是:
1,保证所有想渲染成纯黑色的物体带有"RenderType"="Opaque"标签,这个基本上是自然保证的,因为unity里物体的默认shader都带有"RenderType"="Opaque"标签。
2,保证所有想渲染为Unlit+Color的物体带有"RenderType"="GroupBloom",这个需要我们手动去添加,或者更有条理的办法是创建一个标准shader,命名为“Custom/bloomEmitter”,并为其添加"RenderType"="GroupBloom"标签,然后对于所有的发光物体,都使用此shader。
至此我们就将发光物体与不发光物体以不同的replace shader渲染到bloomRT上了,得到bloomRT如下:
然后再把standard assets中的blur脚本添加到Camera_renderBloomRT上,得到bloomRT如下:
至此bloomRT已完成,我们在AE_GroupBloom.shader的setting中事先将bloomRT赋给BloomTex分量。即bloomRT就是BloomTex。
我们还应该为Camera_renderBloomRT添加下面脚本letRenderTextureAspectEqualsToScreenAspect.cs:
using UnityEngine;
using System.Collections;
public class letRenderTextureAspectEqualsToScreenAspect : MonoBehaviour {
public RenderTexture m_rt;
// Use this for initialization
void Start () {
float screenAspect = (float)(Screen.width) / Screen.height;
////Debug.Log (screenAspect);
m_rt.width = (int)(m_rt.height*screenAspect);
}
}
并将bloomRT通过Inspector赋给m_rt.
这样在游戏开发执行时在保持bloomRT的高度不变的前提下重设其宽度,使bloomRT的长宽比与屏幕的长宽比相等,从而保证bloomRT能够正确地与原始游戏画面进行合成。
另外需要注意的是bloomRT的尺寸不必与屏幕相等,出于效率考虑bloomRT的尺寸应在能保证视觉效果可接受的前提下尽可能地小。越小越好。
最后将Camera_renderBloomRT使成prefab,以方便以后使用。
(完)