浏览代码

Refactored ScreenSpaceTracing

Added Estimate algorithm
/feature-ScreenSpaceProjection
Frédéric Vauchelles 6 年前
当前提交
f781f1f8
共有 6 个文件被更改,包括 245 次插入127 次删除
  1. 279
      ScriptableRenderPipeline/Core/CoreRP/ShaderLibrary/ScreenSpaceRaymarching.hlsl
  2. 4
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Editor/Material/Lit/LitUI.cs
  3. 3
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Material/Lit/Lit.cs
  4. 78
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Material/Lit/Lit.hlsl
  5. 4
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Material/Lit/Lit.shader
  6. 4
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Material/Lit/LitTessellation.shader

279
ScriptableRenderPipeline/Core/CoreRP/ShaderLibrary/ScreenSpaceRaymarching.hlsl


#ifndef UNITY_SCREEN_SPACE_RAYMARCHING_INCLUDED
#define UNITY_SCREEN_SPACE_RAYMARCHING_INCLUDED
// -------------------------------------------------
// Algorithm uniform parameters
// -------------------------------------------------
// HiZ : Min mip level
// Linear : Mip level
// Estimate : Mip Level
// HiZ : Max mip level
struct ScreenSpaceHiZRaymarchInput
{
float3 startPositionVS;
float startLinearDepth;
float3 dirVS;
float4x4 projectionMatrix;
int2 bufferSize;
int minLevel;
int maxLevel;
#ifdef DEBUG_DISPLAY
bool writeStepDebug;
#endif
};
struct ScreenSpaceLinearRaymarchInput
{
float3 startPositionVS;
float startLinearDepth;
float3 dirVS;
float4x4 projectionMatrix;
int2 bufferSize;
#ifdef DEBUG_DISPLAY
bool writeStepDebug;
#endif
};
// -------------------------------------------------
// Output
// -------------------------------------------------
float distance;
float linearDepth;
uint2 positionSS;
float2 positionNDC;
float distance; // Distance raymarched
float linearDepth; // Linear depth of the hit point
uint2 positionSS; // Position of the hit point (SS)
float2 positionNDC; // Position of the hit point (NDC)
#ifdef DEBUG_DISPLAY
float3 debugOutput;

// -------------------------------------------------
// Utilities
// -------------------------------------------------
// Calculate the ray origin and direction in TXS
// out positionTXS : (x, y, 1/depth)
// out rayTXS : (x, y, 1/depth)
float3 startPositionVS,
float3 dirVS,
float3 rayOriginVS,
float3 rayDirVS,
float3 positionVS = startPositionVS;
float3 rayEndVS = startPositionVS + dirVS * 10;
float3 positionVS = rayOriginVS;
float3 rayEndVS = rayOriginVS + rayDirVS * 10;
float4 positionCS = ComputeClipSpacePosition(positionVS, projectionMatrix);
float4 rayEndCS = ComputeClipSpacePosition(rayEndVS, projectionMatrix);

rayTXS = rayEndTXS - rayStartTXS;
}
// Check whether the depth of the ray is above the sampled depth
// Arguments are inversed linear depth
bool IsPositionAboveDepth(float rayDepth, float invLinearDepth)
{
// as depth is inverted, we must invert the check as well

float SampleHiZDepth(float2 positionTXS, int level)
// Sample the Depth buffer at a specific mip and linear depth
float LoadDepth(float2 positionTXS, int level)
float hiZLinearDepth = LinearEyeDepth(pyramidDepth, _ZBufferParams);
float invHiZLinearDepth = 1 / hiZLinearDepth;
return invHiZLinearDepth;
float linearDepth = LinearEyeDepth(pyramidDepth, _ZBufferParams);
return linearDepth;
// Sample the Depth buffer at a specific mip and return 1/linear depth
float LoadInvDepth(float2 positionTXS, int level)
{
float linearDepth = LoadDepth(positionTXS, level);
float invLinearDepth = 1 / linearDepth;
return invLinearDepth;
}
bool CellAreEquals(int2 cellA, int2 cellB)
{
return cellA.x == cellB.x && cellA.y == cellB.y;
}
// Calculate intersection between the ray and the depth plane
// positionTXS.z is 1/depth
// rayTXS.z is 1/depth
float3 IntersectDepthPlane(float3 positionTXS, float3 rayTXS, float invDepth, out float distance)

return positionTXS + rayTXS * distance;
}
bool CellAreEquals(int2 cellA, int2 cellB)
{
return cellA.x == cellB.x && cellA.y == cellB.y;
}
// Calculate intersection between a ray and a cell
float3 IntersectCellPlanes(
float3 positionTXS,
float3 rayTXS,

}
#ifdef DEBUG_DISPLAY
// -------------------------------------------------
// Debug Utilities
// -------------------------------------------------
float3 dirVS,
float3 rayDirVS,
float3 rayTXS,
float3 startPositionTXS,
bool hitSuccessful,

