為了加深自己對着色器語言的認識,於是就着手寫了一個簡版的"飛線"。
做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圖: