首页 > 其他分享 >unity中实现地球与大气层的特效渲染(非物理向)

unity中实现地球与大气层的特效渲染(非物理向)

时间:2023-04-15 13:36:03浏览次数:48  
标签:贴图 特效 大气 渲染 xyz unity 云层 float3 glow

       在很多太空科幻类的电影、游戏中、我们常常看到在太空中的星球的场景,在这些场景中我们可以看到真实的行星地表光影效果和云层、以及非常炫酷的大气层效果。在unity中我们也可以创建类似的效果。本文我将介绍如何在unity shader中编写地球特效渲染。

 

上图是最终的效果。实现这样的效果可以使用基于物理的大气渲染或体素渲染(详见【Unity/大气渲染】单次散射的原理和简单实现 - Relolihentai - 博客园 (cnblogs.com))。在本文我主要介绍通过特效的形式实现地球渲染,并非基于物理的大气渲染。以下是我的思路。

主要的思路是:对效果进行分层。我们使用三个同心球来渲染地球,从里到外分别是地表、云层、大气层。以下将分别描述。

1、 地表

地表的渲染其实没有太多特别之处。主体部分直接用标准冯氏光照模型,如果能够获取到高清的地表金属度和粗糙度贴图的话也也可以用PBR来达到更真实的效果(不过地球哪来的PBR贴图啊),在一些影视级别的制作中,为了追求真实性,甚至可以使用高度贴图而非法线贴图来真实模拟出地表的凹凸效果,并且将海水与大陆分离来获取更加真实的水体渲染,不过这都不是本文的重点。

我们直接将三张贴图:表面颜色贴图、表面法线贴图和陆地-海洋贴图传递给shader,其中陆地-海洋图主要用于区分不同地区的高光强度的区别。

现在的效果如下:

 

接下来,我们可以加入一个地球的标志性的夜晚灯光的特效。在冯氏光照漫反射项的计算的过程中,我们计算了光源方向与法线方向的点积。夜晚地球暗面的灯光的强度可以视为反向平行光的光照强度。因此直接用1减去点积,即可以得到初步的一个值。将这叠加上去,效果如下:

 

然后我们开始制作云层。

2、 云层

云层我们使用球体+透明度剔除的方案来制作。给一张云层贴图即可,将灰度直接映射到剔除检测值上,就可以完成一个最基础的云层,同时也使用最基础的冯氏光照。

补充说明:为了增强真实性,可以在地球表面上通过贴图的方式显示云层阴影(因为云层和地表是同心球的方式,所以无法正常投影)。在地表shader中新建一个外部变量,将云层的旋转量传入shader,通过云层的旋转量来偏移uv坐标,采样云层贴图,通过云层密度来控制阴影浓度。以上思路所得的结果如下图

 

3、 大气层

大气层是三者之中最复杂的。我们虽然不使用完全基于物理的大气渲染方法,但是我们也可以通过简化的运算来模仿大气投射的效果。

首先假设地面是一个平面,当摄像机在太空中俯瞰地表一点时,光所在大气层中穿过的距离等于大气层厚度除以观察角的正弦值,即

\(l=\frac{h}{\sinα}\)

示意图如下:

 

由此图容易知道,当我们假设大气不存在散射,并且消光效应对于各个波长的光线是均匀分布的话,那么可以知道:假设垂直向下看时的“浓度”为f,那么对于观察角度为α时,“大气浓度”为

\(\frac{f}{\sinα}\)

那么我们就将这个式子拿到球形的地球-大气层模型中套用即可。我们可以通过地球球心坐标和大气与地球半径来计算某一片元处的光在大气中穿过的距离。示意图如下:

 

最终计算得到大气的浓度是f*r/l*sinalpha. 其中f是单位距离上大气的浓度值,r是地球半径,l是地球到球心的距离,α是大气表面该点到摄像机的向量和地球球心到摄像机的向量的夹角。

注意,我们不仅需要考虑穿过大气看到地表的情况,也应该考虑穿过大气看到外太空的情况,所以此处应该分情况讨论。

