前言
懒散了很长一段时间,准备重新把写文章这件事拾起来,这两年从初步接触webgl到目前已经算是有些了解,感觉自己也成长了很多,借鉴游戏引擎效果做了一些示例,发现目前webgl端没有什么实现方案,于是在这里分析一下这两年完成的一些示例。
效果图
废话不多说,先上效果图
思路
思路是参考iq大神高度雾文章(iquilezles.org/articles/fo...)
大体思路是视线位置,观察点位置,计算视线在雾气中的纵向深度,结合雾气的高度衰减,实现效果。(具体计算参考iq大神文章)
实现步骤
- 首先 开启一个后期通道,因为需要世界位置,所以输入需要depthTexture,需要雾效果和场景进行融合 需要sceneTexture。视线位置cameraPosition,如果要做流动效果需要一张白噪声图noiseTexture。
js
uniforms: {
depthTexture: null,
sceneTexture: null,
noiseTexture: null,
cameraPosition: [0, 0, 0],
projectionviewInverse: new Float32Array(16),
fogColor: [1, 1, 1],
fogDensity: 1,
heightFallOff: 0.25,
fogHeight: 10,
fogStartDis: -10.96,
fogGradientDis: 50,
maxOpacity: 1.0,
sunDir: [0.6, 0.1, 1],
sunColor: [255 / 255, 250 / 255, 205 / 255],
fogInscatteringExp: 12.9,
flowOffset: [0, 0],
flowStrength: 1
},
2.第一步 根据pv矩阵的逆矩阵和深度值,结合屏幕坐标位置,反算该像素点的世界坐标
js
vec4 depthTexel = texture2D(depthTexture, v_Uv);
vec4 projectedPos = vec4(v_Uv * 2.0 - 1.0, depthTexel.r * 2.0 - 1.0, 1.0);
vec4 pos = projectionviewInverse * projectedPos;
vec3 posWorld = pos.xyz / pos.w;
2.获取相机位置,视线方向,并通过iq文章中的雾气系数计算,计算雾气系数,这里参考(zhuanlan.zhihu.com/p/61138643) 在计算雾气浓度的时候,根据自定义了很多参数,如雾气开始距离fogStartDis,雾气高度衰减程度heightFallOff,雾气高度fogHeight,使命召唤手游的雾气实现方案也是这写这篇文章的大佬实现的。我自定义添加了雾气衰减距离fogGradientDis。最终实现的这个雾气的计算。
js
float applyFog(float distance, vec3 rayOri, vec3 rayDir){
vec3 rayOri_pie = rayOri + rayDir * fogStartDis;
float c = fogDensity / heightFallOff;
vec2 data = vec2(-max(0., rayOri_pie.y - fogHeight) * heightFallOff, -max(0., distance - fogStartDis) * rayDir.y * heightFallOff);
vec2 expData = exp(data);
float opticalThickness = c * expData.x * (1.0 - expData.y) / rayDir.y;
float extinction = exp(-opticalThickness);
float fogAmount = 1. - extinction;
float distanceFactor = clamp(distance / fogGradientDis, 0.0, 1.0);
return fogAmount * distanceFactor;
}
js
vec3 cap = cameraPosition;
vec3 viewDir = cap - posWorld;
vec3 rayStep = -normalize(viewDir) * fogStartDis;
vec3 rayOri_step = cap + rayStep;
float rayLength = length(viewDir);
float fog = applyFog(rayLength, cap, -normalize(viewDir));
- 然后后续是模拟大气辐射部分 根据视线方向,传入的太阳光方向,太阳光的颜色,雾气散射程度来进行和雾气的混合。
js
#ifdef SUN_LIGHT
float inscatterFactor = pow(clamp(dot(-normalize(viewDir), -normalize(sunDir)) ,0.0, 1.0), fogInscatteringExp);
finalFogColor = mix(fogColor, sunColor, clamp(inscatterFactor, 0.0, 1.0));
#endif
- 然后是雾气流动部分 这部分是根据噪声图对uv进行扰动,然后雾气浓度乘扰动系数(flowOffset是每次render动态增加的)
js
#ifdef FOG_FLOW
float noise = texture2D(noiseTexture, v_Uv + flowOffset).r;
fog = fog * mix(1., noise, flowStrength);
#endif
5.最后是混合部分,把场景颜色和雾气颜色,根据雾气浓度进行混合。
js
vec4 finalColor = texture2D(sceneTexture, v_Uv);
finalColor.rgb = mix(finalColor.rgb, finalFogColor, fog);
gl_FragColor = finalColor;
完整代码
js
const fogShader = {
name: 'ec_heightfog',
defines: {
'SUN_LIGHT': false,
'FOG_FLOW': false
},
uniforms: {
depthTexture: null,
sceneTexture: null,
noiseTexture: null,
cameraPosition: [0, 0, 0],
projectionviewInverse: new Float32Array(16),
fogColor: [1, 1, 1],
fogDensity: 1,
heightFallOff: 0.25,
fogHeight: 10,
fogStartDis: -10.96,
fogGradientDis: 50,
maxOpacity: 1.0,
sunDir: [0.6, 0.1, 1],
sunColor: [255 / 255, 250 / 255, 205 / 255],
fogInscatteringExp: 12.9,
flowOffset: [0, 0],
flowStrength: 1
},
vertexShader: defaultVertexShader,
fragmentShader: `
varying vec2 v_Uv;
uniform sampler2D depthTexture;
uniform sampler2D sceneTexture;
uniform vec3 cameraPosition;
uniform mat4 projectionviewInverse;
uniform vec3 fogColor;
uniform float fogDensity;
uniform float heightFallOff;
uniform float fogHeight;
uniform float fogStartDis;
uniform float fogGradientDis;
uniform float maxOpacity;
#ifdef SUN_LIGHT
uniform vec3 sunDir;
uniform vec3 sunColor;
uniform float fogInscatteringExp;
#endif
#ifdef FOG_FLOW
uniform sampler2D noiseTexture;
uniform vec2 flowOffset;
uniform float flowStrength;
#endif
float applyFog(float distance, vec3 rayOri, vec3 rayDir){
vec3 rayOri_pie = rayOri + rayDir * fogStartDis;
float c = fogDensity / heightFallOff;
vec2 data = vec2(-max(0., rayOri_pie.y - fogHeight) * heightFallOff, -max(0., distance - fogStartDis) * rayDir.y * heightFallOff);
vec2 expData = exp(data);
float opticalThickness = c * expData.x * (1.0 - expData.y) / rayDir.y;
float extinction = exp(-opticalThickness);
float fogAmount = 1. - extinction;
float distanceFactor = clamp(distance / fogGradientDis, 0.0, 1.0);
return fogAmount * distanceFactor;
}
void main() {
vec4 depthTexel = texture2D(depthTexture, v_Uv);
vec4 projectedPos = vec4(v_Uv * 2.0 - 1.0, depthTexel.r * 2.0 - 1.0, 1.0);
vec4 pos = projectionviewInverse * projectedPos;
vec3 posWorld = pos.xyz / pos.w;
vec3 cap = cameraPosition;
vec3 viewDir = cap - posWorld;
vec3 rayStep = -normalize(viewDir) * fogStartDis;
vec3 rayOri_step = cap + rayStep;
float rayLength = length(viewDir);
float fog = applyFog(rayLength, cap, -normalize(viewDir));
fog = clamp(fog, 0.0, maxOpacity);
vec3 finalFogColor = fogColor;
#ifdef SUN_LIGHT
float inscatterFactor = pow(clamp(dot(-normalize(viewDir), -normalize(sunDir)) ,0.0, 1.0), fogInscatteringExp);
finalFogColor = mix(fogColor, sunColor, clamp(inscatterFactor, 0.0, 1.0));
#endif
vec4 finalColor = texture2D(sceneTexture, v_Uv);
#ifdef FOG_FLOW
float noise = texture2D(noiseTexture, v_Uv + flowOffset).r;
fog = fog * mix(1., noise, flowStrength);
#endif
finalColor.rgb = mix(finalColor.rgb, finalFogColor, fog);
gl_FragColor = finalColor;
}
`
后续准备
后续准备实现一些工作时做过的效果,大多数都是我结合目前市面上web引擎,游戏引擎,优化实现的,或多或少做了一些优化改进。例如
- ssr
- ssao
- ssgi
- gtao
- godray,
- 支持骨骼动画线框模式方案
- 粒子雨雪
- 后期雨雪视角与地面雨滴涟漪效果
- 后期实现体渲染技术方案
- 节点材质系统的实现方案
- 正交相机天空盒显示
- 动态直方图效果
- csm阴影实现
- css3d相机控制组件
- 动态天空盒(这个很麻烦,要分很多部分,应该是写不了)
- 高空云层
- 动态河流方案
- 后期锯齿问题处理方案
后续看看评论区对那个感兴趣 我结合一下看看是不是需要写很多字,来决定下一篇更新那个内容。