Draw the HTML5 clock with pure Shading Language and shadinghtml5
Today is the last day of 2014. This time will always remind people of the clock. In a few hours, people on earth will be one year old, so let a HTML5 version of the clock is the task we want to complete today, the implementation of HTML5 clock rendering will generally adopt three ways, the first way to use CSS implementation, such as http://www.css-tricks.com/examples/CSS3Clock; the second method is to use SVG, such as http://www.css-tricks.com/examples/css3clock/?the third method to draw the 2dformat of cavnas. for example, the clock example customized in the vector manual of HT for Web is as follows, the implementation code is attached to the last part of this article.
The above three methods are easy to understand. Today we will adopt the WebGL pure Shading Language implementation method, which is extremely efficient, after all, we use the GPU hardware-accelerated WebGL technology. From the perspective of CPU code, there are only two triangles to draw, the drawing logic of the real dial is fully implemented when the GPU performs Fragment Shading on two triangles.
Through the http://js.do/hightopo/glsl-clock here play the final implementation effect and implementation code, the most important thing to use GLSL is to determine the current Coordinate Position of the gl_FragColor color, we will always be divided into the dial, outer ring, scale, hour hand, minute hand and second hand parts. The continuous Blend code behind the code is equivalent to the logic of layer-by-layer drawing. The following function technical points are described:
- Clamp (uv,-size/2.0, size/2.0) in the Rect function is our technique to determine whether a vertex is in the rectangle area.
- The Rotate (vec2 uv, float angle) function rotates coordinate points to a horizontal or vertical position for us to determine the Rect and Line parameters for comparison.
- Blend function mix (shapeColor, backColor, smoothstep (0.0, 0.005, shape) is a common mix and smoothstep for better edge smoothing GLSL Techniques
In order to illustrate the effect of mix and smoothstep fusion, I made an example of a http://js.do/hightopo/glsl-smooth-clrcle, you can try to remove the # define SMOOTH after the obvious problem of the edge sawtooth, you can also adjust smoothstep (0.49, 0.5, d) experience the progressive effect of small parameters such as 0.49 for 0.3. The following is a comprehensive comparison of the effects:
The implementation code of the GLSL Fragment Shader is as follows:
#ifdef GL_ESprecision mediump float;#endifuniform float time;uniform vec2 resolution;float pi = 3.1415926; float tau = pi * 2.0;vec2 Rotate(vec2 uv,float angle);float Circle(vec2 uv,float r);float Rect(vec2 uv,vec2 size,float r);float Line(vec2 uv,vec2 start,vec2 end,float r);float Merge(float a,float b);float Outline(float a,float r);vec3 Blend(vec3 backColor, vec3 shapeColor, float shape);float SecStep(float x); void main( void ) {vec2 res = resolution / resolution.y;vec2 uv = ( gl_FragCoord.xy / resolution.y );uv -= res / 2.0; float secAng = (SecStep(time) / 60.0) * tau;float minAng = (time / 3600.0) * tau;float hourAng = (time / 43200.0) * tau;float clockFace = Circle(uv, 0.45);float clockTrim = Outline(clockFace, 0.01);vec2 secDomain = Rotate(uv, secAng); float clockSec = Line(secDomain, vec2(0.0, -0.15), vec2(0.0, 0.35), 0.001);clockSec = Merge(clockSec, Circle(uv, 0.01));clockSec = Merge(clockSec, Rect(secDomain - vec2(0.0, -0.08), vec2(0.012, 0.07), 0.0));float clockMin = Line(Rotate(uv, minAng), vec2(0.0,-0.08), vec2(0.0, 0.35), 0.005);float clockHour = Line(Rotate(uv, hourAng), vec2(0.0,-0.05), vec2(0.0,0.3), 0.007);clockHour = Merge(clockHour, Circle(uv, 0.02));float tickMarks = 1.0;vec2 tickDomain = uv;for(int i = 0;i < 60;i++){tickDomain = Rotate(tickDomain, tau / 60.0);vec2 size = (mod(float(i + 1), 5.0) == 0.0) ? vec2(0.08, 0.01) : vec2(0.04, 0.002); tickMarks = Merge(tickMarks, Rect(tickDomain - vec2(0.38, 0.0), size, 0.0));}vec3 faceColor = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 1.0, 1.0), uv.x+0.5); vec3 trimColor = mix(vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0), uv.y + 0.5); vec3 secColor = vec3(1.0, 0.0, 0.0); vec3 handColor = vec3(0.0, 0.0, 0.0);vec3 color = mix(vec3(1.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0), uv.y+0.5);color = Blend(color, faceColor, clockFace);color = Blend(color, trimColor, clockTrim);color = Blend(color, trimColor, tickMarks); color = Blend(color, handColor, clockHour);color = Blend(color, handColor, clockMin);color = Blend(color, secColor, clockSec);gl_FragColor = vec4(color, 1.0);}float SecStep(float x){float interp = smoothstep(0.80, 1.0, mod(x, 1.0));return floor(x) + interp + (sin(interp * pi)) ;} float Line(vec2 uv,vec2 start,vec2 end,float r){return Rect(uv-(end+start)/2.0, vec2(r, end.y - start.y), r);}float Rect(vec2 uv,vec2 size,float r){return length(uv - clamp(uv, -size/2.0, size/2.0)) - r;} vec2 Rotate(vec2 uv,float angle){return mat2(cos(angle), sin(angle),-sin(angle), cos(angle)) * uv;}float Circle(vec2 uv,float r){return length(uv) - r;}float Merge(float a,float b){return min(a, b);}float Outline(float a,float r){return abs(a) - r;}vec3 Blend(vec3 backColor, vec3 shapeColor, float shape){ return mix(shapeColor, backColor, smoothstep(0.0, 0.005, shape));}
In the HT for Web vector manual, the custom clock example implementation code is as follows:
function init() {dataModel = new ht.DataModel();graphView = new ht.graph.GraphView(dataModel);view = graphView.getView();view.className = 'main';document.body.appendChild(view);window.addEventListener('resize', function(e) {graphView.invalidate();}, false);ht.Default.setCompType('clock-face', function(g, rect, comp, data, view) {var cx = rect.x + rect.width / 2;var cy = rect.y + rect.height / 2;var theta = 0;var r = Math.min(rect.width, rect.height)/2 * 0.92;g.strokeStyle = "#137";for (var i = 0; i < 60; i++) { g.beginPath();g.arc(cx + Math.cos(theta) * r, cy + Math.sin(theta) * r, i % 5 === 0 ? 4 : 1, 0, Math.PI * 2, true);g.closePath();g.lineWidth = i % 5 === 0 ? 2 : 1;g.stroke();theta = theta + (6 * Math.PI / 180);}});ht.Default.setImage('clock', {width: 500,height: 500,comps: [{type: 'circle',relative: true,rect: [0, 0, 1, 1],background: 'yellow',gradient: 'linear.northeast'},{type: 'clock-face',relative: true,rect: [0, 0, 1, 1]},{type: function(g, rect, comp, data, view) {// get current timevar date = data.a('date');if(!date){return;}var hours = date.getHours();var minutes = date.getMinutes();var seconds = date.getSeconds();hours = hours > 12 ? hours - 12 : hours;var hour = hours + minutes / 60;var minute = minutes + seconds / 60;var clockRadius = 250;// save current contextg.save();g.translate(clockRadius, clockRadius);g.beginPath();// draw numbersg.font = '36px Arial';g.fillStyle = '#000';g.textAlign = 'center';g.textBaseline = 'middle';for (var n = 1; n <= 12; n++) {var theta = (n - 3) * (Math.PI * 2) / 12;var x = clockRadius * 0.75 * Math.cos(theta);var y = clockRadius * 0.75 * Math.sin(theta);g.fillText(n, x, y);}// draw hourg.save();var theta = (hour - 3) * 2 * Math.PI / 12;g.rotate(theta);g.beginPath();g.moveTo(-15, -5);g.lineTo(-15, 5);g.lineTo(clockRadius * 0.5, 1);g.lineTo(clockRadius * 0.5, -1);g.fill();g.restore();// draw minuteg.save();var theta = (minute - 15) * 2 * Math.PI / 60;g.rotate(theta);g.beginPath();g.moveTo(-15, -4);g.lineTo(-15, 4);g.lineTo(clockRadius * 0.8, 1);g.lineTo(clockRadius * 0.8, -1);g.fill();g.restore();// draw secondg.save();var theta = (seconds - 15) * 2 * Math.PI / 60;g.rotate(theta);g.beginPath();g.moveTo(-15, -3);g.lineTo(-15, 3);g.lineTo(clockRadius * 0.9, 1);g.lineTo(clockRadius * 0.9, -1);g.fillStyle = '#0f0';g.fill();g.restore();g.restore();}}]});var node = new ht.Node();node.setPosition(150, 150);node.setSize(250, 250);node.setImage('clock');node.a('date', new Date());node.s('image.stretch', 'centerUniform');dataModel.add(node);graphView.setEditable(true);setInterval(function(){node.a('date', new Date());}, 1000);}