int maxLevel,
int maxMipLevel,
inout ScreenSpaceRayHit hit)
{
float3 debugOutput = float3(0, 0, 0);

debugOutput = float3(float2(startPositionTXS.xy) / bufferSize, 0);
break;
case DEBUGSCREENSPACETRACING_DIR_VS:
debugOutput = dirVS * 0.5 + 0.5;
debugOutput = rayDirVS * 0.5 + 0.5;
break;
case DEBUGSCREENSPACETRACING_DIR_NDC:
debugOutput = float3(rayTXS.xy * 0.5 + 0.5, frac(0.1 / rayTXS.z));

debugOutput = float(iteration) / float(maxIterations);
break;
case DEBUGSCREENSPACETRACING_MAX_USED_LEVEL:
debugOutput = float(maxUsedLevel) / float(maxLevel);
debugOutput = float(maxUsedLevel) / float(maxMipLevel);
break;
}
}

}
#endif
// -------------------------------------------------
// Algorithm: rough estimate
// -------------------------------------------------
struct ScreenSpaceEstimateRaycastInput
{
float2 referencePositionNDC; // Position of the reference (NDC)
float3 referencePositionWS; // Position of the reference (WS)
float referenceLinearDepth; // Linear depth of the reference
float3 rayOriginWS; // Origin of the ray (WS)
float3 rayDirWS; // Direction of the ray (WS)
float3 depthNormalWS; // Depth plane normal (WS)
float4x4 viewProjectionMatrix; // View Projection matrix of the camera
#ifdef DEBUG_DISPLAY
bool writeStepDebug;
#endif
};
// Fast but very rough estimation of scene screen space raycasting.
// * We approximate the scene as a depth plane and raycast against that plane.
// * The reference position is usually the pixel being evaluated in front of opaque geometry
// * So the reference position is used to sample and get the depth plane
// * The reference depth is usually, the depth of the transparent object being evaluated
bool ScreenSpaceEstimateRaycast(
ScreenSpaceEstimateRaycastInput input,
out ScreenSpaceRayHit hit)
{
uint mipLevel = clamp(_SSRayMinLevel, 0, int(_PyramidDepthMipSize.z));
uint2 bufferSize = uint2(_PyramidDepthMipSize.xy);
// Get the depth plane
float depth = LoadDepth(input.referencePositionNDC * (bufferSize >> mipLevel), mipLevel);
// Calculate projected distance from the ray origin to the depth plane
float depthFromReference = depth - input.referenceLinearDepth;
float offset = dot(input.depthNormalWS, input.rayOriginWS - input.referencePositionWS);
float depthFromRayOrigin = depthFromReference - offset;
// Calculate actual distance from ray origin to depth plane
float hitDistance = depthFromRayOrigin / dot(input.depthNormalWS, input.rayDirWS);
float3 hitPositionWS = input.rayOriginWS + input.rayDirWS * hitDistance;
hit.distance = hitDistance;
hit.positionNDC = ComputeNormalizedDeviceCoordinates(hitPositionWS, input.viewProjectionMatrix);
hit.positionSS = hit.positionNDC * bufferSize;
hit.linearDepth = LoadDepth(hit.positionSS, 0);
#ifdef DEBUG_DISPLAY
FillScreenSpaceRaymarchingHitDebug(
bufferSize,
float3(0, 0, 0), // rayDirVS
float3(0, 0, 0), // rayTXS
float3(0, 0, 0), // startPositionTXS
true, // hitSuccessful
1, // iteration
1, // iterationMax
0, // maxMipLevel
0, // maxUsedLevel
hit);
if (input.writeStepDebug)
{
ScreenSpaceTracingDebug debug;
ZERO_INITIALIZE(ScreenSpaceTracingDebug, debug);
FillScreenSpaceRaymarchingPreLoopDebug(float3(0, 0, 0), debug);
FillScreenSpaceRaymarchingPreIterationDebug(1, mipLevel, debug);
FillScreenSpaceRaymarchingPostIterationDebug(
1, // iteration
uint2(1, 1), // cellSize
float3(0, 0, 0), // positionTXS
hitDistance, // iterationDistance
1 / hit.linearDepth, // 1 / sampled depth
debug);
FillScreenSpaceRaymarchingPostLoopDebug(
1, // maxUsedLevel
1, // iteration
float3(0, 0, 0), // rayTXS
hit,
debug);
_DebugScreenSpaceTracingData[0] = debug;
}
#endif
return true;
}
// -------------------------------------------------
// Algorithm: HiZ raymarching
// -------------------------------------------------
// Based on Yasin Uludag, 2014. "Hi-Z Screen-Space Cone-Traced Reflections", GPU Pro5: Advanced Rendering Techniques
// Based on 2017. "Autodesk Gamedev | Notes On Screen Space HiZ Tracing", https://gamedev.autodesk.com/blogs/1/post/5866685274515295601
struct ScreenSpaceHiZRaymarchInput
{
float3 rayOriginVS; // Ray origin (VS)
float3 rayDirVS; // Ray direction (VS)
float4x4 projectionMatrix; // Projection matrix of the camera
#ifdef DEBUG_DISPLAY
bool writeStepDebug;
#endif
};
bool ScreenSpaceHiZRaymarch(
ScreenSpaceHiZRaymarchInput input,
out ScreenSpaceRayHit hit)

