【温故知新】——BABYLON.js基础·常用知识点总结

前言:复习BABYLON.js官网101基础文档之后,总结了一些在公司项目中常用的基础知识点。官网文档地址:http://doc.babylonjs.com/babylon101/


一、创建场景的模板:

var createScene = function () {

    // 创建场景空间
    var scene = new BABYLON.Scene(engine);

    // 在场景中添加一个摄像头并将其附加到画布上
    var camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);

    // 在场景中添加灯光
    var light1 = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(1, 1, 0), scene);
    var light2 = new BABYLON.PointLight("light2", new BABYLON.Vector3(0, 1, -1), scene);

    //这是你创建和操作网格的地方
    var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene);
    return scene;

};

当编写自己的HTML时,只需要将createScene function嵌入到一个HTML页面结构中,并带有一个<script>标签和其他一些条目。

你需要加载巴比伦的javascript代码。同样,在平板电脑和手机上使用的是指针事件,而不是鼠标事件,所以PEP事件系统也需要加载。

 

二、创建一个固定形状的一般形式是

var shape = BABYLON.MeshBuilder.CreateShape(name, options, scene);

options参数允许您做一些事情,比如设置形状的大小,并设置您是否可以更新它。

1、 Box盒子

var box = BABYLON.MeshBuilder.CreateBox("box", {}, scene); //默认的盒子box

var myBox = BABYLON.MeshBuilder.CreateBox("myBox", {height: 5, width: 2, depth: 0.5}, scene);
  • option参数:

          size  height  width  depth(深度) faceColors(面颜色,6种颜色的数组,每个框面一个)

         faceUV(面UV6个矢量阵列,每个盒子面一个) updatable(可更新的)  sideOrientation(边方向)

2、Sphere球体

var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene); //默认的球体sphere

var mySphere = BABYLON.MeshBuilder.CreateSphere("mySphere", {diameter: 2, diameterX: 3}, scene);
  • option参数:

         segments(段数)  diameter(直径)   diameterX    diameterY   diameterZ   arc(弧度)

         slice(切片)  updatable(可更新的)  sideOrientation(边方向)

3、Plane平面

var plane = BABYLON.MeshBuilder.CreatePlane("plane", {}, scene); //默认的平面plane

var myPlane = BABYLON.MeshBuilder.CreatePlane("myPlane", {width: 5, height: 2}, scene);
  • option参数:

        size   width   height   updatable(可更新的)   sideOrientation(边方向)  

        frontUVs(前UV,只有当边方向设置为 sideOrientation:BABYLON.Mesh.DOUBLESIDE

        backUVs(后UV,只有当边方向设置为 sideOrientation:BABYLON.Mesh.DOUBLESIDE

        sourcePlane(源平面,源平面(数学)网格将被转换为)

        sourcePlane是一个平面网格的独特选择,它提供了一种方法来定向和定位它。

        现在只考虑它的方向在创建上是矢量(0,0,1)现在你要让方向成为矢量(0,1,1)然后你用它来创建一个源平面

var sourcePlane = new BABYLON.Plane(0, -1, 1, 0);

sourcePlane.normalize();

    这就创建了一个数学平面,它被用作定位源。第四个参数是向方向矢量方向移动的距离。这里暂时设为0

 

4、Ground地面

var ground = BABYLON.MeshBuilder.CreateGround("ground", {}, scene); //默认的地面ground

var myGround = BABYLON.MeshBuilder.CreateGround("myGround", {width: 6, height: 4, subdivsions: 4}, scene);
  • option参数:

          width    height    updatable(可更新的)    subdivisions(数量的平方细分)

          CreateGround的一个变体是createfromheightmap它可以让你形成起伏的地面而不是平坦的平面

     ① Face Colors or UV

         这只适用于有限数量的网格,它们有不同的面孔,如盒子,而不是球体。

         这让你可以给每个物体一个面,给他们一个单独的颜色或图像。

     找出面颜色和面UV

    ② Updatable

     当一个网格Updatable的参数集为true时,它意味着可以改变与网格的每个顶点相关联的数据,从而改变网格的形状。

    ③ Side Orientation

        侧方向选项用于改变网格的每一面

这个选项有四种可能的值:

    BABYLON.Mesh.FRONTSIDE,

    BABYLON.Mesh.BACKSIDE,

    BABYLON.Mesh.DOUBLESIDE,

    BABYLON.Mesh     //默认值是默认值,并且等于当前的FRONTSIDE

通过移动你的屏幕指针向左和向右旋转平面,在下面的例子中,你可以比较默认和双边……

   ④ Front and Back UV

    当一个网格有一个面向侧的选项时,它被设置为双侧DOUBLESIDE,那么前面和后面就有可能显示不同的图像。

三、创建参数化形状

101课程中,你只会遇到线条,你只需要使用MeshBuilder方法,而不是旧的传统网格Mesh方法。

 

1Lines线

线是一系列三维的线段,其中一条线段的末端是下一个线段的开始。线是由三维空间中的一系列点points来描述的。

这三个点(0,0,0),(0,1,1),(0,1,0)将形成两个线段。

这些点在Babylon.js中被构造为Vector3对象,并在一个数组中按顺序排列,以传递给CreateLines方法,以位置向量给出的。

var myPoints = [];

var point1 = new BABYLON.Vector3(0, 0, 0);
myPoints.push(point1);
var point2 = new BABYLON.Vector3(0, 1, 1);
myPoints.push(point2);
var point3 = new BABYLON.Vector3(0, 1, 0);
myPoints.push(point3);

//或者

var myPoints =[
    new BABYLON.Vector3(0, 0, 0),
    new BABYLON.Vector3(0, 1, 1),
    new BABYLON.Vector3(0, 1, 0)
];

然后,这些点的数组必须传递给点选项

//创建线lines

var lines = BABYLON.MeshBuilder.CreateLines("lines", {points: myPoints}, scene);

您可以创建虚线CreateDashedLines,并将破折号dashNb的数量作为一个选项。
  • CreateLines  option参数:

          points(向量的数组,这条线的路径)    updatable(可更新的)   instance(要更新的线网格的实例)

          colors(颜色数组,每个点颜色)   useVertexAlpha(false 表示不需要阿尔法混合--更快)

  • CreateDashedLines  option参数:

         points(向量的数组,这条线的路径)   dashSize(破折号的大小)  gapSize(缝隙的大小)

        dashNb(破折号的预定数量)   updatable(可更新的)   instance(要更新的线网格的实例)

  • Updatable Option

     实线和虚线有一个可更新的选项。当这是true的时候,就有可能改变与直线的每个顶点相关的数据,从而改变线的路径。

  • Instance Option

         线条也有一个实例选项,这意味着有另一种方法可以通过传递一组新的点来更新线路的路径。

      为了使其工作,创建的原始行必须具有可更新选项Updatable为true,并且所创建的线作为option实例的值传递。

         数组中的点数量必须保持不变。

//创建线

var lines = BABYLON.MeshBuilder.CreateLines("lines", {points: myArray, updatable: true}, scene);

//更新现有的线实例:这里不需要场景参数

lines = BABYLON.MeshBuilder.CreateLines("lines", {points: myNewArray, instance: lines});

大多数但不是所有的参数形状都有实例选项,所以可以用这种方式更新它们的网格。

 

四、参照系

Babylon.js使用两种参照系,世界轴和本地轴。世界轴的原点永远不会改变。

在所有的diagrams图表和playgrounds上X轴是红色的,Y轴是绿色的,Z轴是蓝色的

当网格被创建时,它们的中心被放置在世界轴的原点,它们的位置总是相对于世界轴。

本地轴与网格一起移动。本地轴的原点总是在网格的创建中心,不管它的位置是什么。

网格的旋转和放大的中心是在本地轴的原点,但是通过使用一个转换节点或一个矩阵来设置一个轴心点,它们可以被改变到那个点。

 

五、向量

所有的位置,旋转和缩放都是用new BABYLON.Vector3(x, y, z),并且可以单独设置。

 

六、定位、旋转和缩放

1Position——定位

    位置是pilot使用一个矢量(xyz)来表示轴,本地轴与试点移动。

pilot.position = new BABYLON.Vector3(2, 3, 4);

//或单个组件

pilot.position.x  =  2;
pilot.position.y  =  3;
pilot.position.z  =  4;

本地轴和世界轴仍然保持着相同的方向。

 

2、Rotation——旋转

3D空间中旋转的警告总是很棘手的。旋转被应用到形状的顺序改变了形状的最终方向,你还需要知道正在使用哪个参照系。

在BabylonJS中旋转设置使用

pilot.rotation = new BABYLON.Vector3(alpha, beta, gamma);

//或者

pilot.rotation.x  =  alpha; //绕x轴旋转
pilot.rotation.y  =  beta;  //绕y轴旋转
pilot.rotation.z  =  gamma; //绕z轴旋转

alpha, beta gamma是用弧度来测量的

在三次旋转之后,你需要立即停下来思考一下,你需要问三个不同的轴——“在哪个顺序中,他们应用了哪一种参考系,在哪个方向?”

下列两种惯例中的任何一种都可以被认为是在BabylonJS的旋转中使用的。因为两者都导致了相同的结果。

 

惯例1——本地轴Local Axes

对于本地轴的旋转,网格与旋转中心在轴的原点处,在坐标轴上关于本地轴的旋转顺序y,x,z,当你看正轴的方向时,所有的旋转都是逆时针的。

下图显示了试点的初始起始位置,然后是关于本地y轴的π/2的旋转,然后是关于本地x轴的π/2的旋转,最后是本地z轴的π/2的旋转。

 

惯例2——世界轴World Axes

与惯例1相比,旋转的中心不会改变,但是旋转的轴是这样的。

世界轴使用旋转是在本地轴的原点处将网格与旋转中心旋转,在坐标轴上关于世界轴的旋转顺序z,x,y,

当你看正轴的方向时,所有的旋转都是逆时针的。

 

总结:无论你怎么想它的结果都是一样的。下面的结果都是一样的

pilot.rotation = new BABYLON.Vector3(alpha, beta, gamma);

pilot.rotation.x  =  alpha;
pilot.rotation.y  =  beta;
pilot.rotation.z  =  gamma;

pilot.rotation.z  =  gamma;
pilot.rotation.x  =  alpha;
pilot.rotation.y  =  beta;

pilot.rotation.y  =  beta;
pilot.rotation.z  =  gamma;
pilot.rotation.x  =  alpha;

问题:如果你想要一个旋转的序列从一个关于x轴的旋转开始,然后是关于y轴,然后是z轴?

对于世界轴,你使用rotate method

对于本地轴,Babylon.js既有rotate method,也有 addRotation method.

你可以用addRotation method来排列一个旋转序列。

这个方法提供了一个关于一个轴的旋转值,它可以从第一个轴到最后一个,如下面的例子所示。

mesh.addRotation(Math.PI/2, 0, 0).addRotation(0, Math.PI/2, 0).addRotation(0, 0, Math.PI/2);

一个接一个从初始位置的试点,后跟一个绕本地x轴旋转π/ 2,然后绕本地y轴旋转π/ 2和最后绕本地z轴旋转π/ 2。

一般mesh.addRotation(alpha, beta, gamma) 至少需要alpha, beta, gamma中的两个为0,分别表示关于本地x,y,z轴的旋转

3、Scaling——缩放

//沿着x轴、y轴和z轴进行缩放
mesh.scaling = new BABYLON.Vector3(scale_x, scale_y, scale_z);
//或单独设置 mesh.scaling.y
= 5;

七、材质对光线的反应

无论这种材质是一种颜色还是一种质地,它对光线的反应都是不同的。

 

  1.Diffuse散射-在光下观察的材料的基本颜色或纹理;

  2.Specular高光-由光照射到材料上的高光;

  3.Emissive放射——材料的颜色或质地,就像自亮;

  4.Ambient环境-由环境背景照明所点燃的材料的颜色或纹理。

 

散射和高光材质需要一个光源light source 来创建

环境颜色需要设置场景的环境颜色,给环境背景照明。

scene.ambientColor = new BABYLON.Color3(1, 1, 1);

Transparency透明度——你可以通过材料看到的水平可以被设置,对于透明  

部分的图像,它可以被使用,这样材质的适当部分

是不可见的。这就需要设置一个alpha属性。

 

八、材质颜色

创建一个材质使用:
var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene);

用一种、一些或全部的:

  1. diffuseColor固有色贴图,
  2. specularColor高光反射颜色贴图,
  3. emissiveColor放射色 和
  4. ambientColor环境色贴图

来设置材料的颜色。

请记住,只有场景环境颜色scene ambient color 设置好时,才会使用ambientColor

var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene);

myMaterial.diffuseColor = new BABYLON.Color3(1, 0, 1);
myMaterial.specularColor = new BABYLON.Color3(0.5, 0.6, 0.87);
myMaterial.emissiveColor = new BABYLON.Color3(1, 1, 1);
myMaterial.ambientColor = new BABYLON.Color3(0.23, 0.98, 0.53);

mesh.material = myMaterial;
  • Ambient Color——环境颜色

         场景的环境颜色,必须存在,是白色的。

     当一个场景的环境颜色组件被设置为0时,例如红色,那么无论在材质环境中红色的值是什么,它都不会产生任何影响。

  • Transparent Color——透明色

     透明性是通过将一个材质alpha属性从0(不可见)设置为1(不透明)来实现的。

myMaterial.alpha = 0.5;

九、材质纹理

纹理是使用保存的图像形成的

//创建一个材质使用
var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene);

用一种、一些或全部的

  1. diffuseTexture固有纹理,
  2. specularTexture高光反射纹理,
  3. emissiveTexture放射纹理 和
  4. ambientTexture 环境纹理

  来设置材料的纹理。

  请记住,只有场景环境颜色scene ambient color 设置好时,才会使用ambientTexture

var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene);
myMaterial.diffuseTexture
= new BABYLON.Texture("PATH TO IMAGE", scene); myMaterial.specularTexture = new BABYLON.Texture("PATH TO IMAGE", scene); myMaterial.emissiveTexture = new BABYLON.Texture("PATH TO IMAGE", scene); myMaterial.ambientTexture = new BABYLON.Texture("PATH TO IMAGE", scene); mesh.material = myMaterial;
>>>>>注意:当没有指定法线时,Babylon's standard material 将计算法线

 透明材质纹理

