3.23 | v0.2
这种麦浪/可交互草有人感兴趣嘛? ,有空整理出来。
3.22 | v0.1
效果图
最近在学习unity的地编,用Shader Graph实现了个简单的卡通风格水体。看见论坛有个小伙伴在问基于深度图的水体,所以试着在creator实现了一下,先来一张效果GIF图。(由于暂时无法对Terrain生成深度图,暂时就用石头代替了)
实现
-
基于PBR Shader的实现,这里直接使用了默认的builtin-standard材质。随便调下参数就可以有不错的高光和反射效果。
-
水波的实现,这里使用双层法线混合,通过改变法线的速度和强度来实现。
Shader代码:
// 法线1
// 上下文中的uv使用水面世界位置的xz值,即uv = v_position.xz
vec2 uv0 = uv * bigNormalParam.x + cc_time.x * bigNormalParam.y;
vec3 nmmp0 = texture(normalMap, uv0).xyz - vec3(0.5);
vec3 normal0 = (nmmp0.x * bigStrength) * tangentNormalize + (nmmp0.y * bigStrength) * bitangentNormalize + nmmp0.z * normalNormalize;
// 法线2
vec2 uv1 = uv * smallNormalParam.x + cc_time.x * smallNormalParam.y;
vec3 nmmp1 = texture(normalMap, uv1).xyz - vec3(0.5);
vec3 normal1 = (nmmp1.x * smallStrength) * tangentNormalize + (nmmp1.y * smallStrength) * bitangentNormalize + nmmp1.z * normalNormalize;
// 法线混合
Unity_NormalBlend_float(normal0, normal1, s.normal); -
水体边缘的柔和处理,这里使用水面的深度值和场景深度值做smoothstep,水面深度值通过一个变量来控制,场景深度值由深度图获得,由于creator暂时不支持深度图,这里直接使用了麒麟子大佬的实现。
Shader代码:
vec3 vertVec = v_position - cameraPos.xyz;
// 求出水面的深度值
float vertZ = dot(vertVec, cameraDirection);
// 重映射
float vertZR = Remap(vertZ, 0.0, far, 0.0, 1.0);
// 水面的深度
float waterAlphaZR = Remap((vertZ + depthParam.x), 0.0, far, 0.0, 1.0);
// 水面边缘过渡
float a = smoothstep(linear01Depth, vertZR, waterAlphaZR); -
浅水颜色/深水颜色/雾颜色的过渡,浅水和深水的颜色过渡还是和水体边缘检测一样的处理,通过另一个变量来控制深度,更远处的雾颜色用雾距离变量与水和摄像机的距离比来控制。
Shader代码:
// 控制水面的颜色深度
float waterColorZR = Remap((vertZ + depthParam.y), 0.0, far, 0.0, 1.0);
float colorSmo = smoothstep(linear01Depth, vertZR, waterColorZR);
// 浅水和深水颜色的过渡
vec4 depthCol = Lerp(colorSmo, shallowColor, deepColor);
// 求与摄像机的距离
float dis = distance(v_position, cameraPos.xyz);
// 0 - 1
float disCla = clamp(dis / depthParam.z, 0.0, 1.0);
// 与雾颜色过渡
vec4 col = Lerp(disCla, depthCol, fogColor); -
浅水区的水面波纹,这里采用水面颜色与Voronoi图相加,通过控制采样Voronoi图的角度偏移/密度/功率来实现。关于Voronoi图大家可以自行搜索,这里贴一张普通噪声图和Voronoi噪声图的对比。
Shader代码:
vec2 uv2 = uv * depthParam.w + cc_time.x * 0.05;
float angleOffset = cc_time.x * 3.0;
float voronoiOut = 0.0;
vec4 rippleColor = vec4(2.0, 2.0, 2.0, 0.0);
// 采样Voronoi图
Unity_Voronoi_float(uv2, angleOffset, 0.7, voronoiOut);
// 与水面颜色混合
col += (pow(voronoiOut * (1.0 - colorSmo), 5.0) * rippleColor); -
Shader中的自定义函数
// 以下是Unity中的实现
// 法线强度混合
void Unity_NormalBlend_float(vec3 A, vec3 B, out vec3 Out) {
Out = normalize(vec3(A.rg + B.rg, A.b * B.b));
}
// 重映射
float Remap(float x, float t1, float t2, float s1, float s2) {
return (x - t1) / (t2 - t1) * (s2 - s1) + s1;
}
// 插值
vec4 Lerp(float x, vec4 a, vec4 b) {
return a + x * (b - a);
}
// Voronoi图的采样
vec2 Unity_Voronoi_RandomVector_float(vec2 UV, float offset)
{
mat2 m = mat2(15.27, 47.63, 99.41, 89.98);
UV = fract(sin(UV * m) * 46839.32);
return vec2(sin(UV.y * +offset) * 0.5 + 0.5, cos(UV.x * offset) * 0.5 + 0.5);
}
// Voronoi图的采样
void Unity_Voronoi_float(vec2 UV, float AngleOffset, float CellDensity, out float Out) {
vec2 g = floor(UV * CellDensity);
vec2 f = fract(UV * CellDensity);
float t = 8.0;
vec3 res = vec3(8.0, 0.0, 0.0);
for(int y = -1; y <= 1; y++) {
for(int x = -1; x <= 1; x++) {
vec2 lattice = vec2(x, y);
vec2 offset = Unity_Voronoi_RandomVector_float(lattice + g, AngleOffset);
float d = distance(lattice + offset, f);
if(d < res.x) {
res = vec3(d, offset.x, offset.y);
Out = res.x;
}
}
}
}
OVER!
此贴仅抛砖引玉,后续会整理一下详细拆分和细节讲解。也会在creator中使用地编/INSTANCE 草/原神角色模型导入/RPG游戏中的人物动作和战斗动作等,期待creator新版的Terrain和Marionette系统,也希望creator可以尽快完善渲染流程。