ZERO_INITIALIZE(ScreenSpaceRayHit, hit);
bool hitSuccessful = true;
int iteration = 0;
int minLevel = max(input.minLevel, _SSRayMinLevel);
int maxLevel = min(input.maxLevel, _SSRayMaxLevel);
int minMipLevel = max(_SSRayMinLevel, 0);
int maxMipLevel = min(_SSRayMaxLevel, int(_PyramidDepthMipSize.z));
uint2 bufferSize = uint2(_PyramidDepthMipSize.xy);
input.startPositionVS,
input.dirVS,
input.rayOriginVS,
input.rayDirVS,
input.bufferSize,
bufferSize,
int maxUsedLevel = input.minLevel;
int maxUsedLevel = minMipLevel;
ScreenSpaceTracingDebug debug;
ZERO_INITIALIZE(ScreenSpaceTracingDebug, debug);
FillScreenSpaceRaymarchingPreLoopDebug(startPositionTXS, debug);

float2 crossOffset = CROSS_OFFSET * cellPlanes;
cellPlanes = clamp(cellPlanes, 0, 1);
int currentLevel = minLevel;
uint2 cellCount = input.bufferSize >> currentLevel;
int currentLevel = minMipLevel;
uint2 cellCount = bufferSize >> currentLevel;
while (currentLevel >= minLevel)
while (currentLevel >= minMipLevel)
{
if (iteration >= MAX_ITERATIONS)
{

cellCount = input.bufferSize >> currentLevel;
cellCount = bufferSize >> currentLevel;
cellSize = uint2(1, 1) << currentLevel;
#ifdef DEBUG_DISPLAY

int mipLevelDelta = -1;
// Sampled as 1/Z so it interpolate properly in screen space.
const float invHiZDepth = SampleHiZDepth(positionTXS.xy, currentLevel);
const float invHiZDepth = LoadInvDepth(positionTXS.xy, currentLevel);
float iterationDistance = 0;
if (IsPositionAboveDepth(positionTXS.z, invHiZDepth))

hit.distance += iterationDistance;
currentLevel = min(currentLevel + mipLevelDelta, maxLevel);
currentLevel = min(currentLevel + mipLevelDelta, maxMipLevel);
#ifdef DEBUG_DISPLAY
maxUsedLevel = max(maxUsedLevel, currentLevel);

#endif
// Check if we are out of the buffer
if (any(positionTXS.xy > input.bufferSize)
if (any(int2(positionTXS.xy) > bufferSize)
|| any(positionTXS.xy < 0))
{
hitSuccessful = false;

}
hit.linearDepth = 1 / positionTXS.z;
hit.positionNDC = float2(positionTXS.xy) / float2(input.bufferSize);
hit.positionNDC = float2(positionTXS.xy) / float2(bufferSize);
hit.positionSS = uint2(positionTXS.xy);
}

hit,
debug);
FillScreenSpaceRaymarchingHitDebug(
input.bufferSize, input.dirVS, rayTXS, startPositionTXS, hitSuccessful, iteration, MAX_ITERATIONS, maxLevel, maxUsedLevel,
bufferSize, input.rayDirVS, rayTXS, startPositionTXS, hitSuccessful, iteration, MAX_ITERATIONS, maxMipLevel, maxUsedLevel,
hit);
if (input.writeStepDebug)
_DebugScreenSpaceTracingData[0] = debug;

}
// Based on DDA
// -------------------------------------------------
// Algorithm: Linear raymarching
// -------------------------------------------------
// Based on DDA (https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm))
// Based on Morgan McGuire and Michael Mara, 2014. "Efficient GPU Screen-Space Ray Tracing", Journal of Computer Graphics Techniques (JCGT), 235-256
struct ScreenSpaceLinearRaymarchInput
{
float3 rayOriginVS; // Ray origin (VS)
float3 rayDirVS; // Ray direction (VS)
float4x4 projectionMatrix; // Projection matrix of the camera
#ifdef DEBUG_DISPLAY
bool writeStepDebug;
#endif
};
// Basically, perform a raycast with DDA technique on a specific mip level of the Depth pyramid.
bool ScreenSpaceLinearRaymarch(
ScreenSpaceLinearRaymarchInput input,
out ScreenSpaceRayHit hit)

