兩個月前,剛接觸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);
}
}
Camera.OnRenderImage的官方文檔中寫道:
OnRenderImage is called after all rendering is complete to render image.
Postprocessing effects.
It allows you to modify final image by processing it with shader based filters. The incoming image is source render texture. The result should end up in destination render texture. When there are multiple image filters attached to the camera, they process image sequentially, by passing first filter's destination as the source to the next filter.
This message is sent to all scripts attached to the camera.
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上,所以OnRenderImage的sourceTexture就是Main Camera渲染的畫面,destTexture就是Inspector中顯示的Main Camera的Camera組件中的Target Texture屬性,我們這里用其默認值None,表示直接渲染到屏幕(此時若在腳本中輸出destTexture的值,結果為Null)(否則可以賦一個renderTexture,使此相機渲染到renderTexture上)。Graphics.Blit(sourceTexture, destTexture, m_groupBloomMaterail)的意思就是將Main Camera渲染的畫面經過m_groupBloomMaterial中的shader過濾后傳送到屏幕(而且sourceTexture會用作m_groupBloomMaterial的shader的_MainTex)。可見,這便是典型的后處理過程,m_groupBloomMaterial中的shder便是后處理shader。
(另外上面OnRenderImage的文檔中還提到:如果相機上添加多個后處理腳本則會依次執行,而且前一個的destTexture用作下一個的sourceTexture,本例中尚且用不到這個)。
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 {
ZWrite Off //注意,這句和下一句對於iOS特別重要,如果沒有這兩句,在iOS真機上運行將會黑屏。
ZTest Always
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(注意,由於我們並不需要深度信息,所以這個bloomRT可以選為No depth buffer,不會影響渲染到渲染效果),新建一個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;
// Use this for initialization
void Start () {
GetComponent<Camera> ().enabled = false;//it is equals to uncheck Camera component in Inspector
synchronizePosAndRotWithMainCamera ();
synchronizeProjModeAndFrustomWithMainCamera ();
}
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;
}
void synchronizeProjModeAndFrustomWithMainCamera(){
GetComponent<Camera>().orthographic=m_mainCameraRef.orthographic;
GetComponent<Camera> ().orthographicSize = m_mainCameraRef.orthographicSize;
GetComponent<Camera> ().nearClipPlane = m_mainCameraRef.nearClipPlane;
GetComponent<Camera> ().farClipPlane = m_mainCameraRef.farClipPlane;
GetComponent<Camera> ().fieldOfView = m_mainCameraRef.fieldOfView;
}
}
此腳本做了三件事兒:
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。
(完)
