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. 后期锯齿问题处理方案

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

相关推荐
朱昆鹏1 小时前
开源 Claude Code + Codex + 面板 的未来vibecoding平台
前端·后端·github
lyrieek1 小时前
pgadmin的导出图实现,还在搞先美容后拍照再恢复?
前端
永远是我的最爱1 小时前
基于.NET的小小便利店前台收银系统
前端·sqlserver·.net·visual studio
从文处安1 小时前
「九九八十一难」第一难:前端数据mock指南(TS + VUE)
前端
Zhencode2 小时前
Vue3 响应式依赖收集与更新之effect
前端·vue.js
x-cmd2 小时前
[x-cmd] jsoup 1.22.1 版本发布,引入 re2j 引擎,让 HTML 解析更安全高效
前端·安全·html·x-cmd·jsoup
天下代码客2 小时前
使用electronc框架调用dll动态链接库流程和避坑
前端·javascript·vue.js·electron·node.js
weixin199701080162 小时前
【性能提升300%】仿1688首页的Webpack优化全记录
前端·webpack·node.js
冰暮流星3 小时前
javascript之数组
java·前端·javascript
晚霞的不甘3 小时前
Flutter for OpenHarmony天气卡片应用:用枚举与动画打造沉浸式多城市天气浏览体验
前端·flutter·云原生·前端框架