一、前言
终于周末了,心心挂念的依然还是那段代码。没办法,苦逼的命。还是废话少说,先上效果图如图所示,前面两个的效果图是两种模式下的
不同效果,最后一章图片是在其中一种模式下,场景进行了精心布置后的效果图。
看效果图会不自觉的认为实现这个效果是不是要在场景中的物体中进行绘制,如果真是要这样做的话那是在是太耗性能了(毕竟都工作了,本人毕业
了就不想在搞那些不实用的
)。其实,这个是通过控制摄像机的最后渲染来实现的效果的,后面我会给出这个案例的工程文件下载地址。读者
在运行模式的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看到如图所示的统计图,瞬间有没有觉得这个效果
其实还蛮有实用价值的。
每次写到这里其实内心并没有放松下来,反而紧张,继而三省乎,代码有bug乎,语句通顺乎,逻辑完整乎。无关梦想和利益,纯粹喜好,好好
学习,天天向上。附上工程文件:http://www.manew.com/thread-98405-1-1.html。
本文暂时没有评论,来添加一个吧(●'◡'●)