Shader 3d RayMarching8 光照

在 Raymarching 第一篇文章的时候,介绍了基本的 Phong光照模型,同时实现了漫反射和环境光照。本文将实现一些更加真实的光照效果,并根据物理自然现象解释其数学原理,并完成代码编写,Let's Go

漫反射扩散光 Diffuse Lighting

Lambertian光照模型是一种计算机图形学中常用的光照模型,以简洁的数学形式描述了漫反射(Diffuse Reflection)的效果。它基于光的入射角度和表面法线之间的关系来计算光的漫反射强度。它假设反射表面是完全漫反射的,即表面均匀地散射入射光线,没有任何方向依赖性。这种漫反射表面也称为"朗伯表面"。主要概念有

  1. 法向量(Normal Vector, n):垂直于表面的一条单位向量,表示该表面的朝向。
  2. 入射光方向(Light Direction, L):从表面指向光源的方向向量。需要标准化为单位向量。
  3. 漫反射系数(Diffuse Coefficient, kd):表示表面的漫反射能力,取值范围为0到1,0表示完全不反射,1表示完全反射。 其核心公式

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> I d = I l ⋅ K d ⋅ m a x ( c o s ( θ ) , 0 ) I_d = I_l \cdot K_d \cdot max(cos(\theta), 0) </math>Id=Il⋅Kd⋅max(cos(θ),0)

其中

  • Id 为 是漫反射光的强度。
  • Il 为 入射光的强度
  • Kd 为漫反射系数。
  • θ 是光线方向和法向量之间的夹角。 当然看到cos就想到了点积运算, 所以最后代码

Lambertian光照优化计算

在 shader中计算曲面的法向量,需要进行 6 次计算,在raymarching第一节已经讲过了,IQ提出了一种基于方向导数的近似方法,核心思想是沿着光的方向做一次求导就可以近似得到 符合 Lambertian光照模型的结果. 详细可查看这篇文章 iquilezles.org/articles/de... 关键公式是
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Δ v f ( x ) = Δ f ( x ) ⋅ v ∣ v ∣ \Delta_v f(x) = \Delta f(x) \cdot \frac{v}{|v|} </math>Δvf(x)=Δf(x)⋅∣v∣v

求方向导数可以用以下代码实现,eps为微分,取值越小精度越高

glsl 复制代码
float diffuse = (map(pos+eps*light)-map(pos) ) / eps

镜面反射高光 Specular Lighting

Phong光照模型将光照效果分为三个主要部分:

  1. 环境光(Ambient Light):模拟环境中的散射光,通常是一个常量值。
  2. 漫反射光(Diffuse Light):基于表面法线和光线方向的点积,描述光线在表面上的散射效果。
  3. 镜面反射光(Specular Light):描述光线在表面上的镜面反射,通常产生高光效果。 其中镜面反射的核心公式为

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> I s = I l ⋅ K s ⋅ m a x ( c o s ( α ) , 0 ) n I_s = I_l \cdot K_s \cdot max(cos(\alpha), 0)^n </math>Is=Il⋅Ks⋅max(cos(α),0)n

  • Is 为 是镜面反射光的强度。
  • Il 为 入射光的强度
  • Ks 为漫反射系数。
  • α 是反射光与视线方向之间的夹角
  • n 是镜面高光的锐度(反射率),通常称为 "高光指数"
glsl 复制代码
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess);
    vec3 specular = ks * spec * lightColor;

Blinn-Phong优化

Blinn-Phong 反射模型是基于Phong反射模型的一种改进,用于模拟物体表面的光照效果。它在计算镜面反射光(Specular Reflection)时引入了一个半向量(Half-Vector)的概念,使得反射高光的计算更加高效且效果更自然。其中镜面反射的核心公式为
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> I s = I l ⋅ K s ⋅ m a x ( c o s ( β ) , 0 ) n I_s = I_l \cdot K_s \cdot max(cos(\beta), 0)^n </math>Is=Il⋅Ks⋅max(cos(β),0)n

Half-vector是光线方向向量 𝐿与视线向量 𝑉 的平均向量
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> H = L + V ∣ ∣ L + V ∣ ∣ H = \frac{L+V}{||L+V||} </math>H=∣∣L+V∣∣L+V

β 是法向量 𝑁 与半向量 𝐻之间的夹角。

Fresnel 方程与近似计算

Fresnel 方程描述了光在不同介质界面上反射和折射时的行为,提供了反射率和折射率随入射角变化的详细计算方法。广泛应用于光学、计算机图形学和渲染领域,以模拟光线与物体表面交互时的反射和折射现象。在实际的Shader中,Fresnel 的效果经常用于模拟反射率随视角的变化,以生成更逼真的光学现象。例如,水面或者眼睛等表面材料在不同的观察方向上会有不同的反射强度。 例如上图中,远处更多的是看冰山的反射,而近处可以看到水面

当光线从一种介质(折射率为 n1)进入另一种介质(折射率为 𝑛2)时,光被界面反射的比例称为反射比𝑅,完整的 Fresnel 方程计算比较复杂,Schlick 提出了一个简化的近似公式,以便计算中更为高效。Schlick's 近似公式如下:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> R ( θ ) = R 0 + ( 1 − R 0 ) ( 1 − c o s θ ) 5 R(\theta) = R_0 + (1 - R_0)(1-cos\theta)^5 </math>R(θ)=R0+(1−R0)(1−cosθ)5

