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

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

相关推荐
zqx_734 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己1 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H3 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍3 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai3 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端