① 透明度是通过将材质alpha属性从0(不可见)设置为1(不透明)来实现的。

myMaterial.alpha = 0.5;

② 此外,用于纹理的图像可能已经有了透明度设置。在这种情况下,我们将纹理的hasAlpha属性设置为true

myMaterial.diffuseTexture.hasAlpha = true;

>>>>>>为了让立方体的后面通过前面的透明区域可见,我们必须处理背部的选择。

 

③ Back Face Culling——背面剔除

这是一种有效地绘制3D模型的二维屏幕渲染的方法。

通常不需要画出立方体或其他物体的背面,因为它会被前面隐藏起来。

BabylonJS,默认设置是,正如你所期望的,设置为true

myMaterial.backFaceCulling = true;
  • WireFrame——线框

        你可以在线框模式下看到一个网格:

materialSphere1.wireframe = true;

十、相机

对于用户的输入控制,所有的相机都需要连接到画布上

camera.attachControl(canvas, true);

第二个参数是可选的,默认为false。当false的时候,在画布事件上的默认动作是被阻止的。设置为true,允许画布默认动作。

 

说明:

① 一个Gamepad(手柄)可以使用一个控制器。

② 用于触摸控制,无论是PEP还是hand.js都是必要的。

1、Universal Camera——万能相机

默认的行为是:

    1.keyboard键盘-左和右箭头移动相机左右,上下箭头移动它向前和向后;

    2.mouse鼠标——用相机旋转相机的原点;

    3.touch向左或向右滑动,左右移动相机,上下滑动,向前和向后移动;

    4.gamepad手柄-对应于设备。

 

构建一个万能相机

// 参数:名称、位置、场景
var camera = new BABYLON.UniversalCamera("UniversalCamera", new BABYLON.Vector3(0, 0, -10), scene);

// 把相机对准一个特定的位置。在这个例子中是场景的原点
camera.setTarget(BABYLON.Vector3.Zero());

// 把相机固定在画布上
camera.attachControl(canvas, true);

