webgl基于后期处理的高度密度雾实现

前言

懒散了很长一段时间,准备重新把写文章这件事拾起来,这两年从初步接触webgl到目前已经算是有些了解,感觉自己也成长了很多,借鉴游戏引擎效果做了一些示例,发现目前webgl端没有什么实现方案,于是在这里分析一下这两年完成的一些示例。

效果图

废话不多说,先上效果图

思路

思路是参考iq大神高度雾文章(iquilezles.org/articles/fo...)

大体思路是视线位置,观察点位置,计算视线在雾气中的纵向深度,结合雾气的高度衰减,实现效果。(具体计算参考iq大神文章)

实现步骤

  1. 首先 开启一个后期通道,因为需要世界位置,所以输入需要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));
  1. 然后后续是模拟大气辐射部分 根据视线方向,传入的太阳光方向,太阳光的颜色,雾气散射程度来进行和雾气的混合。
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
  1. 然后是雾气流动部分 这部分是根据噪声图对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引擎,游戏引擎,优化实现的,或多或少做了一些优化改进。例如

  1. ssr
  2. ssao
  3. ssgi
  4. gtao
  5. godray,
  6. 支持骨骼动画线框模式方案
  7. 粒子雨雪
  8. 后期雨雪视角与地面雨滴涟漪效果
  9. 后期实现体渲染技术方案
  10. 节点材质系统的实现方案
  11. 正交相机天空盒显示
  12. 动态直方图效果
  13. csm阴影实现
  14. css3d相机控制组件
  15. 动态天空盒(这个很麻烦,要分很多部分,应该是写不了)
  16. 高空云层
  17. 动态河流方案
  18. 后期锯齿问题处理方案

后续看看评论区对那个感兴趣 我结合一下看看是不是需要写很多字,来决定下一篇更新那个内容。

相关推荐
GISer_Jing38 分钟前
Vue前端进阶面试题目(二)
前端·vue.js·面试
乐闻x1 小时前
Pinia 实战教程:构建高效的 Vue 3 状态管理系统
前端·javascript·vue.js
weixin_431449681 小时前
web组态软件
前端·物联网·低代码·编辑器·组态
橘子味小白菜1 小时前
el-table的树形结构后端返回的id没有唯一键怎么办
前端·vue.js
前端Hardy2 小时前
HTML&CSS:比赛记分卡
前端·javascript·css·3d·html
疯狂的沙粒2 小时前
Vue项目开发 element-UI 前端实现 1到10排列选择的按钮
前端·vue.js·ui
刺客-Andy2 小时前
React第六节 组件属性prop的propTypes类型使用介绍
前端·javascript·react.js·typescript
Mr.Liu63 小时前
小程序24-滚动效果:scroll-view组件详解
前端·微信小程序·小程序
三金121383 小时前
局部使用Vue
前端·javascript·vue.js
LinXunFeng3 小时前
Flutter - 子部件任意位置观察滚动数据
前端·flutter·开源