ZERO_INITIALIZE(ScreenSpaceRayHit, hit);
bool hitSuccessful = true;
int iteration = 0;
int level = _SSRayMinLevel;
int level = clamp(_SSRayMinLevel, 0, int(_PyramidDepthMipSize.z));
uint2 bufferSize = uint2(_PyramidDepthMipSize.xy);
input.startPositionVS,
input.dirVS,
input.rayOriginVS,
input.rayDirVS,
input.bufferSize,
bufferSize,
startPositionTXS,
rayTXS);

FillScreenSpaceRaymarchingPreLoopDebug(startPositionTXS, debug);
#endif
float maxAbsAxis = max(abs(rayTXS.x), abs(rayTXS.y));
if (!any(rayTXS.xy))
if (maxAbsAxis < 1E-7)
{
hit.distance = 1 / startPositionTXS.z;
hit.linearDepth = 1 / startPositionTXS.z;

#endif
positionTXS += rayTXS;
float invHiZDepth = SampleHiZDepth(positionTXS.xy, _SSRayMinLevel);
float invHiZDepth = LoadInvDepth(positionTXS.xy, _SSRayMinLevel);
#ifdef DEBUG_DISPLAY
FillScreenSpaceRaymarchingPostIterationDebug(

}
// Check if we are out of the buffer
if (any(positionTXS.xy > input.bufferSize)
if (any(int2(positionTXS.xy) > bufferSize)
|| any(positionTXS.xy < 0))
{
hitSuccessful = false;

}
hit.linearDepth = 1 / positionTXS.z;
hit.positionNDC = float2(positionTXS.xy) / float2(input.bufferSize);
hit.positionNDC = float2(positionTXS.xy) / float2(bufferSize);
hit.positionSS = uint2(positionTXS.xy);
}

hit,
debug);
FillScreenSpaceRaymarchingHitDebug(
input.bufferSize, input.dirVS, rayTXS, startPositionTXS, hitSuccessful, iteration, MAX_ITERATIONS, 0, 0,
bufferSize, input.rayDirVS, rayTXS, startPositionTXS, hitSuccessful, iteration, MAX_ITERATIONS, 0, 0,
hit);
if (input.writeStepDebug)
_DebugScreenSpaceTracingData[0] = debug;

4
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Editor/Material/Lit/LitUI.cs


m_MaterialEditor.ShaderProperty(ssRayMaxLevel, Styles.ssRayMaxLevel);
break;
case Lit.SSRayMethod.Linear:
case Lit.SSRayMethod.Estimate:
{
}
default:
break;
}

CoreUtils.SetKeyword(material, "_TRANSMITTANCECOLORMAP", material.GetTexture(kTransmittanceColorMap) && canHaveRefraction);
CoreUtils.SetKeyword(material, "SSRAY_REFRACTION_HIZ", ssRayMethod == Lit.SSRayMethod.HiZ);
CoreUtils.SetKeyword(material, "SSRAY_REFRACTION_LINEAR", ssRayMethod == Lit.SSRayMethod.Linear);
CoreUtils.SetKeyword(material, "SSRAY_REFRACTION_ESTIMATE", ssRayMethod == Lit.SSRayMethod.Estimate);
}
}
} // namespace UnityEditor

3
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Material/Lit/Lit.cs


public enum SSRayMethod
{
HiZ,
Linear
Linear,
Estimate
}
//-----------------------------------------------------------------------------

78
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Material/Lit/Lit.hlsl


# endif
#endif
float3 EstimateRaycast(float3 V, PositionInputs posInputs, float3 positionWS, float3 rayWS)
{
// For all refraction approximation, to calculate the refracted point in world space,
// we approximate the scene as a plane (back plane) with normal -V at the depth hit point.
// (We avoid to raymarch the depth texture to get the refracted point.)
uint2 depthSize = uint2(_PyramidDepthMipSize.xy);
// Get the depth of the approximated back plane
float pyramidDepth = LOAD_TEXTURE2D_LOD(_PyramidDepthTexture, posInputs.positionNDC * (depthSize >> 2), 2).r;
float depth = LinearEyeDepth(pyramidDepth, _ZBufferParams);
// Distance from point to the back plane
float depthFromPositionInput = depth - posInputs.linearDepth;
float offset = dot(-V, positionWS - posInputs.positionWS);
float depthFromPosition = depthFromPositionInput - offset;
float hitDistanceFromPosition = depthFromPosition / dot(-V, rayWS);
return positionWS + rayWS * hitDistanceFromPosition;
}
// This method allows us to know at compile time what material features should be removed from the code by Tile (Indepenently of the value of material feature flag per pixel).
// This is only useful for classification during lighting, so it's not needed in EncodeIntoGBuffer and ConvertSurfaceDataToBSDFData (where we always know exactly what the material feature is)
bool HasFeatureFlag(uint featureFlags, uint flag)

// a. Get the corresponding color depending on the roughness from the gaussian pyramid of the color buffer
// b. Multiply by the transmittance for absorption (depends on the optical depth)
float3 positionVS = mul(UNITY_MATRIX_V, float4(preLightData.transparentPositionWS, 1)).xyz;
float3 transparentRefractVVS = mul(UNITY_MATRIX_V, float4(preLightData.transparentRefractV, 0)).xyz;
uint2 depthSize = uint2(_PyramidDepthMipSize.xy);
#if SSRAY_REFRACTION_HIZ
// Allocate screen space raymarch input
#if SSRAY_REFRACTION_ESTIMATE
# define SSRAY_MARCH ScreenSpaceEstimateRaycast
ScreenSpaceEstimateRaycastInput ssInput;
ZERO_INITIALIZE(ScreenSpaceEstimateRaycastInput, ssInput);
#elif SSRAY_REFRACTION_HIZ
# define SSRAY_MARCH ScreenSpaceHiZRaymarch
ssInput.startPositionVS = positionVS;
ssInput.startLinearDepth = posInput.linearDepth;
ssInput.dirVS = transparentRefractVVS;
ssInput.projectionMatrix = UNITY_MATRIX_P;
ssInput.bufferSize = depthSize;
ssInput.minLevel = 0;
ssInput.maxLevel = int(_PyramidDepthMipSize.z);
#ifdef DEBUG_DISPLAY
ssInput.writeStepDebug = !any(int2(_MouseClickPixelCoord.xy) - int2(posInput.positionSS));
#endif
hitSuccessful = ScreenSpaceHiZRaymarch(ssInput, hit);
# define SSRAY_MARCH ScreenSpaceLinearRaymarch
#endif
ssInput.startPositionVS = positionVS;
ssInput.startLinearDepth = posInput.linearDepth;
ssInput.dirVS = transparentRefractVVS;
// Initialize screen space raymarch input
#if SSRAY_REFRACTION_HIZ || SSRAY_REFRACTION_LINEAR
float3 positionVS = mul(UNITY_MATRIX_V, float4(preLightData.transparentPositionWS, 1)).xyz;
float3 transparentRefractVVS = mul(UNITY_MATRIX_V, float4(preLightData.transparentRefractV, 0)).xyz;
ssInput.rayOriginVS = positionVS;
ssInput.rayDirVS = transparentRefractVVS;
ssInput.bufferSize = depthSize;
#elif SSRAY_REFRACTION_ESTIMATE
ssInput.referencePositionNDC = posInput.positionNDC;
ssInput.referencePositionWS = posInput.positionWS;
ssInput.referenceLinearDepth = posInput.linearDepth;
ssInput.rayOriginWS = preLightData.transparentPositionWS;
ssInput.rayDirWS = preLightData.transparentRefractV;
ssInput.depthNormalWS = -V;
ssInput.viewProjectionMatrix = UNITY_MATRIX_VP;
#endif
hitSuccessful = ScreenSpaceLinearRaymarch(ssInput, hit);
#endif
hitSuccessful = SSRAY_MARCH(ssInput, hit);
#undef SSRAY_MARCH
#ifdef DEBUG_DISPLAY
if (_DebugLightingMode == DEBUGLIGHTINGMODE_SCREEN_SPACE_TRACING_REFRACTION)