这一部分的代码如下所示:

                    // 大气
                    // 
                    // 当透过大气能看到地面时
                    // α是球心到摄像机向量与片元到摄像机向量的夹角
                    // l是摄像机到球心的距离
                    // r是地球半径
                    // F是大气雾系数(垂直看向地面时雾的强度/单位积分值)
                    // 计算公式是 (F*r)/(l*sinα)
                    //
                    // 当透过大气无法看到地面时
                    // 类似

                    float3 center = mul(unity_ObjectToWorld , float4(0,0,0,1)).xyz;// 球心
                    float3 centerDir = normalize(center.xyz - _WorldSpaceCameraPos.xyz);// 摄像机指向球心的方向
                    float3 fragDir = -normalize(UnityWorldSpaceViewDir(i.worldPos)); // 摄像机指向片元的方向
                    float cosalpha = abs(dot(centerDir, fragDir));
                    float sinalpha = abs(sin(acos(cosalpha)));
                    
                    float3 cV = (center.xyz - _WorldSpaceCameraPos.xyz);
                    float length = pow((cV.x * cV.x + cV.y * cV.y + cV.z * cV.z), 0.5);
                    float tangentAngle = asin(EarthRadius / length);
                    

                    // 最终的大气雾强度
                    float fogStrength;
                    if (sinalpha <= sin(tangentAngle) + 0.0001) { // 透过大气能看到地面
                        float sintheta = abs(sin(acos(length * sinalpha / (EarthRadius + 0.0005))));
                        fogStrength = InnerFog_Strength * AtoFog / sintheta;
                    }
                    else {                              // 透过大气看到宇宙
                        float3 AtoRadiusVec = center.xyz - i.worldPos.xyz;
                        float AtoRadius = pow((AtoRadiusVec.x * AtoRadiusVec.x + AtoRadiusVec.y * AtoRadiusVec.y + AtoRadiusVec.z * AtoRadiusVec.z), 0.5) + 0.0005;
                        float lightLength = 2 * pow((AtoRadius * AtoRadius - (length * sinalpha) * (length * sinalpha)), 0.5);
                        fogStrength = AtoFog * lightLength / (AtoRadius - EarthRadius);
                        fogStrength *= 1 - pow((((length * sinalpha) - EarthRadius) / (AtoRadius - EarthRadius)), 0.45);
                    }

 

但是由于我们没有使用基于物理的大气散射计算,所以电影中常见的从很低的角度观察大气层时的边缘辉光是不存在的,所以我们通过手工的方式添加这部分边缘辉光。边缘辉光既然存在于边缘,那么应该是越到边缘越强,在中心则极弱。于是我们可以采用类似于菲涅尔的做法来计算边缘辉光的强度,这一部分的具体代码如下:

                    fixed Alpha = dot(worldLightDir, worldNormal) * 0.5 + 0.5;
                    Alpha = pow(Alpha, 2.75);
                    
                    // 边缘辉光
                    float TheCos = dot(worldLightDir, fragDir);
                    float glowStrength_1 = 2 * pow(E, 50 * TheCos - 50);
                    float glowStrength_2 = 5 * pow(E, 200 * TheCos - 200);
                    float3 glow_1;
                    float3 glow_2;
                    if (sinalpha >= sin(tangentAngle) - 0.01) {    // 透过大气看到宇宙
                        glow_1 = glowStrength_1 * SunGlowColor.xyz;
                        glow_2 = glowStrength_2 * SunGlowColor.xyz;
                    }
                    else { 
                        glow_1 = float3(0, 0, 0);
                        glow_2 = float3(0, 0, 0);
                    }
                    fixed4 ans_atomosphere_base = fixed4(AtoCol.xyz, fogStrength * Alpha);
                    fixed4 ans_atomosphere_glow = fixed4(glow_1.xyz + glow_2.xyz, pow(fogStrength, 2) * (glow_1.x + glow_1.y + glow_1.z + glow_2.x + glow_2.y + glow_2.z) / 6);

 

这样的大气层已经非常好看了,但是当我们把摄像机运动到晨昏线附近时,晨昏线附近的大气层太蓝了,缺乏更多的太阳光的暖色部分的层次感,因此我们考虑再增加一层太阳光暖色光波长部分的散射光,尤其是在摄像机从背向对准晨昏线是,需要更多的层次来营造更好看的轮廓。我们通过计算光源(太阳)方向和大气表面片元到摄像机的向量的夹角大小来控制这部分暖色光的强度,这部分的实现如下:

                    // 背向辉光
                    float DawnGlowStrength_1 = 1 * pow(E, 8 * TheCos - 8);
                    fixed Strength = dot(worldNormal, worldLightDir);
                    fixed DawnGlowStrength = max((- 27 / 6 * Strength * pow(E, -4 * pow(Strength, 2))), 0);
                    float3 DawnGlow;
                    if (sinalpha >= sin(tangentAngle) - 0.01) {    // 透过大气看到宇宙
                        DawnGlow = DawnGlowStrength_1 * DawnGlowColor.xyz * DawnGlowStrength;
                    }
                    else {
                        DawnGlow = float3(0, 0, 0);
                    }

 

