因为最近要尝试在untiy3d做写实海底的效果,上图为版本5.5里面实现的效果
为了让画面更加逼真,海底植物必不可少
然而海底的海藻海草之类需要加上动画就比较麻烦了
一般的骨骼动画加起来比较麻烦,而且效果比较单调,消耗也很大
更不用说动力学解算了,通常时间长,而且烘焙成顶点动画往往比较困难,而且无法很好循环
使用shader的uv动画或者序列帧动画又不适合写实场景
用unity3d自带风力的话更像是陆地上的植物,没有很好表现水下效果
所以这里使用了shader顶点动画来模拟海草在海底的波动
下面是效果图
主要思路就是对物体表面顶点进行移动
为了方便说明,下面用shaderforge来制作一个简单的世界空间波动效果
使用shaderforge好处不止是简单,因为它能带来更大的兼容性,比如说投影和雾效之类不用自己单独写了
这些效果上面与其他的兼容性还是挺容易犯错的
下图是简单连线方式,可以让一个mesh平面在世界空间内自动产生波的效果
当然这只是简单的效果
成品需要叠加更多的节点
为了让扭动更加自然,不能只用简单的正弦余弦,而是要组合起来,减少规律感觉
自然扭动效果,首先可以让水草有整体的偏移循环,然后是每一个叶子顶点的偏移,最后还可以加上每个叶子uv的扭曲动画,才会更加自然
另外当水草成批复制的时候也会有问题,因为水草的运动效果太一致了,
所以我还在里面加上了世界空间坐标作为产生扰动的参数,这样每一株水草的动画都会有稍许不同了
当然最终效果也是个手艺活,需要一边调一边看效果,还有shaderforge有些bug,比如有些顶点移动效果在scene窗口才看得到,预览里面并不正确
下面放上shaderforge里面的一个粗糙版本,貌似有很多多余节点,不过效果就是上面gif的效果
Shader "Shader Forge/NewShader" {
Properties {
_color ("color", 2D) = "white" {}
_norml ("norml", 2D) = "bump" {}
_anbient ("anbient", Color) = (0.5,0.5,0.5,1)
_specularColor ("specularColor", Color) = (0.5,0.5,0.5,1)
_specular ("specular", Float ) = 1
[HideInInspector]_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
}
SubShader {
Tags {
"Queue"="AlphaTest"
"RenderType"="TransparentCutout"
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#define UNITY_PASS_FORWARDBASE
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma multi_compile_fog
#pragma exclude_renderers gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2
#pragma target 3.0
uniform float4 _LightColor0;
uniform float4 _TimeEditor;
uniform sampler2D _color; uniform float4 _color_ST;
uniform sampler2D _norml; uniform float4 _norml_ST;
uniform float4 _anbient;
uniform float4 _specularColor;
uniform float _specular;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord0 : TEXCOORD0;
float4 vertexColor : COLOR;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
float4 posWorld : TEXCOORD1;
float3 normalDir : TEXCOORD2;
float3 tangentDir : TEXCOORD3;
float3 bitangentDir : TEXCOORD4;
float4 vertexColor : COLOR;
LIGHTING_COORDS(5,6)
UNITY_FOG_COORDS(7)
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.uv0 = v.texcoord0;
o.vertexColor = v.vertexColor;
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.tangentDir = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz );
o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
float node_6620_ang = 1.57;
float node_6620_spd = 1.0;
float node_6620_cos = cos(node_6620_spd*node_6620_ang);
float node_6620_sin = sin(node_6620_spd*node_6620_ang);
float2 node_6620_piv = float2(0.5,0.5);
float4 node_1583 = _Time + _TimeEditor;
float2 node_6620 = (mul((o.uv0+node_1583.g*float2(0.25,0))-node_6620_piv,float2x2( node_6620_cos, -node_6620_sin, node_6620_sin, node_6620_cos))+node_6620_piv);
float2 node_6051 = mul(unity_ObjectToWorld, v.vertex).rgb.rg;
float2 node_6156_skew = node_6051 + 0.2127+node_6051.x*0.3713*node_6051.y;
float2 node_6156_rnd = 4.789*sin(489.123*(node_6156_skew));
float node_6156 = frac(node_6156_rnd.x*node_6156_rnd.y*(1+node_6156_skew.x));
float4 node_2249 = _Time + _TimeEditor;
v.vertex.xyz += (normalize((float3(2,1,1)+v.normal))*o.vertexColor.r*sin(((o.vertexColor.b*3.141592654*(abs(fmod(node_6620.r,5.0))*(7.5+node_6156)))+(node_6156*node_2249.g)))*0.056);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
float3 lightColor = _LightColor0.rgb;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
UNITY_TRANSFER_FOG(o,o.pos);
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
float4 frag(VertexOutput i) : COLOR {
i.normalDir = normalize(i.normalDir);
float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);
float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
float3 _norml_var = UnpackNormal(tex2D(_norml,TRANSFORM_TEX(i.uv0, _norml)));
float3 normalLocal = _norml_var.rgb;
float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // Perturbed normals
float4 _color_var = tex2D(_color,TRANSFORM_TEX(i.uv0, _color));
clip(_color_var.a - 0.5);
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
float3 lightColor = _LightColor0.rgb;
float3 halfDirection = normalize(viewDirection+lightDirection);
// Lighting:
float attenuation = LIGHT_ATTENUATION(i);
float3 attenColor = attenuation * _LightColor0.xyz;
/ Gloss:
float gloss = 0.5;
float specPow = exp2( gloss * 10.0+1.0);
// Specular:
float NdotL = max(0, dot( normalDirection, lightDirection ));
float3 specularColor = _specularColor.rgb;
float3 directSpecular = (floor(attenuation) * _LightColor0.xyz) * pow(max(0,dot(halfDirection,normalDirection)),specPow)*specularColor;
float3 specular = directSpecular;
/// Diffuse:
NdotL = max(0.0,dot( normalDirection, lightDirection ));
float3 directDiffuse = pow(max( 0.0, NdotL), _specular) * attenColor;
float3 indirectDiffuse = float3(0,0,0);
indirectDiffuse += UNITY_LIGHTMODEL_AMBIENT.rgb; // Ambient Light
indirectDiffuse += _anbient.rgb; // Diffuse Ambient Light
float3 diffuseColor = _color_var.rgb;
float3 diffuse = (directDiffuse + indirectDiffuse) * diffuseColor;
/// Final Color:
float3 finalColor = diffuse + specular;
fixed4 finalRGBA = fixed4(finalColor,1);
UNITY_APPLY_FOG(i.fogCoord, finalRGBA);
return finalRGBA;
}
ENDCG
}
Pass {
Name "FORWARD_DELTA"
Tags {
"LightMode"="ForwardAdd"
}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#define UNITY_PASS_FORWARDADD
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#pragma multi_compile_fwdadd_fullshadows
#pragma multi_compile_fog
#pragma exclude_renderers gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2
#pragma target 3.0
uniform float4 _LightColor0;
uniform float4 _TimeEditor;
uniform sampler2D _color; uniform float4 _color_ST;
uniform sampler2D _norml; uniform float4 _norml_ST;
uniform float4 _specularColor;
uniform float _specular;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord0 : TEXCOORD0;
float4 vertexColor : COLOR;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
float4 posWorld : TEXCOORD1;
float3 normalDir : TEXCOORD2;
float3 tangentDir : TEXCOORD3;
float3 bitangentDir : TEXCOORD4;
float4 vertexColor : COLOR;
LIGHTING_COORDS(5,6)
UNITY_FOG_COORDS(7)
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.uv0 = v.texcoord0;
o.vertexColor = v.vertexColor;
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.tangentDir = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz );
o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
float node_6620_ang = 1.57;
float node_6620_spd = 1.0;
float node_6620_cos = cos(node_6620_spd*node_6620_ang);
float node_6620_sin = sin(node_6620_spd*node_6620_ang);
float2 node_6620_piv = float2(0.5,0.5);
float4 node_2231 = _Time + _TimeEditor;
float2 node_6620 = (mul((o.uv0+node_2231.g*float2(0.25,0))-node_6620_piv,float2x2( node_6620_cos, -node_6620_sin, node_6620_sin, node_6620_cos))+node_6620_piv);
float2 node_6051 = mul(unity_ObjectToWorld, v.vertex).rgb.rg;
float2 node_6156_skew = node_6051 + 0.2127+node_6051.x*0.3713*node_6051.y;
float2 node_6156_rnd = 4.789*sin(489.123*(node_6156_skew));
float node_6156 = frac(node_6156_rnd.x*node_6156_rnd.y*(1+node_6156_skew.x));
float4 node_2249 = _Time + _TimeEditor;
v.vertex.xyz += (normalize((float3(2,1,1)+v.normal))*o.vertexColor.r*sin(((o.vertexColor.b*3.141592654*(abs(fmod(node_6620.r,5.0))*(7.5+node_6156)))+(node_6156*node_2249.g)))*0.056);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
float3 lightColor = _LightColor0.rgb;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
UNITY_TRANSFER_FOG(o,o.pos);
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
float4 frag(VertexOutput i) : COLOR {
i.normalDir = normalize(i.normalDir);
float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);
float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
float3 _norml_var = UnpackNormal(tex2D(_norml,TRANSFORM_TEX(i.uv0, _norml)));
float3 normalLocal = _norml_var.rgb;
float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // Perturbed normals
float4 _color_var = tex2D(_color,TRANSFORM_TEX(i.uv0, _color));
clip(_color_var.a - 0.5);
float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w));
float3 lightColor = _LightColor0.rgb;
float3 halfDirection = normalize(viewDirection+lightDirection);
// Lighting:
float attenuation = LIGHT_ATTENUATION(i);
float3 attenColor = attenuation * _LightColor0.xyz;
/ Gloss:
float gloss = 0.5;
float specPow = exp2( gloss * 10.0+1.0);
// Specular:
float NdotL = max(0, dot( normalDirection, lightDirection ));
float3 specularColor = _specularColor.rgb;
float3 directSpecular = attenColor * pow(max(0,dot(halfDirection,normalDirection)),specPow)*specularColor;
float3 specular = directSpecular;
/// Diffuse:
NdotL = max(0.0,dot( normalDirection, lightDirection ));
float3 directDiffuse = pow(max( 0.0, NdotL), _specular) * attenColor;
float3 diffuseColor = _color_var.rgb;
float3 diffuse = directDiffuse * diffuseColor;
/// Final Color:
float3 finalColor = diffuse + specular;
fixed4 finalRGBA = fixed4(finalColor * 1,0);
UNITY_APPLY_FOG(i.fogCoord, finalRGBA);
return finalRGBA;
}
ENDCG
}
Pass {
Name "ShadowCaster"
Tags {
"LightMode"="ShadowCaster"
}
Offset 1, 1
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#define UNITY_PASS_SHADOWCASTER
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_shadowcaster
#pragma multi_compile_fog
#pragma exclude_renderers gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2
#pragma target 3.0
uniform float4 _TimeEditor;
uniform sampler2D _color; uniform float4 _color_ST;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord0 : TEXCOORD0;
float4 vertexColor : COLOR;
};
struct VertexOutput {
V2F_SHADOW_CASTER;
float2 uv0 : TEXCOORD1;
float4 posWorld : TEXCOORD2;
float3 normalDir : TEXCOORD3;
float4 vertexColor : COLOR;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.uv0 = v.texcoord0;
o.vertexColor = v.vertexColor;
o.normalDir = UnityObjectToWorldNormal(v.normal);
float node_6620_ang = 1.57;
float node_6620_spd = 1.0;
float node_6620_cos = cos(node_6620_spd*node_6620_ang);
float node_6620_sin = sin(node_6620_spd*node_6620_ang);
float2 node_6620_piv = float2(0.5,0.5);
float4 node_9812 = _Time + _TimeEditor;
float2 node_6620 = (mul((o.uv0+node_9812.g*float2(0.25,0))-node_6620_piv,float2x2( node_6620_cos, -node_6620_sin, node_6620_sin, node_6620_cos))+node_6620_piv);
float2 node_6051 = mul(unity_ObjectToWorld, v.vertex).rgb.rg;
float2 node_6156_skew = node_6051 + 0.2127+node_6051.x*0.3713*node_6051.y;
float2 node_6156_rnd = 4.789*sin(489.123*(node_6156_skew));
float node_6156 = frac(node_6156_rnd.x*node_6156_rnd.y*(1+node_6156_skew.x));
float4 node_2249 = _Time + _TimeEditor;
v.vertex.xyz += (normalize((float3(2,1,1)+v.normal))*o.vertexColor.r*sin(((o.vertexColor.b*3.141592654*(abs(fmod(node_6620.r,5.0))*(7.5+node_6156)))+(node_6156*node_2249.g)))*0.056);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
TRANSFER_SHADOW_CASTER(o)
return o;
}
float4 frag(VertexOutput i) : COLOR {
i.normalDir = normalize(i.normalDir);
float3 normalDirection = i.normalDir;
float4 _color_var = tex2D(_color,TRANSFORM_TEX(i.uv0, _color));
clip(_color_var.a - 0.5);
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
FallBack "Diffuse"
CustomEditor "ShaderForgeMaterialInspector"
}
还有要注意就是模型面数少的话调试的时候可能遇到动态批处理问题
shader测试使用很少面数物体比如box进行测试的时候可能会遇到动态批处理产生的错误效果问题,
常见的效果就是scene和game窗口看到的物体效果不一样,而且单个物体还行,多个物体的时候会发现有的物体会闪烁消失等等异常的发生都和动态批处理有关
关于disablebatching引起的shader问题,其实只有很少面数的物体才需要关心,因为顶点超过900的物体根本不会被动态批处理
下面普及下动态批处理,以当前版本为准,在老生常谈的里面加上一些新版本特性和自己的理解
1批处理动态物体需要在每个顶点上进行一定的消耗,支持小于900顶点的网格物体。
-1着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;
-2着色器使用顶点位置,法线,UV0,UV1和切向量,那你只能批处理180顶点以下的物体。
-3这些可能都会随着版本变化,另外300顶点限制不是那么具体,有时候支持面数很少
(这也意味着默认的有着标准材质的球体和胶囊体就没有动态批处理)
2不同轴的缩放物体也支持批处理了,但是不能出现缩放“镜像”物体,比如x缩放是1和-1的,以前不支持不等比例缩放的动态批处理,现在支持了,比如一个是方块1:1:1的,另一个是长方体也可以批处理
3使用不同材质的实例化物体(instance)将会导致批处理失败。但是物体可以在投影设置上随意不影响动态批处理。
4拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)。
5多pass的shader也影响动态批处理。比如说默认标准材质球有时候物体面数稍微高点是不能动态批处理的,但是用nature-speedtree的就可以批处理
当然动态批处理后并不意味性能一定提高,在有些显卡上性能反而是下降的
当然你模型面数多完全可以不考虑这个,这个一般是用在粒子之类有大量相同的物体上的,这里提到主要是因为box测试时遇到些问题
具体规则官方参考
https://docs.unity3d.com/Manual/DrawCallBatching.html