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

  • 13
    点赞
  • 124
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
计算机图形学作业题 1. 计算机中由图形的形状参数(方程或分析表达式的系数,线段的端点坐标等)加属性参数 (颜色、线型等)来表示图形称图形的参数表示;枚举出图形中所有的点称图形的点阵 表示,简称为图像(数字图像) 2. 什么是计算机图形学计算机图形学有哪些研究内容? 3. 计算机图形学有哪些应用领域? 4. 计算机图形学有哪些相关学科分支?它们的相互关系是怎样的? 5. 图形系统的软件系统由哪些软件组成?举例说明。 6. 了解计算机图形系统的硬件。 7. 什么是显示器的分辨率、纵横比、刷新率? 8. 什么是像素、分辨率、颜色数?分辨率、颜色数与显存的关系? 分辨率M(N、颜色个数K与显存大小V的关系: 例:分辨率为1024像素/行(768行/帧,每像素24位(bit)颜色(224种颜色)的显示 器,所需的显存为:1024(768(24位(bit)=1024(768(24/8=2359296字节(byte)。 或:每像素16777216种颜色(24位真彩色),1024(768的分辨率,所需显存为:102 4(768(log216777216位显存=2359296字节显存。 9. 什么是图元的生成?分别列举两种直线和圆扫描转换算法。 10. OpenGL由核心库GL(Graphics Library)和实用函数库GLU(Graphics Library Utilities)两个库组成。 11. 区域填充算法要求区域是连通的,因为只有在连通区域中,才可能将种子点的颜色扩 展到区域内的其它点。 区域可分为 向连通区域和 向连通区域。区域填充算法有 填充算法和 填充算法。 12. 字符生成有哪两种方式? 点阵式(bitmap fonts点阵字——raster光栅方法):采用逐位映射的方式得到字符的点阵和编码——字模位 点阵。 笔画式(outline fonts笔画字——stroke方法):将字符笔画分解为线段,以线段端点坐标为字符字模的编 码。 13. 图形信息包含图形的 和 。 14. 什么是图形变换?图形变换只改变图形的 不改变图形的 。图形变换包括 和 ( )。 15. 熟练掌握二维图形的齐次坐标表示、平移、比例、旋转、对称变换以及复合变换的方 法和原则。 16. 图形的几何变换包括 、 、 、 、 ;图形连续作一次以上的几何变换称 变换。 17. 试写出图示多边形绕点A(xo,yo)旋转的变换矩阵。要求写出求解过程及结果。 18. 试写出针对固定参考点、任意方向的比例变换矩阵。 19. 试写出对任意直线y=mx+b的对称变换矩阵。 20. 什么是窗口?什么是视区?什么是观察变换? 21. 简述二维观察变换的流程。 22. 试述窗口到视区的变换步骤,并推出变换矩阵。 23. 已知w1=10,w2=20,w3=40,w4=80, v1=80,v2=110,v3=10,v4=130, 窗口中一点P(15,60),求视区中的映射点P'? 24. 在观察变换前必须确定图形的哪部分在窗口内,那些部分在窗口外,这个选择处理过 程称为 。 25. 使用Open GL的变换函数,若程序中先后调用的几个变换函数所定义的矩阵及顺序为L, M, N,其作用顺序为: 。 26. 试列举你所知的直线和多边形裁剪算法。 27. 简述Cohen-Sutherland(代码)线段裁剪算法。 28. 窗口和多边形如下图,应用Sutherland- Hodgman算法(逐边裁剪算法),对多边形进行裁剪。请以左、上、右、下的顺序列出 窗口各边裁剪多边形后所得的多边形顶点表。 29. 任何满足欧拉公式的形体称为 形体。 30. 超二次曲面通过将额外的参数插入 曲面方程而形成。 31. 在曲线、曲面的表示上,参数方程有何优点? 32. 要变换参数曲线曲面可以直接变换它的 ,而对于非参数形式则必须变换 。 33. 欧几里得曲线是 物体,沿三维曲线路径的位置可用 参数描述。 34. 规格化参变量 t [0, 1] 使得曲线曲面的 容易确定。 35. 什么是插值?什么是逼近?什么是拟合? 36. 给定一组有序的数据点 Pi ,i =0, 1, …, n,称为控制点,构造一条曲线顺序通过每个控制点,称为对这组控制点进行 ,所构造的曲线称为 。 37. 构造一条曲线使之在某种意义下最接近给定的数据点,而不要求通过其中任何一个点 ,称为对这些数据点进行 ,所构造的曲线为 曲线。 38. 拟合(Fitting)是 和 的统称。 39. 对于一组有序的型值点,确定一种参数分割,称之对这组型值点 。确定某个单参数矢函数,即确定参数曲线方程,称为曲线的 。 40. 参数域中所有节点构成的序列称为 矢量。 41. 什么是参数化?什么是参数区间的规格化? 42. 什么是参数连续性? 二条曲线P

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值