Hey folks, I’m aware that it’s almost a month after my initial posts, but I figured I’d post the solution we went with for our project - just in case anybody else is looking for resources on this topic (it was a pain in the ass for me!). Credit goes to our awesome guest programmer, who I’ll mention properly if I get permission.
It’s a bit of a mess, but our shader code was as follows…
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "RS/WindGrassRS"
{
Properties
{
// Surface shader parameters
_Color ("Color", Color) = (1,1,1,1)
_EmColor ("Emission Color", Color) = (1,1,1,1)
// _MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
// Wind effect parameters
_WindDirection ("Wind Direction", vector) = (1,0, 1,0)
_WindTex ("Wind Texture", 2D) = "white" {}
_WorldSize ("World Size", vector) = (1,1,1,1)
_WindSpeed ("Wind Speed", vector) = (1,1,1,1)
_OverallBillowEffect("Overall Billow Effect", Range(0,1))= 0.0
//--- TRIPLANAR STUFF ---//
_Texture1 ("Texture 1", 2D) = "white" {}
_Texture2 ("Texture 2", 2D) = "white" {}
_Texture3 ("Texture 3", 2D) = "white" {}
_Scale ("Scale", Range(0.001, 0.2)) = 0.1
//--- OUTLINE STUFF ---//
//_Outline ("_Outline", Range(0,0.1)) = 0
//_OutlineColor ("Outline Color", Color) = (1, 1, 1, 1)
//--- DISSOLVE STUFF ---//
_SliceGuide ("Slice Guide (RGB)", 2D) = "white" {}
_SliceAmount ("Slice Amount", Range(0.0, 1.0)) = 0.5
_PercentSeaweed ("Percentage Seaweed", float) = 1
_PercentStormy ("Percentage Stormy", float) = 0
_PercentBreathing ("Percentage Breathing", float) = 0
}
SubShader
{
Tags {"RenderType" = "Opaque"}
//Cull Front
//Cull Off // DISSOLVE STUFF!
LOD 200
CGPROGRAM
/*
Pass {
Tags { "RenderType"="Opaque" }
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
};
float _Outline;
float4 _OutlineColor;
float4 vert(appdata_base v) : SV_POSITION {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 normal = mul((float3x3) UNITY_MATRIX_MV, v.normal);
normal.x *= UNITY_MATRIX_P[0][0];
normal.y *= UNITY_MATRIX_P[1][1];
o.pos.xy += normal.xy * _Outline;
return o.pos;
}
half4 frag(v2f i) : COLOR {
return _OutlineColor;
}
ENDCG
}
*/
#pragma surface surf Standard vertex:vert
struct Input
{
// float2 uv_MainTex;
//--- TRIPLANAR STUFF ---//
float3 worldNormal;
float3 worldPos;
//--- DISSOLVE STUFF ---//
//float2 uv_SliceGuide;
//float _SliceAmount;
//--- VAINS ---//
float3 localPos; // Local position of vertex
float electricalOffset;
};
//sampler2D _MainTex;
half _Glossiness;
half _Metallic;
fixed4 _Color;
fixed4 _EmissionColor;
float3 _WindDirection;
sampler2D _WindTex;
float4 _WorldSize;
float4 _WindSpeed;
float _OverallBillowEffect;
//--- TRIPLANAR STUFF ---//
sampler2D _Texture1;
sampler2D _Texture2;
sampler2D _Texture3;
float _Scale;
//--- DISSOLVE STUFF ---//
sampler2D _SliceGuide;
float _SliceAmount;
float _PercentSeaweed;
float _PercentStormy;
float _PercentBreathing;
// our vert modification function
void vert(inout appdata_full v, out Input o) // TODO: Add in out Input o...
{
UNITY_INITIALIZE_OUTPUT(Input, o);
o.localPos = v.vertex.xyz;
float4 localSpaceVertex = v.vertex;
// Takes the mesh's verts and turns it into a point in world space
// this is the equivalent of Transform.TransformPoint on the scripting side
float4 worldSpaceVertex = mul( unity_ObjectToWorld, localSpaceVertex );
// normalize position based on world Size
float2 samplePos = ((worldSpaceVertex.xz + _WorldSize.xz/2) / _WorldSize.xz);
// scroll sample position based on time
samplePos += _Time.x * _WindSpeed.xy;
// Sample wind texture
const float da = 0.05;
float windSample = tex2Dlod(_WindTex, float4(samplePos, 0, 0));
windSample += tex2Dlod(_WindTex, float4(samplePos.x + da, samplePos.y, 0, 0));
windSample += tex2Dlod(_WindTex, float4(samplePos.x, samplePos.y + da, 0, 0));
windSample += tex2Dlod(_WindTex, float4(samplePos.x - da, samplePos.y, 0, 0));
windSample += tex2Dlod(_WindTex, float4(samplePos.x, samplePos.y - da, 0, 0));
windSample /= 5;
// windSample = sin(_Time.x * 10); // TESTING: Shows that it is the windsample that's screwing it up :c.
worldSpaceVertex.x += (windSample * _WindDirection.x * v.color) * _OverallBillowEffect;
worldSpaceVertex.z += (windSample * _WindDirection.z * v.color) * _OverallBillowEffect;
// takes the new modified position of the vert in world space and then puts it back in local space
v.vertex = mul( unity_WorldToObject, worldSpaceVertex );
// Stuff for vains
float2 samplePos2 = ((worldSpaceVertex.xz + _WorldSize.xz / 2) / _WorldSize.xz);
o.electricalOffset = tex2Dlod(_WindTex, float4(samplePos2, 0, 0));
//float2 modelPos = mul(float4(0, 0, 0, 1), unity_ObjectToWorld).xz / _WorldSize.xz;
//o.electricalOffset = (tex2Dlod(_WindTex, float4(modelPos, 0, 0)) + _Time.x) % 1;
}
float getLightVal(float3 p1, float3 p2, Input IN) {
// Unpacking vects
float offsetMult = p1.x, freq = p1.y, timePercOutside = p1.z;
float numWaves = p2.x;
float sinMult = p2.y, sinAdd = p2.z;
// Parametric func
float off = (IN.electricalOffset*offsetMult + _Time.x * freq / timePercOutside) * timePercOutside;
float t = -IN.localPos.y*numWaves + off;
return clamp(sin(t) * sinMult + sinAdd, 0, 1);
}
void surf(Input IN, inout SurfaceOutputStandard o)
{
//--- TRIPLANAR STUFF ---//
//fixed4 col1 = tex2D(_Texture2, IN.worldPos.yz * _Scale);// * _Color;
//fixed4 col2 = tex2D(_Texture1, IN.worldPos.xz * _Scale);// * _Color;
//fixed4 col3 = tex2D(_Texture3, IN.worldPos.xy * _Scale);// * _Color;
fixed4 col1 = tex2D(_Texture2, IN.worldPos.yz * _Scale);// * _Color;
fixed4 col2 = tex2D(_Texture1, IN.worldPos.xz * _Scale);// * _Color;
fixed4 col3 = tex2D(_Texture3, IN.worldPos.xy * _Scale);// * _Color;
float3 vec = abs(IN.worldNormal);
vec /= vec.x + vec.y + vec.z + 0.001f;
fixed4 col = vec.x * col1 + vec.y * col2 + vec.z * col3;
/*
fixed4 slice1 = tex2D(_SliceGuide, IN.worldPos.yz * _Scale);
fixed4 slice2 = tex2D(_SliceGuide, IN.worldPos.xz * _Scale);
fixed4 slice3 = tex2D(_SliceGuide, IN.worldPos.xy * _Scale);
fixed4 sliceAll = vec.x * slice1 + vec.y * slice2 + vec.z * slice3;
clip (sliceAll.rgb - _SliceAmount);
*/
o.Albedo = col;
//o.Emission = col + _EmissionColor;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = col.a;
// TEMP: Testing for veins - Base version w/ ~2 waves per tree
// Sharper waves, visable best from the side. (STORMY)
//o.Albedo = col / 2;
//float off = (IN.electricalOffset*3.14 + _Time.x * 3) * 4;
//float t = -IN.localPos.y / 3.14 + off;
//o.Emission = col * clamp(sin(t) * 50 - 48.7, 0, 1);
// Stormy (kinda)
//o.Albedo = col / 2;
//float off = (IN.electricalOffset*3.14 + _Time.x * 7) * 3; // More 'breathing'
//float t = -IN.localPos.y /1.5 + off;
//o.Emission = col * clamp(sin(t) * 2 - 0.8, 0, 1);
// SEAWEED
//o.Albedo = col / 2;
//float off = (IN.electricalOffset + _Time.x * 40);
//o.Emission = 0.05 + col * (0.2 + 0.5*sin(-IN.localPos.y*2.3 + off));
// SEAWEED (parametric)
//float offsetMult = 1, freq = 40, timePercOutside = 1;
//float numWaves = 2.3;
//float sinMult = 0.5, sinAdd = 0.2;
const float3 p1W = float3(1, 40, 1);
const float3 p2W = float3(2.3, 0.5, 0.2);
float sw = getLightVal(p1W, p2W, IN);
// Stormy (kinda/breathing) (parametric)
//float offsetMult = 3.14, freq = 21, timePercOutside = 3;
//float numWaves = 1/1.5;
//float sinMult = 2, sinAdd = -0.8;
const float3 p1STB = float3(3.14, 21, 3);
const float3 p2STB = float3(1 / 1.5, 2, -0.8);
float stb = getLightVal(p1STB, p2STB, IN);
// Stormy (parametric)
//float offsetMult = 3.14, freq = 12, timePercOutside = 3;
//float numWaves = 1 / 3.14;
//float sinMult = 50, sinAdd = -48.7;
const float3 p1ST = float3(3.14, 12, 3);
const float3 p2ST = float3(1 / 3.14, 50, -48.7);
float st = getLightVal(p1ST, p2ST, IN);
// Interpolate
//const float3 p1 = _PercentSeaweed * p1W + _PercentStormy * p1ST + _PercentBreathing * p1STB;
//const float3 p2 = _PercentSeaweed * p2W + _PercentStormy * p2ST + _PercentBreathing * p2STB;
float brightness = _PercentSeaweed * sw + _PercentStormy * st + _PercentBreathing * stb;
// Shader vals...
o.Albedo = col / 2;
o.Emission = col * brightness;
}
ENDCG
}
FallBack "Diffuse"
}
@gamedevbill - Many thanks for the advice, man! I’m going to sit down and study that now at my leisure!
@DrDust265 - Really appreciate that, man! That tutorial drove me a little crazy, and I’d appreciate the chance to sit down and really study a fully-working version of the shader.