转自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
floatw = dot(output.pos.xyz, _SonarWaveVector);
#else
floatw = 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.
floatp = _SonarWaveParams.y;
w = (pow(w, p) + pow(1 - w, p * 4)) * 0.5;
// Amplify.
w *= _SonarWaveParams.x;
fixed3 col = _SonarWaveColor * w + _SonarAddColor;
代码中使用了预编译命令,是考虑了前面说的效果图中的两种模式的切换。为此,还必须在前面添加预编译命令
[C#]纯文本查看复制代码
#pragma multi_compile SONAR_DIRECTIONAL SONAR_SPHERICAL
这个命令的大概意思就是说可以将这两个模式的Shader一起编译,然后就可以在C#代码里通过
[C#]纯文本查看复制代码
Shader.DisableKeyword("SONAR_SPHERICAL");
或者
[C#]纯文本查看复制代码
Shader.EnableKeyword("SONAR_SPHERICAL");
来进行切换
完整顶点片段Shader代码如下:
[C#]纯文本查看复制代码
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"
structvertexInput
{
float4 vertex : POSITION;
float3 normal:NORMAL;
};
structvertexOutput
{
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
floatw = dot(output.pos.xyz, _SonarWaveVector);
#else
floatw = 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.
floatp = _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);
returnoutput;
}
float4 frag (vertexOutput input) : COLOR
{
returninput.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
structInput
{
float3 worldPos;
};
float3 _SonarBaseColor;
float3 _SonarWaveColor;
float4 _SonarWaveParams;// Amp, Exp, Interval, Speed
float3 _SonarWaveVector;
float3 _SonarAddColor;
voidsurf(Input IN, inout SurfaceOutput o)
{
#ifdef SONAR_DIRECTIONAL
floatw = dot(IN.worldPos, _SonarWaveVector);
#else
floatw = 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.
floatp = _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来渲染摄像机
[C#]纯文本查看复制代码
GetComponent().SetReplacementShader(shader,null);
其他的功能就是出传递参数给Shader来实现最终随时间变换的效果,完整的代码如下:
usingUnityEngine;
[RequireComponent(typeof(Camera))]
publicclassSonarFxControl : MonoBehaviour
{
// 声纳的模式
publicenumSonarMode { Directional, Spherical }
[SerializeField] SonarMode _mode = SonarMode.Directional;
publicSonarMode mode {get{return_mode; }set{ _mode = value; } }
// 声纳波的方向(仅仅在Directional模式下)
[SerializeField] Vector3 _direction = Vector3.forward;
publicVector3 direction {get{return_direction; }set{ _direction = value; } }
// 声纳波区域(仅仅在Spherical模式下)
[SerializeField] Vector3 _origin = Vector3.zero;
publicVector3 origin {get{return_origin; }set{ _origin = value; } }
// 背景颜色(Surfface Shader下有用)
[SerializeField] Color _baseColor =newColor(0.2f, 0.2f, 0.2f, 0);
publicColor baseColor {get{return_baseColor; }set{ _baseColor = value; } }
// 声纳波的颜色
[SerializeField] Color _waveColor =newColor(1.0f, 0.2f, 0.2f, 0);
publicColor waveColor {get{return_waveColor; }set{ _waveColor = value; } }
// 波的高度\振幅
[SerializeField]float_waveAmplitude = 2.0f;
publicfloatwaveAmplitude {get{return_waveAmplitude; }set{ _waveAmplitude = value; } }
// 波的颜色指数
[SerializeField]float_waveExponent = 22.0f;
publicfloatwaveExponent {get{return_waveExponent; }set{ _waveExponent = value; } }
// 波的时间间隔
[SerializeField]float_waveInterval = 20.0f;
publicfloatwaveInterval {get{return_waveInterval; }set{ _waveInterval = value; } }
// 波的速度
[SerializeField]float_waveSpeed = 10.0f;
publicfloatwaveSpeed {get{return_waveSpeed; }set{ _waveSpeed = value; } }
// 额外的颜色
[SerializeField] Color _addColor = Color.black;
publicColor addColor {get{return_addColor; }set{ _addColor = value; } }
[SerializeField] Shader shader;
intbaseColorID;
intwaveColorID;
intwaveParamsID;
intwaveVectorID;
intaddColorID;
voidAwake()
{
baseColorID = Shader.PropertyToID("_SonarBaseColor");
waveColorID = Shader.PropertyToID("_SonarWaveColor");
waveParamsID = Shader.PropertyToID("_SonarWaveParams");
waveVectorID = Shader.PropertyToID("_SonarWaveVector");
addColorID = Shader.PropertyToID("_SonarAddColor");
}
voidOnEnable()
{
GetComponent().SetReplacementShader(shader,null);
Update();
}
voidOnDisable()
{
GetComponent().ResetReplacementShader();
}
voidUpdate()
{
Shader.SetGlobalColor(baseColorID, _baseColor);
Shader.SetGlobalColor(waveColorID, _waveColor);
Shader.SetGlobalColor(addColorID, _addColor);
var param =newVector4(_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看到如图所示的统计图,瞬间有没有觉得这个效果
其实还蛮有实用价值的。