R0是在法线方向的反射率,可以通过
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> R 0 = ( n 1 − n 2 n 1 + n 2 ) 2 R_0 = (\frac{n_1-n_2}{n_1+n_2})^2 </math>R0=(n1+n2n1−n2)2

θ 是入射光与法线之间的夹角。写成伪代码有

glsl 复制代码
float fresnelSchlick(float cosTheta, float r0)
{
    return r0 + (1.0 - r0) * pow(1.0 - cosTheta, 5.0);
}

环境光 Ambient Light

正常代码中环境光是全局都是一样,但实际上随着高度越高,空气越好环境光损失的越小,所以在这里增加一个 Y方向的 调整。同时sqrt 平滑光线分布。

glsl 复制代码
float sky_dif = sqrt(clamp(0.5 + 0.5 * nor.y, 0.0, 1.0));

地面反射光 (Bounce Light)

空旷地面也会反射大自然的光线,这个光线的强度取决于物体离地面的距离,越近越强。同时也取决物体表面的朝向,如果完全朝上也就是 Y分量特别大,地面的反射光就会被遮蔽。 所以 IQ创造了这样的函数, 同样也用sqrt平滑法向量光线分布

glsl 复制代码
float bou_dif = sqrt(clamp(0.1 - 0.9 * nor.y, 0.0, 1.0)) * 
	clamp(1.0 - 0.1 * pos.y, 0.0, 1.0);

代码实现

glsl 复制代码
void calcLight(
    inout vec3 col,
    float dist,
    vec3 rayDirection,
    vec3 sunLightDirection,
    vec3 position,
    float time
) {
    vec3 surfaceNormal = calcNormal( position, time );
    float kSpecular = 1.0;

    float sunDiffuse = clamp(dot( surfaceNormal, sunLightDirection ), 0.0, 1.0 );
    vec3  halfVector = normalize( sunLightDirection-rayDirection );
    float isBocked = castRay(position+SURFACE_DIST*surfaceNormal, sunLightDirection,time).y;
    float sunShadow  = step(isBocked,0.0);
      
    float blinnSpecular = kSpecular*pow(clamp(dot(surfaceNormal,halfVector),0.0,1.0),8.0);
    float fresnelSchlick = 0.04+0.96*pow(clamp(1.0+dot(halfVector,rayDirection),0.0,1.0),5.0);
    float sumSpecular = blinnSpecular * fresnelSchlick * sunDiffuse;

    float skyDiffuse = sqrt(clamp( 0.5+0.5*surfaceNormal.y, 0.0, 1.0 ));
    float faceToLand = sqrt(clamp( 0.1-0.9*surfaceNormal.y, 0.0, 1.0 ));
    float nearToLand = clamp(1.0-0.1*position.y,0.0,1.0);
    float bounceDiffuse = faceToLand * nearToLand;
    
    vec3 light = vec3(0.0);   
    light += skyDiffuse*vec3(0.50,0.70,1.00);          // sky is blue    蓝天
    light += bounceDiffuse*vec3(0.40,1.00,0.40);       // land is green  绿地
    light += sunDiffuse*vec3(8.10,6.00,4.20)*sunShadow;// sun is red     红日

    col = col*light;
    col += sumSpecular*vec3(8.10,6.00,4.20)*sunShadow;

    col = mix( col, vec3(0.5,0.7,0.9), 1.0-exp( -0.0001*dist*dist*dist ) );

}

最后我们得到这么美丽的空间,🎉🎉🎉🎉🎉🎉

www.shadertoy.com/view/McfcR7

相关推荐
爱看书的小沐7 小时前
【小沐杂货铺】基于Three.JS绘制太阳系Solar System(GIS 、WebGL、vue、react)
javascript·vue.js·webgl·three.js·地球·太阳系·三维地球
大松鼠君2 天前
轿车3D展示
前端·webgl·three.js
伶俜monster3 天前
UV 法向量实验室:Threejs 纹理与光照炼金术
前端·webgl·three.js
xhload3d3 天前
智能网联汽车云控平台 | 图扑数字孪生
3d·gis·智慧城市·html5·webgl·数字孪生·可视化·工业互联网·车联网·智慧交通·智能网联·汽车云控
三木前端4 天前
像设计师一样处理图像,只需几行 JavaScript 代码!
前端·javascript·webgl
伶俜monster7 天前
模型苏醒计划:Threejs 让静态模型「叛逆」起来
前端·webgl·three.js
DragonBallSuper9 天前
在Cesium中使用ThreeJs材质(不是场景融合哦)
webgl·材质·threejs·cesium·可视化渲染
GISBox12 天前
PLY格式文件如何转换成3DTiles格式——使用GISBox软件实现高效转换
arcgis·信息可视化·node.js·gis·webgl·cesium
XZen14 天前
DeepSeek + 码上掘金 学习shadertoy之 —— Catmull-Rom样条插值
javascript·webgl·计算机图形学
海盗123414 天前
Unity导出WebGL,无法显示中文
unity·webgl