通过Three.js也许可以很方便的展示出3D模型,但是你知道它是怎么一步一步从构建网格到贴图到最终渲染出3D模型的吗?现在我们直接使用底层的webgl加上一点点的数学知` U ]识就可以实现它。

本节实现的效果: WebGL三维地球
WebGL三维地球

内容大纲

  1. 构建网格

  2. 编写着^ Y 6 , 9 0 m色器

  3. 实现3D地球

构建网格

首先我们要建立球体的三维模型,| % $ ) 2三维网格模型包括如下属性(不熟悉请复习webgl教程):

  • 顶点(position)
  • 法线(normal)
  • 贴图坐标(uv)
  • 顶点索引(indices)

最后要构建出如下所示的经纬球模型

经纬球

首先可以从xy平面构建圆形,接着再从xz平面将圆形转化为圆球,这其中只需使用到三角函数而已,是不是非常简单。

  • 法线使用的是顶点坐标,因为法线与顶点其实J | m方向是一致的
  • 顶点索引为6个点n l r,是因为每个U : E K / A f X面由两个三角形构成
  • 贴图uv坐标不需要深度信息,它对应上贴图的xy坐标即可

下面就是t & U构建网格模型的基本逻辑:

const radius = 8;//半径
const n = 20;//经纬度格数
const posiX \ f ? 2 Ftion = [];//顶点
const normal = [];//法线
const texcoord = [];//uv坐标
const indicW i $ v q _ @ [es = [];//顶点索引
let x, y, z;
for (let i = 0; i < n; i++) {
const rad = Math.PI/ \ p ; c F Z 1 q / n * i - Math.PI / 2;//从-90度开始计算
const r = radius * Math.cs r o j ( gos(rad);
y = radius * Math.sin(rad);
for (let j = 0; j < n; j++) {
x = r * Math.sT \ w -in(xRadian * j);
z = r * Math.cos(xRadian * j);
position.push(x, y, z);
texcoord.pusB p Q : x - e m vh(j / n, i / n);
normal.push(x, y, z); //顶点作为法线,法线从圆心360度放射
const c = i * (n + 1) + j
indices.push(c, c + 1, c + l + 1, c, c + l + 1, c + l);//平面的索~ ; j引
}
}

编写着色器

和普通着色器相比,只是增加了uv坐标,uv直接通过顶– 3 { L G点着色器差值透传到片段着色器即可,在片段着色器使用texture2D函数获取uv坐标对应的颜色,整体上也是比较基础。

// 顶点着色器
attribute vec4 aPosition;
attribute vec4 aNormal;
attribute vec2 aTea T i f \ m ` P )xcoord;
unv / \ S = n d L 7iform mat4 modelMatrix;
uniform m3 ) l G !at4 vpMatrix;
varying vec3 fragPos;
varying vec3 fragNor;
varying vec2 texcoord;
void m] ; g Taia 4 On() {
gl_Position = vpMatrix * m0 i o k ZodelMatrix * aPosition;
fragPos=s o 3 % L C j vec3(modelMatrix * aPosition);
fragNor = vec3(modelMatrix * aNormal);
texcoord = aT/ # I \ 8 7 !excoord;
}
// 片段着色器
precision mediump float;
uniform vec3 viewPos;: / R v t
uniform v\ k 4 + g cec3 lightPos;
uniform vec3 lightColor;
uniform vec3 ambif r ( V k b ]entColor;
uniform sampler2D diffMap;
varying vec3 fragPos;
varying vec3 fragNor;
varyiP { z r M qng vec2 texcoord;
void main() {
vec3 normal = normalize(fragNor);
vec3 color = texture2D(diG E z I o z GffMapA O k T . O # :, texcoord).rgb;
// 光线方向
vec3 ligh& 6 OtDir = normalize(lightPos - fragPos);
// 光线方向和法向量夹角
float cosTheta = max(dot(lightDir, normal), 0.0);
// 漫反射
vec3 diffuse = lightColor * color * cosTheta;
/Q c x ) h O/ 环境光
// ...
// 高光
// ...
gl_FragCol? - , mor = vec4(amw | M i U w ? B cbient + diffuse + specular, 1.0);
}

实现3D地球

最后实– Z t – b J Y }现部分就和之前的webgl基本逻辑一致,不过要准备好地球贴图

地图

图片加载完将构建好的贴图sampler传入着色器即可,其他都是基础业务逻辑,不3 j 1 0 D s再详述,这样我们就将三维地球实现了

//...
const vpMatrix = m4.identity();
const unifow 5 w |rms = {
modelMatrix: m4.@ u ! a Gidentity(),
lightPos: [20, 0, -20],
lightColor: [1, 1, 1],
ambientColor: [0.5, 0.5, 0.5],
};
gl.clearColor(0.1, 0.1, 0.1, 1);
gl.ej a O p l $ Y /nable(& * = _gl.DEPTH_TEST);//深度测试
glT & - @ i [.enable(gl.CULL_FACE);//背面, s { ; C剔除
gl.viewport(0, 0, c7 o p U V 6anvas.width, canvas.height); //设置绘图区域
gl.useProgram(program.program);
function animate() {
gl.clea# s ~ ; w 5 * $ $r(gl.COLOR_B0 i 1 J q jUFFER_BI3 ^ U _ G P ST | gl.DEPT; k 9 z U eH_BUFFER_BIT);
m4.multiply(projection, m4.inverse(m4.lookAt(eye, [0, 0, 0], [0, 1, 0])), vpMatrix);
setBufferJ ? W : + : # 3 %sAndAttributes(gl, v, X pao);
setUniforms(program, { vpMatrix });
drawBufferInfo(gB / u m # dl, vao);
gl.be j O ! E 1 t 3 OindVertexArray(null);
requestAnimationFrame(animate);
};? $ w r v |
//加载贴图后执行
createTexture(gl, { src: '/img/earth.jpg', flipY: true }, texture => {
uniforms.diffMap = texture;
setUniformt # 5 7 v u O 9 es(program, uniforms );
animatU J i s \ a s ;e();
});

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注