news 2026/7/7 21:34:01

臭双非的技术学习之旅——光线步进体积光篇

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
臭双非的技术学习之旅——光线步进体积光篇

基于光线步进的体积光 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 } } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/7 18:53:17

基于springboot的医院药品管理系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有实…

作者头像 李华
网站建设 2026/7/7 17:20:49

JavaScript学习笔记:16.模块

JavaScript学习笔记&#xff1a;16.模块 上一篇用迭代器和生成器搞定了“智能遍历”&#xff0c;这一篇咱们来解锁JS大型项目的“核心管理工具”——模块&#xff08;Modules&#xff09;。你肯定经历过这样的场景&#xff1a;写小demo时&#xff0c;把所有代码堆在一个script标…

作者头像 李华
网站建设 2026/7/7 5:53:09

展望:RPA与AI在企业微信自动化领域的未来融合趋势

一、 引言&#xff08;Introduction&#xff09; 现状回顾&#xff1a; 当前企业微信的RPA自动化主要集中在模拟重复性、规则明确的操作&#xff08;如批量发送、信息抓取&#xff09;。 技术瓶颈&#xff1a; 纯RPA在处理非结构化数据、复杂决策和自然语言交互方面存在局限性…

作者头像 李华
网站建设 2026/7/7 11:52:47

小学生学C++编程 (string精讲)

一、从“char 字符数组时代”&#xff0c;进“string 新时代”。&#x1f3f0;《string 王国奇遇记》—— C 中的 string 类型1、故事开场&#xff1a;老房子 vs 智能城堡 &#x1f3da;️&#x1f3f0;在 C 世界里&#xff1a;&#x1f9d3; char 字符数组&#x1f449; 像一排…

作者头像 李华
网站建设 2026/7/6 23:35:10

KAIST突破:AI实现SVG矢量图形智能动画

这项由韩国科学技术院&#xff08;KAIST&#xff09;AI实验室的尹周烈&#xff08;Jooyeol Yun&#xff09;和朱在筠&#xff08;Jaegul Choo&#xff09;教授领导的研究发表于2024年12月&#xff0c;论文编号为arXiv:2512.14336v1。有兴趣深入了解的读者可以通过该编号查询完整…

作者头像 李华
网站建设 2026/7/7 8:55:44

香港大学团队破解电脑操作难题:让AI通过看视频学会自己使用电脑

这项由香港大学的陆俊杰、徐艺恒、王俊力等研究者领导&#xff0c;联合阿里巴巴Qwen团队的黄斌源、林俊洋等专家共同完成的研究发表于2025年10月&#xff0c;论文编号为arXiv:2510.19488v1。这是一项关于如何让人工智能学会使用电脑的突破性研究&#xff0c;有兴趣深入了解的读…

作者头像 李华