视觉高级篇24#如何模拟光照让3d场景更逼真?(下)(代码片段)

凯小默 凯小默     2023-01-11     664

关键词:

说明

【跟月影学可视化】学习笔记。

什么是镜面反射?

如果若干平行光照射在表面光滑的物体上,反射出来的光依然平行,这种反射就是镜面反射。越光滑的材质,它的镜面反射效果也就越强,并且物体表面会有闪耀的光斑,也叫镜面高光

镜面反射的性质:入射光与法线的夹角等于反射光与法线的夹角

如何实现有向光的镜面反射?

实现镜面反射效果的步骤:

  1. 求出反射光线的方向向量
  2. 根据相机位置计算视线与反射光线夹角的余弦
  3. 使用系数和指数函数设置镜面反射强度
  4. 将漫反射和镜面反射结合起来,让距离光源近的物体上形成光斑

下面以点光源为例来实现光斑:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>如何实现点光源的镜面反射</title>
        <style>
            canvas 
                border: 1px dashed #fa8072;
            
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script type="module">
            import  Renderer, Camera, Transform, Sphere, Box, Cylinder, Torus, Orbit, Program, Mesh, Color  from './common/lib/ogl/index.mjs';
            // JavaScript Controller Library
            import * as dat from './common/lib/dat.gui.js';
            console.log(dat)

            const canvas = document.querySelector('canvas');
            const renderer = new Renderer(
                canvas,
                width: 512,
                height: 512,
            );

            const gl = renderer.gl;
            gl.clearColor(1, 1, 1, 1);
            const camera = new Camera(gl, fov: 35);
            camera.position.set(0, 0, 10);
            camera.lookAt([0, 0, 0]);

            const scene = new Transform();

            // 在顶点着色器中,将物体变换后的坐标传给片元着色器
            const vertex = `
                precision highp float;

                attribute vec3 position;
                attribute vec3 normal;
                uniform mat4 modelViewMatrix;
                uniform mat4 viewMatrix;
                uniform mat4 projectionMatrix;
                uniform mat3 normalMatrix;
                uniform vec3 cameraPosition;

                varying vec3 vNormal;
                varying vec3 vPos;
                varying vec3 vCameraPos;

                void main() 
                    vec4 pos = modelViewMatrix * vec4(position, 1.0);
                    vPos = pos.xyz;
                    // 求光源与点坐标的方向向量
                    vCameraPos = (viewMatrix * vec4(cameraPosition, 1.0)).xyz;
                    vNormal = normalize(normalMatrix * normal);
                    gl_Position = projectionMatrix * pos;
                
            `;

            // 传入环境光 ambientLight 和材质反射率 materialReflection
            // 片元着色器中计算光线方向与法向量夹角的余弦
            const fragment = `
                precision highp float;

                uniform vec3 ambientLight;
                uniform vec3 materialReflection;
                uniform vec3 pointLightColor;
                uniform vec3 pointLightPosition;
                uniform mat4 viewMatrix;
                uniform vec3 pointLightDecayFactor;

                varying vec3 vNormal;
                varying vec3 vPos;
                varying vec3 vCameraPos;

                void main() 
                    // 光线到点坐标的方向
                    vec3 dir = (viewMatrix * vec4(pointLightPosition, 1.0)).xyz - vPos;

                    // 光线到点坐标的距离,用来计算衰减
                    float dis = length(dir);

                    // 归一化
                    dir = normalize(dir);

                    // 与法线夹角余弦
                    float cos = max(dot(normalize(dir), vNormal), 0.0);

                    // 反射光线:使用 GLSL 的内置函数 reflect,这个函数能够返回一个向量相对于某个法向量的反射向量
                    vec3 reflectionLight = reflect(-dir, vNormal);
                    vec3 eyeDirection = vCameraPos - vPos;
                    eyeDirection = normalize(eyeDirection);

                    // 与视线夹角余弦
                    float eyeCos = max(dot(eyeDirection, reflectionLight), 0.0);

                    // 镜面反射:指数取 20.0,系数取 3.0。
                    // 指数越大,镜面越聚焦,高光的光斑范围就越小。
                    // 系数能改变反射亮度,系数越大,反射的亮度就越高。
                    float specular = 3.0 *  pow(eyeCos, 20.0);

                    // 计算衰减
                    float decay = min(1.0, 1.0 /
                        (pointLightDecayFactor.x * pow(dis, 2.0) + pointLightDecayFactor.y * dis + pointLightDecayFactor.z));

                    // 计算漫反射
                    vec3 diffuse = decay * cos * pointLightColor;
                    
                    // 合成颜色
                    gl_FragColor.rgb = specular + (ambientLight + diffuse) * materialReflection;
                    gl_FragColor.a = 1.0;
                
            `;

            // 创建四个不同的几何体,初始化它们的环境光 ambientLight 以及材质反射率 materialReflection
            const sphereGeometry = new Sphere(gl);
            const cubeGeometry = new Box(gl);
            const cylinderGeometry = new Cylinder(gl);
            const torusGeometry = new Torus(gl);

            // 添加一个水平向右的白色平行光
            const ambientLight =  value: [1, 1, 1] ;

            const directional = 
                pointLightPosition:  value: [3, 3, 0] ,
                pointLightColor:  value: [0.5, 0.5, 0.5] ,
                pointLightDecayFactor:  value: [0, 0, 1] ,
            ;

            const program1 = new Program(gl, 
                vertex,
                fragment,
                uniforms: 
                    ambientLight,
                    materialReflection: value: [250/255, 128/255, 114/255],
                    ...directional
                ,
            );
            const program2 = new Program(gl, 
                vertex,
                fragment,
                uniforms: 
                    ambientLight,
                    materialReflection: value: [218/255, 165/255, 32/255],
                    ...directional
                ,
            );
            const program3 = new Program(gl, 
                vertex,
                fragment,
                uniforms: 
                    ambientLight,
                    materialReflection: value: [46/255, 139/255, 87/255],
                    ...directional
                ,
            );
            const program4 = new Program(gl, 
                vertex,
                fragment,
                uniforms: 
                    ambientLight,
                    materialReflection: value: [106/255, 90/255, 205/255],
                    ...directional
                ,
            );

            const torus = new Mesh(gl, geometry: torusGeometry, program: program1);
            torus.position.set(0, 1.3, 0);
            torus.setParent(scene);

            const sphere = new Mesh(gl, geometry: sphereGeometry, program: program2);
            sphere.position.set(1.3, 0, 0);
            sphere.setParent(scene);

            const cube = new Mesh(gl, geometry: cubeGeometry, program: program3);
            cube.position.set(0, -1.3, 0);
            cube.setParent(scene);

            const cylinder = new Mesh(gl, geometry: cylinderGeometry, program: program4);
            cylinder.position.set(-1.3, 0, 0);
            cylinder.setParent(scene);

            const controls = new Orbit(camera);

            // 添加动画
            requestAnimationFrame(update);
            function update() 
                requestAnimationFrame(update);
                controls.update();

                torus.rotation.y -= 0.02;
                sphere.rotation.y -= 0.03;
                cube.rotation.y -= 0.04;
                cylinder.rotation.y -= 0.02;

                renderer.render(scene, camera);
            

            // 添加控制
            const gui = new dat.GUI();
            const palette = 
                light: '#FFFFFF',
                reflection1: '#fa8072', // salmon rgb(250, 128, 114) [250/255, 128/255, 114/255, 1]
                reflection2: '#daa520', // goldenrod rgb(218, 165, 32) [218/255, 165/255, 32/255, 1]
                reflection3: '#2e8b57', // seagreen rgb(46, 139, 87) [46/255, 139/255, 87/255, 1]
                reflection4: '#6a5acd', // slateblue rgb(106, 90, 205) [106/255, 90/255, 205/255, 1]
            ;
            gui.addColor(palette, 'light').onChange((val) => 
                const color = new Color(val);
                program1.uniforms.ambientLight.value = color;
                program2.uniforms.ambientLight.value = color;
                program3.uniforms.ambientLight.value = color;
                program4.uniforms.ambientLight.value = color;
            );
            gui.addColor(palette, 'reflection1').onChange((val) => 
                program1.uniforms.materialReflection.value = new Color(val);
            );
            gui.addColor(palette, 'reflection2').onChange((val) => 
                program2.uniforms.materialReflection.value = new Color(val);
            );
            gui.addColor(palette, 'reflection3').onChange((val) => 
                program3.uniforms.materialReflection.value = new Color(val);
            );
            gui.addColor(palette, 'reflection4').onChange((val) => 
                program4.uniforms.materialReflection.value = new Color(val);
            );
        </script>
    </body>
