⬆️ ⬇️

Unity3D 3.x Terrain Bump Specular Shader

Currently, Unity3D does not support overlaying the specular map with the built-in landscape. Googling about this has brought not very impressive results in the form of this shader and some of its modifications. Inspired by the picture and downloading the archive, I was disappointed. Firstly, for the operation of the shader on the landscape, it is necessary to hang a script that controls the shader (which is very inconvenient), and secondly, in this implementation, more than 4 normal maps can not be assigned.

In this article I will describe the process of creating your own shader for the landscape, at the same time telling how the standard shader works.



Landscape rendering mechanism in standard unit shaders



To draw a landscape in a unit, two shaders are used:

Hidden / TerrainEngine / Splatmap / Lightmap-FirstPass and Hidden / TerrainEngine / Splatmap / Lightmap-AddPass , you can download them here



The first shader draws the first 4 textures of the landscape. The second shader sequentially draws the rest of the textures 4 at a time until the textures run out.



So what is transferred to the shader from the engine:

')

... struct Input { float2 uv_Control : TEXCOORD0; float2 uv_Splat0 : TEXCOORD1; float2 uv_Splat1 : TEXCOORD2; float2 uv_Splat2 : TEXCOORD3; float2 uv_Splat3 : TEXCOORD4; }; sampler2D _Control; sampler2D _Splat0,_Splat1,_Splat2,_Splat3; ... 




_SplatX - texture with material

_Control - control card. This is a texture in which each of the channels sets the brightness of one of the materials at a certain point. The control map is created on the basis of the landscape materials map (Alphamaps) for each of the four materials in the depths of the engine. Just because the control texture has 4 channels, the shaders render no more than 4 materials at a time.









Let us see what happens next in the shader:



It has a single procedure in which the color of the current point (o.Albedo) is considered, and it is equal to the sum of the brightness products of a point from the RGBA control card and its color from the material texture.



  ... void surf (Input IN, inout SurfaceOutput o) { half4 splat_control = tex2D (_Control, IN.uv_Control); half3 col; col = splat_control.r * tex2D (_Splat0, IN.uv_Splat0).rgb; col += splat_control.g * tex2D (_Splat1, IN.uv_Splat1).rgb; col += splat_control.b * tex2D (_Splat2, IN.uv_Splat2).rgb; col += splat_control.a * tex2D (_Splat3, IN.uv_Splat3).rgb; o.Albedo = col; o.Alpha = 0.0; } ... 




The result of his work can be seen below:







The flare in this case occurred due to the imposition of several materials (channels) on the control card, in real conditions this should not be, because usually one material on a landscape smoothly passes into another, and quite rarely you have to put one material on another.



I will not consider the second shader, since it is almost identical, only used for textures with an index above 3.



Creating your own shader


Since in one pass there can be only 4 materials and I don’t want to resort to using scripts to assign normals to the shader, we will thrust the normals through the landscape, as shown in the picture below.

Now every second texture on the landscape is normal to the previous material. It is important that in the inspector of this texture the type is set - normal. In addition, we have an unused channel A in the texture of the material, in which the Specular map fits perfectly.







New procedure surf:



 void surf (Input IN, inout SurfaceOutput o) { fixed4 splat_control = tex2D (_Control, IN.uv_Control); fixed3 col; fixed spec; // RGBA     fixed4 d1 = tex2D (_Splat0, IN.uv_Splat0); // RGBA     fixed4 d2 = tex2D (_Splat2, IN.uv_Splat2); //      fixed3 n1 = UnpackNormal( tex2D (_Splat1, IN.uv_Splat1) ); //      fixed3 n2 = UnpackNormal( tex2D (_Splat3, IN.uv_Splat3) ); //        col = splat_control.r * d1.rgb; //  (  ) o.Normal = normalize(lerp(fixed3(0.5,0.5,1), n1, clamp(splat_control.r + 0.3,0,1))); //        ,      . "0.1" -   ,    . spec = (1 - d1.a) * splat_control.r * 0.1; //        col += splat_control.b * d2.rgb; o.Normal += normalize(lerp(fixed3(0.5,0.5,1), n2, clamp(splat_control.b + 0.3,0,1))); spec += (1 - d2.a) * splat_control.b * 0.1; //  ,   basemap' o.Albedo = col * 0.5; //  o.Specular = spec; o.Gloss = spec; o.Alpha = 0.0; } 




Everything should be clear in the procedure, since apart from arithmetic operations there is almost nothing left. The only thing I would like to make out is this line:

o.Normal = lerp(fixed3(0.5,0.5,1), n1, clamp(splat_control.r + 0.3,0,1));

A normal is a unit vector perpendicular to the surface. And since we need to smoothly reduce it, we cannot simply multiply it by some coefficient. To solve this problem, I interpolate the current normal in the so-called. "Zero normal", in which the texture will not be any relief.



In order for the normal map to apply, the mech must have counted tangents (a vector perpendicular to the normal and parallel to the surface, pointing upward to the U coordinate on the scan). Usually they are considered by the software in which the model is developed, but since the landscape is built in a unit on the fly, there are no tangents there.

We'll have to calculate the tangents inside the shader themselves:



 void vert (inout appdata_full v) { fixed3 T1 = float3(1, 0, 0); if (dot(T1,v.normal) > 0.99) { T1 = float3(0,1,0); //workaround } fixed3 Bi = cross(T1, v.normal); fixed3 newTangent = normalize(cross(v.normal, Bi)); v.tangent.xyz = newTangent.xyz; if (dot(cross(v.normal,newTangent),Bi) < 0) v.tangent.w = -1.0f; else v.tangent.w = 1.0f; } 




The shader for subsequent passes is almost identical.



For those who create the landscape dynamically, do not forget to correct the indices of materials in the code. They must be multiplied by two, because the odd indices are normals.



materials[x, z, material_number*2] = 1;



Cons of this method:



Briefly, for those who are too lazy to read:



Result with a standard texture, and maps removed from it (became / was):





Download Shader

Source: https://habr.com/ru/post/144016/



All Articles