diff options
| -rw-r--r-- | 3ner.cginc | 10 | ||||
| -rw-r--r-- | 3ner.shader | 16 | ||||
| -rw-r--r-- | Impostor.shader | 63 | ||||
| -rw-r--r-- | Scripts/Impostors.cs | 16 | ||||
| -rw-r--r-- | features.cginc | 4 | ||||
| -rw-r--r-- | globals.cginc | 14 | ||||
| -rw-r--r-- | impostor.cginc | 151 | ||||
| -rw-r--r-- | pbr.cginc | 6 |
8 files changed, 107 insertions, 173 deletions
@@ -16,6 +16,7 @@ #include "interpolators.cginc" #include "ray_marching.cginc" #include "vertex.cginc" +#include "impostor.cginc" v2f vert(appdata v) { #if defined(SHADOW_CASTER_PASS) && !defined(_SHADOW_CASTER) @@ -28,6 +29,12 @@ v2f vert(appdata v) { UNITY_TRANSFER_INSTANCE_ID(v, o); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); +#if defined(_IMPOSTORS) + float3 impostorWorldPos = mul(unity_ObjectToWorld, v.vertex); + impostor_vert(v.vertex, impostorWorldPos); + v.vertex = mul(unity_WorldToObject, float4(impostorWorldPos, 1)); +#endif + #if defined(_TESSELLATION) o.tpos = v.vertex; #endif @@ -53,6 +60,7 @@ v2f vert(appdata v) { deform_normal(o.objPos, o.normal.xyz, o.tangent.xyz); #endif deform(o.objPos); + propagateObjPos(o); UNITY_TRANSFER_LIGHTING(o, v.uv1); @@ -63,7 +71,7 @@ v2f vert(appdata v) { TRANSFER_SHADOW(o); #endif - return o; + return o; } //ifex _Tessellation_Enabled==0 diff --git a/3ner.shader b/3ner.shader index 4818c08..b210532 100644 --- a/3ner.shader +++ b/3ner.shader @@ -377,6 +377,22 @@ Shader "yum_food/3ner" [HideInInspector] m_end_Marble("Marble", Float) = 0 //endex + //ifex _Impostors_Enabled==0 + [HideInInspector] m_start_Impostors("Impostors", Float) = 0 + [ThryToggle(_IMPOSTORS)] _Impostors_Enabled("Enable", Float) = 0 + _Impostors_Atlas("Atlas", 2D) = "white" {} + _Impostors_Normal_Atlas("Normal Atlas", 2D) = "bump" {} + _Impostors_Metallic_Gloss_Atlas("Metallic Gloss Atlas", 2D) = "white" {} + _Impostors_Depth_Atlas("Depth Atlas", 2D) = "white" {} + _Impostors_Grid_Resolution("Grid Resolution", Int) = 5 + _Impostors_Sphere_Radius("Sphere Radius", Float) = 1.0 + _Impostors_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5 + + [Toggle] _Impostors_Debug_Mode("Debug Mode", Float) = 0 + [Toggle] _Impostors_Debug_Depth("Debug Depth", Float) = 0 + [HideInInspector] m_end_Impostors("Impostors", Float) = 0 + //endex + [HideInInspector] m_end_Gimmicks("Gimmicks", Float) = 0 //ifex _Tessellation_Enabled==0 diff --git a/Impostor.shader b/Impostor.shader deleted file mode 100644 index f2eb602..0000000 --- a/Impostor.shader +++ /dev/null @@ -1,63 +0,0 @@ -Shader "yum_food/Gimmicks/Impostors" -{ - Properties - { - _ImpostorAtlas("Impostor Atlas", 2D) = "white" {} - _ImpostorDepthAtlas("Impostor Depth Atlas", 2D) = "white" {} - _GridResolution("Grid Resolution", Int) = 5 - _SphereRadius("Sphere Radius", Float) = 1.0 - _Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5 - _Color("Tint", Color) = (1, 1, 1, 1) - - [Toggle] _DebugMode("Debug Mode", Float) = 0 - [Toggle] _DebugDepth("Debug Depth", Float) = 0 - - [Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull", Float) = 0 - [Enum(Off, 0, On, 1)] _ZWrite("ZWrite", Int) = 1 - } - - SubShader - { - Tags { "RenderType" = "TransparentCutout" "Queue" = "AlphaTest" } - - Pass - { - Name "FORWARD" - Tags { "LightMode" = "ForwardBase" } - - Cull [_Cull] - ZWrite [_ZWrite] - - CGPROGRAM - #pragma target 3.0 - #pragma vertex vert - #pragma fragment frag - #pragma multi_compile_fog - #pragma multi_compile_instancing - - #include "impostor.cginc" - ENDCG - } - - Pass - { - Name "SHADOW" - Tags { "LightMode" = "ShadowCaster" } - - Cull [_Cull] - - CGPROGRAM - #pragma target 3.0 - #pragma vertex vert - #pragma fragment frag - #pragma multi_compile_shadowcaster - #pragma multi_compile_instancing - - #define IMPOSTOR_SHADOW_PASS - #include "impostor.cginc" - ENDCG - } - } - - FallBack "Transparent/Cutout/Diffuse" -} diff --git a/Scripts/Impostors.cs b/Scripts/Impostors.cs index e8513eb..b82dc8f 100644 --- a/Scripts/Impostors.cs +++ b/Scripts/Impostors.cs @@ -312,10 +312,10 @@ public class Impostors : MonoBehaviour var exportSettings = new (Texture2D atlas, TextureExportSettings settings, string materialProp)[] { - (albedoAtlas, new TextureExportSettings("albedo", mipmaps: true, sRGB: true, alphaTransparency: true), "_ImpostorAtlas"), - (normalAtlas, new TextureExportSettings("normal", mipmaps: true, sRGB: false), "_ImpostorNormalAtlas"), - (metallicGlossAtlas, new TextureExportSettings("metallic_gloss", mipmaps: true, sRGB: false), "_ImpostorMetallicGlossAtlas"), - (depthAtlas, new TextureExportSettings("depth", isEXR: true, mipmaps: false, sRGB: false, filter: FilterMode.Bilinear, uncompressed: true), "_ImpostorDepthAtlas") + (albedoAtlas, new TextureExportSettings("albedo", mipmaps: true, sRGB: true, alphaTransparency: true), "_Impostors_Atlas"), + (normalAtlas, new TextureExportSettings("normal", mipmaps: true, sRGB: false), "_Impostors_Normal_Atlas"), + (metallicGlossAtlas, new TextureExportSettings("metallic_gloss", mipmaps: true, sRGB: false), "_Impostors_Metallic_Gloss_Atlas"), + (depthAtlas, new TextureExportSettings("depth", isEXR: true, mipmaps: false, sRGB: false, filter: FilterMode.Bilinear, uncompressed: true), "_Impostors_Depth_Atlas") }; string[] paths = new string[exportSettings.Length]; @@ -336,14 +336,16 @@ public class Impostors : MonoBehaviour { DestroyExistingImpostor(); - Shader shader = Shader.Find("yum_food/Gimmicks/Impostors"); + Shader shader = Shader.Find("yum_food/3ner"); if (shader == null) { Debug.LogError("Shader not found"); return; } impostorMaterial = new Material(shader); for (int i = 0; i < textures.Length; i++) impostorMaterial.SetTexture(exportSettings[i].materialProp, textures[i]); - impostorMaterial.SetInt("_GridResolution", gridResolution); - impostorMaterial.SetFloat("_SphereRadius", sphere_radius_); + impostorMaterial.SetInt("_Impostors_Grid_Resolution", gridResolution); + impostorMaterial.SetFloat("_Impostors_Sphere_Radius", sphere_radius_); + impostorMaterial.SetFloat("_Impostors_Enabled", 1); + impostorMaterial.SetFloat("_Cull", (float)UnityEngine.Rendering.CullMode.Front); AssetDatabase.CreateAsset(impostorMaterial, Path.Combine(OutputFolder, $"{baseName}_mat.mat")); impostorObject = GameObject.CreatePrimitive(PrimitiveType.Quad); diff --git a/features.cginc b/features.cginc index d9bc95e..36ee2bf 100644 --- a/features.cginc +++ b/features.cginc @@ -121,4 +121,8 @@ #pragma shader_feature_local _RAY_MARCHING_HEXAGON //endex +//ifex _Impostors_Enabled==0 +#pragma shader_feature_local _IMPOSTORS +//endex + #endif // __FEATURES_INC diff --git a/globals.cginc b/globals.cginc index 10f0556..b1de3d8 100644 --- a/globals.cginc +++ b/globals.cginc @@ -181,6 +181,20 @@ float _Parallax_Heightmap_Bias; float _Parallax_Heightmap_Ray_Marching_Steps; #endif // _PARALLAX_HEIGHTMAP_RAY_MARCHING +#if defined(_IMPOSTORS) +Texture2D _Impostors_Atlas; +float4 _Impostors_Atlas_TexelSize; +Texture2D _Impostors_Normal_Atlas; +Texture2D _Impostors_Metallic_Gloss_Atlas; +Texture2D _Impostors_Depth_Atlas; +int _Impostors_Grid_Resolution; +float _Impostors_Sphere_Radius; +float _Impostors_Cutoff; +float _Impostors_Debug_Mode; +float _Impostors_Debug_Depth; +float3 _Impostors_Main_Camera_Pos; +#endif // _IMPOSTORS + #if defined(_VERTEX_DEFORMATION) float _Vertex_Deformation_Slot_0_Enabled; int _Vertex_Deformation_Slot_0_Opcode; diff --git a/impostor.cginc b/impostor.cginc index 4236778..1d5c9b5 100644 --- a/impostor.cginc +++ b/impostor.cginc @@ -2,19 +2,10 @@ #define __IMPOSTOR_INC #include "UnityCG.cginc" +#include "globals.cginc" #include "vertex_deformation.hlsl" -SamplerState bilinear_clamp_s; -Texture2D _ImpostorAtlas; -float4 _ImpostorAtlas_TexelSize; -int _GridResolution; -float _Cutoff; -float _SphereRadius; -float3 _ImpostorMainCameraPos; - -float4 _Color; -float _DebugMode; - +// Utility functions for hemispherical octahedral mapping float2 HemiOctEncode(float3 N) { N.y = max(N.y, 1e-4); float3 p = hemi_octahedron_to_plane(normalize(N), 0, float3(1,0,0), float3(0,1,0), 1); @@ -40,27 +31,7 @@ float3 DirFromCell(float2 cell, float gridRes) { return HemiOctDecode(uv); } -float2 ClampUvInCell(float2 uv) { - uv = saturate(uv); - float2 halfTexelInCell = 0.5 * _ImpostorAtlas_TexelSize.xy * (float)_GridResolution; - return clamp(uv, halfTexelInCell, 1.0 - halfTexelInCell); -} - -float4 SampleAtlasGrad(float2 uv, float2 cell, float2 uvGradX, float2 uvGradY) { - uv = ClampUvInCell(uv); - - float invGridRes = rcp((float)_GridResolution); - float2 atlasUv = (cell + uv) * invGridRes; - - // Important: gradients must be computed in-cell; do not let integer cell offsets affect mip selection. - return _ImpostorAtlas.SampleGrad(bilinear_clamp_s, atlasUv, uvGradX * invGridRes, uvGradY * invGridRes); -} - -// Branchless barycentric weights in a unit square split into two triangles along the (0,0)-(1,1) diagonal. -// Returns weights for the 4 square corners: -// .x = (0,0), .y = (0,1), .z = (1,0), .w = (1,1) -// Only three weights are non-zero for any point (true barycentric within one triangle), -// but sampling both possible "middle" vertices avoids triangle-selection discontinuities. +// Branchless barycentric weights in a unit square split into two triangles float4 GridCellBarycentric4(float2 p) { float w00 = 1.0 - max(p.x, p.y); float w11 = min(p.x, p.y); @@ -86,40 +57,43 @@ float2 VirtualPlaneUV(float3 frameDir, float3 pivotToCam, float3 vertexToCam) { return uv * -1.0 + 0.5; } -struct appdata { - float4 vertex : POSITION; - UNITY_VERTEX_INPUT_INSTANCE_ID -#ifdef IMPOSTOR_SHADOW_PASS - float3 normal : NORMAL; -#endif -}; +// General purpose ray-sphere intersection +bool RaySphereIntersect(float3 ro, float3 rayDir, float3 origin, float radius) { + float3 originToRo = ro - origin; + float b = dot(originToRo, rayDir); + float c = dot(originToRo, originToRo) - radius * radius; + return (b * b - c) >= 0.0; +} -struct v2f { -#ifdef IMPOSTOR_SHADOW_PASS - V2F_SHADOW_CASTER; -#else - float4 pos : SV_POSITION; - UNITY_FOG_COORDS(7) -#endif - float3 worldPos : TEXCOORD1; - float3 centerPos : TEXCOORD2; - UNITY_VERTEX_OUTPUT_STEREO -}; +#if defined(_IMPOSTORS) + +float2 ClampUvInCell(float2 uv) { + uv = saturate(uv); + float2 halfTexelInCell = 0.5 * _Impostors_Atlas_TexelSize.xy * (float)_Impostors_Grid_Resolution; + return clamp(uv, halfTexelInCell, 1.0 - halfTexelInCell); +} + +float4 SampleAtlasGrad(float2 uv, float2 cell, float2 uvGradX, float2 uvGradY) { + uv = ClampUvInCell(uv); -v2f vert(appdata v) { - v2f o; - UNITY_SETUP_INSTANCE_ID(v); - UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); + float invGridRes = rcp((float)_Impostors_Grid_Resolution); + float2 atlasUv = (cell + uv) * invGridRes; + + // Important: gradients must be computed in-cell; do not let integer cell offsets affect mip selection. + return _Impostors_Atlas.SampleGrad(bilinear_clamp_s, atlasUv, uvGradX * invGridRes, uvGradY * invGridRes); +} +// Billboard vertex transformation for impostors +void impostor_vert(float4 vertexOS, inout float3 worldPos) { float3 center = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz; - o.centerPos = center; + float3 scale = float3( length(unity_ObjectToWorld._m00_m10_m20), length(unity_ObjectToWorld._m01_m11_m21), length(unity_ObjectToWorld._m02_m12_m22)); -#ifdef IMPOSTOR_SHADOW_PASS - float3 camPos = _ImpostorMainCameraPos; +#ifdef SHADOW_CASTER_PASS + float3 camPos = _Impostors_Main_Camera_Pos; #else float3 camPos = _WorldSpaceCameraPos; #endif @@ -128,56 +102,35 @@ v2f vert(appdata v) { float3 viewWS = normalize(camPos - center); float3 right, up; BillboardBasis(viewWS, right, up); - float3 worldPos = center + v.vertex.x * right * scale.x + v.vertex.y * up * scale.y; - o.worldPos = worldPos; - -#ifdef IMPOSTOR_SHADOW_PASS - v.vertex = mul(unity_WorldToObject, float4(worldPos, 1)); - v.normal = -viewWS; - TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) -#else - o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1)); - UNITY_TRANSFER_FOG(o, o.pos); -#endif - - return o; + worldPos = center + vertexOS.x * right * scale.x + vertexOS.y * up * scale.y; } -// General purpose ray-sphere intersection. -bool RaySphereIntersect(float3 ro, float3 rayDir, float3 origin, float radius) { - float3 originToRo = ro - origin; - float b = dot(originToRo, rayDir); - float c = dot(originToRo, originToRo) - radius * radius; - return (b * b - c) >= 0.0; -} +// Sample impostor atlas with view-dependent blending +float4 impostor_frag(float3 worldPos) { + // Calculate center in fragment shader to avoid extra interpolator + float3 center = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz; -float4 frag(v2f i) : SV_Target { // Sphere culling first - float3 viewDir = normalize(i.worldPos - _WorldSpaceCameraPos); - bool didIntersect = RaySphereIntersect(_WorldSpaceCameraPos, viewDir, i.centerPos, _SphereRadius); - clip(didIntersect - 0.5); - - // Camera position for grid computation (matches billboard orientation) -#ifdef IMPOSTOR_SHADOW_PASS - float3 camPos = _ImpostorMainCameraPos; +#ifdef SHADOW_CASTER_PASS + float3 camPos = _Impostors_Main_Camera_Pos; #else float3 camPos = _WorldSpaceCameraPos; #endif - float3 center = i.centerPos; + float3 viewDir = normalize(worldPos - camPos); + bool didIntersect = RaySphereIntersect(camPos, viewDir, center, _Impostors_Sphere_Radius); + clip(didIntersect - 0.5); // For lattice lookup, use the camera-to-impostor-center direction (matches billboard orientation). - // Using the per-fragment view ray (camera→quad point) causes the selected lattice points/weights - // to vary across the billboard, which reads as "not smooth" while moving around the impostor. float3x3 worldToObject = (float3x3)unity_WorldToObject; float3 viewOS = normalize(mul(worldToObject, normalize(camPos - center))); // Get continuous grid position and find the 4 frames - float gridRes = (float)_GridResolution; + float gridRes = (float)_Impostors_Grid_Resolution; float2 grid = GridFromDir(viewOS, gridRes); float2 gridFloor = floor(grid); float2 gridFrac = frac(grid); - // Branchless barycentric blend weights (avoids discontinuities along triangle/cell boundaries). + // Branchless barycentric blend weights float4 bw = GridCellBarycentric4(gridFrac); // Frame cells (square corners) @@ -194,7 +147,7 @@ float4 frag(v2f i) : SV_Target { // Compute virtual plane UVs for all 4 frames float3 pivotToCamOS = mul(worldToObject, camPos - center); - float3 vertexPosOS = mul(worldToObject, i.worldPos - center); + float3 vertexPosOS = mul(worldToObject, worldPos - center); float3 vertexToCamOS = pivotToCamOS - vertexPosOS; float2 uv00 = VirtualPlaneUV(dir00, pivotToCamOS, vertexToCamOS); @@ -209,19 +162,13 @@ float4 frag(v2f i) : SV_Target { float4 s11 = SampleAtlasGrad(uv11, cell11, ddx(uv11), ddy(uv11)); float4 col = s00 * bw.x + s01 * bw.y + s10 * bw.z + s11 * bw.w; -#ifndef IMPOSTOR_SHADOW_PASS - if (_DebugMode > 0.5) + if (_Impostors_Debug_Mode > 0.5) return float4(bw.yz, 0, 1); -#endif - col *= _Color; - clip(col.a - _Cutoff); -#ifdef IMPOSTOR_SHADOW_PASS - SHADOW_CASTER_FRAGMENT(i) -#else - UNITY_APPLY_FOG(i.fogCoord, col); + clip(col.a - _Impostors_Cutoff); return col; -#endif } -#endif +#endif // _IMPOSTORS + +#endif // __IMPOSTOR_INC @@ -6,6 +6,7 @@ #include "instancing.cginc" #include "interpolators.cginc" #include "texture_utils.cginc" +#include "impostor.cginc" struct Pbr { float4 albedo; @@ -159,9 +160,14 @@ Pbr getPbr(v2f i) { float2 uv_parallax = i.uv01.xy; #endif // _PARALLAX_HEIGHTMAP +#if defined(_IMPOSTORS) + pbr.albedo = impostor_frag(i.worldPos); + pbr.albedo *= _Color; +#else pbr.albedo = _MainTex.Sample(aniso16_trilinear_repeat_s, uv_parallax * _MainTex_ST.xy + _MainTex_ST.zw); pbr.albedo *= _Color; apply_marble(i.worldPos, pbr.albedo.xyz); +#endif float3 normal_tangent = UnpackNormal(_BumpMap.Sample(aniso16_trilinear_repeat_s, uv_parallax * _BumpMap_ST.xy)); normal_tangent.xy *= _BumpScale; |