</html>

什么是 Phong 反射模型?

冯氏反射模型是由美国犹他大学(University of Utah)的 Bui Tuong Phong 于1975年在他的博士论文中提出的,都用其名字命名。

Phong 光照模型是真实图形学中提出的第一个有影响的光照明模型,该模型只考虑物体对直接光照的反射作用,认为环境光是常量,没有考虑物体之间相互的反射光,物体间的反射光只用环境光表示。Phong光照模型属于简单光照模型。

Phong 模型认为物体表面反射光线由三部分组成:

  • 环境光(Ambient):场景中的其他间接光照
  • 漫反射(Diffuse):散射部分(大但不光亮)
  • 高光反射(Specular):镜面反射部分(小而亮)


在上图中,光线是白色的,环境光和漫反射部分是蓝色的,高光部分是白色的。

高光部分反射的光区域比较小,但强度很大;漫反射部分的强度根据物体表面方向的不同而不同;而环境光部分是跟方向无关的。

Phong 反射模型的完整公式如下:

光源部分:

  • lights:所有光源的集合,对于每盏光,可分为高光和漫反射两部分
  • i s i_s is:光源高光部分的强度(可以理解为就是RGB)
  • i d i_d id:光源漫反射部分的强度(可以理解为就是RGB)
  • i a i_a ia:环境光部分的强度(可以理解为就是RGB)

