【计算机图形学】结课大作业——光照模型(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


<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>BULBtitle>
    <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

你可能感兴趣的:(计算机图形学,图形学,webgl,three.js)