2、Arc Rotate Camera——弧旋转相机

  • 相机总是指向一个给定的目标位置,并可以在目标周围旋转,目标是旋转的中心。
  • 它可以用光标和鼠标来控制,也可以用触摸事件来控制。
  • 它相对于目标(地球)的位置可以由三个参数设定,alpha(弧度)经度(横切面)的旋转,beta(弧度)的纬度(纵切面)的旋转和半径(目标位置的距离)。
  • 由于技术原因,将beta值设置为0PI会导致问题,在这种情况下,beta偏移0.1弧度(大约0.6度)。
  • alpha和beta都是顺时针方向增长的
  • 相机的位置也可以由一个矢量来设定,这个矢量将会超过alpha、beta和半径的任何现值。这比计算所需的角度要容易得多。
  • 无论是使用键盘、鼠标还是触摸式滑动,都可以改变alpha值和向下方向的变化。

 

构建一个弧旋转相机:

//参数:alpha、beta、半径、目标位置、场景
var camera = new BABYLON.ArcRotateCamera("Camera", 0, 0, 10, new BABYLON.Vector3(0, 0, 0), scene);

//定位摄像机覆盖alpha,beta,半径
camera.setPosition(new BABYLON.Vector3(0, 0, 20));

//把相机连接到画布上
camera.attachControl(canvas, true);

通过使用CTRL+MouseLeftClick,也可以使用ArcRotateCamera来进行Panning移镜头操作。你可以指定使用MouseRightClick来替代,通过在attachControl调用中设置useCtrlForPanning:

camera.attachControl(canvas, noPreventDefault, useCtrlForPanning);

如果需要的话,你也可以通过设置来完全禁用panning :

scene.activeCamera.panningSensibility = 0;

3 . FollowCamera——跟随相机

  • 给它一个网格作为目标,无论它现在处于什么位置,它都会移动到一个目标位置来查看目标。
  • 当目标移动时,跟随相机也会移动。
  • 当它被创建时,跟随相机的初始位置设置,然后目标位置设置为三个参数:
  1.  距离目标的距离-摄像机半径camera.radius;
  2.  目标上方的高度
  3.  在x y平面上的角度
  • 相机移动到目标位置的速度是通过它的加速度(camera.cameraAcceleration)达到最大速度(camera.maxCameraSpeed)。

 

构建一个跟随相机:

// 参数:名称、位置、场景    
var camera = new BABYLON.FollowCamera("FollowCam", new BABYLON.Vector3(0, 10, -10), scene);

//半径:相机距离目标距离
camera.radius = 30;

// 相机高于目标中心(原点)的目标高度
camera.heightOffset = 10;

// 目标在x y平面上绕目标(中心)的目标旋转角度
camera.rotationOffset = 0;

//从当前位置到目标位置移动相机的加速度
camera.cameraAcceleration = 0.005

//停止的加速度
camera.maxCameraSpeed = 10

//把相机连接到画布上
camera.attachControl(canvas, true);

注意:在目标创建后设置相机目标,并注意到BABYLONJSv2.5变化。

创建目标网格

camera.target = targetMesh;   // 2.4和更早的版本
camera.lockedTarget = targetMesh; //2.5版本开始

十一、灯光

所有的网格都允许光通过它们,除非阴影生成被激活。允许的默认灯光数是4,但这可以增加。

1、The Point Light——点光源

var light = new BABYLON.PointLight("pointLight", new BABYLON.Vector3(1, 10, 1), scene);

2、The Directional Light——平行光源

var light = new BABYLON.DirectionalLight("DirectionalLight", new BABYLON.Vector3(0, -1, 0), scene);

3、The Spot Light——聚光灯

聚光灯是由一个位置、一个方向、一个角度和一个指数来定义的。这些值定义了一个从位置开始的光锥,向方向发射。 

在弧度中,角度定义了聚光灯的锥形光束的大小(照明区域),指数定义了光的衰减速度和距离(范围)。

var light = new BABYLON.SpotLight("spotLight", new BABYLON.Vector3(0, 30, -10), new BABYLON.Vector3(0, -1, 0), Math.PI / 3, 2, scene);

