c0d3n4m 发表于 2022-4-20 20:53

超好玩的ShaderToy-恒星近照[Unity复刻+优化+解析]

ShaderToy-简单恒星近身照效果

直接来看复刻在Unity中的效果图



复刻到unity效果图

    因为ShaderToy网站的代码都是GLSL的,在Unity中复刻需要手写转换到HLSL,本章节最后我会将全部代码分享出来。
    复刻完成后,大致瞅了眼代码发现有很多优化的点,话不多说直接开搞。(新手TA只能优化一点点)



优化前



优化后

   虽然性能的优化还有提升空间,但是符合HLSL书写格式那当然轻轻松松搞定,下面将直接解析整体效果的实现。
Unity URP管线下-恒星近照代码详解

   我们先从效果上做分析,构成恒星近照效果主要有四部分 1-背景颜色 2-恒星球体本体 3-光晕效果4-日珥效果。
下面我们逐模块分析如何实现效果
背景颜色

在宇宙空间中,恒星属于自发光星体,所以由自身向外发散,所以在效果上我们只要让亮度从中心向两边递减就可以。



背景颜色效果



背景颜色代码部分

代码部分注意事项: 为了方便效果展示,uv使用的模型本地坐标(positionOS.xy)其中第一行的系数可以调整发光大小。这部分可以根据需求灵活自定义。
恒星球体本体

也就是恒星正中间熔岩部分的效果,一开始想用燥波算法替换采样图片,但是燥波算法还是由于高重复度+效果不好,所以直接使用图片采样。



图片采样实现熔岩效果

这部分只需要采样图片并在顶点着色器中加上时间就可以实现熔岩流动效果,不过最关键的是我们需要一个圆形区域来显示恒星。当然也非常简单,只需要利用UV坐标画圆就行。



画圆代码



绘制圆形区域

   这里乘 0.4 的是为了最终效果不那么亮,当然这个值完全可以自定义。[这里为了性能考虑只采样了一个r通道的颜色值,因为后续效果模块整合到一起后发现只采样一个通道效果完全没问题]



恒星本体

光晕效果

光晕的产生主要因为人眼/相机 成像时像差现象导致 《物理光学与应用光学》这本书有详细解读产生的原因,但在计算机或者说游戏世界中,这种效果需要用代码去模拟这种现象,也就是在自发光物体的周围有一圈光圈。
本次案例中恒星是一个标准圆形,所以难度降低,只需要做出一个标准圆环的光晕效果。OK~思路清晰我们直接实践:第一步我们先实现一个圆形从中间向两边发散。这里有一个取巧的办法[先看模型本地坐标x方向取平方的效果]



(modelUV.x)^2

    这时我们可以发现从左往右有白→黑→白的渐变过渡效果
    【这里是因为使用的Unity自带的面片模型,Unity自带模型的中点在正中间,所以中点左边的坐标是负值,这时如果我们取平方,那么负值会成正,这样下来左边区域就会有白色部分出现】-同理y方向的原理也是一样,这时我们把两个坐标相加可以得到如下效果 [这样计算:(modelUV.x)^2 +(modelUV.y)^2= dot (modelUV.x,modelUV.y)]



自中间向四周发散基底效果

这时我们上色需要利用白色区域,所以直接取反获得中间白色圆



取反后的效果[自带渐变效果]

这样计算是什么高效的,能够根据不同模型自动计算,不过只限定中点在几何中心的模型,OK~目前我们得到了想要的圆形,那么接下来就是在圆形边缘实现一圈光晕效果,也就是图中粉圈附近实现高亮。



光晕计算



光晕计算

这样效果就大致出来了,至于取平方根是因为想让光晕效果更明显一些,但是问题来了,这样子会导致光晕强度太高,圆形中间强度不够,所以我们可以直接乘上dot (modelUV.x,modelUV.y)的倒数,这样能够使得我们中心黑色区域也有一部分亮度



