为了加深自己对着色器语言的认识,于是就着手写了一个简版的"飞线"。
做3D的或者做可视化的应该对这个词不陌生,一般会用在地理方面的3D需求上,废话不多说,先上今天的demo的gif图示:
看完效果,让我们继续一步一步地看是怎么实现的
一、写在着色器之前
三部分:
1⃣️地球
// 添加地球 var globeMesh; function addglobe() { var globeTextureLoader = new THREE.TextureLoader(); globeTextureLoader.load('../texture/earth.jpeg', function (texture1) { console.log(texture1) var globeGgeometry = new THREE.SphereGeometry(60, 100, 100); var globeMaterial = new THREE.MeshStandardMaterial({map: texture1}); globeMesh = new THREE.Mesh(globeGgeometry, globeMaterial); scene.add(globeMesh); }); }
地球的旋转,直接在每次渲染的时候改rotation就好了,这里不啰嗦。
2⃣️路径线
var flyline; function addline(){ var curve = new THREE.CubicBezierCurve3( new THREE.Vector3( -70, 0, 0 ), new THREE.Vector3( -35, 100, 0 ), new THREE.Vector3( 35, 100, 0 ), new THREE.Vector3( 70, 0, 0 ) ); var points = curve.getPoints( 50 ); var geometry = new THREE.BufferGeometry().setFromPoints( points ); var material = new THREE.LineBasicMaterial( { color : 0xff0000 } ); flyline = new THREE.Line( geometry, material ); scene.add(flyline); }
利用three.js提供的贝塞尔曲线类结合材质生成了上面你所看到的贝塞尔曲线。
3⃣️夜空背景
var scene; function initScene() { scene = new THREE.Scene(); var bgTexture = new THREE.TextureLoader().load("../texture/starfiled.jpeg"); scene.background = bgTexture; }
不难看出是给场景添加了一个背景贴图。
二、线条的材质替换成shader材质
我们可以看到,目前线条的材质是带有特定颜色的 THREE.LineBasicMaterial 材质。现在我们要将其换成shader材质。
//创建ShaderMaterial纹理的函数 function createMaterial(vertexShader, fragmentShader) { var vertShader = document.getElementById(vertexShader).innerHTML; //获取顶点着色器的代码 var fragShader = document.getElementById(fragmentShader).innerHTML; //获取片元着色器的代码 //配置着色器里面的attribute变量的值 var attributes = {}; //配置着色器里面的uniform变量的值 var uniforms = { time: {type: 'f', value: 1.0} }; var meshMaterial = new THREE.ShaderMaterial({ uniforms: uniforms, defaultAttributeValues : attributes, vertexShader: vertShader, fragmentShader: fragShader, transparent: true }); return meshMaterial; }
这段生成shader材质的代码其实很简单,注释都有,就不啰嗦了,因为这个简要的demo所用到的动画只需要一个参数,那么我们就只传一个参数吧(time)。
顶点着色器代码:
<!-- 顶点着色器 --> <script id="vertex-shader" type="x-shader/x-vertex"> void main(){ vec3 posChanged = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(posChanged,1.0); } </script>
代码只做了一件事,将物体顶点进行了矩阵变换,转化为屏幕上的点(上一篇博客也有)。
片元着色器代码:
<script id="fragment-shader-7" type="x-shader/x-fragment"> uniform float time;
void main( void ) { float start = time; float end = start + 20.0; float opacity = 0.0;
if(gl_FragCoord.x > start && gl_FragCoord.x < end){ opacity = 1.0; } gl_FragColor = vec4(1.0,1.0,1.0,opacity); } </script>
因为片元着色器逐点绘制,所以对于gl_FragCoord这个内置变量,表示的就是屏幕上的点(一个window下,iframe下也是一个单独window);
gl_FragCoord 坐标是以左下角为(0,0),右上角为(屏宽,屏高),拿上述iframe来说,右上角就是(966,772);其实更准确的说法请参照下面这段话:
但今天不是来研究这些的,我们继续看着色器代码。
因为视图是每一帧绘制一次,假如现在是第199帧(time+=1.0 了199次,此时是200.0)
void main( void ) { float start = time; //200 float end = start + 20.0; //220 float opacity = 0.0; if(gl_FragCoord.x > start && gl_FragCoord.x < end){ opacity = 1.0; } gl_FragColor = vec4(1.0,1.0,1.0,opacity); }
那么,上述曲线显示的部分,左端点的x轴就是200像素点,右端点的x轴就是220像素点。针对着色器传过来的顶点(逐个遍历),顶点x不在该范围内的顶点的颜色透明度都是0,也就是看不到,
到了下一帧(time值变化),这个可视区域的范围就又就变了,于是一帧一帧合起来就重现了我们刚开始看到的那张gif图: