欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  互联网

【计算机图形学】结课大作业—光照模型(3D场景)

程序员文章站 2022-03-12 21:57:32
效果 >_<...

效果 >_<

【计算机图形学】结课大作业—光照模型(3D场景)

 
 

技术栈

  1. 【前端】HTML / CSS / JavaScript
  2. 【图形学】WebGL / Three.js
     
     

思路

three.js开发一般是比较套路的——init() + animate()

  1. init()时把所有的场景摆放好
  2. animate()就是一个递归调用的渲染过程。
     

如何实现整个场景的搭建?

  1. 初始化场景(Scene)、相机(Camera)、渲染器(Render)、控件(control)自然不必多说——都是three.js基本操作,有非常固定的模板
  2. 一个模型是怎么形成的?以场景之一的地球仪为例:step1先新建一个球形的几何体(Geometry);step2接着新建一个标准材质(Material),然后给材质贴图(图片diffuse+凹凸感bump+粗糙感roughness);step3最后一就可以通过几何体+材质,生成一个网格模型(Mesh)啦!!!
  3. 如果不使用封装好的模型呢?那么我们将看到这些模型最原始的模样——矩阵。原生的GL代码实现一个立方体,我们需要把它分解为6个面,每个面又是一个矩阵;其实这和直接调用封装类也没啥区别,比如给出三个点、再给出一个方向向量,一个立方体所有点在空间中的位置就确定了——只是这个计算过程不需要我们写出而已
  4. 关于两个光源——灯泡点状光就是空间中一个向外辐散射线(或者说向量)的点,主要参数是强度和衰减;户外半球光模拟的是就是太阳射向地球的光照,这无限接近于平行光直射,主要参数有光强,甚至还模拟类真实的户外,天空光和地面光的双重作用
  5. 关于镜子——镜子就是先新建一个镜子载体,然后在这个载体上涂上反射(Reflect)材料。反射类THREE.Reflector是已经封装好了的,如果想要用原生GL代码实现,思路也不难:通过镜子平面做法线,找到摄像机关于平面法线的对称位置,将一个虚拟摄像机(virtualCamera)放在那里,将它渲染出的图形作为纹理贴到镜面就行啦!——这个过程中,矩阵计算将发挥极大的作用

 
 

代码结构

变量声明

var scene, camera, renderer 场景、相机、渲染器
var controls 控件
var metLoader 材质加载器
var stats; 状态显示类(FPS/显存占用)
var gui; 设置选择栏(光强1/光强2/曝光/阴影/显示镜子)
var bulbLight, hemiLight 光照(灯泡点状光+户外半球光)
var floorGeometry, floorMat, floorMesh 地板(几何体 + 材质 = 网格模型)
var ballGeometry, ballMat, ballMesh 地球仪(几何体 + 材质 = 网格模型)
var boxGeometry, boxMat, boxMesh 木盒子(几何体 + 材质 = 网格模型)
var cyGeometry, cyMat, cyMesh 木圆柱(几何体 + 材质 = 网格模型)
var wallGeometry, wallMat, wallMesh 墙(几何体 + 材质 = 网格模型)
var mirrorGeometry, mirror 镜子载体 + 镜子

关键函数

init(){...} 初始化
function animate(){...} 动画递归
unction render(){...} 渲染

 
 

完整代码

代码可直接运行。结构非常清晰。注释非常非常非常详细。

▽▽▽ index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <title>BULB</title>
    <link rel="stylesheet" type="text/css" href="style.css" />
    <script src="loli/three.js"></script>
    <script src="loli/stats.min.js"></script>
    <script src="loli/dat.gui.min.js"></script>
    <script src="loli/OrbitControls.js"></script>
    <script src="loli/Detector.js"></script>
    <script src="loli/Reflector.js"></script>
    <script src="samarua.js"></script>
</head>

<body>
    <div id="container"></div>
    <script>
        init();
        animate();
    </script>
</body>

</html>

▽▽▽ style.css

body {
    margin: 0;
}

▽▽▽ samarua.js

// >_< !


// 场景、相机、渲染器————three.js老三样
var scene, camera, renderer;

// 控件
var controls;

// 材质加载器
var metLoader;

// 时钟
var clock;

// 光照(灯泡点状光+户外半球光)
var bulbLight, hemiLight;

// 灯泡是整个场景的主角儿,我们给它加上几何体和材质
var bulbGeometry;
var bulbMat;


// 地板(几何体 + 材质 = 网格模型)
var floorGeometry, floorMat, floorMesh;
// 地球仪(几何体 + 材质 = 网格模型)
var ballGeometry, ballMat, ballMesh;
// 木盒子(几何体 + 材质 = 网格模型)
var boxGeometry, boxMat, boxMesh;
// 木头圆柱(几何体 + 材质 = 网格模型)
var cyGeometry, cyMat, cyMesh;
// 墙(几何体 + 材质 = 网格模型)
var wallGeometry, wallMat, wallMesh;

// 镜子载体+镜子
var mirrorGeometry, mirror;

// 状态显示类(FPS/显存占用)
var stats;

// 设置选择栏(光强1/光强2/曝光/阴影/显示镜子)
var gui;


//【灯泡点状光】
var bulbLightPowers = {
    // 1瓦(W)
    // 流明(lum/lm/lux/lx)
    "110000 lux (1000W)": 110000,
    "3500 lux (300W)": 3500,
    "1700 lux (100W)": 1700,
    "800 lux (60W)": 800,
    "400 lux (40W)": 400,
    "180 lux (25W)": 180,
    "20 lux (4W)": 20,
    "Off": 0
}

//【户外半球光】
var hemiLightPowers = {
    "0.0001 lux (无月之夜)": 0.0001,
    "0.5 lux (月圆之夜)": 0.5,
    "3.4 lux (路灯)": 3.4,
    "50 lux (阴雨)": 50,
    "100 lux (客厅)": 100,
    "350 lux (聚光灯)": 350,
    "1000 lux (奥特曼)": 1000,
    "50000 lux (核爆)": 50000
};

// 参数
var params = {
    shadows: true,
    exposure: 0.68,
    bulbPower: Object.keys(bulbLightPowers)[4],
    hemiPower: Object.keys(hemiLightPowers)[0],
    mirror: false
};





/**
 * 初始化
 */
function init() {
    initSCRC();         // 初始化场景、相机、渲染器、控件
    initPrepare();      // 初始化准备(始终、材质加载器)
    initLight();        // 初始化光照(两种)
    initFloor();        // 初始化地板
    initBall();         // 初始化地球仪
    initBox();          // 初始化木盒子
    initCy();           // 初始化木圆柱
    initWall();         // 初始化墙
    initMirror();       // 初始化镜子
    initMsg();          // 初始化状态信息
    initGUI();          // 初始化选择栏
}



/**
 * 初始化场景(Scene)、相机(Camera)、渲染器(Renderer)
 * 初始化控件(Controls)
 */
function initSCRC() {

    // 初始化场景
    scene = new THREE.Scene();

    // 初始化相机
    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
    camera.position.x = -4;
    camera.position.z = 4;
    camera.position.y = 2;

    // 初始化渲染器
    renderer = new THREE.WebGLRenderer();
    renderer.physicallyCorrectLights = true;
    renderer.gammaInput = true;
    renderer.gammaOutput = true;
    renderer.shadowMap.enabled = true;
    renderer.toneMapping = THREE.ReinhardToneMapping;
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.getElementById('container').appendChild(renderer.domElement);
    // 因为本Demo设计光影,因此渲染器更加复杂————(自上而下)允许光影精确渲染、伽玛色彩矫正、允许阴影、Reinhard经验公式、设置像素比

    // 初始化控件
    controls = new THREE.OrbitControls(camera, renderer.domElement);

}


/**
 * 初始化准备
 */
function initPrepare() {
    metLoader = new THREE.TextureLoader();
    clock = new THREE.Clock();
}


/**
 * 初始化光源(两个哦>_<)
 */
function initLight() {

    bulbLight = new THREE.PointLight(0xffee88, 1, 100, 2);              // 灯泡点状光(颜色、强度、距离、衰减)
    hemiLight = new THREE.HemisphereLight(0xddeeff, 0x0f0e0d, 0.02);    // 户外半球光(天空光颜色、地面光颜色、强度)

    // 灯泡是整个场景的主角儿,我们给它加上几何体和材质
    bulbGeometry = new THREE.SphereBufferGeometry(0.02, 16, 8);
    bulbMat = new THREE.MeshStandardMaterial({
        emissive: 0xffffee,
        emissiveIntensity: 1,
        color: 0x000000
    });
    bulbLight.add(new THREE.Mesh(bulbGeometry, bulbMat));
    bulbLight.position.set(0, 2, 0);
    bulbLight.castShadow = true;

    // 别忘了放置到场景中!
    scene.add(bulbLight);
    scene.add(hemiLight);

}




// 下面,将要初始化场景中的网格模型们
// 一定记住这个三步走:网格模型(Mesh) = 几何体(Geometry) + 材质(Meterial)

/**
 * 初始化地板
 */
function initFloor() {

    // Step1————几何体
    floorGeometry = new THREE.PlaneBufferGeometry(20, 20);

    // Step2————材质

    // Step2.1————标准材质(粗糙度/颜色/金属光泽/凹凸映射)
    floorMat = new THREE.MeshStandardMaterial({
        roughness: 0.8,
        color: 0xffffff,
        metalness: 0.2,
        bumpScale: 0.0005
    });


    // Step2.2————贴图(图片diffuse+凹凸感bump+粗糙感roughness)
    // http://www.yanhuangxueyuan.com/threejs/examples/textures/hardwood2_diffuse.jpg
    metLoader.load("loli/img/hardwood2_diffuse.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;       // x轴方向重复映射
        map.wrapT = THREE.RepeatWrapping;       // y轴方向重复映射
        map.anisotropy = 4;
        map.repeat.set(10, 24);                 // 重复映射次数(x轴,y轴)————贴图不够大,所以要这样做
        floorMat.map = map;
        floorMat.needsUpdate = true;
    });
    metLoader.load("loli/img/hardwood2_bump.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;
        map.wrapT = THREE.RepeatWrapping;
        map.anisotropy = 4;
        map.repeat.set(10, 24);
        floorMat.bumpMap = map;
        floorMat.needsUpdate = true;
    });
    metLoader.load("loli/img/hardwood2_roughness.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;
        map.wrapT = THREE.RepeatWrapping;
        map.anisotropy = 4;
        map.repeat.set(10, 24);
        floorMat.roughnessMap = map;
        floorMat.needsUpdate = true;
    });

    // Step3————网格模型
    floorMesh = new THREE.Mesh(floorGeometry, floorMat);
    floorMesh.receiveShadow = true;
    floorMesh.rotation.x = -Math.PI / 2.0;

    // 还是别忘了加到场景中>_<!
    scene.add(floorMesh);

    // 之后初始化网格模型的注释就不再这么详细了,一定要记住三部曲————几何体 + 材质 = 网格模型

}


/**
 * 初始化地球仪
 */
function initBall() {

    ballGeometry = new THREE.SphereBufferGeometry(0.5, 32, 32);

    ballMat = new THREE.MeshStandardMaterial({
        color: 0x4169E1,
        roughness: 0.5,
        metalness: 1.0
    });
    metLoader.load("loli/img/earth_atmos_2048.jpg", function (map) {
        map.anisotropy = 4;
        ballMat.map = map;
        ballMat.needsUpdate = true;
    });
    metLoader.load("loli/img/earth_specular_2048.jpg", function (map) {
        map.anisotropy = 4;
        ballMat.metalnessMap = map;
        ballMat.needsUpdate = true;
    });

    ballMesh = new THREE.Mesh(ballGeometry, ballMat);
    ballMesh.position.set(1, 0.5, 1);
    ballMesh.rotation.y = Math.PI;
    ballMesh.castShadow = true;

    scene.add(ballMesh);

}


/**
 * 初始化盒子
 */
function initBox() {

    boxGeometry = new THREE.BoxBufferGeometry(0.5, 0.5, 0.5);

    boxMat = new THREE.MeshStandardMaterial({
        roughness: 0.7,
        color: 0xffffff,
        bumpScale: 0.002,
        metalness: 0.2
    });
    metLoader.load("loli/img/brick_diffuse.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;
        map.wrapT = THREE.RepeatWrapping;
        map.anisotropy = 4;
        map.repeat.set(1, 1);
        boxMat.map = map;
        boxMat.needsUpdate = true;
    });
    metLoader.load("loli/img/brick_bump.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;
        map.wrapT = THREE.RepeatWrapping;
        map.anisotropy = 4;
        map.repeat.set(1, 1);
        boxMat.bumpMap = map;
        boxMat.needsUpdate = true;
    });

    boxMesh = new THREE.Mesh(boxGeometry, boxMat);
    boxMesh.position.set(-0.5, 0.25, -1);
    boxMesh.castShadow = true;

    scene.add(boxMesh);

}


/**
 * 初始化木头圆柱
 */
function initCy() {

    cyGeometry = new THREE.CylinderGeometry(0.3, 0.3, 1, 100, 100);

    cyMat = new THREE.MeshStandardMaterial({
        roughness: 0.8,
        color: 0xffffff,
        metalness: 0.2,
        bumpScale: 0.0005
    });
    metLoader.load("loli/img/hardwood2_diffuse.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;       // x轴方向重复映射
        map.wrapT = THREE.RepeatWrapping;       // y轴方向重复映射
        map.anisotropy = 4;
        map.repeat.set(10, 24);                 // 重复映射次数(x轴,y轴)————贴图不够大,所以要这样做
        cyMat.map = map;
        cyMat.needsUpdate = true;
    });
    metLoader.load("loli/img/hardwood2_bump.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;
        map.wrapT = THREE.RepeatWrapping;
        map.anisotropy = 4;
        map.repeat.set(10, 24);
        cyMat.bumpMap = map;
        cyMat.needsUpdate = true;
    });
    metLoader.load("loli/img/hardwood2_roughness.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;
        map.wrapT = THREE.RepeatWrapping;
        map.anisotropy = 4;
        map.repeat.set(10, 24);
        cyMat.roughnessMap = map;
        cyMat.needsUpdate = true;
    });

    cyMesh = new THREE.Mesh(cyGeometry, cyMat);
    cyMesh.position.x = -1.8;
    cyMesh.position.y = 0;
    cyMesh.position.z = 0;
    cyMesh.castShadow = true;

    scene.add(cyMesh);

}


/**
 * 初始化墙 
 */
function initWall() {

    wallGeometry = new THREE.BoxBufferGeometry(10, 8, 0.5);

    wallMat = new THREE.MeshStandardMaterial({
        roughness: 0.7,
        color: 0xffffff,
        bumpScale: 0.002,
        metalness: 0.2
    });
    metLoader.load("loli/img/brick_diffuse.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;
        map.wrapT = THREE.RepeatWrapping;
        map.anisotropy = 4;
        map.repeat.set(1, 1);
        wallMat.map = map;
        wallMat.needsUpdate = true;
    });
    metLoader.load("loli/img/brick_bump.jpg", function (map) {
        map.wrapS = THREE.RepeatWrapping;
        map.wrapT = THREE.RepeatWrapping;
        map.anisotropy = 4;
        map.repeat.set(1, 1);
        wallMat.bumpMap = map;
        wallMat.needsUpdate = true;
    });

    wallMesh = new THREE.Mesh(wallGeometry, wallMat);
    wallMesh.position.set(0, 0, -5);
    wallMesh.castShadow = true;

    scene.add(wallMesh);

}


/**
 * 初始化镜子
 */
function initMirror() {

    // 初始化
    mirrorGeometry = new THREE.PlaneBufferGeometry(10, 8);
    mirror = new THREE.Reflector(mirrorGeometry, {
        clipBias: 0.003,
        textrueWidth: window.innerWidth * window.devicePixelRatio,
        textrueHeight: window.innerHeight * window.devicePixelRatio,
        color: 0x777777,
        recursion: 1
    });

    // 摆放好位置
    // 镜像渲染不清晰...给放远一点...
    mirror.position.x = 20;
    mirror.position.y = 0;
    mirror.position.z = 2;
    mirror.rotation.y = -Math.PI / 2.0;
    scene.add(mirror);

}


/**
 * 初始化状态显示类(FPS/显存占用)
 */
function initMsg() {
    stats = new Stats();
    document.getElementById('container').appendChild(stats.dom);
}


/**
 * 初始化设置栏
 */
function initGUI() {

    gui = new dat.GUI();

    gui.add(params, 'bulbPower', Object.keys(bulbLightPowers));
    gui.add(params, 'hemiPower', Object.keys(hemiLightPowers));
    gui.add(params, 'exposure', 0, 1);
    gui.add(params, 'shadows');
    gui.add(params, 'mirror');
    gui.open();

}





/**
 * 动画(本质是递归渲染)
 */
function animate() {

    requestAnimationFrame(animate);

    render();

}


/**
 * 渲染
 */
var previousShadowMap = false;

function render() {

    // 灯光扩散相关参数(渲染器的属性)
    renderer.toneMappingExposure = Math.pow(params.exposure, 5.0);
    // 灯光阴影相关参数
    renderer.shadowMap.enabled = params.shadows;
    bulbLight.castShadow = params.shadows;
    if (params.shadows !== previousShadowMap) {
        floorMat.needsUpdate = true;
        ballMat.needsUpdate = true;
        boxMat.needsUpdate = true;
        previousShadowMap = params.shadows;
    }
    // 灯光亮度相关参数(灯光本身的属性)
    bulbLight.power = bulbLightPowers[params.bulbPower];
    bulbMat.emissiveIntensity = bulbLight.intensity / Math.pow(0.02, 2.0);
    // 环境光的相关参数
    hemiLight.intensity = hemiLightPowers[params.hemiPower];

    // 镜子的位置(位置调远,假装镜子没了...)
    mirror.position.x = params.mirror ? 5 : 100;

    // 控制灯泡运动的函数(1.25的基础上,用一个cos进行调整)
    var time = Date.now() * 0.0005;
    bulbLight.position.y = Math.cos(time) * 0.75 + 1.25;

    // 渲染
    renderer.render(scene, camera);

    // 左上角状态显示栏的更新
    stats.update();

}

 
 
 
 

 
 
 
 

 
 
 
 

E N D END END

本文地址:https://blog.csdn.net/m0_46202073/article/details/110632179