光晕计算最终效果

日珥效果

OK~前期准备工作我们已经完成一大半了,接下来就是最复杂的部分,也就是日珥效果的计算。
这里我们直接参照shaderToy上作者的计算方式。



日珥范围计算



限定日珥范围

这段代码的意思就是圈定日珥效果范围的计算,至于为什么有fVal1和fVal2,这部分我们后面再说



发散方向计算

当然日珥的形成是恒星的一种天体物理现象,所以我们要实现这样的效果就需要较高的随机生成。
shader中实现随机效果基本两种大方案,第一种就是直接去采样一张随机贴图。这里我们使用第二种-燥波计算。燥波计算的代码使用原作者的,整体上就是柏林噪声的移动形态,通过一个时间的加入达到动态燥波实现,这里篇幅有限不展开说了。(燥波算法将在最下方展示)
上面的散发方向计算可以让我们燥波计算生成的随机效果从中心向四周发散。接下来我们看看燥波计算的核心区域



燥波算法应用

整体上算法分为两个区域 1-水平上燥波产生 2-角度上燥波产生 ,至于为什么都计算两次,这是因为如果只计算一次那么产生的随机效果在整体上会有规律变化切会有卡顿发生,这是因为燥波算法伪随机的局限性。所以计算两次两个结果取偏移在相加,会得到非常不错的效果。



距离+角度随机燥波计算效果

当让现在生成的燥波效果还是有些粗糙,日珥的效果我们需要更加细腻的效果。所以我们再执行一次燥波算法



再次执行燥波算法

当然第二次执行燥波算法是在第一次已经生成的燥波效果上再次计算一边,这一次我们直接加入之前写好的日珥范围,并且为了让噪声生成的更加细腻一些,加入一个常量来拉细生成的燥波效果



细腻后的噪声效果

OK~得到大致的日珥效果我们需要再次限定他的生效范围



限定生效范围代码

这样我们在原有fVal1 和 fVal2 的基础上限定这个范围 第三行的算法只是用来让效果更随机一些,当然这些额外效果完全可以自定义添加



限定范围后的效果

但是呢我们的范围和强度还是太大了,而且日珥是在太阳便面产生,也就是说越靠近边缘,日珥效果更加明显,所以我们要处理一下中间部分。



再次限定中心区域



代码部分

其中dist是前面定义背景颜色的范围,这里直接使用并且利用系数限定范围值,然后利用一个step函数来剔除掉中间部分。OK~这样我们加上颜色就能得到明显的高效日珥效果。



上色后的日珥效果

最终效果整合




最终效果整合

终于我们实现了一个高性能的恒星效果图,并且优化大部分代码,下面放出代码和最终效果图.效果非常不错(个人感觉)



最终效果图,动态图见最上

