WebGL进阶:光照与阴影 —— 让数字世界充满真实之光

引言

        想象在一个阳光明媚的日子里,你站在一片树林里,光线透过树叶的缝隙洒落下来,形成斑驳陆离的光影。这种自然界的光影变化,让整个场景充满了层次感和深度。同样地,在WebGL的世界里,通过引入光照与阴影,可以让3D模型看起来更加真实生动,仿佛它们也沐浴在这变幻莫测的光线下。

光照是什么?

        在现实生活中,光源的存在赋予了物体形状和体积感,不同的光源角度和强度会改变物体外观。在WebGL中,光照模拟就是通过算法计算出不同光源对物体的影响,从而增强场景的真实性。这包括直接光照(比如太阳光)、环境光(周围环境反射的光线)和材质本身的特性(光泽度、颜色)。

如何在WebGL中实现光照效果?

        首先,我们需要定义光源的位置和类型(方向光、点光)。然后,在顶点着色器中计算光照的方向向量,接着在片段着色器中使用光照公式来调整每个像素的颜色输出。

代码示例

假设我们有一个简单的立方体,下面是如何在其上应用光照效果的示例:

const lightPosition = [5.0, 5.0, 10.0]; // 光源位置
const lightColor = [1.0, 1.0, 1.0, 1.0]; // 光源颜色

在顶点着色器中:

// 定义顶点的法线向量,用于计算光照影响
attribute vec3 a_normal;

// 变量v_lightIntensity将在顶点着色器中被赋值,
// 并传入片段着色器以调整颜色
varying vec4 v_lightIntensity;

void main(void) {
    // ...
    
    // 计算从光源到顶点的单位方向向量
    vec3 toLightVector = normalize(vec3(lightPosition[0] - position.x,
                                        lightPosition[1] - position.y,
                                        lightPosition[2] - position.z));
    
    // 使用法线计算光照强度
    v_lightIntensity = dot(normal, toLightVector);
    
    // ...
}

在片段着色器中:

precision mediump float;

varying vec4 v_lightIntensity;

void main(void) {
    // 基础颜色乘以光照强度得出最终颜色
    gl_FragColor = vec4(baseColor.rgb * v_lightIntensity.rgb, baseColor.a);
}

环境光遮蔽(AO):揭示世界的隐秘角落

        想象走进一个古老图书馆,书架间的微弱光线营造出一种神秘氛围,这是因为光线难以触及的角落产生了自然的暗影效果。在WebGL中,环境光遮蔽(AO)正是用来模仿这种现象,增加场景深度和复杂性的一种技术。

AO是如何工作的?

        AO通过分析场景中的几何结构,确定哪些区域因邻近物体的阻挡而接收较少的环境光。在着色器中,我们可以计算每个点周围的平均光照程度,以此决定其阴影的浓淡。

代码示例

考虑一个简单的场景,我们可以在片段着色器中引入AO计算:

// 简化版AO计算,使用随机采样点进行估计
const int NUM_SAMPLES = 8; // 我们设定采样次数为8次,意味着我们将从周围随机选择8个点来评估当前点受到的环境光遮挡情况。

float aoFactor = 0.0; // 初始化环境光遮蔽因子为0,该变量将存储最终的AO值,范围一般在0到1之间。

for(int i = 0; i < NUM_SAMPLES; ++i) {
    vec3 sampleVec = randomSampleSphere(); // 在单位球面上选取一个随机向量。这是为了模拟来自各个方向的潜在光线,检查是否被遮挡。
    
    vec3 offset = sampleVec * radius; // 计算相对于当前位置的一个偏移量,半径决定了我们检测周围多少距离内的遮挡物。
    // 注意:这里的radius应根据实际情况调整,例如可以是模型的平均尺寸或者是某个固定小数值,以便检测近距离内的障碍物。

    vec3 neighborPos = position + offset; // 得到邻接位置,即当前点加上由样本向量指示的偏移量。

    float neighborDist = length(neighborPos); // 测量邻接位置的距离。实际上,这里应该测量的是从当前点到邻接点之间的距离,而非邻接点到原点的距离。
    // 更准确的做法应该是计算length(neighborPos - position),但在上下文缺失的情况下,原代码可能意在表达这个意思。
    
    if(neighborDist <= radius) { // 如果邻接位置在我们设定的检测范围内,则视为存在遮挡物。
        aoFactor += 1.0 / float(NUM_SAMPLES); // 当找到遮挡物时,AO因子增加,表明当前点受到的环境光减少。
    }
}

aoFactor = clamp(1.0 - aoFactor, 0.0, 1.0); // 对AO因子进行限制,确保其值在0到1之间。1减去aoFactor是因为没有遮挡的地方应当得到最大的光照(接近1.0),完全被遮挡的区域则接近0.0。

        需要注意的是,上面的代码示例是在理想化情境下的简化版,真实的环境中,neighborDist的比较逻辑应当是判断邻接点与当前点之间的距离是否小于某阈值(通常是radius),而不是邻接点本身距离原点的距离。

        此外,randomSampleSphere()函数在GLSL中并不直接可用,需要自行编写或导入预定义的函数库。该函数的作用是从单位球面上均匀分布地选取一个点,常用于诸如AO计算这样的光照效果中,以模拟来自四面八方的光线。

法线贴图:重塑表面质感

        还记得我们在前文提到的乐高模型吗?如果想要在不增加大量多边形的情况下,让模型表面拥有丰富的细节和质感,法线贴图就是答案。它允许我们“欺骗”眼睛,使其认为平面表面具有复杂的凹凸结构。

法线贴图的工作原理
        不同于纹理映射只改变颜色,法线贴图通过更改着色器中的光照计算来反映表面的假想凹凸感。具体而言,它提供了一个新的法线向量,用于替换原始几何体上的法线。

代码示例

在片段着色器中,我们可以这样使用法线贴图:

// 使用texture2D函数从预先准备的法线贴图(normalSampler)中采样法线信息,uv变量代表了纹理坐标,用于定位具体的像素
vec3 normalMap = texture2D(normalSampler, uv).rgb;
// 是将采样得到的法线贴图颜色值从范围[0, 1]转换为[-1, 1],以匹配典型的法线向量格式
normalMap = 2.0 * normalMap - 1.0; 
// 对采样得到的法线向量进行空间变换,使其适应当前模型的坐标系。modelMatrix是指模型矩阵,它描述了模型在场景中的位置、旋转和缩放
vec3 transformedNormal = normalize(modelMatrix * vec4(normalMap, 0.0)).xyz;

结语

        通过本章的深入探索,我们为WebGL场景中的对象增添了生动的光照效果,正如自然界中的光影变化丰富了我们的视觉体验。同时我们已经掌握了如何运用环境光遮蔽和法线贴图来丰富WebGL场景的细节。就像一位技艺高超的画家,懂得在明暗对比和纹理之间寻找平衡,我们也能够在数字领域中创造出既具真实感又不失艺术性的作品。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妍思码匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值