基于光线步进的体积光 2025年4月16日12:59:42
体积光是在体积云的基础上完成的。
首先我们要解决一个问题,我的光的颜色是怎么出来的?
回顾一下体积云,体积云的颜色,是射线上的float叠加出来的,那么体积光也是这样,他是这样一个算法原理
在光线步进的情况下,也是算密度,如何计算那个步进的小段里面有没有密度呢。因为是体积光,我们采取的是这样一个操作,让步进的那个位置向上进行采样,与最顶上相交得到一个二维点坐标,使用这个坐标去采样一个噪声,如果采样到的是黑的,那就是无光,也就是没密度,白的反之,而这个向上也不用垂直,我们可以拿太阳光方向,从而达到一个体积光的效果。
那就开码!
也是复习一下体积云部分了,首先思路理清
从摄像机出发,那么就要有一个本源发射点,碰到容器盒,一个容器发射点,一个发射方向,做好数据准备,一步步下去,还是挺简单的。
之后把累加的密度加入衰减公式,可以达到这样的效果。
此时的代码是这样的
Shader "JH/体积光复习版" { Properties { _Noise("Noise", 2D) = "white" {} _BlueNoise("BlueNoise", 2D) = "white" {} _CausticMap("CausticMap", 2D) = "white" {} _Absorption ("Absorption", float) = 0.5 _AbsorptionLerp("AbsorptionLerp", Range(0, 1)) = 0.5 _Density("Density", float) = 0.5 _Color1("Color1", Color) = (1,1,1,1) _Color2("Color2", Color) = (1,1,1,1) } SubShader { Cull front Tags { "RenderType"="Transparent" } LOD 100 Pass { Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" struct appdata { float4 pos_OS : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 pos_CS : SV_POSITION; float3 pos_OS : TEXCOORD1; }; sampler2D _Noise; sampler2D _BlueNoise; sampler2D _CausticMap; float _Absorption; float _AbsorptionLerp; float4 _Color1; float4 _Color2; float _Density; bool intersectAABB(float3 rayOrigin, float3 rayDir, float3 boxMin, float3 boxMax, out float2 tNearFar) { float3 tMin = (boxMin - rayOrigin) / rayDir; float3 tMax = (boxMax - rayOrigin) / rayDir; float3 t1 = min(tMin, tMax); float3 t2 = max(tMin, tMax); float tNear = max(max(t1.x, t1.y), t1.z); float tFar = min(min(t2.x, t2.y), t2.z); tNearFar = float2(tNear, tFar); return tFar > tNear; } bool intersectPlane(float3 n, float3 p0, float3 rayPos, float3 rayDir, out float t) { // assuming vectors are all normalized float denom = dot(-n, rayDir); if (denom > 1e-6) { float3 difference = p0 - rayPos; t = dot(difference, -n) / denom; return (t >= 0); } return false; } float GetCaustic(float2 uv) { //这里对输出的值进行处理,使得其更加随机 float2 noise = tex2D(_Noise, uv + _Time.y * 0.1); float blueNoise = tex2D(_BlueNoise, uv * 5); float caustic_d1 = tex2D(_CausticMap, uv * 0.8 + _Time.y * 0.02 + noise * 0.2); float caustic_d2 = tex2D(_CausticMap, uv * 0.8 - _Time.y * 0.02 + caustic_d1 * 0.05); float caustic2 = tex2D(_CausticMap, uv * 0.5 - _Time.y * 0.02 + caustic_d2); return caustic2 * blueNoise * 5; } #define _G0 -0.1 #define _G1 0.1 float hg(float a, float g) { float g2 = g * g; return (1 - g2) / (4 * 3.1415 * pow(1 + g2 - 2 * g * (a), 1.5)); } float hg2(float a) { return lerp(hg(a, _G0), hg(a, _G1), _AbsorptionLerp); } float multipleOctaves(float depth, float mu) { float luminance = 0; int octaves = 8; // Attenuation float a = 1; // Contribution float b = 1; // Phase attenuation float c = 1; float phase; for (int i = 0; i < octaves; ++i) { phase = lerp(hg(mu, _G0 * c), hg(mu, _G1 * c), _AbsorptionLerp); luminance += b * phase * exp(-depth * a * _Absorption); a *= 0.2f; b *= 0.5f; c *= 0.5f; } // return hg2(mu) * Transmittance(depth, _Absorption); return luminance; } v2f vert (appdata v) { v2f o; o.pos_CS = UnityObjectToClipPos(v.pos_OS); o.pos_OS = v.pos_OS.xyz; o.uv =v.uv; return o; } fixed4 frag (v2f i) : SV_Target { //对象空间下的光源位置 float3 LDir_OS=normalize(mul((float3x3)unity_WorldToObject, _WorldSpaceLightPos0.xyz)); float height = i.pos_OS.y + 0.5; float noise = tex2D(_Noise, i.pos_OS.xz).r; //对象空间下的相机位置 float3 CameraPos_OS=mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1)).xyz; //对象空间下的发射方向 float3 VDir_OS=normalize(i.pos_OS.xyz-CameraPos_OS); float cosTheta = dot(LDir_OS, VDir_OS); float phase = hg2(cosTheta); //AABB包围盒计算碰撞点以及通过的距离 float3 aabbMin = float3(-0.5, -0.5, -0.5); float3 aabbMax = float3(0.5, 0.5, 0.5); float2 aabbNearFar = (float2)0; bool isHit = intersectAABB(CameraPos_OS, VDir_OS, aabbMin, aabbMax, aabbNearFar); float tNear = max(aabbNearFar.x, 0); float tFar = aabbNearFar.y; float MaxDistance = tFar - tNear; //计算光线与物体的交点 float3 HitPointBegin=CameraPos_OS+VDir_OS*tNear; float3 HitPointEnd=CameraPos_OS+VDir_OS*tFar; //开始步进 float Step_Count=50;//步进次数 float Step_Situation=1.0/Step_Count;//步进状态 float3 Density=0; UNITY_LOOP for(int i=0;i<Step_Count;i++) { //步进位置使用lerp进行获取 float3 pos= lerp(HitPointBegin,HitPointEnd,i*Step_Situation); //获取该位置向光照方向的射线与顶面的交点 //首先获得距离 float Distance_From_Pos_To_Plane=0; //第一个是求交平面的法线方向,第二个是求交平面的位置,第三个为出发点,第四个为方向,第五个是inout的距离值 intersectPlane(float3(0,-1,0),float3(0, 0.5, 0),pos,LDir_OS,Distance_From_Pos_To_Plane); //获得交点 float3 PlaneHitPoint=pos+LDir_OS*Distance_From_Pos_To_Plane; //使用交点坐标采样噪声贴图 float3 caustic = GetCaustic(PlaneHitPoint.xz); //计算密度 Density+=caustic*Step_Situation; } return exp(-Density*_Absorption).xyzz; } ENDCG } } }之后加上体积云里面的大气散射函数,以及加上相应的颜色,既可以达到这样的效果
追加代码
Density = Density * smoothstep(0.2, 1.1 + noise, height) * 20; Density = Density * multipleOctaves(height, cosTheta); float3 color = lerp(_Color1, _Color2, height) * 0.5 + Density; float alpha = exp(-MaxDistance * _Density); return float4(color, alpha);其实体积光的核心算法已经完成,但是我们仍要精益求精
我们先把体积光颜色改成彩色的
它的原理是这样的
我们不是到顶面去采样噪声吗,其实也可以到顶面采样一些别的东西。
比如说彩色的体积光,也不是所有的都可以成为的,所以我们可以在顶上采样一个遮罩,使得特定区域的体积光成为彩色。
之后我们可以使用HSV函数,H不就是色相吗,刚刚好符合我们的预期。我们用pos 的y轴塞进去,使得高度不同的体积光颜色不同,这不彩色的就来了,之后再加上一些参数进行优化。效果就出来了。
float noise = tex2D(_Noise, PlaneHitPoint.xz).r; noise = lerp(1, 1.3, noise); float3 causticColorful = 5 * GetCaustic(PlaneHitPoint.xz) * hsv2rgb( float3( (pos.y + _RainbowOffset + dot(LDir_OS, -VDir_OS) * 0.1) * _RainbowScale * noise + 0.5, _RainbowIntensity, 1) ); float rinbowMask = tex2D(_RainbowMask, PlaneHitPoint.xz * 3); caustic = lerp(caustic, causticColorful*1.5, rinbowMask) * 1.5;我把完整的代码放在这里
Shader "JH/体积光复习版" { Properties { _Noise("Noise", 2D) = "white" {} _BlueNoise("BlueNoise", 2D) = "white" {} _CausticMap("CausticMap", 2D) = "white" {} _Absorption ("Absorption", float) = 0.5 _AbsorptionLerp("AbsorptionLerp", Range(0, 1)) = 0.5 _Density("Density", float) = 0.5 [Header(Rainbow)] _RainbowMask("RainbowMask", 2D) = "white" {} _RainbowIntensity("RainbowIntensity", float) = 1 _RainbowScale("RainbowScale", float) = 1 _RainbowOffset("RainbowOffset", float) = 0 [Space(30)] _Color1("Color1", Color) = (1,1,1,1) _Color2("Color2", Color) = (1,1,1,1) } SubShader { Cull front Tags { "RenderType"="Transparent" } LOD 100 Pass { Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" struct appdata { float4 pos_OS : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 pos_CS : SV_POSITION; float3 pos_OS : TEXCOORD1; }; sampler2D _Noise; sampler2D _BlueNoise; sampler2D _CausticMap; float _Absorption; float _AbsorptionLerp; float4 _Color1; float4 _Color2; float _Density; sampler2D _RainbowMask; float _RainbowIntensity; float _RainbowScale; float _RainbowOffset; bool intersectAABB(float3 rayOrigin, float3 rayDir, float3 boxMin, float3 boxMax, out float2 tNearFar) { float3 tMin = (boxMin - rayOrigin) / rayDir; float3 tMax = (boxMax - rayOrigin) / rayDir; float3 t1 = min(tMin, tMax); float3 t2 = max(tMin, tMax); float tNear = max(max(t1.x, t1.y), t1.z); float tFar = min(min(t2.x, t2.y), t2.z); tNearFar = float2(tNear, tFar); return tFar > tNear; } bool intersectPlane(float3 n, float3 p0, float3 rayPos, float3 rayDir, out float t) { // assuming vectors are all normalized float denom = dot(-n, rayDir); if (denom > 1e-6) { float3 difference = p0 - rayPos; t = dot(difference, -n) / denom; return (t >= 0); } return false; } float GetCaustic(float2 uv) { //这里对输出的值进行处理,使得其更加随机 float2 noise = tex2D(_Noise, uv + _Time.y * 0.1); float blueNoise = tex2D(_BlueNoise, uv * 5); float caustic_d1 = tex2D(_CausticMap, uv * 0.8 + _Time.y * 0.02 + noise * 0.2); float caustic_d2 = tex2D(_CausticMap, uv * 0.8 - _Time.y * 0.02 + caustic_d1 * 0.05); float caustic2 = tex2D(_CausticMap, uv * 0.5 - _Time.y * 0.02 + caustic_d2); return caustic2 * blueNoise * 5; } #define _G0 -0.1 #define _G1 0.1 float hg(float a, float g) { float g2 = g * g; return (1 - g2) / (4 * 3.1415 * pow(1 + g2 - 2 * g * (a), 1.5)); } float hg2(float a) { return lerp(hg(a, _G0), hg(a, _G1), _AbsorptionLerp); } float multipleOctaves(float depth, float mu) { float luminance = 0; int octaves = 8; // Attenuation float a = 1; // Contribution float b = 1; // Phase attenuation float c = 1; float phase; for (int i = 0; i < octaves; ++i) { phase = lerp(hg(mu, _G0 * c), hg(mu, _G1 * c), _AbsorptionLerp); luminance += b * phase * exp(-depth * a * _Absorption); a *= 0.2f; b *= 0.5f; c *= 0.5f; } // return hg2(mu) * Transmittance(depth, _Absorption); return luminance; } float3 hsv2rgb(float3 c) { float3 rgb = clamp(abs(fmod(c.x * 6. + float3(0., 4., 2.), 6.) - 3.) - 1., 0., 1.); rgb = rgb * rgb * (3. - 2. * rgb); return c.z * lerp((float3)(1.), rgb, c.y); } float3 hsv2rgb(float h, float s, float v) { return hsv2rgb(float3(h, s, v)); } v2f vert (appdata v) { v2f o; o.pos_CS = UnityObjectToClipPos(v.pos_OS); o.pos_OS = v.pos_OS.xyz; o.uv =v.uv; return o; } fixed4 frag (v2f i) : SV_Target { //对象空间下的光源位置 float3 LDir_OS=normalize(mul((float3x3)unity_WorldToObject, _WorldSpaceLightPos0.xyz)); float height = i.pos_OS.y + 0.5; float noise = tex2D(_Noise, i.pos_OS.xz).r; //对象空间下的相机位置 float3 CameraPos_OS=mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1)).xyz; //对象空间下的发射方向 float3 VDir_OS=normalize(i.pos_OS.xyz-CameraPos_OS); float cosTheta = dot(LDir_OS, VDir_OS); float phase = hg2(cosTheta); //AABB包围盒计算碰撞点以及通过的距离 float3 aabbMin = float3(-0.5, -0.5, -0.5); float3 aabbMax = float3(0.5, 0.5, 0.5); float2 aabbNearFar = (float2)0; bool isHit = intersectAABB(CameraPos_OS, VDir_OS, aabbMin, aabbMax, aabbNearFar); float tNear = max(aabbNearFar.x, 0); float tFar = aabbNearFar.y; float MaxDistance = tFar - tNear; //计算光线与物体的交点 float3 HitPointBegin=CameraPos_OS+VDir_OS*tNear; float3 HitPointEnd=CameraPos_OS+VDir_OS*tFar; //开始步进 float Step_Count=50;//步进次数 float Step_Situation=1.0/Step_Count;//步进状态 float3 Density=0; UNITY_LOOP for(int i=0;i<Step_Count;i++) { //步进位置使用lerp进行获取 float3 pos= lerp(HitPointBegin,HitPointEnd,i*Step_Situation); //获取该位置向光照方向的射线与顶面的交点 //首先获得距离 float Distance_From_Pos_To_Plane=0; //第一个是求交平面的法线方向,第二个是求交平面的位置,第三个为出发点,第四个为方向,第五个是inout的距离值 intersectPlane(float3(0,-1,0),float3(0, 0.5, 0),pos,LDir_OS,Distance_From_Pos_To_Plane); //获得交点 float3 PlaneHitPoint=pos+LDir_OS*Distance_From_Pos_To_Plane; //使用交点坐标采样噪声贴图 float3 caustic = GetCaustic(PlaneHitPoint.xz); //加入彩虹 float noise = tex2D(_Noise, PlaneHitPoint.xz).r; noise = lerp(1, 1.3, noise); float3 causticColorful = 5 * GetCaustic(PlaneHitPoint.xz) * hsv2rgb( float3( (pos.y + _RainbowOffset + dot(LDir_OS, -VDir_OS) * 0.1) * _RainbowScale * noise + 0.5, _RainbowIntensity, 1) ); float rinbowMask = tex2D(_RainbowMask, PlaneHitPoint.xz * 3); caustic = lerp(caustic, causticColorful*1.5, rinbowMask) * 1.5; //计算密度 Density+=caustic*Step_Situation; } Density = Density * smoothstep(0.2, 1.1 + noise, height) * 20; Density = Density * multipleOctaves(height, cosTheta); float3 color = lerp(_Color1, _Color2, height) * 0.5 + Density; float alpha = saturate(exp(-MaxDistance * _Density)); return float4(color, alpha); } ENDCG } } }