4 、The Hemispheric Light——半球光(模拟环境光)

  • 一个半球的光是一种简单的模拟环境光的方法。
  • 一个半球的光是由一个方向来定义的,通常是向天空“向上”。
  • 然后,通过设置颜色属性来实现完全的效果。
var light = new BABYLON.HemisphericLight("HemiLight", new BABYLON.Vector3(0, 1, 0), scene);

十二、灯光相关

1、Color Properties——颜色属性

  • 有三种灯光的属性会影响颜色。
  • 其中的两种漫射和高光适用于所有四种类型的光,第三种,地面颜色ground color,只适用于半球光。
  1.  Diffuse漫射给一个物体提供了基本的颜色
  2.  Specular高光在物体上产生高光颜色
  • 带有黑色背景色的白色半球光是一种有用的照明方法
  • 白色/黑色半球光-向上的像素白色(漫射),向下的像素黑色(底色)

 

2、Limitations——限制

Babylon.js允许你创建和注册尽可能多的灯,但要知道,一个标准材质只能处理一个定义的数字同步灯。

(默认情况下,这个值等于4,这意味着场景灯光列表中的前四个指示灯)。你可以用这段代码来改变这个数字:

var material = new BABYLON.StandardMaterial("mat", scene);
material.maxsimultaneousLights = 6;

但是要小心!因为有更多的动态灯光,Babylon.js将生成更大的阴影,这可能与手机或小型平板电脑等低端设备不兼容。

在这种情况下,Babylon.js将尝试用更少的灯光重新编译着色器。

 3、On, Off or Dimmer——开启,关闭或调光

  • 每一个灯都可以关闭
light.setEnabled(false);
  • 或打开
light.setEnabled(true);

想要调暗或调量光线?然后设置强度intensity属性(默认值为1

light0.intensity = 0.5;
light1.intensity = 2.4;

对于点灯和聚光灯你可以设置光的范围range熟悉

light.range = 100;

4、Choosing Meshes to Light——选择灯光点亮的网格

当一个光被创造出来时,所有的现有网格都将被它点亮。有两种方法可以将一些网格从被点亮的地方排除出去。

一个网格可以添加到独占的网格数组中,或者添加不被排除在包含的列表中。

被排除的网格的数量可能是决定使用哪种方法的一个因素。

  • Lighting Normals——照明法线

         光对一个网格的反应取决于每个网格顶点的值,称为法线。

         每个平面的正面是当法线指向你时你看到的,背面是相反的面。

         正如你所看到的,灯光只会影响到正面而不是背面。

 

  • Lightmaps——灯光贴图

         复杂的照明在运行时计算成本非常高。为了节省计算,灯光贴图可以用来储存计算出的照明,这种纹理将被应用到一个给定的网格中

var lightmap = new BABYLON.Texture("lightmap.png", scene);
var material = new BABYLON.StandardMaterial("material", scene);
material.lightmapTexture = lightmap

        使用纹理作为阴影贴图而不是光照贴图,设置 material.useLightmapAsShadowmap字段为true

        场景灯光与光照贴图混合的方式是基于场景中灯光的lightmapMode模式。

light.lightmapMode = BABYLON.Light.LIGHTMAP_DEFAULT;

        使光照贴图的纹理在光线应用后被混合。

light.lightmapMode = BABYLON.Light.LIGHTMAP_SPECULAR;

下面这个和LIGHTMAP_DEFAULT是一样的,除了光的高光和阴影将被应用

light.lightmapMode = BABYLON.Light.LIGHTMAP_SHADOWSONLY;

这和lightmapdefault是一样的除了只有从这个光中得到的阴影将被应用

 

  • Projection Texture——投影纹理

        在某些情况下,定义光的漫射颜色(扩散给物体的基本颜色)是很好的,从一个恒定的颜色的纹理。想象一下,你正在试图模拟教堂内部的灯光效果。

        穿过彩色玻璃的光线将投射在地面上。

        这同样适用于从投影仪上发出的光线,或者你可以在迪斯科舞厅看到的灯光效果。

>>>>为了支持这个特性,可以依赖于灯光的projectionTexture属性。到目前为止,只支持聚光灯SpotLight


 

注:转载请注明出处

猜你喜欢

转载自www.cnblogs.com/ljq66/p/9895598.html