Shader 3d RayMarching13 几何抽象空间光影

378 阅读10分钟

月薪 13800 的运维工程师又来学习艺术的技术啦。 今天在 shadertoy 上看到周推荐 www.shadertoy.com/view/tttSW7 。在一个抽象的几何世界中与正弦波重复的运动,真实的光影效果带来黑白阴影的微妙变化,宁静简洁,很有禅意。 让我想起齐白石对于画画的评语,“作画妙在似与不似之间,太似为媚俗,不似为欺世“

2410200429

本文将分析其实现原理,特别是光影部分的实现,掌握之后配合一些简单几何体与 Anmiation也可以做出这样几何抽象空间的视觉艺术

Light的简单介绍

在讨论技术之前,回想小学画画老师老师经常会在绘画课中将暗部,亮部,明暗交界线的一系列概念。看到下图的对比,左边是简单的亮部和暗部,但是右边的图却有不同程度的明暗,看起来更加真实。原因是环境除了有直接光之外,还有光线经过多次反射、折射、散射后照射到物体表面的光。 还还有光在不同表面之间相互遮挡的现象。 观察现实世界越多,总结出的规律越多,变让场景更加真实 2410193400 因此我们对于光做一个简单区分

  • 直接光(Direct Light):直接从光源到达物体表面的光,包括点光源(Point Light)、聚光灯(Spot Light)、平行光(Directional Light)等。直接光通常用于模拟明确的光源,如太阳光或灯光。这部分光能够在较短时间内计算出阴影、光强度和高光等效果。其计算方法使用着色器计算光照模型(如Phong或Blinn-Phong模型)来处理

  • 间接光(Indirect Light):间接光是光线经过多次反射、折射、散射后照射到物体表面的光。包含反射光、透射光和散射光等。间接光导致全局照明效果,比如颜色漂移和环境遮蔽。其实现有 间接光的计算主要应用于光照模拟和渲染中,通常用于创造更真实的光影效果。

光照模型理解

关于其几何体生成,RayMarching技术,软阴影实现相关的技术原理这里不在赘述,有需要可以翻看本系列相关的内容

  1. Raymarching技术介绍
  2. 柔软的阴影
  3. SDF原型
  4. 直接光照模型

本文将重点分析其光照模型的实现,也就是以下的代码

float sceneEnergy(vec3 point, vec3 viewDirection) {
  vec3 lightDirection = normalize(vec3(2.0, 1.0, cos(iTime * 0.1) * 3.0));
  vec3 normal = sceneNormal(point);
    
  vec3 skin = normal * 0.005;

  // Super rough reading of the rendering equation follows.
  float incidence = clamp(dot(normal, lightDirection), 0.0, 1.0);
    
  vec3 bounceDirection = reflect(viewDirection, normal);
  float reflection = texture(iChannel0, bounceDirection.xy).x;
  float diffuse = texture(iChannel1, normal.xy).x;
    
  float fresnel = 1.0 - abs(dot(viewDirection, normal));
  float hemisphere = mix(diffuse, reflection, fresnel * 0.3) * 0.5;
  float occlusion = ao(point, normal) * 0.5 + 0.5;
  
  vec3 halfVector = normalize(lightDirection + -viewDirection);
  float normalDotHalf = max(dot(normal, halfVector), 0.0);
  float specular = pow(normalDotHalf, 0.8) * 0.4;

  float shadow = softShadow(point + skin, lightDirection, 0.15);
  return (hemisphere * occlusion * (1.0 - ((1.0 - shadow) * 0.5)))
    + (incidence * shadow * occlusion)
    + (specular * shadow);
}

这段 GLSL 代码是一个简化版的光照计算,用于计算某个三维场景中一个点的能量(即光照强度)。初始参数有一些需要解释的地方

  1. LIghtDirection:动态定义光源方向,并根据时间(iTime)而改变。使光线在 z 轴上有一个随时间变化的分量。这样能够达到一种阴影轻微浮动移动的效果,有点类似于我们在路灯下走路,影子不断的拉长,变短。
  2. Normal关于 sdf中法线方向的计算,在系列中多次提到,也可以查看柔软的阴影
  3. 自阴影问题: vec3 skin = normal * 0.005; 法线稍微偏移,用于光影计算中的偏移。这样可以有效避免在环境光遮蔽中自阴影的问题,自阴影问题是计算机图形学中在渲染阴影时常遇到的一个问题。它发生在阴影贴图技术中,当物体的表面因为精度问题而错误地被认为处于阴影之下,就会导致自阴影现象。这通常是由于浮点数精度或深度缓冲的误差引起的。为了解决这个问题,通常会在计算深度比较时加一个小的偏移量(称为“偏移”或“斜率偏移”)。这样可以避免表面因浮点误差被错误地判断为在阴影中,从而减少不必要的自阴影现象。此偏移量需要调节得当,过大可能导致正确的阴影消失,过小则可能无效。

