- Home /
URP Height Map Shader Problem,URP Custom Shader: Cannot access the UVs of the object
Hello, I am trying to add a height map to a grass shader. Unfortunately I can't access the Uvs of the object to get the coordinate at the texture. What could be the reason for this? Unfortunately I am still quite new to shaders. Thanks for your help
Here the Shader:
Shader "Unlit/GeoGrass" {
Properties {
_Color("Colour", Color) = (1,1,1,1)
_Color2("Colour2", Color) = (1,1,1,1)
_Width("Width", Float) = 1
_RandomWidth("Random Width", Float) = 1
_Height("Height", Float) = 1
_RandomHeight("Random Height", Float) = 1
_WindStrength("Wind Strength", Float) = 0.1
[Space]
_TessellationUniform("Tessellation Uniform", Range(1, 64)) = 1
_GrassMap("GrassVisibilityMap", 2D) = "white" {}
_GrassThreshold("Grass Visibility Threshold", Range(-0.1, 1)) = 0.5
}
SubShader {
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
LOD 300
Cull Off
Pass {
Name "ForwardLit"
Tags {"LightMode" = "UniversalForward"}
HLSLPROGRAM
// Required to compile gles 2.0 with standard srp library
#pragma prefer_hlslcc gles
#pragma exclude_renderers d3d11_9x gles
#pragma target 4.5
#pragma require geometry
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma hull hull
#pragma domain domain
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
// Defines
#define BLADE_SEGMENTS 1
// Includes
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
#include "grass_structs.hlsl"
#include "CustomTessellation.hlsl"
#include "grass.hlsl"
// Fragment
float4 frag(GeometryOutput input) : SV_Target {
#if SHADOWS_SCREEN
float4 clipPos = TransformWorldToHClip(input.positionWS);
float4 shadowCoord = ComputeScreenPos(clipPos);
#else
float4 shadowCoord = TransformWorldToShadowCoord(input.positionWS);
#endif
Light mainLight = GetMainLight(shadowCoord);
return lerp(_Color, _Color2, input.uv.y) * mainLight.shadowAttenuation;
}
ENDHLSL
}
}
}
And here are the other classes:
grass.hlsl
float rand(float3 seed) {
return frac(sin(dot(seed.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
}
// https://gist.github.com/keijiro/ee439d5e7388f3aafc5296005c8c3f33
float3x3 AngleAxis3x3(float angle, float3 axis) {
float c, s;
sincos(angle, s, c);
float t = 1 - c;
float x = axis.x;
float y = axis.y;
float z = axis.z;
return float3x3(
t * x * x + c, t * x * y - s * z, t * x * z + s * y,
t * x * y + s * z, t * y * y + c, t * y * z - s * x,
t * x * z - s * y, t * y * z + s * x, t * z * z + c
);
}
float3 _LightDirection;
float4 GetShadowPositionHClip(float3 positionWS, float3 normalWS) {
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
return positionCS;
}
float4 WorldToHClip(float3 positionWS, float3 normalWS) {
#ifdef SHADOW
return GetShadowPositionHClip(positionWS, normalWS);
#else
return TransformWorldToHClip(positionWS);
#endif
}
// Variables
CBUFFER_START(UnityPerMaterial) // Required to be compatible with SRP Batcher
float4 _Color;
float4 _Color2;
float _Width;
float _RandomWidth;
float _WindStrength;
float _Height;
float _RandomHeight;
sampler2D _GrassMap;
float4 _GrassMap_ST;
float _GrassThreshold;
CBUFFER_END
// Vertex, Geometry & Fragment Shaders
Varyings vert(Attributes input) {
Varyings output = (Varyings)0;
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
// Seems like GetVertexPositionInputs doesn't work with SRP Batcher inside geom function?
// Had to move it here, in order to obtain positionWS and pass it through the Varyings output.
output.positionOS = input.positionOS; //vertexInput.positionCS; //
output.positionWS = vertexInput.positionWS;
output.normal = input.normal;
output.tangent = input.tangent;
return output;
}
[maxvertexcount(BLADE_SEGMENTS * 2 + 1)]
void geom(uint primitiveID : SV_PrimitiveID, triangle Varyings input[3], inout TriangleStream<GeometryOutput> triStream) {
GeometryOutput output = (GeometryOutput)0;
float grassVisibility = tex2Dlod(_GrassMap, float4(input[0].uv, 0, 0)).r;
if(grassVisibility >= _GrassThreshold) {
// Construct World -> Tangent Matrix (for aligning grass with mesh normals)
float3 normal = input[0].normal;
float4 tangent = input[0].tangent;
float3 binormal = cross(normal, tangent) * tangent.w;
float3x3 tangentToLocal = float3x3(
tangent.x, binormal.x, normal.x,
tangent.y, binormal.y, normal.y,
tangent.z, binormal.z, normal.z
);
float3 positionWS = input[0].positionWS;
float r = rand(positionWS.xyz);
float3x3 randRotation = AngleAxis3x3(r * TWO_PI, float3(0, 0, 1));
// Wind (based on sin / cos, aka a circular motion, but strength of 0.1 * sine)
float2 wind = float2(sin(_Time.y + positionWS.x * 0.5), cos(_Time.y + positionWS.z * 0.5)) * _WindStrength * sin(_Time.y + r);
float3x3 windMatrix = AngleAxis3x3((wind * PI).y, normalize(float3(wind.x, wind.y, 0)));
float3x3 transformMatrix = mul(tangentToLocal, randRotation);
float3x3 transformMatrixWithWind = mul(mul(tangentToLocal, windMatrix), randRotation);
float bend = rand(positionWS.xyz) - 0.5;
float width = _Width + _RandomWidth * (rand(positionWS.zyx) - 0.5);
float height = _Height + _RandomHeight * (rand(positionWS.yxz) - 0.5);
float3 normalWS = mul(transformMatrix, float3(0, 1, 0)); //?
// Handle Geometry
// Base 2 vertices
output.positionWS = positionWS + mul(transformMatrix, float3(width, 0, 0));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, 0);
triStream.Append(output);
output.positionWS = positionWS + mul(transformMatrix, float3(-width, 0, 0));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, 0);
triStream.Append(output);
// Final vertex at top of blade
output.positionWS = positionWS + mul(transformMatrixWithWind, float3(0, bend, height));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, 1);
triStream.Append(output);
triStream.RestartStrip();
}
}
And here the structs: grass_struct.hlsl
struct Attributes {
float4 positionOS : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
};
struct Varyings {
float4 positionOS : SV_POSITION;
float3 positionWS : TEXCOORD1;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct GeometryOutput {
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD1;
float2 uv : TEXCOORD0;
};
In the grass.hlsl file I try to achieve the effect with these lines:
float grassVisibility = tex2Dlod(_GrassMap, float4(input[0].uv, 0, 0)).r;
if(grassVisibility >= _GrassThreshold) {
...
}
The input[0].uv does not return the appropriate values
,Hello, I am trying to add a Height Map in a Grass Shader in URP. However, I am not quite getting anywhere. I know that I have to check the UVs and which value is there on the texture. But my problem is that I can't get the UVs from the object and query them. Does anyone have any ideas? It would help me a lot. Thanks
Here is the Grass Shader:
Shader "Unlit/GeoGrass" {
Properties {
_Color("Colour", Color) = (1,1,1,1)
_Color2("Colour2", Color) = (1,1,1,1)
_Width("Width", Float) = 1
_RandomWidth("Random Width", Float) = 1
_Height("Height", Float) = 1
_RandomHeight("Random Height", Float) = 1
_WindStrength("Wind Strength", Float) = 0.1
[Space]
_TessellationUniform("Tessellation Uniform", Range(1, 64)) = 1
_GrassMap("GrassVisibilityMap", 2D) = "white" {}
_GrassThreshold("Grass Visibility Threshold", Range(-0.1, 1)) = 0.5
}
SubShader {
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
LOD 300
Cull Off
Pass {
Name "ForwardLit"
Tags {"LightMode" = "UniversalForward"}
HLSLPROGRAM
// Required to compile gles 2.0 with standard srp library
#pragma prefer_hlslcc gles
#pragma exclude_renderers d3d11_9x gles
#pragma target 4.5
#pragma require geometry
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma hull hull
#pragma domain domain
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
// Defines
#define BLADE_SEGMENTS 1
// Includes
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
#include "grass_structs.hlsl"
#include "CustomTessellation.hlsl"
#include "grass.hlsl"
// Fragment
float4 frag(GeometryOutput input) : SV_Target {
#if SHADOWS_SCREEN
float4 clipPos = TransformWorldToHClip(input.positionWS);
float4 shadowCoord = ComputeScreenPos(clipPos);
#else
float4 shadowCoord = TransformWorldToShadowCoord(input.positionWS);
#endif
Light mainLight = GetMainLight(shadowCoord);
return lerp(_Color, _Color2, input.uv.y) * mainLight.shadowAttenuation;
}
ENDHLSL
}
}
}
Here is the code that is accessed
grass_struct.hlsl
struct Attributes {
float4 positionOS : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
};
struct Varyings {
float4 positionOS : SV_POSITION;
float3 positionWS : TEXCOORD1;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct GeometryOutput {
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD1;
float2 uv : TEXCOORD0;
};
grass.hlsl
float rand(float3 seed) {
return frac(sin(dot(seed.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
}
// https://gist.github.com/keijiro/ee439d5e7388f3aafc5296005c8c3f33
float3x3 AngleAxis3x3(float angle, float3 axis) {
float c, s;
sincos(angle, s, c);
float t = 1 - c;
float x = axis.x;
float y = axis.y;
float z = axis.z;
return float3x3(
t * x * x + c, t * x * y - s * z, t * x * z + s * y,
t * x * y + s * z, t * y * y + c, t * y * z - s * x,
t * x * z - s * y, t * y * z + s * x, t * z * z + c
);
}
float3 _LightDirection;
float4 GetShadowPositionHClip(float3 positionWS, float3 normalWS) {
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
return positionCS;
}
float4 WorldToHClip(float3 positionWS, float3 normalWS) {
#ifdef SHADOW
return GetShadowPositionHClip(positionWS, normalWS);
#else
return TransformWorldToHClip(positionWS);
#endif
}
// Variables
CBUFFER_START(UnityPerMaterial) // Required to be compatible with SRP Batcher
float4 _Color;
float4 _Color2;
float _Width;
float _RandomWidth;
float _WindStrength;
float _Height;
float _RandomHeight;
sampler2D _GrassMap;
float4 _GrassMap_ST;
float _GrassThreshold;
CBUFFER_END
// Vertex, Geometry & Fragment Shaders
Varyings vert(Attributes input) {
Varyings output = (Varyings)0;
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
// Seems like GetVertexPositionInputs doesn't work with SRP Batcher inside geom function?
// Had to move it here, in order to obtain positionWS and pass it through the Varyings output.
output.positionOS = input.positionOS; //vertexInput.positionCS; //
output.positionWS = vertexInput.positionWS;
output.normal = input.normal;
output.tangent = input.tangent;
return output;
}
[maxvertexcount(BLADE_SEGMENTS * 2 + 1)]
void geom(uint primitiveID : SV_PrimitiveID, triangle Varyings input[3], inout TriangleStream<GeometryOutput> triStream) {
GeometryOutput output = (GeometryOutput)0;
float grassVisibility = tex2Dlod(_GrassMap, float4(input[0].uv, 0, 0)).r;
if(grassVisibility >= _GrassThreshold) {
// Construct World -> Tangent Matrix (for aligning grass with mesh normals)
float3 normal = input[0].normal;
float4 tangent = input[0].tangent;
float3 binormal = cross(normal, tangent) * tangent.w;
float3x3 tangentToLocal = float3x3(
tangent.x, binormal.x, normal.x,
tangent.y, binormal.y, normal.y,
tangent.z, binormal.z, normal.z
);
float3 positionWS = input[0].positionWS;
float r = rand(positionWS.xyz);
float3x3 randRotation = AngleAxis3x3(r * TWO_PI, float3(0, 0, 1));
// Wind (based on sin / cos, aka a circular motion, but strength of 0.1 * sine)
float2 wind = float2(sin(_Time.y + positionWS.x * 0.5), cos(_Time.y + positionWS.z * 0.5)) * _WindStrength * sin(_Time.y + r);
float3x3 windMatrix = AngleAxis3x3((wind * PI).y, normalize(float3(wind.x, wind.y, 0)));
float3x3 transformMatrix = mul(tangentToLocal, randRotation);
float3x3 transformMatrixWithWind = mul(mul(tangentToLocal, windMatrix), randRotation);
float bend = rand(positionWS.xyz) - 0.5;
float width = _Width + _RandomWidth * (rand(positionWS.zyx) - 0.5);
float height = _Height + _RandomHeight * (rand(positionWS.yxz) - 0.5);
float3 normalWS = mul(transformMatrix, float3(0, 1, 0)); //?
// Handle Geometry
// Base 2 vertices
output.positionWS = positionWS + mul(transformMatrix, float3(width, 0, 0));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, 0);
triStream.Append(output);
output.positionWS = positionWS + mul(transformMatrix, float3(-width, 0, 0));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, 0);
triStream.Append(output);
// Final vertex at top of blade
output.positionWS = positionWS + mul(transformMatrixWithWind, float3(0, bend, height));
output.positionCS = WorldToHClip(output.positionWS, normalWS);
output.uv = float2(0, 1);
triStream.Append(output);
triStream.RestartStrip();
}
}
With grass.hlsl I try to achieve the effect with these lines:
float grassVisibility = tex2Dlod(_GrassMap, float4(input[0].uv, 0, 0)).r;
if(grassVisibility >= _GrassThreshold) {
...
}