Shader "xxxy/URP/Sun"
{
    Properties
    {
      _SunTex("SunTexture",2D) = "white" {}

      _SunColor1("Base Color",color) = (0.8, 0.65, 0.3)
      _SunColor2("Base Color",color) = (0.8, 0.35, 0.1)
    }
    SubShader
    {
      Tags { "Queue"="Geometry" "RenderType" = "Opaque" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" }
      LOD 100
      Cull Off
      Pass
      {
            Name "Unlit"
            HLSLPROGRAM

            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"

            CBUFFER_START(UnityPerMaterial)
                half4_SunColor1, _SunColor2;
                float4 _SunTex_ST;
            CBUFFER_END
            TEXTURE2D (_SunTex);SAMPLER(sampler_SunTex);

            struct Attributes
            {
                float4 positionOS       : POSITION;
                float2 uv               : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionCS       : SV_POSITION;
                float2 uv               : TEXCOORD0;
                float4 positionOS         : TEXCOORD1;
            };

            float snoise(float3 uv, float time)    // by trisomie21 燥波算法-屏闪效果
            {
                const float3 s = float3(1e0, 1e2, 1e4);
                uv *= time;         
                float3 uv0 = floor(fmod(uv, time))*s;
                float3 uv1 = floor(fmod(uv+float3(1,1,1), time))*s;
               
                float3 f = frac(uv); f = f*f*(3.0-2.0*f);
               
                float4 v = float4(uv0.x+uv0.y+uv0.z, uv1.x+uv0.y+uv0.z,
                uv0.x+uv1.y+uv0.z, uv1.x+uv1.y+uv0.z);
               
                float4 r = frac(sin(v*1e-3)*1e5);
                float r0 = lerp(lerp(r.x, r.y, f.x), lerp(r.z, r.w, f.x), f.y);
               
                r = frac(sin((v + uv1.z - uv0.z)*1e-3)*1e5);
                float r1 = lerp(lerp(r.x, r.y, f.x), lerp(r.z, r.w, f.x), f.y);

                return lerp(r0, r1, f.z)*2.-1.;
            }

            Varyings vert(Attributes v)
            {
                Varyings o = (Varyings)0;
                o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
                o.uv = TRANSFORM_TEX(v.uv, _SunTex) + _Time.x*0.5;
                o.positionOS = v.positionOS;
                return o;
            }

            half4 frag(Varyings i) : SV_Target
            {
                half4c;

                floattime      = _Time.y * 0.1;
                float2 modelUV   = i.positionOS.xy;

                //中心贴图
                halfsunCenter= (1-step(0.5,length(modelUV*2)))*0.4;//画0.5半径圆-用于恒心中心高亮
                sunCenter *= SAMPLE_TEXTURE2D( _SunTex, sampler_SunTex, i.uv).r;

                //区域外颜色
                floatdist      = length(modelUV);
                floatfinalDist = 1-dist;
                float3 DistColor = finalDist * _SunColor2.rgb;

                //光晕计算 modelUV = i.positionOS.xy;
                float2 halosize = modelUV *4;      
                float r = dot(halosize,halosize);
                float Halo = (1.0-sqrt(abs(1.0-r)))/(r);
                float3 finalHalo = Halo * _SunColor1.rgb;

                //燥波计算 && 日珥计算
                float fade      = pow( length( 2.0 * modelUV ), 0.5 );
                float fVal1   = 1.0 - fade;
                float fVal2   = 1.0 - fade;
                float angle   = atan2( modelUV.x, modelUV.y )*0.2;//绕中心点向外散发
                float3 coord    = float3( angle, dist, time * 0.1 );
                float Noise1= abs( snoise( coord + float3(0.0, 0, time * 0.015), 15.0));
                float Noise2= abs( snoise( coord + float3(0.0, -time * (0.15), time * 0.015), 45.0));
                float power = 6;
                fVal1 += (1 / power) * snoise(coord + float3(0.0, -time, time * 0.2), (power * (10.0) * (Noise1 + 1.0)));
                fVal2 += (1 / power) * snoise(coord + float3(0.0, -time, time * 0.2), (power * (25.0) * (Noise2 + 1.0)));
                float corona= pow( fVal1 * abs( 1.1 - fade), 2.0 ) * 12.0;
                corona       += pow( fVal2 * abs( 1.1 - fade), 2.0 ) * 12.0;
                corona       *= 1.2 - Noise1;
                float coronaScale = 1-pow(abs(dist*3.8),25);
                corona *= step(dist,1-coronaScale);
                half3 finalCorona = corona * _SunColor1.rgb;

                half4 finalColor;
                finalColor.rgb   = finalHalo + sunCenter*0.6 + finalCorona + DistColor;
                finalColor.a   = 1;

                c = finalColor;

                return c;
            }
            ENDHLSL
      }
    }
}

RecursiveFrog 发表于 2022-4-20 20:55

有点帅
页: [1]
查看完整版本: 超好玩的ShaderToy-恒星近照[Unity复刻+优化+解析]