原文:http://www.manew.com/thread-98405-1-1.html
一、前言
睡前再來一篇吧,明天週一了,加油吧!廢話不多說了,先上效果圖如圖所示,前面兩個的效果圖是兩種模式下的:
不同效果,最後一章圖片是在其中一種模式下,場景進行了精心佈置後的效果圖。
看效果圖會不自覺的認爲實現這個效果是不是要在場景中的物體中進行繪製,如果真是要這樣做的話那是在是太耗性能了(畢竟都工作了,本人畢業了就不想在搞那些不實用的)。
其實,這個是通過控制攝像機的最後渲染來實現的效果的,後面我會給出這個案例的工程文件下載地址。
讀者在運行模式的Scene視圖裏將看到場景的物體(立方體)並沒有被額外渲染,有了這個思路對於後面理解代碼的實現原理會很有幫助。
二、原理
1、Shader部分—光照計算
這裏用到的光照計算都比較簡單,光照模式我使用了最簡單的漫反射模式,代碼如下:
//光照計算
float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
float3 normalDirection = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
float3 diffuseReflection = _LightColor0.rgb * max(0.0, dot(normalDirection, lightDirection));
output.col = float4(diffuseReflection, 1.0);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
如圖所示爲漫反射的示意圖,它的計算公式爲:
2、Shader部分—聲納光波的計算
Shader代碼如下:
//聲納光波計算
#ifdef SONAR_DIRECTIONAL
float w = dot(output.pos.xyz, _SonarWaveVector);
#else
float w = length(output.pos.xyz - _SonarWaveVector);
#endif
// Moving wave.
w -= _Time.y * _SonarWaveParams.w;
// Get modulo (w % params.z / params.z)
w /= _SonarWaveParams.z;
w = w - floor(w);
// Make the gradient steeper.
float p = _SonarWaveParams.y;
w = (pow(w, p) + pow(1 - w, p * 4)) * 0.5;
// Amplify.
w *= _SonarWaveParams.x;
fixed3 col = _SonarWaveColor * w + _SonarAddColor;
代碼中使用了預編譯命令,是考慮了前面說的效果圖中的兩種模式的切換。爲此,還必須在前面添加預編譯命令
#pragma multi_compile SONAR_DIRECTIONAL SONAR_SPHERICAL
這個命令的大概意思就是說可以將這兩個模式的Shader一起編譯,然後就可以在C#代碼裏通過
Shader.DisableKeyword("SONAR_SPHERICAL");
或者
Shader.EnableKeyword("SONAR_SPHERICAL");
來進行切換。
完整頂點片段Shader代碼如下:
Shader "CgInUnity/SonarFxVF"
{
Properties
{
_SonarBaseColor("Base Color", Color) = (0.1, 0.1, 0.1, 0)
_SonarWaveColor("Wave Color", Color) = (1.0, 0.1, 0.1, 0)
_SonarWaveParams("Wave Params", Vector) = (1, 20, 20, 10)
_SonarWaveVector("Wave Vector", Vector) = (0, 0, 1, 0)
_SonarAddColor("Add Color", Color) = (0, 0, 0, 0)
}
SubShader
{
Tags{ "LightMode" = "ForwardBase" }
// make sure that all uniforms are correctly set
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile SONAR_DIRECTIONAL SONAR_SPHERICAL
#include "UnityCG.cginc"
struct vertexInput
{
float4 vertex : POSITION;
float3 normal:NORMAL;
};
struct vertexOutput
{
float4 pos:SV_POSITION;
float4 col:COLOR;
};
float3 _SonarBaseColor;
float3 _SonarWaveColor;
float4 _SonarWaveParams; // Amp, Exp, Interval, Speed
float3 _SonarWaveVector;
float3 _SonarAddColor;
uniform float4 _LightColor0;
vertexOutput vert (vertexInput input)
{
vertexOutput output;
//光照計算
float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
float3 normalDirection = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
float3 diffuseReflection = _LightColor0.rgb * max(0.0, dot(normalDirection, lightDirection));
output.col = float4(diffuseReflection, 1.0);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
//聲納光波計算
#ifdef SONAR_DIRECTIONAL
float w = dot(output.pos.xyz, _SonarWaveVector);
#else
float w = length(output.pos.xyz - _SonarWaveVector);
#endif
// Moving wave.
w -= _Time.y * _SonarWaveParams.w;
// Get modulo (w % params.z / params.z)
w /= _SonarWaveParams.z;
w = w - floor(w);
// Make the gradient steeper.
float p = _SonarWaveParams.y;
w = (pow(w, p) + pow(1 - w, p * 4)) * 0.5;
// Amplify.
w *= _SonarWaveParams.x;
fixed3 col = _SonarWaveColor * w + _SonarAddColor;
output.col += float4(col, 1);
return output;
}
float4 frag (vertexOutput input) : COLOR
{
return input.col;
}
ENDCG
}
}
}
喜歡用簡潔的Surface Shader代碼的童鞋可以用如下代碼替換:
Shader "CgInUnity/SonarFxSurf"
{
Properties
{
_SonarBaseColor ("Base Color", Color) = (0.1, 0.1, 0.1, 0)
_SonarWaveColor ("Wave Color", Color) = (1.0, 0.1, 0.1, 0)
_SonarWaveParams ("Wave Params", Vector) = (1, 20, 20, 10)
_SonarWaveVector ("Wave Vector", Vector) = (0, 0, 1, 0)
_SonarAddColor ("Add Color", Color) = (0, 0, 0, 0)
}
SubShader
{
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf Lambert
#pragma multi_compile SONAR_DIRECTIONAL SONAR_SPHERICAL
struct Input
{
float3 worldPos;
};
float3 _SonarBaseColor;
float3 _SonarWaveColor;
float4 _SonarWaveParams; // Amp, Exp, Interval, Speed
float3 _SonarWaveVector;
float3 _SonarAddColor;
void surf(Input IN, inout SurfaceOutput o)
{
#ifdef SONAR_DIRECTIONAL
float w = dot(IN.worldPos, _SonarWaveVector);
#else
float w = length(IN.worldPos - _SonarWaveVector);
#endif
// Moving wave.
w -= _Time.y * _SonarWaveParams.w;
// Get modulo (w % params.z / params.z)
w /= _SonarWaveParams.z;
w = w - floor(w);
// Make the gradient steeper.
float p = _SonarWaveParams.y;
w = (pow(w, p) + pow(1 - w, p * 4)) * 0.5;
// Amplify.
w *= _SonarWaveParams.x;
// Apply to the surface.
o.Albedo = _SonarBaseColor;
o.Emission = _SonarWaveColor * w + _SonarAddColor;
}
ENDCG
}
Fallback "Diffuse"
}
這個代碼看似簡單,但是對於處在學習基礎原理階段的童鞋,我的建議還是多動手寫寫頂點片段Shader,實現簡單的光照模式、頂點變換等等對於學習Shader會非常有幫助的。
3、C#腳本部分
腳本部分最關鍵的就是使用上述的Shader去渲染攝像機,這個需要通過如下的代碼來實現嗎,這段代碼的意思是替換成這個Shader來渲染攝像機
GetComponent<Camera>().SetReplacementShader(shader, null);
其他的功能就是出傳遞參數給Shader來實現最終隨時間變換的效果,完整的代碼如下:
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class SonarFxControl : MonoBehaviour
{
// 聲納的模式
public enum SonarMode { Directional, Spherical }
[SerializeField] SonarMode _mode = SonarMode.Directional;
public SonarMode mode { get { return _mode; } set { _mode = value; } }
// 聲納波的方向(僅僅在Directional模式下)
[SerializeField] Vector3 _direction = Vector3.forward;
public Vector3 direction { get { return _direction; } set { _direction = value; } }
// 聲納波區域(僅僅在Spherical模式下)
[SerializeField] Vector3 _origin = Vector3.zero;
public Vector3 origin { get { return _origin; } set { _origin = value; } }
// 背景顏色(Surfface Shader下有用)
[SerializeField] Color _baseColor = new Color(0.2f, 0.2f, 0.2f, 0);
public Color baseColor { get { return _baseColor; } set { _baseColor = value; } }
// 聲納波的顏色
[SerializeField] Color _waveColor = new Color(1.0f, 0.2f, 0.2f, 0);
public Color waveColor { get { return _waveColor; } set { _waveColor = value; } }
// 波的高度\振幅
[SerializeField] float _waveAmplitude = 2.0f;
public float waveAmplitude { get { return _waveAmplitude; } set { _waveAmplitude = value; } }
// 波的顏色指數
[SerializeField] float _waveExponent = 22.0f;
public float waveExponent { get { return _waveExponent; } set { _waveExponent = value; } }
// 波的時間間隔
[SerializeField] float _waveInterval = 20.0f;
public float waveInterval { get { return _waveInterval; } set { _waveInterval = value; } }
// 波的速度
[SerializeField] float _waveSpeed = 10.0f;
public float waveSpeed { get { return _waveSpeed; } set { _waveSpeed = value; } }
// 額外的顏色
[SerializeField] Color _addColor = Color.black;
public Color addColor { get { return _addColor; } set { _addColor = value; } }
[SerializeField] Shader shader;
int baseColorID;
int waveColorID;
int waveParamsID;
int waveVectorID;
int addColorID;
void Awake()
{
baseColorID = Shader.PropertyToID("_SonarBaseColor");
waveColorID = Shader.PropertyToID("_SonarWaveColor");
waveParamsID = Shader.PropertyToID("_SonarWaveParams");
waveVectorID = Shader.PropertyToID("_SonarWaveVector");
addColorID = Shader.PropertyToID("_SonarAddColor");
}
void OnEnable()
{
GetComponent<Camera>().SetReplacementShader(shader, null);
Update();
}
void OnDisable()
{
GetComponent<Camera>().ResetReplacementShader();
}
void Update()
{
Shader.SetGlobalColor(baseColorID, _baseColor);
Shader.SetGlobalColor(waveColorID, _waveColor);
Shader.SetGlobalColor(addColorID, _addColor);
var param = new Vector4(_waveAmplitude, _waveExponent, _waveInterval, _waveSpeed);
Shader.SetGlobalVector(waveParamsID, param);
if (_mode == SonarMode.Directional)
{
Shader.DisableKeyword("SONAR_SPHERICAL");
Shader.SetGlobalVector(waveVectorID, _direction.normalized);
}
else
{
Shader.EnableKeyword("SONAR_SPHERICAL");
Shader.SetGlobalVector(waveVectorID, _origin);
}
}
}
三、結語
最後可以看看這種方式實現的聲納波效果的性能消耗如何,我們可以在運行的時候打開state看到如圖所示的統計圖,瞬間有沒有覺得這個效果其實還蠻有實用價值的。
好了,感謝原作者的分享.
每天進步一點點!!!