Hello everybody, have a question to do with my terrain triplanar texture atlas surface shader, and needing advice or direction in solving a problem that I am having when adding effects for rain and snow, both effects I believe can be solved with the one solution, but what I am struggling with is my limitation on shader maths (Matrix). Problem: Snow building up or water ripples effects being render when inside of caves and tunnels or beneath terrain overhangs. Solution: looking for a solution similar to how shadows work inside shaders, what I have been trying to achieve is, by calculating a vertex position with a up vector against the world matrix, and if no object detected than calculate snow and rain effects, but I have no idea how to detect an object, or even if an object is above or below the current model vertex position, Is this even possible? Truly believe maths is the solution, or will I need to think up another approach, currently used up all of the vertex colors and texture coords channels for rendering and blending the textures of the terrain, which rules out those as a solution, have tried messing with image effect shader but that didn't workout well at all. Any help, ideas or direction will be much appreciated.
Shadows work by rendering the scene from the point of view of a light and storing the closest distance from the light to the first object and storing it as a texture, these are shadow maps. Shaders are given the same matrix as used by the light to see the world and that texture when rendering to the screen, then the shader checks if it's location is further away from the light than the value recorded on the shadow map for that direction. For your terrain you'd need to do some similar; you need to find some way to store the information about if each part of the terrain can see the sky or not. A shadowmap like solution could be possible, but you're probably better off storing that data in the terrain vertices themselves if possible, like the alpha of the vertex color. I don't know what you're doing to generate your terrain, but I wouldn't be surprised if the system you're using didn't already have some method of determining sky visibility.
Thank you! Also thanks for the great example of shaders calculating shadows too! Yes you are right, would be much easier and less work of my shader, to calculate this information during the mesh generation and would be a better approach. Having used all the color channels (RGBA) for blending textures, I might try and use the texture coord channel 2 that is reserved for light-maps, pass through the snow texture coords of any surfaces that is in the open terrain, or the same coords for a texture that isn't on the surface, and being a procedural terrain, dont think I will be requiring light-maps. Closing this thread, as it has been answered by bgolus.
Note the texture coordinates can be float3 or float4 values, it's just that they're usually treated as float2 values. You can use the first two components of texcoord0 (xy) for your texture coordinates, and then use z and w for whatever data you want.
bgolus I have implement a triplanar bump fragment shader code you shared in another post, and by the way, very noice fragment shader too. https://forum.unity3d.com/threads/triplanar-shader-using-world-normals-bump-mapping-help.426757/ However having issuse with the normals and have tried and tried to fix it, my problem seems to be with implementing into my triplanar atlas fragment shader and appears though its a seaming issue within the atlas coords. I have tried messing with unity filter and mip maps options of the bump textures, also messed around with the uv continuous of tex2D coords without success. I am really liking your triplanar bump fragment shader look, and wondering if you able help me to spot my error implementing the code or suggestions on where to focus in hunting down a solution, but if this is impossible, thats no biggie, will use the triplanar atlas fragment shader created earlier. The shader code below is still in a hacky kind of state as I am still testing a playing around with it, but all advice is totally welcomed. Code (CSharp): Shader "Custom/Terrain/Triplanar/Atlas/Diffuse Bump" { Properties { _TexOffset ("Texture offset alignment", Range(0, 1)) = 0 _TexPower ("Textures merging", Range(1, 20)) = 7 _TileCount ("Texture atlas total tiles", Float) = 16 // Diffuse Settings [HideInInspector] _TopTex ("Top Texture", 2D) = "white" {} _TopBump ("Top normal map", 2D) = "bump" {} _TopColor ("Top color tint", Color) = (1.0, 1.0, 1.0, 1.0) [HideInInspector] _SideTex ("Side Texture", 2D) = "White" {} _SideBump ("Side normal map", 2D) = "bump" {} _SideColor ("Side color tint", Color) = (1.0, 1.0, 1.0, 1.0) [HideInInspector] _BotTex ("Bottom Texture", 2D) = "white" {} _BotBump ("Bottom normal map", 2D) = "bump" {} _BotColor ("Bottom color tint", Color) = (1.0, 1.0, 1.0, 1.0) _TileLookup ("Texture atlas total tiles for lookup texture", Range(1, 32)) = 8 _LightTex ("Lighting Map (RGB)", 2D) = "white" {} // Ramp Settings _SpecExp("Specular Exponent", Range(0.1, 150)) = 20.0 _SpecBoost("Specular Boost", Float) = 0.3 } CGINCLUDE #include "UnityCG.cginc" #ifndef ATLAS_INCLUDED #define ATLAS_INCLUDED #include "Assets/Resources/Shader Bowl/Shaders/IncludeAtlas.cginc" #endif #ifndef SNOW_INCLUDED #define SNOW_INCLUDED #include "Assets/Resources/Shader Bowl/Shaders/IncludeSnow.cginc" #endif // Textures uniform sampler2D _TopTex; uniform sampler2D _TopBump; uniform sampler2D _SideTex; uniform sampler2D _SideBump; uniform sampler2D _BotTex; uniform sampler2D _BotBump; uniform sampler2D _LightTex; uniform half4 _TopTex_ST; uniform half4 _BotTex_ST; uniform half4 _SideTex_ST; uniform half4 _LightTex_ST; // Properties uniform half4 _TopColor; uniform half4 _SideColor; uniform half4 _BotColor; uniform half _TexOffset; uniform half _TexPower; uniform float _SpecExp; uniform float _SpecBoost; uniform float3 blendingWeights; float3 BlendNormalWeights(float2 main, float3 pos, float3 norm) { // extract world normal from the unused w component of world to tangent matrix float3 projNormal = saturate(pow(norm * 1.4, 4)); // "normalize" projNormal x+y+z to equal 1, ensures even blend projNormal /= projNormal.x + projNormal.y + projNormal.z; float xsign = sign(norm.x); float2 zy = pos.zy * float2(xsign, 1.0) * _SideTex_ST.xy + _SideTex_ST.zw; float3 xNorm = BumpMain(_SideBump, main.xy, zy); float ysign = sign(norm.y); float2 zx = pos.zx * _TopTex_ST.xy + _TopTex_ST.zw; float3 yNorm = ysign > 0 ? BumpMain(_TopBump, main.xy, zx) : BumpMain(_BotBump, main.xy, zx); float zsign = sign(norm.z); float2 xy = pos.xy * float2(-zsign, 1.0) * _SideTex_ST.xy + _SideTex_ST.zw; float3 zNorm = BumpMain(_SideBump, main.xy, xy); // use normal blending to wrap normal map to surface normal xNorm = normalize(half3(xNorm.xy * float2(xsign, 1.0) + norm.zy, norm.x)); yNorm = normalize(half3(yNorm.xy + norm.zx, norm.y)); zNorm = normalize(half3(zNorm.xy * float2(-zsign, 1.0) + norm.xy, norm.z)); // reorient normals to their world axis xNorm = xNorm.zyx; // hackily swizzle channels to match unity "right" yNorm = yNorm.yzx; // hackily swizzle channels to match unity "up" zNorm = zNorm.xyz; // no swizzle needed // blend normals together return xNorm * projNormal.x + yNorm * projNormal.y + zNorm * projNormal.z; } fixed4 BlendTexWeights(float2 main, float4 splat1, float4 splat2, fixed4 color, float3 norm) { float y = sign(norm.y); fixed4 top; if (y > 0) { top = lerp(TexMain(_TopTex, main.xy, UVy), TexMain(_TopTex, splat1.xy, UVy), color.r); top = lerp(top, TexMain(_TopTex, splat1.zw, UVy), color.b); top = lerp(top, TexMain(_TopTex, splat2.xy, UVy), color.g); top = lerp(top, TexMain(_TopTex, splat2.zw, UVy), color.a); } else { top = lerp(TexMain(_BotTex, main.xy, UVy), TexMain(_BotTex, splat1.xy, UVy), color.r); top = lerp(top, TexMain(_BotTex, splat1.zw, UVy), color.b); top = lerp(top, TexMain(_BotTex, splat2.xy, UVy), color.g); top = lerp(top, TexMain(_BotTex, splat2.zw, UVy), color.a); } fixed4 side = lerp(TexMain(_SideTex, main.xy, UVx), TexMain(_SideTex, splat1.xy, UVx), color.r); side = lerp(side, TexMain(_SideTex, splat1.zw, UVx), color.b); side = lerp(side, TexMain(_SideTex, splat2.xy, UVx), color.g); side = lerp(side, TexMain(_SideTex, splat2.zw, UVx), color.a); fixed4 face = lerp(TexMain(_SideTex, main.xy, UVz), TexMain(_SideTex, splat1.xy, UVz), color.r); face = lerp(face, TexMain(_SideTex, splat1.zw, UVz), color.b); face = lerp(face, TexMain(_SideTex, splat2.xy, UVz), color.g); face = lerp(face, TexMain(_SideTex, splat2.zw, UVz), color.a); top *= _TopColor; side *= _SideColor; face *= _BotColor; if(_Snow > 0) { top.rgb = Snow_Blend(top, norm); side.rgb = Snow_Blend(side, norm); } return side.xyzw * blendingWeights.xxxx + top.xyzw * blendingWeights.yyyy + face.xyzw * blendingWeights.zzzz; } ENDCG Subshader { Tags{"Queue"="Geometry" "IgnoreProjector"="True" "RenderType"="Opaque"} LOD 200 Pass { Tags {"LightMode"="ForwardBase"} CGPROGRAM #include "Lighting.cginc" #include "AutoLight.cginc" #pragma target 3.0 #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #pragma multi_compile_fwdbase struct appdata { fixed4 color : COLOR; float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float2 texcoord : TEXCOORD; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 texcoord3 : TEXCOORD3; }; struct v2f { fixed4 color : COLOR0; float3 normal : NORMAL0; float3 worldNorm : NORMAL1; float4 pos : SV_POSITION; float3 worldPos : POSITION1; float2 tc_Main : TEXCOORD0; float2 tc_Light : TEXCOORD1; float4 tc_Splat1 : TEXCOORD2; float4 tc_Splat2 : TEXCOORD3; float3 viewDir : TEXCOORD4; float3 lightDir : TEXCOORD5; SHADOW_COORDS(6) UNITY_FOG_COORDS(7) }; v2f vert(in appdata v) { v2f o; o.color = v.color; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.worldNorm = UnityObjectToWorldNormal(v.normal); o.normal = mul(float4(v.normal, 0.0), unity_WorldToObject).xyz; o.viewDir = WorldSpaceViewDir(v.vertex); o.lightDir = UnityWorldSpaceLightDir(o.worldPos); o.tc_Main.xy = TRANSFORM_TEX(v.texcoord.xy, _TopTex); o.tc_Splat1.xy = TRANSFORM_TEX(v.texcoord2.xy, _TopTex); o.tc_Splat1.zw = TRANSFORM_TEX(v.texcoord2.zw, _TopTex); o.tc_Splat2.xy = TRANSFORM_TEX(v.texcoord3.xy, _TopTex); o.tc_Splat2.zw = TRANSFORM_TEX(v.texcoord3.zw, _TopTex); float width = (1.0 / _TileLookup); o.tc_Light = TRANSFORM_TEX(v.texcoord1.xy, _LightTex); float uu = ((o.tc_Light.x - 1.0) * width) + (width * _LightTex_ST.x); float vv = (o.tc_Light.y * 1.0) + (1.0 * _LightTex_ST.y); o.tc_Light = float2(uu, vv); TRANSFER_SHADOW(o); UNITY_TRANSFER_FOG(o, o.pos); return o; } fixed4 frag(in v2f i) : SV_TARGET { // Tile counter INV_TILE_COUNT = 1 / _TileCount; INV_TILE_LOOKUP = 1 / _TileLookup; // Bending texture weights blendingWeights = pow(abs(normalize(i.worldNorm)), _TexPower); blendingWeights = (blendingWeights - 0.2) * _TexPower; blendingWeights = max(blendingWeights, 0); blendingWeights /= (blendingWeights.x + blendingWeights.y + blendingWeights.z).x; // Triplanar UVs UVx = i.worldPos.yz * _SideTex_ST.xy + _SideTex_ST.zw - _TexOffset; UVy = i.worldPos.xz * _TopTex_ST.xy + _TopTex_ST.zw - _TexOffset; UVz = float2(i.worldPos.x, -i.worldPos.y) * _BotTex_ST.xy + _BotTex_ST.zw - _TexOffset; // Albedo fixed4 albedo = BlendTexWeights(i.tc_Main, i.tc_Splat1, i.tc_Splat2, i.color, i.worldNorm); // Normal float3 normal = BlendNormalWeights(i.tc_Main, i.worldPos, i.worldNorm); // Compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed). UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); // Diffuse float4 diffuse = saturate(dot(normalize(normal), _WorldSpaceLightPos0.xyz)) * _LightColor0 * atten; float4 lighting; if(0.0 == _WorldSpaceLightPos0.w) { // Directional light float4 light = tex2D(_LightTex, i.tc_Light); // Darken light's illumination with shadow, keep ambient intact. lighting = float4(diffuse + ShadeSH9(half4(normal, 1)), 1) * light; } else { // Directions i.normal = normalize(i.normal); i.viewDir = normalize(i.viewDir); i.lightDir = normalize(i.lightDir); float3 reflLightDir = reflect(-i.lightDir, i.normal); // Specular float vDotR = max(0.0, dot(i.viewDir, reflLightDir)); float4 specular = atten * _LightColor0 * _SpecBoost * pow(vDotR, _SpecExp); // Point or spot light lighting = diffuse + specular; } fixed3 col = albedo.rgb * lighting.rgb; UNITY_APPLY_FOG(i.fogCoord, col); return fixed4(col, 1); } ENDCG } Pass { Tags { "LightMode" = "ForwardAdd" } ZWrite Off BlendOp Max CGPROGRAM #include "Lighting.cginc" #include "AutoLight.cginc" #pragma target 3.0 #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #pragma multi_compile_fwdadd_fullshadows struct appdata { fixed4 color : COLOR; float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 texcoord3 : TEXCOORD3; }; struct v2f { fixed4 color : COLOR0; float3 normal : NORMAL0; float3 worldNorm : NORMAL1; float4 pos : SV_POSITION; float3 worldPos : POSITION1; float2 tc_Main : TEXCOORD0; float2 tc_Light : TEXCOORD1; float4 tc_Splat1 : TEXCOORD2; float4 tc_Splat2 : TEXCOORD3; float3 viewDir : TEXCOORD4; float3 lightDir : TEXCOORD5; SHADOW_COORDS(6) UNITY_FOG_COORDS(7) }; v2f vert(in appdata v) { v2f o; o.color = v.color; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.worldNorm = UnityObjectToWorldNormal(v.normal); o.viewDir = WorldSpaceViewDir(v.vertex); o.normal = mul(float4(v.normal, 0.0), unity_WorldToObject).xyz; o.lightDir = UnityWorldSpaceLightDir(o.worldPos); o.tc_Main.xy = TRANSFORM_TEX(v.texcoord.xy, _TopTex); o.tc_Light.xy = TRANSFORM_TEX(v.texcoord1.xy, _LightTex); o.tc_Splat1.xy = TRANSFORM_TEX(v.texcoord2.xy, _TopTex); o.tc_Splat1.zw = TRANSFORM_TEX(v.texcoord2.zw, _TopTex); o.tc_Splat2.xy = TRANSFORM_TEX(v.texcoord3.xy, _TopTex); o.tc_Splat2.zw = TRANSFORM_TEX(v.texcoord3.zw, _TopTex); TRANSFER_SHADOW(o); UNITY_TRANSFER_FOG(o, o.pos); return o; } fixed4 frag(in v2f i) : SV_TARGET { // Tile counter INV_TILE_COUNT = 1 / _TileCount; // Bending texture weights blendingWeights = pow(abs(normalize(i.worldNorm)), _TexPower); blendingWeights = (blendingWeights - 0.2) * _TexPower; blendingWeights = max(blendingWeights, 0); blendingWeights /= (blendingWeights.x + blendingWeights.y + blendingWeights.z).x; // Triplanar UVs UVx = i.worldPos.yz * _SideTex_ST.xy + _SideTex_ST.zw - _TexOffset; UVy = i.worldPos.xz * _TopTex_ST.xy + _TopTex_ST.zw - _TexOffset; UVz = float2(i.worldPos.x, -i.worldPos.y) * _BotTex_ST.xy + _BotTex_ST.zw - _TexOffset; // Albedo fixed4 albedo = BlendTexWeights(i.tc_Main, i.tc_Splat1, i.tc_Splat2, i.color, i.worldNorm); // Normal float3 normal = BlendNormalWeights(i.tc_Main, i.worldPos, i.worldNorm); // Compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed). UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); #ifndef USING_DIRECTIONAL_LIGHT // Lighting float4 lighting = saturate(dot(normalize(normal), normalize(UnityWorldSpaceLightDir(i.worldPos)))) * _LightColor0 * atten; #else // Directions i.normal = normalize(i.normal); i.viewDir = normalize(i.viewDir); i.lightDir = normalize(i.lightDir); float3 reflLightDir = reflect(-i.lightDir, i.normal); // Specular float vDotR = max(0.0, dot(i.viewDir, reflLightDir)); float4 specular = atten * _LightColor0 * _SpecBoost * pow(vDotR, _SpecExp); // Diffuse float4 diffuse = saturate(dot(normalize(normal), _WorldSpaceLightPos0.xyz)) * _LightColor0 * atten; // Lighting float4 lighting = specular + diffuse; #endif fixed3 col = albedo.rgb * lighting.rgb; UNITY_APPLY_FOG(i.fogCoord, col); return fixed4(col, 1); } ENDCG } Pass { Tags {"LightMode"="ShadowCaster"} CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster struct v2fs { V2F_SHADOW_CASTER; }; v2fs vert(appdata_base v) { v2fs o; TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) return o; } float4 frag(v2fs i) : SV_Target { SHADOW_CASTER_FRAGMENT(i) } ENDCG } } FallBack "Diffuse" } Included Code (CSharp): // Global variables uniform half INV_TILE_COUNT; uniform half INV_TILE_LOOKUP; uniform float2 UVx, UVy, UVz; uniform half _TileCount; uniform half _TileLookup; // Functions inline fixed3 BumpMain(sampler2D bump, float2 coords, float2 uv) { coords.x = coords.x - 1; coords.y = _TileCount - coords.y; float2 uvContinuous = (coords + uv) * INV_TILE_COUNT; return UnpackNormal(tex2D(bump, float2((coords + frac(uv)) * INV_TILE_COUNT), ddx(uvContinuous), ddy(uvContinuous))); } inline fixed4 TexMain(sampler2D tex, float2 coords, float2 uv) { coords.x = coords.x - 1; coords.y = _TileCount - coords.y; float2 uvContinuous = (coords + uv) * INV_TILE_COUNT; return tex2D(tex, float2((coords + frac(uv)) * INV_TILE_COUNT), ddx(uvContinuous), ddy(uvContinuous)); } Thanks for any type of help.
To me it just looks like your average atlas issues when not using a gutter between tiles. It's just being accentuated by the normal map rendering (which looks like it's a lower resolution than the albedo textures?).
ahhh, I never thought about trying a gutter, very nice idea! hmmm, if I remember correctly the normal maps are just a copy of the original, however will go over all settings and make sure that the resolution are indeed the correct size. Would be nice to see if this works, and thank you a tone for such a fast response, keep you updated if you are interested?
You might also want to spend the time looking into using Texture2DArray instead of an atlas. A little more work, but removes the pain of adding gutters to the atlas, and having to do manual repeating of the UVs.
Interesting, I never heard of or come across this type of concept, would you mind enlightening me? Is it hard?
https://docs.unity3d.com/Manual/SL-TextureArrays.html https://docs.unity3d.com/ScriptReference/Texture2DArray.html Short version is you have to construct a texture array yourself from script (preferably in editor) by copying several texture assets and their mip maps into a single texture array asset using CopyTexture() then save that asset and apply it to your shader. It's not as straight forward as just applying textures to your material, but it has a ton of benefits for rendering.
Thank you kind sir, weighing the pros and cons, as I am seeing a bit of work needed for implementing into the game engine, but the rendering benefits might tip the scales
Hey bgolus, Would you mind helping me out again? And by the way, thank you with helping me out in the past, have also including a few screen-shots of the texture array and bump mapping, as your suggested solution in fixing my shader in earlier posts, worked like a charm! I am having issues with the lighting in my fragment shader, it appears though its calculating light sources as per vertex instead of per pixel, pretty sure my code should be calculating per pixel, would this be correct or am I missing something? One directional light source One directional & one spot light source In these screen-shots one can see the procedural chunk meshes lighting calculated as a whole mesh (kinda like vertex lighting), is it possible to calculate the lighting from a source instead? Ocean Shader Code (CSharp): Pass { Tags {"LightMode"="ForwardBase"} CGPROGRAM #include "Lighting.cginc" #include "AutoLight.cginc" #ifndef FRAG_LIGHTING_INCLUDED #define FRAG_LIGHTING_INCLUDED #include "Assets/Resources/Shader Bowl/Shaders/FragmentLighting.cginc" #endif #pragma target 3.5 #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #pragma multi_compile_fwdbase struct appdata { fixed4 color : COLOR; float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD; float4 texcoord1 : TEXCOORD1; }; struct v2f { fixed4 color : COLOR0; float3 normal : NORMAL0; float3 worldNorm : NORMAL1; float4 pos : SV_POSITION; float3 worldPos : POSITION1; float2 uvMain : TEXCOORD0; float darkness : TEXCOORD1; half4 screenPos : TEXCOORD4; float4 grabPos : TEXCOORD5; float4 bumpCoords : TEXCOORD6; float4 viewInterpolator : TEXCOORD7; SHADOW_COORDS(8) UNITY_FOG_COORDS(9) }; v2f vert(in appdata v) { float ns = snoise(float3(v.vertex.x * _NoiseScale, v.vertex.z * _NoiseScale, _Time.x * _WaveSpeed)); v.vertex.y += ns * _HeightScale; v2f o; o.color = v.color; o.normal = mul(float4(v.normal, 0.0), unity_WorldToObject).xyz; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.worldNorm = UnityObjectToWorldNormal(v.normal); o.screenPos = ComputeScreenPos(o.pos); o.grabPos = ComputeGrabScreenPos(o.pos); o.darkness = pow((v.texcoord1.x - 1) / (_LightCount - 1), _DarknessExp); o.uvMain = TRANSFORM_TEX(v.texcoord.xy, _MainTex); half2 tileableUv = mul(unity_ObjectToWorld,(v.vertex)).xz; o.bumpCoords.xyzw = (tileableUv.xyxy + _Time.xxxx * _BumpDirection.xyzw) * _BumpTiling.xyzw; o.viewInterpolator.xyz = o.worldPos - _WorldSpaceCameraPos; o.viewInterpolator.w = saturate(OFFSETS.y); TRANSFER_SHADOW(o); UNITY_TRANSFER_FOG(o, o.pos); return o; } fixed4 frag(in v2f i, fixed facing : VFACE) : SV_TARGET { // Using VFACE to calculate lighting for back and front faces of the quad. i.normal.z *= facing; // flip Z based on facing // Directions i.normal = normalize(i.normal); // Ambient float4 ambient; CalculateAmbientLight(i.worldNorm, ambient); // Compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed). UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); // Diffuse float4 diffuse = saturate(dot(normalize(i.normal), _WorldSpaceLightPos0.xyz)) * _LightColor0 * atten; // Darken light's illumination with shadow, keep ambient intact. float3 lighting = (diffuse * atten + ambient) * i.darkness; // Albedo texture fixed4 albedo = TexMain(i.worldPos, i.uvMain); // Depth test float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)); depth = LinearEyeDepth(depth); // Blending edges half4 edgeBlendFactors = saturate(_InvFadeParemeter * (depth - i.screenPos.w)); edgeBlendFactors.y = 1.0 - edgeBlendFactors.y; // Alpha shoreline albedo += edgeBlendFactors.y; albedo.a = edgeBlendFactors.w * edgeBlendFactors.x; // Final shoreline color fixed4 bgColor = tex2Dproj(_BackgroundTexture, i.grabPos); albedo = lerp(bgColor * edgeBlendFactors.z, albedo, albedo.a); albedo = lerp(albedo, bgColor, edgeBlendFactors.y); // Foam shoreline half4 foam = Foam(_ShoreTex, i.bumpCoords * 2.0); albedo.rgb += foam.rgb * _Foam.x * (edgeBlendFactors.y + saturate(i.viewInterpolator.w - _Foam.y)); // Final color fixed3 col = albedo.rgb * lighting; fixed alpha = albedo.a; // Fog color UNITY_APPLY_FOG(i.fogCoord, col); return fixed4(col, alpha); } ENDCG } Code (CSharp): Pass { Tags { "LightMode" = "ForwardAdd" } ZWrite Off BlendOp Max CGPROGRAM #include "Lighting.cginc" #pragma target 3.5 #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #pragma multi_compile_fwdadd_fullshadows struct appdata { fixed4 color : COLOR; float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD; float4 texcoord1 : TEXCOORD1; }; struct v2f { fixed4 color : COLOR0; float3 normal : NORMAL0; float3 worldNorm : NORMAL1; float4 pos : SV_POSITION; float3 worldPos : POSITION1; float2 uvMain : TEXCOORD0; float2 uvLight : TEXCOORD1; float3 viewDir : TEXCOORD2; float3 lightDir : TEXCOORD3; half4 screenPos : TEXCOORD4; float4 grabPos : TEXCOORD5; float4 bumpCoords : TEXCOORD6; float4 viewInterpolator : TEXCOORD7; UNITY_FOG_COORDS(8) }; v2f vert(in appdata v) { float ns = snoise(float3(v.vertex.x * _NoiseScale, v.vertex.z * _NoiseScale, _Time.x * _WaveSpeed)); v.vertex.y += ns * _HeightScale; v2f o; o.color = v.color; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.worldNorm = UnityObjectToWorldNormal(v.normal); o.normal = mul(float4(v.normal, 0.0), unity_WorldToObject).xyz; o.viewDir = WorldSpaceViewDir(v.vertex); o.lightDir = UnityWorldSpaceLightDir(o.worldPos); o.screenPos = ComputeScreenPos(o.pos); o.grabPos = ComputeGrabScreenPos(o.pos); o.uvLight = v.texcoord1.xy; o.uvMain = TRANSFORM_TEX(v.texcoord.xy, _MainTex); half2 tileableUv = mul(unity_ObjectToWorld,(v.vertex)).xz; o.bumpCoords.xyzw = (tileableUv.xyxy + _Time.xxxx * _BumpDirection.xyzw) * _BumpTiling.xyzw; o.viewInterpolator.xyz = o.worldPos - _WorldSpaceCameraPos; o.viewInterpolator.w = saturate(OFFSETS.y); UNITY_TRANSFER_FOG(o, o.pos); return o; } fixed4 frag(in v2f i, fixed facing : VFACE) : SV_TARGET { // Using VFACE to calculate lighting for back and front faces of the quad. i.normal.z *= facing; // flip Z based on facing #ifndef USING_DIRECTIONAL_LIGHT // Lighting float3 lighting = saturate(dot(normalize(i.normal), normalize(UnityWorldSpaceLightDir(i.worldPos)))) * _LightColor0.rgb; #else // Directions i.normal = normalize(i.normal); i.lightDir = normalize(i.lightDir); i.viewDir = normalize(i.lightDir); // Light directions half3 reflViewDir = reflect(-i.viewDir, i.normal); half3 reflLightDir = reflect(-i.lightDir, i.normal); // Specular float vDotR = max(0.0, dot(i.viewDir, reflLightDir)); float3 specular = _LightColor0.rgb * _SpecBoost * pow(vDotR, _SpecExp); // Diffuse float3 diffuse = saturate(dot(i.worldNorm, _WorldSpaceLightPos0.xyz)) * _LightColor0.rgb; // Lighting float3 lighting = diffuse + specular; #endif // Albedo texture fixed4 albedo = TexMain(i.worldPos, i.uvMain); // Depth test float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)); depth = LinearEyeDepth(depth); // Blending edges half4 edgeBlendFactors = saturate(_InvFadeParemeter * (depth - i.screenPos.w)); edgeBlendFactors.y = 1.0 - edgeBlendFactors.y; // Alpha shoreline albedo += edgeBlendFactors.y; albedo.a = edgeBlendFactors.w * edgeBlendFactors.x; // Final shoreline color fixed4 bgColor = tex2Dproj(_BackgroundTexture, i.grabPos); albedo = lerp(bgColor * edgeBlendFactors.z, albedo, albedo.a); albedo = lerp(albedo, bgColor, edgeBlendFactors.y); // Foam shoreline half4 foam = Foam(_ShoreTex, i.bumpCoords * 2.0); albedo.rgb += foam.rgb * _Foam.x * (edgeBlendFactors.y + saturate(i.viewInterpolator.w - _Foam.y)); // Final color fixed3 col = albedo.rgb * lighting; fixed alpha = albedo.a; // Fog UNITY_APPLY_FOG(i.fogCoord, col); return fixed4(col, alpha); } ENDCG } Terrain Shader Code (CSharp): Pass { Tags {"LightMode"="ForwardBase"} CGPROGRAM #include "Lighting.cginc" #include "AutoLight.cginc" #ifndef FRAG_LIGHTING_INCLUDED #define FRAG_LIGHTING_INCLUDED #include "Assets/Resources/Shader Bowl/Shaders/FragmentLighting.cginc" #endif #pragma target 3.5 #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynligtmap novertexlight struct appdata { fixed4 color : COLOR; float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 texcoord3 : TEXCOORD3; }; struct v2f { fixed4 color : COLOR0; float ambient : COLOR1; float3 normal : NORMAL0; float3 worldNorm : NORMAL1; float4 pos : SV_POSITION; float3 worldPos : POSITION1; float tc_Main : TEXCOORD0; float tc_Light : TEXCOORD1; float4 tc_Splat : TEXCOORD2; float3 viewDir : TEXCOORD3; float3 lightDir : TEXCOORD4; float3 brightness : TEXCOORD5; float darkness : TEXCOORD6; SHADOW_COORDS(7) UNITY_FOG_COORDS(8) }; v2f vert (appdata v) { v2f o; o.color = v.color; o.normal = mul(float4(v.normal, 0.0), unity_WorldToObject).xyz; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.worldNorm = UnityObjectToWorldNormal(v.normal); o.viewDir = WorldSpaceViewDir(v.vertex); o.lightDir = UnityWorldSpaceLightDir(o.worldPos); o.brightness = float3(1, 1, 1); o.tc_Light = v.texcoord1.x - 1; o.darkness = pow(o.tc_Light / (_LightCount - 1), _DarknessExp); o.ambient = smoothstep(0, 1, dot(float3(0,1,0), _WorldSpaceLightPos0.xyz)); o.ambient = dot(float3(0,1,0), _WorldSpaceLightPos0.xyz); o.tc_Main = v.texcoord.x - 1 + ((v.texcoord.y - 1) * _TileCount); o.tc_Splat.x = v.texcoord2.x - 1 + ((v.texcoord2.y - 1) * _TileCount); o.tc_Splat.y = v.texcoord2.z - 1 + ((v.texcoord2.w - 1) * _TileCount); o.tc_Splat.z = v.texcoord3.x - 1 + ((v.texcoord3.y - 1) * _TileCount); o.tc_Splat.w = v.texcoord3.z - 1 + ((v.texcoord3.w - 1) * _TileCount); TRANSFER_SHADOW(o); UNITY_TRANSFER_FOG(o,o.pos); return o; } half4 frag (v2f i) : SV_Target { // Extract world normal from the unused w component of world to tangent matrix _ProjectNormal = saturate(pow(i.worldNorm * 1.4, 4)); // "normalize" project normal x+y+z to equal 1, ensures even blend _ProjectNormal /= _ProjectNormal.x + _ProjectNormal.y + _ProjectNormal.z; // Directions i.normal = normalize(i.normal); i.lightDir = normalize(i.lightDir); half3 normal; fixed4 albedo; GetTriplanarTextures(i.worldPos, i.worldNorm, i.tc_Main, i.tc_Splat, i.color, albedo, normal); // Exposure fixed4 exposure; GetLighting(i.worldPos, i.worldNorm, i.tc_Light, exposure); // Blending float weight = smoothstep(0.9, 1, dot(exposure.rgb, i.brightness) / 3); // Compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed). UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); // Diffuse float nDotL = dot(normalize(normal), i.lightDir) * 0.5 + 0.5; float4 ramp = tex2D(_RampTex, nDotL.xx); // Snow fixed3 snow = Snow_Blend(albedo, normal); float snowing = _Snow * i.darkness; albedo.rgb = lerp(albedo.rgb, snow, snowing) * ramp; // Ambient float4 ambient; CalculateAmbientLight(normal, ambient); // Diffuse float3 diffuse = saturate(dot(normalize(normal), _WorldSpaceLightPos0.xyz)) * _LightColor0.rgb * ramp.rgb * i.ambient; atten = pow(atten, weight); float3 lighting = (diffuse * atten + ambient.rgb) * i.darkness; // Final color fixed3 col = albedo.rgb * lighting; // Fog color UNITY_APPLY_FOG(i.fogCoord, col); return half4(col, 1); } ENDCG } Code (CSharp): Pass { Tags {"LightMode"="ForwardAdd"} ZWrite Off BlendOp Max CGPROGRAM #include "Lighting.cginc" #pragma target 3.5 #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #pragma multi_compile_fwdadd_fullshadows #pragma multi_compile_fwdadd nolightmap nodirlightmap nodynligtmap novertexlight struct appdata { fixed4 color : COLOR; float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 texcoord3 : TEXCOORD3; }; struct v2f { fixed4 color : COLOR0; float3 normal : NORMAL0; float3 worldNorm : NORMAL1; float4 pos : SV_POSITION; float3 worldPos : POSITION1; float tc_Main : TEXCOORD0; float2 tc_Light : TEXCOORD1; float4 tc_Splat : TEXCOORD2; float3 viewDir : TEXCOORD3; float3 lightDir : TEXCOORD4; float3 brightness : TEXCOORD5; float darkness : TEXCOORD6; UNITY_FOG_COORDS(7) }; v2f vert (appdata v) { v2f o; o.color = v.color; o.normal = mul(float4(v.normal, 0.0), unity_WorldToObject).xyz; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.worldNorm = UnityObjectToWorldNormal(v.normal); o.viewDir = WorldSpaceViewDir(v.vertex); o.lightDir = UnityWorldSpaceLightDir(o.worldPos); o.brightness = float3(1, 1, 1); o.tc_Light = v.texcoord1.x - 1; o.darkness = pow(o.tc_Light / (_LightCount - 1), _DarknessExp); o.tc_Main = v.texcoord.x - 1 + ((v.texcoord.y - 1) * _TileCount); o.tc_Splat.x = v.texcoord2.x - 1 + ((v.texcoord2.y - 1) * _TileCount); o.tc_Splat.y = v.texcoord2.z - 1 + ((v.texcoord2.w - 1) * _TileCount); o.tc_Splat.z = v.texcoord3.x - 1 + ((v.texcoord3.y - 1) * _TileCount); o.tc_Splat.w = v.texcoord3.z - 1 + ((v.texcoord3.w - 1) * _TileCount); UNITY_TRANSFER_FOG(o,o.pos); return o; } half4 frag (v2f i) : SV_Target { // Extract world normal from the unused w component of world to tangent matrix _ProjectNormal = saturate(pow(i.worldNorm * 1.4, 4)); // "normalize" project normal x+y+z to equal 1, ensures even blend _ProjectNormal /= _ProjectNormal.x + _ProjectNormal.y + _ProjectNormal.z; // Directions i.normal = normalize(i.normal); i.lightDir = normalize(i.lightDir); half3 normal; fixed4 albedo; GetTriplanarTextures(i.worldPos, i.worldNorm, i.tc_Main, i.tc_Splat, i.color, albedo, normal); // Exposure fixed4 exposure; GetLighting(i.worldPos, i.worldNorm, i.tc_Light, exposure); // Blending float weight = smoothstep(0.9, 1, dot(exposure.rgb, i.brightness) / 3); // Ramp float nDotL = dot(normalize(normal), i.lightDir); float4 ramp = tex2D(_RampTex, float2(nDotL * 0.5 + 0.5, 0.5)); // Snow fixed3 snow = Snow_Blend(albedo, normal); float snowing = _Snow * i.darkness; albedo.rgb = lerp(albedo.rgb, snow, snowing) * ramp; #ifndef USING_DIRECTIONAL_LIGHT // Lighting float3 lighting = saturate(dot(normalize(normal), normalize(UnityWorldSpaceLightDir(i.worldPos)))) * _LightColor0.rgb * ramp.rgb; #else // Directions i.viewDir = normalize(i.viewDir); float3 reflLightDir = reflect(-i.lightDir, normal); // Specular float vDotR = max(0.0, dot(i.viewDir, reflLightDir)); float3 specular = _LightColor0.rgb * _SpecBoost * pow(vDotR, _SpecExp); // Rim float fr = pow(1 - max(0.0, dot(normal, i.viewDir)), 4); float rim = fr * _RimBoost * pow(vDotR, _RimExp); // Diffuse float3 diffuse = saturate(dot(normalize(normal), _WorldSpaceLightPos0.xyz)) * _LightColor0.rgb * ramp.rgb; // Point or spot light float3 lighting = diffuse + specular + rim; #endif half3 col = albedo.rgb * lighting; UNITY_APPLY_FOG(i.fogCoord, col); return half4(col, 1); } ENDCG }
Your shaders aren't calculating any kind of attenuation (distance falloff) or cone for your lights, just the lighting angle is calculated from a point rather than a direction, so it's not too surprising that the entire surface is being lit with near uniform brightness. The way Unity does additional per-pixel lights is by rendering the entire object again for each light that touches it, up to the number of per pixel lights you have in the quality settings, so if you're not calculating a falloff you're going to see the entire mesh lit. Usually Unity will limit the light's affect to only the region on screen the light covers, based on the light's bounds, but either your light is really big and thus covering the entire screen, or it doesn't do it on transparent objects.
Ahhhhh, thank you bgolus! After going through the cat code rendering documents, makes total sense, your bloods worth bottling. Time to byte the bullet and implement correct lighting, and may as well try out deferred lighting, shadows and fog too, hoping to gain frames with trees and grass with lights and shadows (which forward rendering was struggling with in my engine). Thanks mate!