{"id":2768,"date":"2016-04-27T00:00:38","date_gmt":"2016-04-26T23:00:38","guid":{"rendered":"http:\/\/phoenixgamedevelopment.com\/blog\/?p=2768"},"modified":"2016-04-29T04:16:41","modified_gmt":"2016-04-29T03:16:41","slug":"p-152-virtual-world-concept-update-116-texture-blending","status":"publish","type":"post","link":"https:\/\/phoenixgamedevelopment.com\/blog\/p-152-virtual-world-concept-update-116-texture-blending\/","title":{"rendered":"P-152: Virtual World Concept Update 116: Texture Blending"},"content":{"rendered":"<p>I have figured out how to blend my terrain textures together. I have not completely finished the system yet, but I have proof of concept.<\/p>\n<p>The general idea is the same as &#8220;texture splatting&#8221;, however, Texture Splatting requires an alpha map. This is a map of the whole terrain which is used to determine how much one texture should be blended into the next.<\/p>\n<p>The problem for me was that my terrain is generated procedurally, so I cant have a single alpha map.<\/p>\n<p>The solution occurred to me when I realised that the alpha map is essentially just a map of opacity\u00a0values at any given altitude. So, if we assume that the terrains height is between 0 and 100, any height over 75 might be 100% grass, any height under 25 might be 100% sand. The values in the middle should be smoothly blended between the two, using a simple algorithm.<\/p>\n<p>Producing this opacity value is actually quite easy, if you know the minimum and maximum height values for the range of heights that you are blending between. I simply rescale the height range so that it is between 0 and 1, that gives me the opacity of the second texture. I subtract this value from 1 to get the first textures\u00a0opacity.<\/p>\n<p>For the concept test, I simply used two textures, very simple blending, and I hardcoded the height ranges. This is the result:<\/p>\n<p><a href=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-08_59_52-PM.jpg\" rel=\"attachment wp-att-2773\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2773\" src=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-08_59_52-PM-300x195.jpg\" alt=\"P152_DEBUG 26\/04\/2016 , 08:59:52 PM Torque 3D MIT - P152\" width=\"300\" height=\"195\" srcset=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-08_59_52-PM-300x195.jpg 300w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-08_59_52-PM-768x498.jpg 768w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-08_59_52-PM-1024x664.jpg 1024w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-08_59_52-PM.jpg 1922w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_23_59-PM.jpg\" rel=\"attachment wp-att-2772\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2772\" src=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_23_59-PM-300x195.jpg\" alt=\"P152_DEBUG 26\/04\/2016 , 09:23:59 PM Torque 3D MIT - P152\" width=\"300\" height=\"195\" srcset=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_23_59-PM-300x195.jpg 300w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_23_59-PM-768x498.jpg 768w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_23_59-PM-1024x664.jpg 1024w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_23_59-PM.jpg 1922w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_24_52-PM.jpg\" rel=\"attachment wp-att-2771\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2771\" src=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_24_52-PM-300x195.jpg\" alt=\"P152_DEBUG 26\/04\/2016 , 09:24:52 PM Torque 3D MIT - P152\" width=\"300\" height=\"195\" srcset=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_24_52-PM-300x195.jpg 300w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_24_52-PM-768x498.jpg 768w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_24_52-PM-1024x664.jpg 1024w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_24_52-PM.jpg 1922w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_30_54-PM.jpg\" rel=\"attachment wp-att-2770\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2770\" src=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_30_54-PM-300x195.jpg\" alt=\"P152_DEBUG 26\/04\/2016 , 09:30:54 PM Torque 3D MIT - P152\" width=\"300\" height=\"195\" srcset=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_30_54-PM-300x195.jpg 300w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_30_54-PM-768x498.jpg 768w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_30_54-PM-1024x664.jpg 1024w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-09_30_54-PM.jpg 1922w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>As can be clearly seen, the terrain gradually blends from 100% sand coverage to 100% grass coverage, with no seams in between. There is some repitition\u00a0in this image, but that is due to the textures that I used, not the shader. I am confident that I can extend this system to cover all of the textures that I will be applying using a simple series of if statements to determine the range. This, however, is not ideal, and this leads me to the final problem in this system.<\/p>\n<p>I do not know the exact scale of values in the terrain. They do not seem to be identical to the raw height values of the vertices of the spherical terrain, nor do they correspond to some known range, such as 0-1. They also change depending on the height scale of the terrain, which makes sense. My algorithm with not work unless I can determine the minimum and maximum height values of the terrain at the current scale. I am not sure how to get these values, and the fact that I can&#8217;t simply print out values from the shader is making things more complex. Once I solve this, I can simply the aforementioned IF ELSE statements to blend the textures at the correct heights.<\/p>\n<p>This is my next, and possibly final, goal in MS1, other than some debugging and tidying up.<\/p>\n<p>Below is a final image I took of the height range. The area in red is the blend region, the area above and below have hardcoded textures (100% opacity). I had to set the values for this region manually, which wouldn&#8217;t work for the final project, since the height scale is variable.<\/p>\n<p><a href=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-07_07_05-PM.jpg\" rel=\"attachment wp-att-2774\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2774\" src=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-07_07_05-PM-300x195.jpg\" alt=\"P152_DEBUG 26\/04\/2016 , 07:07:05 PM Torque 3D MIT - P152\" width=\"300\" height=\"195\" srcset=\"https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-07_07_05-PM-300x195.jpg 300w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-07_07_05-PM-768x498.jpg 768w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-07_07_05-PM-1024x664.jpg 1024w, https:\/\/phoenixgamedevelopment.com\/blog\/wp-content\/uploads\/2016\/04\/PhoenixGameDevelopment-26_04_2016-07_07_05-PM.jpg 1922w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Finally, here is the Pixel shader that I used to generate this, very basic blending effect:<\/p>\n<blockquote><p>\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n\/\/Structures<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<\/p>\n<p>struct ConnectData {<br \/>\n\/\/float4 HPOS : POSITION;<br \/>\nfloat2 texCoord : TEXCOORD0;<br \/>\nfloat3 pos : TEXCOORD1;<br \/>\n};<br \/>\nstruct Fragout {<br \/>\nfloat4 col : COLOR0;<br \/>\n};<\/p>\n<p>\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n\/\/Main<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nFragout main<br \/>\n(<br \/>\nConnectData IN,<br \/>\nuniform sampler2D dirtgrass : register(S0),<br \/>\nuniform sampler2D grass1 : register(S1),<br \/>\nuniform sampler2D drygrass : register(S2),<br \/>\nuniform sampler2D grass2 : register(S3),<\/p>\n<p>uniform sampler2D rocks1 : register(S4),<br \/>\nuniform sampler2D rocktest : register(S5),<\/p>\n<p>uniform sampler2D sand : register(S6),<br \/>\nuniform sampler2D snowtop : register(S7)<\/p>\n<p>)<br \/>\n{<br \/>\nFragout OUT;<\/p>\n<p>\/\/uses the base textures&#8217; color to set difuse color<br \/>\nfloat4 diffuseColor = tex2D( dirtgrass, IN.texCoord );<\/p>\n<p>float4 someColor;<\/p>\n<p>float OldMin = 0.94575;<br \/>\nfloat OldMax = 0.99;<\/p>\n<p>float NewMin = 0;<br \/>\nfloat NewMax = 1;<\/p>\n<p>float OldValue = IN.pos.z;<\/p>\n<p>float NewValue = (((OldValue &#8211; OldMin) * (NewMax &#8211; NewMin)) \/ (OldMax &#8211; OldMin)) + NewMin;<br \/>\n\/\/simple concept test of blending (two textures only):<\/p>\n<p>if(OldValue &lt;= OldMin){<br \/>\nsomeColor = tex2D( sand, IN.texCoord );<br \/>\nOUT.col = someColor;<br \/>\n}<\/p>\n<p>else if (OldValue &gt;= OldMax){<br \/>\nsomeColor = tex2D( grass2, IN.texCoord );<br \/>\nOUT.col = someColor;<br \/>\n}<\/p>\n<p>else{<br \/>\n\/\/blend<br \/>\nfloat4 blend1 = tex2D( sand, IN.texCoord );\/\/float4(1.0,0.0,0.0,1.0);<br \/>\nfloat4 blend2 = tex2D( grass2, IN.texCoord );\/\/float4(0.0,0.0,1.0,1.0);<\/p>\n<p>float a1 = 1-NewValue;<br \/>\nfloat a2 = NewValue;<br \/>\nsomeColor = (blend1 * a1) + (blend2 * a2);<br \/>\nOUT.col = someColor;<\/p>\n<p>}<br \/>\nreturn OUT;<br \/>\n}<\/p><\/blockquote>\n<p>And the Vertex shader:<\/p>\n<p>&nbsp;<\/p>\n<blockquote><p>#define IN_HLSL<br \/>\n#include &#8220;shdrConsts.h&#8221;<br \/>\n#include &#8220;hlslStructs.h&#8221;<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n\/\/ Constants<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>struct Appdata {<br \/>\nfloat4 position : POSITION;<br \/>\nfloat4 texCoord : TEXCOORD0;<br \/>\n};<\/p>\n<p>struct Conn {<br \/>\nfloat4 outHPOS : POSITION;<br \/>\nfloat2 outTexCoord : TEXCOORD0;<br \/>\nfloat3 pos : TEXCOORD1;<br \/>\n};<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n\/\/ Main<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Conn main(<br \/>\nAppdata In,<br \/>\nuniform float4x4 modelview : register(VC_WORLD_PROJ),<br \/>\nuniform float4x4 texMat : register(VC_TEX_TRANS1)<br \/>\n)<br \/>\n{<br \/>\nConn Out;<br \/>\n\/\/take vert and get coord by transforming by modelviewmatrix<br \/>\nOut.outHPOS = mul(modelview, In.position);<br \/>\n\/\/set base texture coord<br \/>\nOut.outTexCoord = mul(texMat, In.texCoord);<br \/>\nfloat3 pos = In.position.xyz; \/\/mul( cubeTrans, In.pos ).xyz;<br \/>\nOut.pos = pos;\/\/mul(objToTangentSpace, In.position.xyz \/ 100.0);<br \/>\nreturn Out;<br \/>\n}<\/p><\/blockquote>\n<p>&nbsp;<\/p>\n<p>This is obviously very simple, and will be improved in the future, likely after I solve the height scaling issue.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have figured out how to blend my terrain textures together. I have not completely finished the system yet, but I have proof of concept. The general idea is the same as &#8220;texture splatting&#8221;, however, Texture Splatting requires an alpha map. This is a map of the whole terrain which is used to determine how [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,17,10,11],"tags":[],"class_list":["post-2768","post","type-post","status-publish","format-standard","hentry","category-astral-realms","category-p-0","category-software-and-games","category-spiritus-astrum","has-post-title","has-post-date","has-post-category","has-post-tag","has-post-comment","has-post-author",""],"aioseo_notices":[],"builder_content":"","_links":{"self":[{"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/posts\/2768"}],"collection":[{"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/comments?post=2768"}],"version-history":[{"count":5,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/posts\/2768\/revisions"}],"predecessor-version":[{"id":2778,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/posts\/2768\/revisions\/2778"}],"wp:attachment":[{"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/media?parent=2768"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/categories?post=2768"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/tags?post=2768"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}