其他
塞尔达的3D渲染风格,能在小游戏跑起来?
01
工具
Demo 中的人物是一个塞尔达爱好者的同人作品,作者提供了很多人物模型来给大家免费使用。
由于下载下来的模型是没有动作的,可以在以下网站上搜索一些简单的动作来使用。
02
植被渲染
初始状态
vec2 uv = mainTiling.xy + mainTiling.zw * v_uv;
vec4 col = texture(mainTexture, uv);
添加修改色
float rand = texture(randMap, worldPos.xz * randMapTiling).r;
col.rgb = mix(col.rgb, hue.rgb, rand * hue.a);
模拟 AO
float getMask () {
#if Mask_Type == Mask_Channel_Color
return a_color.r;
#elif Mask_Type == Mask_Channel_Uv
return 1. - a_texCoord.y;
#else
return 0.;
#endif
}
float mask = getMask();
float ao = mix(col.a, col.a * mask, ambientOcclusion);
col.rgb *= ao;
vec4 offset = vec4(0.);
float strength = windStrength;
float sine = sin(windSpeed * (cc_time.x ));
sine = sine * mask * strength;
// 计算 xz 平面偏移
offset.xz = vec2(sine);
// 计算 y 方向弯曲度
float windWeight = length(offset.xz) + 0.0001;
windWeight = pow(windWeight, 1.5);
offset.y = windWeight * mask;
sine = mix(sine * 0.5 + 0.5, sine, windSwinging);
float f = length(positionOS.xz) * windRandVertex;
// 重新计算 sin 值,rand 为之前用 noise 贴图获取的噪声值,rand随机范围0-1
float sine = sin(s.speed * (cc_time.x + rand + f));
vec2 gustUV = (worldPos.xz * windGustFrequency * windSpeed) + (cc_time.x * windSpeed * windGustFrequency) * -windDirection。xy;
float gust = texture(windMap, gustUV).r;
gust *= windGustStrength * mask;
// col 为之前计算的结果
col.rgb += (gust * v_color.a * windGustTint);
float getShadowAttenuation () {
float shadowAttenuation = 0.0;
#if CC_RECEIVE_SHADOW
// cc_shadowInfo 的定义可以在引擎中 cc-shadow.chunk 文件中找到, 其中的数据格式为 :
// x -> width; y -> height; z -> pcf; w -> bais;
// z -> pcf 对应的是场景中设置 pcf 选项的值
float pcf = cc_shadowInfo.z + 0.001;
// CCGetShadowFactorXX 为引擎内部 PCF 阴影计算方法, 后面的数字表示采样 shadowmap 的次数,采样次数越多获得的阴影模糊效果越好,表现得越柔软,消耗的性能也越高
if (pcf > 3.0) {
shadowAttenuation = CCGetShadowFactorX25();
}
else if (3.0 > pcf && pcf > 2.0) {
shadowAttenuation = CCGetShadowFactorX9();
}
else if (2.0 > pcf && pcf > 1.0) {
shadowAttenuation = CCGetShadowFactorX5();
}
else {
shadowAttenuation = CCGetShadowFactorX1();
}
#endif
return shadowAttenuation;
}
float shadowAttenuation = getShadowAttenuation();
**PCF** 阴影设置:
![12](./pics/12.png)
// 获取阴影强度,并暴露参数 shadowIntensity 自由调节阴影强度
shadowAttenuation = 1. - min(shadowAttenuation, shadowIntensity);
col.rgb *= shadowAttenuation;
vec3 ld = normalize(cc_mainLitDir.xyz);
// viewDirectionWS 为树叶到摄像机的方向
vec3 viewDirectionWS = normalize(cc_cameraPos.xyz - worldPos.xyz);
// translucency 暴露为材质参数方便调整效果
float VdotL = max(0., dot(viewDirectionWS, ld)) * translucency;
VdotL = pow(VdotL, 4.) * 8.;
float tMask = VdotL * shadowAttenuation;
vec3 tColor = col.rgb + BlendOverlay(cc_mainLitColor.rgb * cc_mainLitColor.w, color);
col.rgb = mix(col.rgb, tColor, tMask);
当玩家在草地上移动时,草地受到玩家碰撞挤压,应该是会明显向周围弯曲的。
要做到这一点,我们需要将希望产生交互的物体绘制到一张高度贴图上,贴图中的信息包括物体的高度、物体在 XZ 轴上挤压的方向、挤压的力度。
float mask = -v_normal.y * heightStrength;// * v_color.r;
float height = (v_position.y + heightOffset);
// 将值从 (-1, 1) 重新映射到 (0, 1)
vec2 dir = (v_normal.xz * extendStrength) * 0.5 + 0.5;
vec4 heightMapInfo = vec4(dir.x, height, dir.y, mask);
// cc_grass_bend_uv.xy 为高度图摄像机的世界坐标
// cc_grass_bend_uv.z 为高度图摄像机拍摄的范围
// 使用像素的世界坐标值减去高度图摄像机的世界坐标再除以范围就可以得到高度图中的 uv 坐标
vec2 getBendMapUV(in vec3 wPos) {
vec2 uv = (wPos.xz - cc_grass_bend_uv.xy) / cc_grass_bend_uv.z + 0.5;
return uv;
}
// cc_grass_bend_map 就是高度贴图了
vec4 getBendVector(vec3 wPos) {
vec2 uv = getBendMapUV(wPos);
vec4 v = texture(cc_grass_bend_map, uv);
//Remap from 0.1 to -1.1
v.x = v.x * 2.0 - 1.0;
v.z = v.z * 2.0 - 1.0;
return v;
}