{"id":7,"date":"2017-01-11T23:23:20","date_gmt":"2017-01-12T04:23:20","guid":{"rendered":"http:\/\/mattrobertson.ca\/blog\/?p=7"},"modified":"2017-01-11T23:30:56","modified_gmt":"2017-01-12T04:30:56","slug":"diablo-style-resource-bars-in-two-tris-or-less","status":"publish","type":"post","link":"https:\/\/mattrobertson.ca\/blog\/2017\/01\/11\/diablo-style-resource-bars-in-two-tris-or-less\/","title":{"rendered":"Diablo Style Resource Bars in Two Tris or Less"},"content":{"rendered":"<blockquote><p>For all the video games I&#8217;ve played and all the interesting UIs I&#8217;ve seen, I dare say the resource bars used in the <a href=\"http:\/\/us.battle.net\/d3\/en\/\">Diablo <\/a>series are my favourite. So what better way to break into blogging than an attempt at recreating them? Before we get into the process, here&#8217;s the final product:<br \/>\n<iframe loading=\"lazy\" src=\"https:\/\/www.shadertoy.com\/embed\/llcSRf?gui=true&amp;t=10&amp;paused=true&amp;muted=false\" width=\"640\" height=\"360\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p><\/blockquote>\n<p>My first goal was to emulate the comparatively simple orbs from Diablo II, pictured below. To do this I needed a few things: a way to render a sphere up to a certain level, a specular highlight\/shadow, and a nice beveled border to surround the orb.<\/p>\n<figure id=\"attachment_28\" aria-describedby=\"caption-attachment-28\" style=\"width: 116px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-28\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2016\/12\/Diablo2.png\" alt=\"\" width=\"116\" height=\"96\" \/><figcaption id=\"caption-attachment-28\" class=\"wp-caption-text\">Source: <a href=\"http:\/\/us.blizzard.com\/en-us\/games\/d2\/\">Diablo 2<\/a><\/figcaption><\/figure>\n<p>To do this, I create a rather simple Orb object to contain the orb&#8217;s colour, fill level, radius, and position. Rendering a partially filled circle is then done in a in a rather hacky way:<\/p>\n<pre class=\"line-numbers\"><code class='language-cpp'>\r\nfloat isColoured(in vec2 uv, in Orb orb){\r\n    float orbBottom = orb.center.y - orb.radius;\r\n    float level = orbBottom + orb.value * orb.radius * 2.;\r\n    if (distance(uv,orb.center) < orb.radius &#038;&#038; (uv.y < level)) {\r\n        return 1.;\r\n    }\r\n    return 0.;\r\n}<\/code><\/pre>\n<p>This of course means that the fill line is a linear progress bar and doesn't represent filled area, but my guess is that Diablo doesn't do that either. The border is done by colouring a threshold around the entire circle and doing a simple linear fade as we move away from the actual edge of the circle. <\/p>\n<pre class=\"line-numbers\"><code class='language-cpp'>\r\nvec3 applyBorder(in vec2 uv, in Orb orb, vec3 currentColour){\r\n    vec3 colour = currentColour;\r\n    float borderDist = orb.radius - distance(uv, orb.center);\r\n    \/\/ Add in a bevelled border\r\n    if (abs(borderDist) < .005) {\r\n        colour = vec3(0.3,0.3,0.3);\r\n        colour += vec3(.7 - abs(borderDist)\/.005) * .25;\r\n    }\r\n    return colour;\r\n}\r\n<\/code><\/pre>\n<p>To do our specular highlight, we convert our uv to a 3d-point and apply the highlight math from wikibooks.<\/p>\n<pre class=\"line-numbers\"><code class='language-cpp'>\r\nvec3 applyHighlight(in vec2 uv, in Orb orb, in Light light, in vec3 eye){\r\n    vec3 colour = vec3(0.);\r\n    if (distance(orb.center, uv) >= orb.radius){\r\n        return colour;\r\n     }\r\n    \r\n    vec2 distFromCent = uv - orb.center;    \r\n    float uvHeight = sqrt(orb.radius - (pow(distFromCent.x,2.) + pow(distFromCent.y,2.)));\r\n    vec3 uvw = vec3(uv, uvHeight);\r\n    vec3 normal = normalize(vec3(uv, uvHeight) - vec3(orb.center, 0.));\r\n    vec3 orbToLight = normalize(light.pos - vec3(orb.center, 0.));\r\n    \r\n    return vec3(pow(dot(reflect(normalize(uvw - light.pos),\r\n                                normal),\r\n                        normalize(eye - uvw)),\r\n                    55.));\r\n}\r\n<\/code><\/pre>\n<p> At this point, I had a decent approximation of the Diablo 2 orbs.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-13\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2016\/12\/NoTextures-300x169.jpg\" alt=\"\" width=\"300\" height=\"169\" srcset=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2016\/12\/NoTextures-300x169.jpg 300w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2016\/12\/NoTextures-768x432.jpg 768w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2016\/12\/NoTextures-1024x576.jpg 1024w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2016\/12\/NoTextures.jpg 1920w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p>The next step was to add textures to make the orbs look more like Diablo III's (except for the fill line, I didn't manage to quite have it glow like I wanted). The first step is to turn our circle into a sphere by warping our UVs, which I did using <a href=\"https:\/\/en.wikipedia.org\/wiki\/UV_mapping#Finding_UV_on_a_sphere\">wikipedia's math<\/a>:<\/p>\n<pre class=\"line-numbers\"><code class='language-cpp'>\r\nvec2 distFromCent = uv - orb.center;\r\nfloat uvHeight = exp(sqrt(orb.radius - (pow(distFromCent.x,2.) + pow(distFromCent.y, 2.))) \/ orb.radius) \/ exp(1.);\r\nvec3 d = normalize(vec3(orb.center, 0.) - vec3(uv, uvHeight));\r\nfloat u = (.5 + atan(d.z, d.x) \/ (2. * 3.14159)) \/ orb.radius \/ .5;\r\nfloat v = (.5 - asin(d.y) \/ 3.14159) \/ orb.radius \/ .5;\r\n<\/code><\/pre>\n<p>Simon Schreibt has a fantastic write-up about <a href=\"https:\/\/simonschreibt.de\/gat\/diablo-3-resource-bubbles\/\">how Diablo III actually implements their resource orbs<\/a>, which tipped me off to using multiple textures multiplicitively as well as scrolling the textures. This is handled like so:<\/p>\n<pre class=\"line-numbers\"><code class='language-cpp'>\r\ncolour *= vec3(texture2D(iChannel1, \r\n               vec2(u + fract(iGlobalTime *.05),\r\n                    v + fract(iGlobalTime * .03))).x)\r\n            \t    * 2.15;\r\ncolour *= vec3(texture2D(iChannel3,\r\n               vec2(u + fract(iGlobalTime * .001),\r\n                    v + fract(iGlobalTime * .04))).x)\r\n            \t    * 2.2;\r\n<\/code><\/pre>\n<p>To shade, we apply a polynomial blend to the edge of the orb. Which gives us a final shader which looks like:<br \/>\n<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2017\/01\/ResOrbs-300x169.png\" alt=\"\" width=\"300\" height=\"169\" class=\"alignnone size-medium wp-image-48\" srcset=\"https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2017\/01\/ResOrbs-300x169.png 300w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2017\/01\/ResOrbs-768x432.png 768w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2017\/01\/ResOrbs-1024x576.png 1024w, https:\/\/mattrobertson.ca\/blog\/wp-content\/uploads\/2017\/01\/ResOrbs.png 1920w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p>Code for this shader is available <a href=\"https:\/\/www.shadertoy.com\/view\/llcSRf\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For all the video games I&#8217;ve played and all the interesting UIs I&#8217;ve seen, I dare say the resource bars used in the Diablo series are my favourite. So what better way to break into blogging than an attempt at<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-7","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/posts\/7","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/comments?post=7"}],"version-history":[{"count":39,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/posts\/7\/revisions"}],"predecessor-version":[{"id":133,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/posts\/7\/revisions\/133"}],"wp:attachment":[{"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/media?parent=7"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/categories?post=7"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mattrobertson.ca\/blog\/wp-json\/wp\/v2\/tags?post=7"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}