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

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

相关推荐
Smile_Gently2 小时前
前端:最简单封装nmp插件(组件)过程。
前端·javascript·vue.js·elementui·vue
luckycoke8 小时前
小程序立体轮播
前端·css·小程序
一 乐8 小时前
高校体育场管理系统系统|体育场管理系统小程序设计与实现(源码+数据库+文档)
前端·javascript·数据库·spring boot·高校体育馆系统
懒羊羊我小弟8 小时前
常用Webpack Loader汇总介绍
前端·webpack·node.js
祈澈菇凉9 小时前
ES6模块的异步加载是如何实现的?
前端·javascript·es6
我爱学习_zwj9 小时前
4.从零开始学会Vue--{{组件通信}}
前端·javascript·vue.js·笔记·前端框架
顾比魁9 小时前
XSS盲打:当攻击者“盲狙”管理员
前端·网络安全·xss
黑客老李9 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
java·运维·服务器·前端·xss
晚风予星9 小时前
简记|LogicFlow自定义BPMN元素节点
前端
Json____10 小时前
使用html css js 开发一个 教育机构前端静态网站模板
前端·css·html·js·前端学习·企业站·教育机构网站