Fresnel 方程与优化

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

当光线从一种介质(折射率为 n1)进入另一种介质(折射率为 𝑛2)时,光被界面反射的比例称为反射比𝑅,完整的 Fresnel 方程计算比较复杂,Schlick 提出了一个简化的近似公式,以便计算中更为高效。Schlick's 近似公式如下:

R(θ)=R0+(1R0)(1cosθ)5R(\theta) = R_0 + (1 - R_0)(1-cos\theta)^5

R0是在法线方向的反射率,可以通过

R0=(n1n2n1+n2)2R_0 = (\frac{n_1-n_2}{n_1+n_2})^2

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

float fresnelSchlick(float cosTheta, float r0)
{
    return r0 + (1.0 - r0) * pow(1.0 - cosTheta, 5.0);
}

半球光照 Hemispherical Lighting

半球光照效果(Hemispherical Lighting)是一种简单却有效的光照模型,常用于模拟户外场景中环境光的影响。它提供了一种柔和的光照过渡,可以营造出自然的氛围。半球光照模型将光源分为两个部分:

  1. 上半球光(天空光):模拟来自天空的光,通常是蓝色或其他冷色调。
  2. 下半球光(地面光):模拟来自地面的反射光,通常是棕色或其他暖色调。

光照强度根据表面法线的方向在这两个光源之间进行插值。具体来说,如果法线指向上(与y轴同向),则天空光的影响更强;如果法线指向下,则地面光的影响更强。法线的 y 分量用于确定两种颜色的混合比例。同时为了体现材质在半球光照模型中加入了菲尼尔系数

镜面高光与优化 Specular Lighting

镜面反射光(Specular Light):描述光线在表面上的镜面反射,通常产生高光效果。其中镜面反射的核心公式为

Is=IlKsmax(cos(α),0)nI_s = I_l \cdot K_s \cdot max(cos(\alpha), 0)^n
  • Is 为 是镜面反射光的强度。
  • Il 为 入射光的强度
  • Ks 为漫反射系数。
  • α 是反射光与视线方向之间的夹角
  • n 是镜面高光的锐度(反射率),通常称为 "高光指数"
    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 反射模型是基于Phong反射模型的一种改进,用于模拟物体表面的光照效果。它在计算镜面反射光(Specular Reflection)时引入了一个半向量(Half-Vector)的概念,使得反射高光的计算更加高效且效果更自然。其中镜面反射的核心公式为

Is=IlKsmax(cos(β),0)nI_s = I_l \cdot K_s \cdot max(cos(\beta), 0)^n

Half-vector是光线方向向量 𝐿与视线向量 𝑉 的平均向量

H=L+VL+VH = \frac{L+V}{||L+V||}

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

2407142058

环境光遮蔽 Ambient Occlusion

这里使用到了这篇论文,The basic technique was invented by Alex Evans, aka Statix (“Fast Approximation for Global Illumnation on Dynamic Scenes”, 2006). 解释可以参考 IQ的介绍iquilezles.org/articles/nv… 以及 引用里面的 shadertoy教程讲解 2410200701 其核心思想就是一句话, 物体表面的一个点法线方向点与改点到物体表面最短距离差别越大,说面这个点处于在某个夹缝中,其发生环境光遮蔽现象的概率越大。

调色与 Gamma矫正

人在自然界看到的世界的感觉,与人从显示设备上看到世界不一样。 同样一个红色,在世界中存在,再从显示器传递rbg(1.0, 0.0, 0.0) 感知不同。 这个时候就需要调色。 调色很多时候需要不要尝试。 并且不是很好解释。例如这个 shader中使用的

vf = (vh * va + 0.004) / (vh * (va + 0.55) + 0.0491) - 0.0821: 这是核心的色调映射公式,通过使用非线性变换来压缩高动态范围(HDR)的颜色信息,使其更适合显示在标准动态范围(SDR)的设备上。

Gamma校正(Gamma Correction)是图形学中一个重要的技术,用于调整图像的亮度,使其更加符合人眼的感知特性。人眼对于亮度变化的感知并不是线性的,因此,直接从计算中得到的图像需要进行gamma校正才能获得正确的视觉效果。人眼对亮度的感知更接近于对数。 代码实现也非常简单,先让颜色暗一点,然后再回去

   const float gammer = 1.6;
   col = pow(col, vec3(gammer));

  // do something
  col = pow(col, vec3(1.0 / gammer)); 

想法

学习完我想尝试将这个光照模型用到一些抽象世界中,目前想用wave + box + sphere 的运动来画一首歌。

reference