场景中材质的参数:

  • k s k_s ks:对入射光的高光反射常数(镜面反射系数)
  • k d k_d kd:对入射光的漫反射常数
  • k a k_a ka​:对环境光的反射常数
  • α:是和物体材质有关的常量,决定了镜面高光的范围。光泽度 α 越大,则亮点越小。

几个向量(全部归一化):

  • L m ^ \\hatL_m Lm^:物体表面某点指向光源m的位置的向量
  • N ^ \\hatN N^:物体表面某点的法线
  • R m ^ \\hatR_m Rm^:光源在物体表面某点发生镜面反射的方向
  • V ^ \\hatV V^:物体表面某点指向摄像机位置的向量

如何实现完整的 Phong 反射模型?

Phong 反射模型的实现整个过程分为三步:定义光源模型、定义几何体材质和实现着色器。

1、定义光源模型

属性(作用) /光源点光源平行光聚光灯
direction 方向 (定义光照方向)
position 位置 (定义光源位置)
color 颜色 (定义光的颜色)
decay 衰减 (光强度随着距离而减小)
angle 角度 (光传播的角度范围)

实现定义一个 Phong 类:用于添加和删除光源,并把光源的属性通过 uniforms 访问器属性转换成对应的 uniform 变量。

class Phong 
  constructor(ambientLight = [0.5, 0.5, 0.5]) 
    this.ambientLight = ambientLight;
    this.directionalLights = new Set();
    this.pointLights = new Set();
    this.spotLights = new Set();
  

  addLight(light) 
    const position, direction, color, decay, angle = light;
    if(!position && !direction) throw new TypeError('invalid light');
    light.color = color || [1, 1, 1];
    if(!position) this.directionalLights.add(light);
    else 
      light.decay = decay || [0, 0, 1];
      if(!angle) 
        this.pointLights.add(light);
       else 
        this.spotLights.add(light);
      
    
  

  removeLight(light) 
    if视觉高级篇23#如何模拟光照让3d场景更逼真?(上)(代码片段)

说明【跟月影学可视化】学习笔记。光照效果简介物体的光照效果是由光源、介质(物体的材质)和反射类型决定的,而反射类型又由物体的材质特点决定。在3D光照模型中,根据不同的光源特点分为四种:环... 查看详情

视觉高级篇23#如何模拟光照让3d场景更逼真?(上)(代码片段)

说明【跟月影学可视化】学习笔记。光照效果简介物体的光照效果是由光源、介质(物体的材质)和反射类型决定的,而反射类型又由物体的材质特点决定。在3D光照模型中,根据不同的光源特点分为四种:环... 查看详情

怎么让unity3d中的模型渲染更逼真

...能出效果,需要经验参考技术A除开模型,那就是材质和光照阴影了 参考技术B这个问题有点深。。。。 查看详情

视觉高级篇25#如何用法线贴图模拟真实物体表面(代码片段)

...同的表面,但实际上它又只是一个光滑的平面。对于视觉效果而言,它的效率比原有的凹凸表面更高,若在特定位置上应用光源,可以让细节程 查看详情

视觉高级篇22#如何用仿射变换来移动和旋转3d物体?(代码片段)

说明【跟月影学可视化】学习笔记。三维仿射变换:平移对于平移变换来说,如果向量P(x0​x_0​x0​​,y0y_0y0​​,z0​z_0​z0​​)沿着向量Q(x1x_1x1​​,y1​y_1​y1​​,z1​z_1​z1​​)平移,只需要让P加上Q,就能得... 查看详情

视觉高级篇22#如何用仿射变换来移动和旋转3d物体?(代码片段)

