這篇主要記錄WebGL的一些基本要點,順便也學習下如何使用FBO與環境貼圖。先看下效果圖(須要支持WebGL,Chrome,火狐,IE11)。javascript
主要實現過程以下,先用FBO輸出當前環境在立方體紋理中,再畫出當前立方體,最後畫球,而且把FBO關聯的紋理貼在這個球面上。html
開始WebGL時,最好有些OpenGL基礎,在前面講Obj完善與MD2時,你們可能已經發現了,由於着色器的添加使用,原來一些Opengl大部分API已經沒有使用。WebGL就和這差很少,大部分功能是着色器完成主要功能,記錄下主要過程,你們能夠比較下前面的,看看是否是很像,爲了熟悉WebGL基本功能,本文沒有利用比較完善的框架,只是用到一個幫助計算矩陣的框架(gl-matrix.js).java
和使用OpenGL同樣,咱們要初始化使用環境,提取一些全局使用量。相關代碼以下:git
1 var gl;//WebGLRenderingContext 2 var cubeVBO;//Cube buffer ID 3 var sphereVBO;//球體VBO 4 var sphereEBO;//球體EBO 5 var cubeTexID;//立方體紋理ID 6 var fboBuffer;//楨緩存對象 7 var glCubeProgram;//立方體着色器應用 8 var glSphereProgram;//球體着色器應用 9 10 var fboWidth = 512;//楨緩存綁定紋理寬度 11 var fboHeight = 512;//楨緩存綁定紋理高度 12 var targets;//立方體貼圖六個方向 13 14 var pMatrix = mat4.create();//透視矩陣 15 var vMatrix = mat4.create();//視圖矩陣 16 var eyePos = vec3.fromValues(0.0, 1.0, 0.0);//眼睛位置 17 var eyeLookat = vec3.fromValues(0.0, -0.0, 0.0);//眼睛方向 18 var spherePos = vec3.fromValues(0.0, -0.0, 0.0);//球體位置 19 var canvanName; 20 21 function webGLStart(cName) { 22 canvanName = cName; 23 InitWebGL(); 24 InitCubeShader(); 25 InitSphereShader(); 26 InitCubeBuffer(); 27 InitSphereBuffer(); 28 InitFBOCube(); 29 //RenderFBO(); 30 gl.clearColor(0.0, 0.0, 0.0, 1.0); 31 gl.enable(gl.DEPTH_TEST); 32 tick(); 33 } 34 35 function InitWebGL() { 36 //var canvas = document.getElementById(canvanName); 37 InitGL(canvanName); 38 } 39 40 function InitGL(canvas) { 41 try { 42 //WebGLRenderingContext 43 gl = canvas.getContext("experimental-webgl"); 44 gl.viewportWidth = canvas.width; 45 gl.viewportHeight = canvas.height; 46 47 targets = [gl.TEXTURE_CUBE_MAP_POSITIVE_X, 48 gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 49 gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 50 gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 51 gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 52 gl.TEXTURE_CUBE_MAP_NEGATIVE_Z]; 53 } catch (e) { } 54 if (!gl) { alert("你的瀏覽器不支持WebGL"); } 55 }
在這裏,咱們初始化在網頁中WebGL的上下方環境,並給出一系列初始化過程。下面先給出房間,也就是其中立方體的相關代碼。github
1 function InitCubeShader() { 2 //WebGLShader 3 var shader_vertex = GetShader("cubeshader-vs"); 4 var shader_fragment = GetShader("cubeshader-fs"); 5 //WebglCubeProgram 6 glCubeProgram = gl.createProgram(); 7 gl.attachShader(glCubeProgram, shader_vertex); 8 gl.attachShader(glCubeProgram, shader_fragment); 9 gl.linkProgram(glCubeProgram); 10 if (!gl.getProgramParameter(glCubeProgram, gl.LINK_STATUS)) { 11 alert("Shader hava error."); 12 } 13 gl.useProgram(glCubeProgram); 14 glCubeProgram.positionAttribute = gl.getAttribLocation(glCubeProgram, "a_position"); 15 glCubeProgram.normalAttribute = gl.getAttribLocation(glCubeProgram, "a_normal"); 16 glCubeProgram.texCoordAttribute = gl.getAttribLocation(glCubeProgram, "a_texCoord"); 17 18 glCubeProgram.view = gl.getUniformLocation(glCubeProgram, "view"); 19 glCubeProgram.perspective = gl.getUniformLocation(glCubeProgram, "perspective"); 20 } 21 22 function InitCubeBuffer() { 23 var cubeData = [ 24 -10.0, -10.0, -10.0, 0.0, 0.0, -10.0, 1.0, 0.0, 25 -10.0, 10.0, -10.0, 0.0, 0.0, -10.0, 1.0, 1.0, 26 10.0, 10.0, -10.0, 0.0, 0.0, -10.0, 0.0, 1.0, 27 28 10.0, 10.0, -10.0, 0.0, 0.0, -10.0, 0.0, 1.0, 29 10.0, -10.0, -10.0, 0.0, 0.0, -10.0, 0.0, 0.0, 30 -10.0, -10.0, -10.0, 0.0, 0.0, -10.0, 1.0, 0.0, 31 32 -10.0, -10.0, 10.0, 0.0, 0.0, 10.0, 0.0, 0.0, 33 10.0, -10.0, 10.0, 0.0, 0.0, 10.0, 1.0, 0.0, 34 10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 1.0, 1.0, 35 36 10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 1.0, 1.0, 37 -10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 0.0, 1.0, 38 -10.0, -10.0, 10.0, 0.0, 0.0, 10.0, 0.0, 0.0, 39 40 -10.0, -10.0, -10.0, 0.0, -10.0, 0.0, 0.0, 0.0, 41 10.0, -10.0, -10.0, 0.0, -10.0, 0.0, 1.0, 0.0, 42 10.0, -10.0, 10.0, 0.0, -10.0, 0.0, 1.0, 1.0, 43 44 10.0, -10.0, 10.0, 0.0, -10.0, 0.0, 1.0, 1.0, 45 -10.0, -10.0, 10.0, 0.0, -10.0, 0.0, 0.0, 1.0, 46 -10.0, -10.0, -10.0, 0.0, -10.0, 0.0, 0.0, 0.0, 47 48 10.0, -10.0, -10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 49 10.0, 10.0, -10.0, 10.0, 0.0, 0.0, 1.0, 0.0, 50 10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 1.0, 1.0, 51 52 10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 1.0, 1.0, 53 10.0, -10.0, 10.0, 10.0, 0.0, 0.0, 0.0, 1.0, 54 10.0, -10.0, -10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 55 56 10.0, 10.0, -10.0, 0.0, 10.0, 0.0, 0.0, 0.0, 57 -10.0, 10.0, -10.0, 0.0, 10.0, 0.0, 1.0, 0.0, 58 -10.0, 10.0, 10.0, 0.0, 10.0, 0.0, 1.0, 1.0, 59 60 -10.0, 10.0, 10.0, 0.0, 10.0, 0.0, 1.0, 1.0, 61 10.0, 10.0, 10.0, 0.0, 10.0, 0.0, 0.0, 1.0, 62 10.0, 10.0, -10.0, 0.0, 10.0, 0.0, 0.0, 0.0, 63 64 -10.0, 10.0, -10.0, -10.0, 0.0, 0.0, 0.0, 0.0, 65 -10.0, -10.0, -10.0, -10.0, 0.0, 0.0, 1.0, 0.0, 66 -10.0, -10.0, 10.0, -10.0, 0.0, 0.0, 1.0, 1.0, 67 68 -10.0, -10.0, 10.0, -10.0, 0.0, 0.0, 1.0, 1.0, 69 -10.0, 10.0, 10.0, -10.0, 0.0, 0.0, 0.0, 1.0, 70 -10.0, 10.0, -10.0, -10.0, 0.0, 0.0, 0.0, 0.0, 71 ]; 72 cubeVBO = gl.createBuffer(); 73 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVBO); 74 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeData), gl.STATIC_DRAW); 75 } 76 77 function RenderCube() { 78 gl.useProgram(glCubeProgram); 79 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVBO); 80 81 gl.vertexAttribPointer(glCubeProgram.positionAttribute, 3, gl.FLOAT, false, 32, 0); 82 gl.enableVertexAttribArray(glCubeProgram.positionAttribute); 83 84 gl.vertexAttribPointer(glCubeProgram.normalAttribute, 3, gl.FLOAT, false, 32, 12); 85 gl.enableVertexAttribArray(glCubeProgram.normalAttribute); 86 87 gl.vertexAttribPointer(glCubeProgram.texCoordAttribute, 2, gl.FLOAT, false, 32, 24); 88 gl.enableVertexAttribArray(glCubeProgram.texCoordAttribute); 89 90 gl.uniformMatrix4fv(glCubeProgram.view, false, vMatrix); 91 gl.uniformMatrix4fv(glCubeProgram.perspective, false, pMatrix); 92 93 gl.drawArrays(gl.TRIANGLES, 0, 36); 94 }
上面的代碼主要分爲初始化立方體的着色器對象,初始化相關緩存,而後繪製立方體,能夠說在Opengl中,若是用着色器來畫,過程也是差很少的,在Opengl裏,已經沒有固定管線的一些功能如InterleavedArrays來指定是頂點仍是法線或是紋理了,統一用vertexAttribPointer來傳遞應用程序與着色器之間的數據。在前面 MD2楨動畫實現裏面後面的參數傳遞改進版也有相關應用。web
相應着立方體着色器主要代碼以下.算法
1 <script id="cubeshader-fs" type="x-shader/x-fragment"> 2 precision mediump float; 3 4 varying vec3 normal; 5 varying vec3 tex1; 6 varying vec3 tex2; 7 void main( void ) 8 { 9 float x = tex1.x * 6.28 * 8.0; //2兀 * 8 10 float y = tex1.y * 6.28 * 8.0; //2兀 * 8 11 //cos(x)= 8個 (1 -1 1) 12 gl_FragColor = vec4(tex2,1.0) * vec4(sign(cos(x)+cos(y))); // 13 //gl_FragColor = vec4(normal*vec3(0.5)+vec3(0.5), 1); 14 } 15 </script> 16 17 <script id="cubeshader-vs" type="x-shader/x-vertex"> 18 attribute vec3 a_position; 19 attribute vec3 a_normal; 20 attribute vec2 a_texCoord; 21 22 uniform mat4 view; 23 uniform mat4 perspective; 24 varying vec3 normal; 25 varying vec3 tex1; 26 varying vec3 tex2; 27 void main( void ) 28 { 29 gl_Position = perspective * view * vec4(a_position,1.0); 30 normal = a_normal; 31 tex1 = vec3(a_texCoord,0.0); 32 tex2 = normalize(a_position)*0.5+0.5; 33 } 34 </script>
着色器中,已經沒有ftransform()功能可供調用,要本身傳遞如模型,視圖,透視矩陣,在這裏,模型是以原點爲中心來繪畫,意思模型視圖矩陣也就是視圖矩陣,因此屏幕位置的計算只須要視圖和透視矩陣。在片段着色器中,x,y是從頂點着色器中的紋理座標傳遞過來,相應過程6.28*8.0,至關於8個360度,用於控制立方體上的方塊顯示,而tex2是着色器中的頂點映射[0,1]的值,分別給立方體的六面分別設置不一樣的意思,而後用二個矢量的乘積來混合這二種顏色顯示,gl_FragColor = vec4(tex2,1.0) * vec4(sign(cos(x)+cos(y)))。canvas
在顯示球體以前,應該先生成當前環境的立方體繪圖,在這裏使用FBO,先生成楨緩存和立方體繪理,並關聯,而後以原點爲中心,分別向上下左右前右繪圖,而後利用楨緩衝分別輸出到立方體上的六個面,主要代碼以下:瀏覽器
1 function InitFBOCube() { 2 // WebGLFramebuffer 3 fboBuffer = gl.createFramebuffer(); 4 gl.bindFramebuffer(gl.FRAMEBUFFER, fboBuffer); 5 fboBuffer.width = 512; 6 fboBuffer.height = 512; 7 8 cubeTexID = gl.createTexture(); 9 gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeTexID); 10 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 11 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 12 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 13 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 14 15 for (var i = 0; i < targets.length; i++) { 16 gl.texImage2D(targets[i], 0, gl.RGBA, fboBuffer.width, fboBuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 17 } 18 gl.bindFramebuffer(gl.FRAMEBUFFER, null); 19 } 20 21 function RenderFBO() { 22 gl.disable(gl.DEPTH_TEST); 23 gl.viewport(0, 0, fboBuffer.width, fboBuffer.height); 24 gl.clearColor(0.0, 0.0, 0.0, 1.0); 25 gl.bindFramebuffer(gl.FRAMEBUFFER, fboBuffer); 26 for (var i = 0; i < targets.length; i++) { 27 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, targets[i], cubeTexID, null); 28 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 29 } 30 31 mat4.perspective(pMatrix, 45, fboBuffer.width / fboBuffer.height, 0.1, 100.0); 32 for (var i = 0; i < targets.length; i++) { 33 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, targets[i], cubeTexID, null); 34 var lookat = vec3.create(); 35 var up = vec3.create(); 36 up[1] = 1.0; 37 if (i == 0) { 38 lookat[0] = -1.0; 39 } else if (i == 1) { 40 lookat[0] = 1.0; 41 } else if (i == 2) { 42 lookat[1] = -1.0; 43 up[0] = 1.0; 44 } else if (i == 3) { 45 lookat[1] = 1.0; 46 up[0] = 1.0; 47 } else if (i == 4) { 48 lookat[2] == -1.0; 49 } else if (i == 5) { 50 lookat[2] = 1.0; 51 } else { 52 } 53 //vec3.fromValues(0.0, 0.0, 0.0) 54 vMatrix = mat4.create(); 55 mat4.lookAt(vMatrix, vec3.fromValues(0.0, 0.0, 0.0), lookat, up); 56 //mat4.scale(vMatrix, vMatrix, vec3.fromValues(-1.0, -1.0, -1.0)); 57 //mat4.translate(vMatrix, vMatrix, spherePos); 58 RenderCube(); 59 } 60 gl.bindFramebuffer(gl.FRAMEBUFFER, null); 61 gl.enable(gl.DEPTH_TEST); 62 }
在上面不知是gl-matrix提供的矩陣算法有問題,仍是原本應該這樣,在上下面的時候生成的紋理圖不對,須要偏轉攝像機的向上矢量。由於這是攝像機位置與目標的生成的Z軸和設定的UP軸平行了,這樣致使不能正確計算X軸,而後對應的UP軸也計算不出來,相應視圖矩陣出現錯誤。緩存
最後是球體的繪畫,代碼主要和立方體的差很少,注意球體的頂點算法。
1 function InitSphereShader() { 2 //WebGLShader 3 var shader_vertex = GetShader("sphereshader-vs"); 4 var shader_fragment = GetShader("sphereshader-fs"); 5 //WebglCubeProgram 6 glSphereProgram = gl.createProgram(); 7 gl.attachShader(glSphereProgram, shader_vertex); 8 gl.attachShader(glSphereProgram, shader_fragment); 9 gl.linkProgram(glSphereProgram); 10 if (!gl.getProgramParameter(glSphereProgram, gl.LINK_STATUS)) { 11 alert("Shader hava error."); 12 } 13 glSphereProgram.positionAttribute = gl.getAttribLocation(glSphereProgram, "a_position"); 14 glSphereProgram.normalAttribute = gl.getAttribLocation(glSphereProgram, "a_normal"); 15 16 glSphereProgram.eye = gl.getUniformLocation(glSphereProgram, "eye"); 17 glSphereProgram.mapCube = gl.getUniformLocation(glSphereProgram, "mapCube"); 18 19 glSphereProgram.model = gl.getUniformLocation(glSphereProgram, "model"); 20 glSphereProgram.view = gl.getUniformLocation(glSphereProgram, "view"); 21 glSphereProgram.perspective = gl.getUniformLocation(glSphereProgram, "perspective"); 22 } 23 24 function InitSphereBuffer() { 25 var radius = 1; 26 var segments = 16; 27 var rings = 16; 28 var length = segments * rings * 6; 29 var sphereData = new Array(); 30 var sphereIndex = new Array(); 31 for (var y = 0; y < rings; y++) { 32 var phi = (y / (rings - 1)) * Math.PI; 33 for (var x = 0; x < segments; x++) { 34 var theta = (x / (segments - 1)) * 2 * Math.PI; 35 sphereData.push(radius * Math.sin(phi) * Math.cos(theta)); 36 sphereData.push(radius * Math.cos(phi)); 37 sphereData.push(radius * Math.sin(phi) * Math.sin(theta)); 38 sphereData.push(Math.sin(phi) * Math.cos(theta)); 39 sphereData.push(radius * Math.cos(phi)) 40 sphereData.push(Math.sin(phi) * Math.sin(theta)); 41 } 42 } 43 for (var y = 0; y < rings - 1; y++) { 44 for (var x = 0; x < segments - 1; x++) { 45 sphereIndex.push((y + 0) * segments + x); 46 sphereIndex.push((y + 1) * segments + x); 47 sphereIndex.push((y + 1) * segments + x + 1); 48 49 sphereIndex.push((y + 1) * segments + x + 1); 50 sphereIndex.push((y + 0) * segments + x + 1) 51 sphereIndex.push((y + 0) * segments + x); 52 } 53 } 54 sphereVBO = gl.createBuffer(); 55 gl.bindBuffer(gl.ARRAY_BUFFER, sphereVBO); 56 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphereData), gl.STATIC_DRAW); 57 sphereVBO.numItems = segments * rings; 58 sphereEBO = gl.createBuffer(); 59 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereEBO); 60 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(sphereIndex), gl.STATIC_DRAW); 61 sphereEBO.numItems = sphereIndex.length; 62 } 63 64 function RenderSphere() { 65 gl.useProgram(glSphereProgram); 66 gl.bindBuffer(gl.ARRAY_BUFFER, sphereVBO); 67 68 gl.vertexAttribPointer(glSphereProgram.positionAttribute, 3, gl.FLOAT, false, 24, 0); 69 gl.enableVertexAttribArray(glSphereProgram.positionAttribute); 70 71 gl.vertexAttribPointer(glSphereProgram.normalAttribute, 3, gl.FLOAT, false, 24, 12); 72 gl.enableVertexAttribArray(glSphereProgram.normalAttribute); 73 74 var mMatrix = mat4.create(); 75 mat4.translate(mMatrix, mMatrix, spherePos); 76 gl.uniform3f(glSphereProgram.eye, eyePos[0],eyePos[1],eyePos[2]); 77 gl.uniformMatrix4fv(glSphereProgram.model, false, mMatrix); 78 gl.uniformMatrix4fv(glSphereProgram.view, false, vMatrix); 79 gl.uniformMatrix4fv(glSphereProgram.perspective, false, pMatrix); 80 81 gl.activeTexture(gl.TEXTURE0); 82 gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeTexID); 83 //gl.uniformMatrix4fv(glSphereProgram.mapCube, 0); 84 85 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereEBO); 86 gl.drawElements(gl.TRIANGLES, sphereEBO.numItems, gl.UNSIGNED_SHORT, 0); 87 gl.bindTexture(gl.TEXTURE_2D, null); 88 }
能夠看到,也是和立方體同樣的三步,初始化着色器,初始化頂點與法線,繪畫。下面給出着色器代碼:
1 <script id="sphereshader-fs" type="x-shader/x-fragment"> 2 precision mediump float; 3 4 varying vec3 normal; 5 varying vec3 eyevec; 6 uniform samplerCube mapCube; 7 void main( void ) 8 { 9 gl_FragColor = textureCube(mapCube, reflect(normalize(-eyevec), normalize(normal))); 10 } 11 </script> 12 13 <script id="sphereshader-vs" type="x-shader/x-vertex"> 14 attribute vec3 a_position; 15 attribute vec3 a_normal; 16 17 uniform mat4 model; 18 uniform mat4 view; 19 uniform mat4 perspective; 20 uniform vec3 eye; 21 22 varying vec3 normal; 23 varying vec3 eyevec; 24 25 void main( void ) 26 { 27 gl_Position = perspective * view * model * vec4(a_position,1.0); 28 eyevec = -eye;// a_position.xyz; 29 normal = a_normal; 30 } 31 </script>
和前面立方體有點不一樣的是,球體有本身的模型矩陣,這也是通常正常的用法,而後傳遞眼睛對應球體頂點矢量與法線傳遞在片段着色器中,在片段着色器中,就有用到前面所生成的立方體紋理,咱們根據眼睛通過頂點經過對應法向量反射到立體體紋理上的點來獲取當前球體所對應的環境顏色,在這裏,咱們能夠直接調用textureCube來完成上面所說的過程,不須要咱們手動來計算。
其中GetShader函數的使用,參照了http://msdn.microsoft.com/zh-TW/library/ie/dn302360(v=vs.85) 這裏的講解。
能夠說,上面主要的繪製函數已經完成,可是咱們這個是能動的,因此須要模擬如客戶端環境每隔多久繪製一次,主要代碼以下:
1 function tick() { 2 Update(); 3 OnDraw(); 4 setTimeout(function () { tick() }, 15); 5 } 6 function OnDraw() { 7 //fbo rander CUBE_MAP 8 RenderFBO(); 9 //element rander 10 gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); 11 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 12 mat4.perspective(pMatrix, 45, gl.viewportWidth / gl.viewportHeight, 0.1, 200.0); 13 mat4.lookAt(vMatrix, eyePos, eyeLookat, vec3.fromValues(0.0, 1.0, 0.0)); 14 RenderCube(); 15 RenderSphere(); 16 } 17 18 var lastTime = new Date().getTime(); 19 function Update() { 20 var timeNow = new Date().getTime(); 21 if (lastTime != 0) { 22 var elapsed = timeNow - lastTime; 23 //3000控制人眼的旋轉速度。8控制人眼的遠近 24 eyePos[0] = Math.cos(elapsed / 3000) * 8; 25 eyePos[2] = Math.sin(elapsed / 2000) * 8; 26 27 spherePos[0] = Math.cos(elapsed / 4000) * 3; 28 spherePos[2] = Math.cos(elapsed / 4000) * 3; 29 } 30 31 }
在上面,每隔15毫秒調用一次Update與Draw函數,其中Update用於更新眼睛與球體位置,Draw繪畫。
有二年沒作網頁上的開發,可能其中javascript的用法讓你們見笑了,動態語言實在太自由了,反而很差寫。
在新的一年的第一天,祝你們事事如意。