大家好,欢迎来到IT知识分享网。
一、原理
体积云与普通材质云最大的区别就是体积云有厚度(体积感),能够进入云中
通过云图噪声生成云的基本形状,然后通过光线步进采样云的密度
1、云图噪声:云图噪声主要用来生成云的形状,噪声的生成可以通过glsl实时生成,也可以通过工具将其生成为2D/3D纹理数据,方便加载使用。比如 Shadertoy柏林+沃利噪声。
2、光线步进:因为云的形状是不规则的,所以无法使用简单的几何算法判断交点,而光线步进则可以很好地解决云的求交问题。因为光线步进的原理是模拟光的前进,所以光从哪儿前进,每次前进多少,最多前进多远这些参数的设置对程序的性能有很大影响。
Cesium实现体积云的方式:体积云不是实体,所以是没有顶点信息的,因此我们只能通过片元着色器来实现,在Cesium中实现体积云有两种方式:
1、基于Primitive:基于Primitive的方式可见“体渲染”相关章节的体渲染实现,此种方式是通过将体内的计算结果显示到Geometry表面上来,所以虽然看起来像体,其实还是Geometry表面渲染。
2、基于后处理:基于后处理实现体渲染,更接近真实的体渲染效果,但是此方式需要在片元着色器中还原世界坐标,相对麻烦一点,不过在后处理中还原世界坐标的相关知识,已经在进阶篇中介绍过,假设您还没有Cesium后处理相关知识,可以先参考进阶篇。
本章优先使用后处理进行体积云的实现,学完后您也可以使用Primitive方法进行实现。要实现体积云,我们可以先参考游戏引擎的相关代码,作者实现体积云也是参考的其他引擎的代码,所以本章的重点在于讲解在Cesium的整体实现思路,至于涉及到的着色器里面的计算原理,作者也是shader菜鸡,也只能看懂大概的执行过程,至于里面的一些数学计算方法也是懵逼得很,所以涉及到的着色器计算不会一一讲解。
二、后处理实现
要在Cesium后处理实现体积云(局部),因为前面我们说了,局部体积云一般是在一个Box内进行渲染,所以我们要先知道如何在后处理中绘制一个Box。首先我们回想一下使用Geometry绘制一个Box,一般需要知道Box的坐标原点,然后是Box的大小信息(长、宽、高),我们先假定Box的坐标为-75.766, 40.08, 90.416,长宽高都为20。
let entity = new Cesium.Entity({
position:position, box:{
dimensions: new Cesium.Cartesian3(20.0, 20.0, 20.0), material:Cesium.Color.BLUE, } }) viewer.entities.add(entity);
在后处理中要绘制一个同样的Box,我的思路是这样的:首先以Box中心点建立一个局部坐标系,每个片元还原到世界坐标,然后转到这个局部坐标系,通过判断坐标值的大小就可以判断这个片元是否在该Box内,如果在内,就设置颜色为蓝色。我们按照思路编写代码,因为要转坐标系,首先我们通过Box的原点坐标建立一个局部坐标系:
//矩阵 let transform = Cesium.Transforms.eastNorthUpToFixedFrame(position); //逆矩阵 let inverse = Cesium.Matrix4.inverse(transform, new Cesium.Matrix4());
然后将次坐标系信息传入后处理着色器,着色器中每个片元坐标先还原为世界坐标,然后转到该坐标系下,最后进行坐标数值比较。
let shader=` uniform sampler2D colorTexture; uniform sampler2D depthTexture; in vec2 v_textureCoordinates; uniform mat4 inverse; void main(){ out_FragColor = texture(colorTexture, v_textureCoordinates); vec4 rawDepthColor = texture(czm_globeDepthTexture, v_textureCoordinates); float depth = czm_unpackDepth(rawDepthColor); if (depth == 0.0) { depth = 1.0; } vec4 eyeCoordinate4 = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth); vec3 eyeCoordinate3 = eyeCoordinate4.xyz/eyeCoordinate4.w; vec4 worldCoordinate4 = czm_inverseView * vec4(eyeCoordinate3,1.) ; vec3 worldCoordinate = worldCoordinate4.xyz / worldCoordinate4.w; vec4 local= inverse * vec4(worldCoordinate,1.); if(local.x>-20.&&local.x<20.&&local.y>-20.&&local.y<20.&&local.z>-20.&&local.z<20.){ out_FragColor=vec4(0.,0.,1.,1); } } `; let stage = new Cesium.PostProcessStage({
fragmentShader: shader, uniforms: {
inverse: inverse } });
求取方式为:相机到片元(世界坐标)的射线与Box如果有交点,那么该片元就被遮蔽,所以问题转为相机到片元的射线和Box求交,射线和Box求交有很多算法,这里采用AABB的方式,shader代码如下:
//边界框最小值 边界框最大值 float2 rayBoxDst(float3 boundsMin, float3 boundsMax, //世界相机位置 光线方向倒数 float3 rayOrigin, float3 invRaydir) {
float3 t0 = (boundsMin - rayOrigin) * invRaydir; float3 t1 = (boundsMax - rayOrigin) * invRaydir; float3 tmin = min(t0, t1); float3 tmax = max(t0, t1); float dstA = max(max(tmin.x, tmin.y), tmin.z); //进入点 float dstB = min(tmax.x, min(tmax.y, tmax.z)); //出去点 float dstToBox = max(0, dstA); float dstInsideBox = max(0, dstB - dstToBox); return float2(dstToBox, dstInsideBox); }
如果distA&&distA<distB则有交点,否则没有交点,加入代码测试
let shader=` uniform sampler2D colorTexture; uniform sampler2D depthTexture; in vec2 v_textureCoordinates; uniform mat4 inverse; vec4 rayBoxDst(vec3 boundsMin, vec3 boundsMax, vec3 rayOrigin, vec3 invRaydir) { vec3 t0 = (boundsMin - rayOrigin) * invRaydir; vec3 t1 = (boundsMax - rayOrigin) * invRaydir; vec3 tmin = min(t0, t1); vec3 tmax = max(t0, t1); float dstA = max(max(tmin.x, tmin.y), tmin.z); //进入点 float dstB = min(tmax.x, min(tmax.y, tmax.z)); //出去点 float dstToBox = max(0., dstA); float dstInsideBox = max(0., dstB - dstToBox); return vec4(dstToBox, dstInsideBox,dstA,dstB); } void main(){ out_FragColor = texture(colorTexture, v_textureCoordinates); vec4 rawDepthColor = texture(czm_globeDepthTexture, v_textureCoordinates); float depth = czm_unpackDepth(rawDepthColor); if (depth == 0.0) { depth = 1.0; } vec4 eyeCoordinate4 = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth); vec3 eyeCoordinate3 = eyeCoordinate4.xyz/eyeCoordinate4.w; vec4 worldCoordinate4 = czm_inverseView * vec4(eyeCoordinate3,1.) ; vec3 worldCoordinate = worldCoordinate4.xyz / worldCoordinate4.w; vec4 worldPos= inverse * vec4(worldCoordinate,1.); vec4 cameraPos= inverse * vec4(czm_viewerPositionWC,1.); vec3 vDirection=worldPos.xyz-cameraPos.xyz;//方向 vec3 rayDir = normalize( vDirection ); vec3 dim= vec3(20.,20.,20.);//盒子长宽高 vec3 box_min = vec3(0.) - dim / 2.; vec3 box_max = vec3(0.) + dim / 2.; vec4 bounds =rayBoxDst(box_min,box_max,cameraPos.xyz,1.0 / rayDir); bounds.x = max( bounds.x, 0.0 ); if ( bounds.z > bounds.w ) return; //盒子外 out_FragColor=vec4(0.,0.,1.,1.); } `;
三、简单的体积云
实现简单的体积云,我们参考这篇博客 体积云渲染实战:ray marching,体积云与体积云光照,这是一篇基于opengl的,为什么选择此示例参考呢?因为该示例一是相对简单,并且流程比较完善,二是涉及到的引擎代码比较少,不像其他示例有很多c#或者c++代码
#define bottom 13 // 云层底部 #define top 20 // 云层顶部 #define width 5 // 云层 xz 坐标范围 [-width, width] // 获取体积云颜色 vec4 getCloud(vec3 worldPos, vec3 cameraPos) {
vec3 direction = normalize(worldPos - cameraPos); // 视线射线方向 vec3 step = direction * 0.25; // 步长 vec4 colorSum = vec4(0); // 积累的颜色 vec3 point = cameraPos; // 从相机出发开始测试 // ray marching for(int i=0; i<100; i++) {
point += step; if(bottom>point.y || point.y>top || -width>point.x || point.x>width || -width>point.z || point.z>width) {
continue; } float density = 0.1; vec4 color = vec4(0.9, 0.8, 0.7, 1.0) * density; // 当前点的颜色 colorSum = colorSum + color * (1.0 - colorSum.a)
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/119459.html