4
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Material/Lit/Lit.shader


// Transparency
[Enum(None, 0, Plane, 1, Sphere, 2)]_RefractionMode("Refraction Mode", Int) = 0
[Enum(HiZ, 0, Linear, 1)]_SSRayRefractionMethod("SS Ray Refraction Method", Int) = 0
[Enum(HiZ, 0, Linear, 1, Estimate, 2)]_SSRayRefractionMethod("SS Ray Refraction Method", Int) = 0
_SSRayMinLevel ("SS Ray Min Mip Level", Int) = 1
_SSRayMaxLevel ("SS Ray Max Mip Level", Int) = 10
_Ior("Index Of Refraction", Range(1.0, 2.5)) = 1.0

#pragma shader_feature _MATERIAL_FEATURE_IRIDESCENCE
#pragma shader_feature _MATERIAL_FEATURE_SPECULAR_COLOR
#pragma shader_feature SSRAY_REFRACTION_HIZ SSRAY_REFRACTION_LINEAR
#pragma shader_feature SSRAY_REFRACTION_HIZ SSRAY_REFRACTION_LINEAR SSRAY_REFRACTION_ESTIMATE
// enable dithering LOD crossfade
#pragma multi_compile _ LOD_FADE_CROSSFADE

4
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Material/Lit/LitTessellation.shader


// Transparency
[Enum(None, 0, Plane, 1, Sphere, 2)]_RefractionMode("Refraction Mode", Int) = 0
[Enum(HiZ, 0, Linear, 1)]_SSRayRefractionMethod("SS Ray Refraction Method", Int) = 0
[Enum(HiZ, 0, Linear, 1, Estimate, 2)]_SSRayRefractionMethod("SS Ray Refraction Method", Int) = 0
_SSRayMinLevel ("SS Ray Min Mip Level", Int) = 1
_SSRayMaxLevel ("SS Ray Max Mip Level", Int) = 10
_Ior("Index Of Refraction", Range(1.0, 2.5)) = 1.0

#pragma shader_feature _MATERIAL_FEATURE_IRIDESCENCE
#pragma shader_feature _MATERIAL_FEATURE_SPECULAR_COLOR
#pragma shader_feature SSRAY_REFRACTION_HIZ SSRAY_REFRACTION_LINEAR
#pragma shader_feature SSRAY_REFRACTION_HIZ SSRAY_REFRACTION_LINEAR SSRAY_REFRACTION_ESTIMATE
// enable dithering LOD crossfade
#pragma multi_compile _ LOD_FADE_CROSSFADE

正在加载...
取消
保存