说明【跟月影学可视化】学习笔记。三维仿射变换:平移对于平移变换来说,如果向量P(x0​x_0​x0​​,y0y_0y0​​,z0​z_0​z0​​)沿着向量Q(x1x_1x1​​,y1​y_1​y1​​,z1​z_1​z1​​)平移,只需要让P加上Q,就能得... 查看详情

视觉高级篇27#如何实现简单的3d可视化图表:github贡献图表的3d可视化?(代码片段)

说明【跟月影学可视化】学习笔记。第一步:准备要展现的数据可以使用这个生成数据:https://github.com/sallar/github-contributions-api这里直接使用月影大佬的github提交数据的数据即可结构大致如下:第二步:用SpriteJS渲... 查看详情

arengine光照估计能力,让虚拟物体在现实世界更具真实感

AR是一项现实增强技术,即在视觉层面上实现虚拟物体和现实世界的深度融合,打造沉浸式AR交互体验。而想要增强虚拟物体与现实世界的融合效果,光照估计则是关键能力之一。人们所看到的世界外观,都是由光... 查看详情

视觉高级篇25#如何用法线贴图模拟真实物体表面(代码片段)

...同的表面,但实际上它又只是一个光滑的平面。对于视觉效果而言,它的效率比原有的凹凸表面更高,若在特定位置上应用光源,可以让细节程度较低的表面生成高细节程度的精确光照方向和反射效果。什么是切... 查看详情

视觉高级篇18#如何生成简单动画让图形动起来?(代码片段)

说明【跟月影学可视化】学习笔记。动画的三种形式固定帧动画:预先准备好要播放的静态图像,然后将这些图依次播放,实现起来最简单,只需要为每一帧准备一张图片,然后循环播放即可。增量动画:... 查看详情

求adobesubstance3dstager2021v1.0.0.5070网盘资源

...,支持多样的格式,涵盖从图像到Web及AR体验,给人类的视觉带来了新的突破。借助该软件,您可以在上下文中做出创意决策,实时完善和调整您的构图,直观呈现并编辑带有复杂光照和阴影的高级素材。还支持光线追踪的功能... 查看详情

如何创建逼真的 .scn 文件?

】如何创建逼真的.scn文件?【英文标题】:Howtocreaterealistic.scnfiles?【发布时间】:2017-12-0212:29:57【问题描述】:看看苹果示例AR应用,有许多看起来很逼真的物体(杯子、蜡烛等)。但是,在Xcode上使用场景工具包编辑器很明显... 查看详情

基于webgl(thingjs)的家具城商场3d展示3d可视化demo

本文将模拟一个家具城,让大家足不出户在家里就能更加直观立体的挑选家具。 第一步,利用CampusBuilder搭建模拟场景。CampusBuilder的模型库有各种各样的模型,使我们搭建出的场景更逼真。使用CampusBuilde创建层级,之后再给... 查看详情

unity虚拟仿真怎么样

...术,由计算机生成现实世界的再现和构想中的世界,借助视觉、听觉及触觉等多种传感通道与虚拟世界进行自然的交互。 参考技术BUnity是一种流行的游戏引擎,其强大的3D图形渲染和物理引擎技术,使其也被广泛应用于虚拟仿真... 查看详情

unity3d画面渲染官方教程对光照和渲染的介绍

...来描述模拟复杂光照行为,比如光的反弹(bounces)和它如何同世间万物进行交互的一系列的技术和数学模型的专业术语。准确的模拟“GI”(全局照明)是一件很有挑战的事情,并且具有很高的计算成本。正因为如此,游戏中(... 查看详情

unity光照系统简介

UNITY_光照系统光照系统又称照明系统;从字面意思理解,光照系统的作用就是给我们的场景带来光源,用于照亮场景。一个五彩缤纷的游戏场景肯定要比一个漆黑一片的游戏场景更具吸引力,想让游戏场景变的更漂亮,光照系统... 查看详情

我只用了3步,实现了一个逼真的3d场景渲染(代码片段)

给3D模型及环境场景渲染出兼具质感和真实感的材质效果,需要经历几步?显然,目前的3D模型材质渲染技术,还无法实现简单几步就能搞定的标准化作业来量化,完成一个质量过关的3D模型渲染,一般需要... 查看详情

3d场景的制作步骤

...的制作步骤1.场景建模的使用目的使用地形生成编辑器在模拟自然地形效果上有很大的优势,但是也有局限性。在表现比较规整的地形,比如一现代化城市,初具规模的小镇,地下宫殿等等就存在这样的局限。因为使用地形编辑... 查看详情