Search Unity

Surface Shader Height Map

Discussion in 'Shaders' started by Harrison_Hough, Mar 16, 2017.

  1. Harrison_Hough

    Harrison_Hough

    Joined:
    Dec 3, 2015
    Posts:
    43
    Hey Guys,

    I like to know the correct way to calculate/use a height map in a surface shader?

    I'm making a Standard (metallic) shader with a heightmap texture. Here is what I currently have not sure if this is the right way to go about it.

    Code (CSharp):
    1. Shader "Custom/PBR/Standard Height" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _BumpMap("Normal Map", 2D) = "bump" {}
    6.         _BumpScale ("Normal ", Float) = 1
    7.             _HeightMap ("Height Map", 2D) = "white" {}
    8.         _HeightMapScale ("Height", Float) = 1
    9.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    10.         _MetallicGlossMap ("Metallic", 2D) = "white" {}
    11.         _Metallic ("Metallic", Range(0,1)) = 0.0
    12.         _OcclusionMap("OcclusionMap", 2D) = "white" {}
    13.         _OcclusionStrength("Occlusion Strength", Float) = 1
    14.  
    15.     }
    16.     SubShader {
    17.         Tags { "RenderType"="Opaque" }
    18.         LOD 200
    19.      
    20.         CGPROGRAM
    21.         // Physically based Standard lighting model, and enable shadows on all light types
    22.         #pragma surface surf Standard fullforwardshadows vertex:vert
    23.  
    24.         // Use shader model 3.0 target, to get nicer looking lighting
    25.         #pragma target 3.0
    26.         #pragma glsl
    27.  
    28.         sampler2D _MainTex;
    29.         sampler2D _OcclusionMap;
    30.         sampler2D _BumpMap;
    31.         sampler2D _MetallicGlossMap;
    32.         sampler2D _HeightMap;
    33.  
    34.         struct Input {
    35.             float2 uv_MainTex;
    36.         };
    37.  
    38.         half _HeightMapScale;
    39.         half _Glossiness;
    40.         half _Metallic;
    41.         half _BumpScale;
    42.         half _OcclusionStrength;
    43.         fixed4 _Color;
    44.  
    45.         void vert(inout appdata_full v,  out Input o) {
    46.             UNITY_INITIALIZE_OUTPUT(Input, o);
    47.             float4 heightMap = tex2Dlod(_HeightMap, float4(v.texcoord.xy,0,0));
    48.             //fixed4 heightMap = _HeightMap;
    49.             v.vertex.z += heightMap.b * _HeightMapScale;
    50.         }
    51.  
    52.         void surf (Input IN, inout SurfaceOutputStandard o) {
    53.             // Albedo comes from a texture tinted by color
    54.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    55.             o.Albedo = c.rgb;
    56.             // Metallic and smoothness come from slider variables
    57.             fixed4 gloss = tex2D(_MetallicGlossMap, IN.uv_MainTex);
    58.             o.Metallic = gloss.r * _Metallic;
    59.             o.Smoothness = gloss.a * _Glossiness;
    60.  
    61.             o.Normal = UnpackScaleNormal(tex2D(_BumpMap, IN.uv_MainTex), _BumpScale);
    62.             o.Occlusion = tex2D(_OcclusionMap, IN.uv_MainTex) * _OcclusionStrength;
    63.  
    64.             o.Alpha = c.a;
    65.         }
    66.         ENDCG
    67.     }
    68.     FallBack "Diffuse"
    69. }
    70.  
     
    Last edited: Mar 16, 2017
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,341
    What effect are you expecting? There are many ways to use a heightmap with a surface shader, and the method you're using is a perfectly valid option, but I get the impression it isn't producing the result you want.

    A height map is just description of data in a texture, it doesn't really say anything about how you use it.

    If you're trying to replicate what the standard shader does with the heightmap, that's parallax offset mapping, which uses a heightmap to distort UVs to give a very basic appeance of additional surface depth. If you're looking to implement that in your surface shader see this post which includes an example of using ParallaxOffset, as well as a link to the more expensive, but better looking Parallax Occlusions Mapping.
    https://forum.unity3d.com/threads/surface-shader-parallaxoffset-issue.438089/
     
    BrainSlugs83 and creative_vlad like this.
  3. BrainSlugs83

    BrainSlugs83

    Joined:
    Jun 18, 2015
    Posts:
    38
    This is not at all the correct way to do it for a couple of reasons:

    1.) You're doing it in the vertex program -- Height maps are per pixel, not per vertex.

    2.) You're just blindly increasing the Z axis -- Height maps operate on the normal of the current
    surface.
     
  4. BrainSlugs83

    BrainSlugs83

    Joined:
    Jun 18, 2015
    Posts:
    38
    Posting here since this page comes up as the topmost result whenever I google the issue, and because I finally figured it out.

    And to clarify, this is assuming you mean "Height Map" as in what the Standard Shader calls a Height Map, example below -- you can see the bricks stick out more as I drag the knob to the right...


    Interestingly enough, even though Unity calls it a "Height Map" in the Standard Shader, the Shader language refers to it as a parallax map (there's a hint in the Height Map documentation here: https://docs.unity3d.com/Manual/StandardShaderMaterialParameterHeightMap.html ) -- Discovering that makes it a bit easier to track down the proper code to make this work.

    (So yeah, turns out @bgolus was on the right track -- but saying "what you're doing looks valid" was misleading for me... anyway, if you're as thick as I can be, the step-by-step is below, give it a shot.)

    First off Declare two Properties for your shader:
    Code (csharp):
    1. _HeightMap("Height Map", 2D) = "white" {}
    2. _HeightPower("Height Power", Range(0,.125)) = 0
    Add two globals to match:
    Code (csharp):
    1. sampler2D _HeightMap;
    2. float _HeightPower;
    to your "Input" struct, add two fields:
    Code (csharp):
    1. float2 uv_HeightMap;
    2. float3 viewDir;
    if your "surf" program, for the very first line add:
    Code (csharp):
    1. float2 texOffset = ParallaxOffset(tex2D(_HeightMap, IN.uv_HeightMap).r, _HeightPower, IN.viewDir);
    then find all your other tex2D lookups and adjust them, like so:
    Code (csharp):
    1. fixed4 c = tex2D(_MainTex, IN.uv_MainTex + texOffset) * _Color;
    2. o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap + texOffset));
    3. //...
    4. o.Albedo = c.rgb;
    5. o.Alpha = c.a;
    Crazily enough, all it's doing is modifying the UVs based on the current view direction to simulate a height effect... it works really well though.
     
    Last edited: Feb 15, 2021
    LOSTSOUL86, Goreduc, xCyborg and 3 others like this.