以上的每一层效果单独的效果和混合的效果如下图所示:

 

最终我们添加一些小的参数的调整,就得到了最终的结果,可以看到还是比较漂亮的,不过不够真实,如果需要真实的话还是要用大气散射模拟的方式来制作。

以上就是本文的全部内容了,因为距离我实现这个效果已经有一段时间了,有些细节我也无法说得很清楚了,还请见谅。

标签:贴图,特效,大气,渲染,xyz,unity,云层,float3,glow
From: https://www.cnblogs.com/yaocenji/p/17320834.html

相关文章

  • Android页面渲染效率优化实践
     1.车系页布局渲染现状 车系页是重要的车系信息页面,更新迭代多年,页面布局不断变化,xml布局文件越写越复杂。获取车系页布局文件耗时:        startTime = System.currentTimeMillis();        setContentView(R.layout.car_series_revision......
  • CommunityToolkit.Mvvm系列文章导航
    包 CommunityToolkit.Mvvm (又名MVVM工具包,以前名为 Microsoft.Toolkit.Mvvm)是一个现代、快速且模块化的MVVM库。它是.NET社区工具包的一部分,围绕以下原则构建:平台和运行时独立 - .NETStandard2.0、 .NETStandard2.1 和 .NET6......
  • Android页面渲染效率优化实践
     1.车系页布局渲染现状 车系页是重要的车系信息页面,更新迭代多年,页面布局不断变化,xml布局文件越写越复杂。获取车系页布局文件耗时:        startTime = System.currentTimeMillis();        setContentView(R.layout.car_series_revision......
  • Android页面渲染效率优化实践
     1.车系页布局渲染现状 车系页是重要的车系信息页面,更新迭代多年,页面布局不断变化,xml布局文件越写越复杂。获取车系页布局文件耗时:        startTime = System.currentTimeMillis();        setContentView(R.layout.car_series_revision......
  • 什么是 三维渲染内核?
    一、引言随着计算机图形学的发展,三维图形已经成为电子游戏、动画电影和可视化、数字孪生等领域的关键技术。为了将三维模型转换成二维图像,我们需要依赖一个称为三维渲染内核的工具。本文将详细介绍三维渲染内核的原理、实现方法和应用,以帮助读者更好地了解这一先进的技术。二、......
  • 什么是 三维渲染内核 的 流渲染模式?有那些功能和优势?
    什么是三维渲染内核的流渲染模式?Streaming流渲染模式是将三维渲染任务放在云服务器上进行,然后将渲染结果以视频流的形式传输到客户端。在这种模式下,客户端负责接收和显示视频流,三维场景的渲染和处理任务不再依赖于客户端设备,而是由云端服务器承担。客户端设备只需要接收云端服务......
  • [计科]渲染性GPU和计算型GPU的区别在哪里?
    使用区别渲染型GPU和计算型GPU主要的区别在于它们被设计用于处理不同类型的工作负载。渲染型GPU主要用于图形渲染和专业3D建模等领域,如游戏开发、影视特效、建筑设计等。渲染型GPU的设计重点在于渲染大量的图形,需要处理的操作主要是三维模型的表面计算、图形纹理映射、几何运算......
  • vue列表渲染之for循环
    vue列表渲染之for循环前端开发中,如果涉及列表渲染,都会提示或要求每个列表项使用唯一的key,那很多开发者就会直接使用数组的index作为key的值,而并不知道key的原理。那么以下会讲解key的作用以及为什么最好不要使用index作为key的属性值。1、作用在虚拟DOM中,key是虚......
  • 【Unity/大气渲染】单次散射的原理和简单实现
    这篇随笔将会简单实现一个基于物理的相对真实的大气渲染效果如下图,太空中的星球、相对真实的天空盒如果没有大气,太阳光没有直接照射到的地方将会是一片黑暗而我们能从太空中看到星球表面泛起的蓝光,日出时的美丽景色,都得于太阳光在大气中的散射地球的大气中充斥着诸如空气分子,......
  • Linux_ubuntu_How to resize Launcher in Unity 2D
    via: http://www.dedoimedo.com/computers/ubuntu-unity-2d-resize-launcher.html ChangeUnity2DconfigurationfilesTheconfigurationfilesarelocatedunder /usr/share/unity-2d/shell.Youwillneedsudotoeditthefilescontainedtherein.Youshouldalsocr......