24 | 如何模拟光照让3D场景更逼真?(下)
月影
该思维导图由 AI 生成,仅供参考
你好,我是月影。今天,我们接着来讲,怎么模拟光照。
上节课,我们讲了四种光照的漫反射模型。实际上,因为物体的表面材质不同,反射光不仅有漫反射,还有镜面反射。
镜面反射与漫反射
什么是镜面反射呢?如果若干平行光照射在表面光滑的物体上,反射出来的光依然平行,这种反射就是镜面反射。镜面反射的性质是,入射光与法线的夹角等于反射光与法线的夹角。
越光滑的材质,它的镜面反射效果也就越强。最直接的表现就是物体表面会有闪耀的光斑,也叫镜面高光。但并不是所有光都能产生镜面反射,我们上节课讲的四种光源中,环境光因为没有方向,所以不参与镜面反射。剩下的平行光、点光源、聚光灯这三种光源,都是能够产生镜面反射的有向光。
那么今天,我们就来讨论一下如何实现镜面反射,然后将它和上节课的漫反射结合起来,就可以实现标准的光照模型,也就是 Phong 反射模型了,从而能让我们实现的可视化场景更加接近于自然界的效果。
如何实现有向光的镜面反射?
首先,镜面反射需要同时考虑光的入射方向以及相机也就是观察者所在的方向。
接着,我们再来说说怎么实现镜面反射效果,一般来说需要 4 个步骤。
第一步,求出反射光线的方向向量。这里我们以点光源为例,要求出反射光的方向,我们可以直接使用 GLSL 的内置函数 reflect,这个函数能够返回一个向量相对于某个法向量的反射向量,正好就是我们要的镜面反射结果。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
本文详细介绍了如何使用光照模拟技术增强3D场景的真实感。通过讲解镜面反射的概念和特性,以及利用GLSL的内置函数reflect实现有向光的镜面反射,作者展示了完整的Phong反射模型,包括环境反射系数、漫反射系数和镜面反射系数的计算公式。文章通过具体的代码示例和技术原理,详细介绍了模拟光照的实现方法,对于想要了解3D场景光照模拟的读者具有很高的参考价值。 此外,文章还介绍了如何定义光源模型对象和几何体材质对象,以及如何利用这些对象创建WebGL程序并渲染出真实的几何体网格。通过Phong类和Material类的定义,读者可以了解如何添加和删除光源、设置光源属性,并定义物体的材质。这些技术内容为读者提供了实现光照模拟的具体步骤和方法,为深入理解和应用光照模拟技术提供了重要参考。 总结来说,本文通过清晰的技术讲解和代码示例,详细介绍了光照模拟的实现方法和相关概念,对于对3D场景光照模拟感兴趣的读者具有很高的参考价值。文章还提到了Phong反射模型的局限性,指出了模型的近似性,并探讨了如何进一步接近真实世界的物理模型。文章内容丰富,适合对3D图形渲染感兴趣的读者深入学习。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《跟月影学可视化》,新⼈⾸单¥68
《跟月影学可视化》,新⼈⾸单¥68
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(4)
- 最新
- 精选
- 哈珀朋友基本等同于把计算机图形学复习了一遍哦2021-09-232
- 量子蔷薇小试牛刀,这是圆形探照灯的实现: // 探照灯 for (int i = 0; i < MAX_LIGHT_COUNT; i++) { vec3 decay = searchLightDecay[i]; if (decay.x == 0.0 && decay.y == 0.0 && decay.z == 0.0) { continue; } vec3 dir = (viewMatrix * vec4(searchLightDirection[i], 0.0)).xyz; dir = normalize(-dir); float c = max(dot(dir, normal), 0.0); vec3 v = (viewMatrix * vec4(searchLightPosition[i], 1.0)).xyz - pos; float l = dot(v, dir); float d = min(1.0, 1.0 / (decay.x * pow(l, 2.0) + decay.y * l + decay.z)); float r = step(length(v - dir * l), searchLightRadius[i]) * step(0.0, dot(v, dir)); vec3 color = searchLightColor[i]; diffuse += c * d * r * color; specular += getSpecular(dir, normal, eye) * d * r * color; } 注意我的 specular 是 vec3 和老师的不一样。 入射光的方向 dir 的计算类似平行光,不受光源位置的影响。 v 是当前坐标到探照灯圆心的向量,与 dir 点乘后就可以得到 v 平行于 dir 方向的分量的长度 l(因为 dir 是单位向量,所以省去了 除以 length(dir) 的操作)。 d 是和老师一样的算法得到的衰减系数。 r 是实现圆形光照范围的关键,前面已经得到了 v 平行于 dir 的分量的长度 l,dir * l 就可以得到真正的分量 v∥,再用 v - v∥ 得到 v⊥,也就是 v 垂直于 dir 的分量,比较 v⊥ 的长度与探照灯半径的大小就能知道当前坐标是否在探照灯的范围内(以上其实就是点乘在向量分解上的应用)。 至此,还没有结束,探照灯是有 position 属性的,因此位于探照灯照射方向背面180°范围内的物体不应该被照亮,这是探照灯与平行光的另一个区别。所以需要判断探照灯圆心到当前坐标的向量也就是 -v 与照射方向也就是 -dir(dir 在 normalize 时取反了,所以这里再取反表示照射的方向)是否同向,或者说夹角是否小于 90°,也就是判断 dot(-v, -dir) 是否大于零,dot(v, dir) 与 dot(-v, -dir) 的结果是相同的,所以省去了取反的操作。2022-11-02归属地:上海1
- Geek_00734evoid main() { // 光线到点坐标的方向 vec3 invLight = (viewMatrix * vec4(searchLightPosition, 1.0)).xyz - vPos; // vec3 invLight = searchLightPosition - vPos; vec3 invNormal = normalize(invLight); // 光线到点坐标的距离,用来计算衰减 float dis = length(invLight); // 求光线中心与法线夹角的余弦 float cosmid = max(dot(normalize(vDir), vNormal), 0.0); // 求光线与法线夹角的余弦 float cosa = max(dot(invNormal, vNormal), 0.0); // 照射范围半径 float radius = searchLightRadius / cosmid; // 光线中心射线与射线夹角 float cosb = max(dot(normalize(vDir), invNormal), 0.0); // 根据正弦定理求对应边长度 float sinb = sqrt(1.0 - cosb * cosb); float sinc = sin(PI/2.0 - acos(cosa) - acos(cosb)); float lenb = dis * sinb / sinc; // 边长度小于半径的为1.0 float r = step(lenb, radius); // 计算衰减 float decay = min(1.0, 1.0 / (searchLightDecayFactor.x * pow(dis, 2.0) + searchLightDecayFactor.y * dis + searchLightDecayFactor.z)); // 计算漫反射 vec3 diffuse = r * decay * cosmid * searchLightColor; // 合成颜色 gl_FragColor.rgb = (ambientLight + diffuse) * materialReflection; gl_FragColor.a = 1.0; } 探照灯 但是感觉算法 不够完美,实在找不到合适的思路了2022-01-19
- Geek_00734e探照灯那个课后作业有答案吗?思来想去做不出来,我的思路是求光源中心射线跟界面的交点,光线照射到的范围为 r/cos(θ) (θ为光线与界面法线夹角), 可是这个思路要求射线与平面交点,这个不知道怎么求,是不是我思路有问题,感觉不大对。 我的理解光源方向 searchLightDirection这个参数可以参照平行光的方式、光源位置 searchLightPosition用于计算距离衰减、光源半径 searchLightRadius 用于确定照射到的范围 这个逻辑怎么算2022-01-18
收起评论