Unity HLSL shader 基础实现
常用
-
Transform
- TransformObjectToHClip()
- TransformObjectToWorldNormal
- TransformWorldToShadowCoord(float3 positionWS):计算阴影坐标
-
Light
- GetMainLight():获得主光源信息
- Light GetMainLight(float4 shadowCoord):计算阴影衰减
- GetAdditionalLightsCount():获取额外光源个数
- Light GetAdditionalLight(uint i, float3 positionWS):获取额外光源信息
- Light GetAdditionalLight(uint i, float3 positionWS, half4 shadowMask):获取额外光源信息(带有shadow buffer)
-
Camera
- _WorldSpaceCameraPos
-
Texture
- TRANSFORM_TEX(uv, texName):对uv进行平移缩放
- SAMPLE_TEXTURE2D(texName, samplerName, uv):采样
- UnpackNormal(tex2D()):get normal in normal map(必须将Texture设为Normal map)
-
Depth
- float Linear01Depth(float depth, float4 zBufferParam):将depth转换至linear space
-
Buffer
- _CameraDepthTexture:depth buffer in viewport(类似于UE的scene depth,无视半透明)
- _CameraColorTexture:color buffer in viewport
Half Lambert
Shader "URP/Stone"
{
// ------------------------------------------------------------------------------------------- Properties ------------------------------------------------------------------------------------
Properties
{
_ColorTint ("Color Tint", Color) = (1,1,1,1)
_AlbedoTex ("Albedo Tex", 2D) = "white" {}
}
// ------------------------------------------------------------------------------------------- SubShader ------------------------------------------------------------------------------------
SubShader
{
// ------------------------------------------------------------------------------------------- Tag ------------------------------------------------------------------------------------
Tags
{
"RenderType"="Opaque"
"RenderPipeline" = "UniversalRenderPipeline"
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
CBUFFER_START(UnityPerMaterial)
half4 _ColorTint;
float4 _AlbedoTex_ST;
CBUFFER_END
TEXTURE2D(_AlbedoTex);
SAMPLER(sampler_AlbedoTex);
struct VSInput
{
float4 positionL : POSITION;
float3 normalL : NORMAL;
float2 uv : TEXCOORD0;
};
struct PSInput
{
float4 positionH : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normalW : TEXCOORD1;
};
ENDHLSL
// ------------------------------------------------------------------------------------------- pass 1 ------------------------------------------------------------------------------------
pass
{
Tags
{
"LightMode" = "UniversalForward"
}
HLSLPROGRAM
#pragma vertex VS
#pragma fragment PS
PSInput VS(VSInput vsInput)
{
PSInput vsOutput;
vsOutput.positionH = TransformObjectToHClip(vsInput.positionL);
vsOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL, true);
vsOutput.uv = TRANSFORM_TEX(vsInput.uv, _AlbedoTex);
return vsOutput;
}
half4 PS(PSInput psInput) : SV_TARGET
{
half4 texDiff = SAMPLE_TEXTURE2D(_AlbedoTex, sampler_AlbedoTex, psInput.uv) * _ColorTint;
Light mainLight = GetMainLight();
half3 lightColor = mainLight.color;
half3 lightDir = normalize(mainLight.direction);
float NoL = saturate(dot(lightDir, psInput.normalW) * 0.5 + 0.5);
return half4(texDiff * NoL * lightColor, texDiff.a);
}
ENDHLSL
}
}
}
Blinn Phone
{
// ------------------------------------------------------------------------------------------- Properties -----------------------------------------------------------------------
Properties
{
_ColorTint("color Tint", Color) = (1, 1, 1, 1)
_TexAlbedo("Albedo tex", 2D) = "white" {}
_SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
_SpecularRange("Specular Range", Range(10, 300)) = 10
}
// ------------------------------------------------------------------------------------------- SubShader ----------------------------------------------------------------------------
SubShader
{
Tags
{
"RendererType" = "Opaque"
"RenderPipeline" = "UniversalRenderPipeline"
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _ColorTint;
float4 _TexAlbedo_ST;
float4 _SpecularColor;
float _SpecularRange;
CBUFFER_END
TEXTURE2D(_TexAlbedo);
SAMPLER(sampler_TexAlbedo);
struct VsInput
{
float4 positionL : position;
float3 normalL : NORMAL;
float2 uv : TEXCOORD0;
};
struct PsInput
{
float4 positionH : SV_POSITION;
float3 normalW : NORMAL;
float3 viewDirW : TEXCOORD1;
float2 uv : TEXCOORD0;
};
ENDHLSL
// ------------------------------------------------------------------------------------------- pass 1 ------------------------------------------------------------------------------
Pass
{
NAME"Main Pass"
Tags
{
"LightMode" = "UniversalForward"
}
HLSLPROGRAM
#pragma vertex VS
#pragma fragment PS
PsInput VS(VsInput vsInput)
{
PsInput psInput;
psInput.positionH = TransformObjectToHClip(vsInput.positionL);
psInput.normalW = TransformObjectToWorldNormal(vsInput.normalL, true);
psInput.viewDirW = normalize(_WorldSpaceCameraPos.xyz - TransformObjectToWorld(vsInput.positionL.xyz));
psInput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
return psInput;
}
half4 PS(PsInput psInput) : SV_TARGET
{
half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv);
Light mainLight = GetMainLight();
half3 lightColor = mainLight.color;
half3 lightDir = normalize(mainLight.direction);
half3 halfVectorW = normalize(psInput.viewDirW + lightDir);
half NoV = dot(psInput.normalW, lightDir) * 0.5 + 0.5;
half NoH = saturate(dot(psInput.normalW, halfVectorW));
half3 diffuseColor = texAlbedo * _ColorTint * mainLight.color * NoV;
half3 specularColor = _SpecularColor * mainLight.color * pow(NoH, _SpecularRange);
half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);
return half4(diffuseColor + specularColor + ambient, texAlbedo.a);
}
ENDHLSL
}
}
}
Normal Map
- 当normal map texture在Unity中被设为"Normal map",此时其中的rgb分量将不再是tangent space下normal的xyz。因为这样Unity可以根据不同平台对normal map进行不同的压缩(只使用其中两个channel),所以尽量使用UnpackNormal()
- "Create from Grayscale":使用height map生成normal map
在切线空间计算光照
{
// ------------------------------------------------------------------------------------------- Properties -----------------------------------------------------------------------
Properties
{
_AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
_TexAlbedo("Albedo Tex", 2D) = "white"{}
_SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
_SpecularRange("Specular Range", Range(1, 200)) = 10
// // "bump" 是untiy内置的normal map,当没有提供Normal map时,untiy会内置的Normal map
_TexNormal("Normal Tex", 2D) = "bump"{}
_NormalScale("Normal Scale", float) = 1
}
// ------------------------------------------------------------------------------------------- SubShader ----------------------------------------------------------------------------
SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalRenderPipeline"
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
CBUFFER_START(UnityPerMaterial)
half4 _AlbedoColorTint;
half4 _SpecularColor;
half _SpecularRange;
float _NormalScale;
float4 _TexNormal_ST;
float4 _TexAlbedo_ST;
CBUFFER_END
TEXTURE2D(_TexAlbedo);
TEXTURE2D(_TexNormal);
SAMPLER(sampler_TexAlbedo);
SAMPLER(sampler_TexNormal);
struct VSInput
{
float4 positionL : position;
float4 tangentL : TANGENT;
float3 normalL : NORMAL;
float4 uv : TEXCOORD0;
};
struct PSInput
{
float4 positionH : SV_POSITION;
float4 uv : TEXCOORD0;
float3 lightDirT : TEXCOORD1;
float3 viewDirT : TEXCOORD2;
};
ENDHLSL
// ------------------------------------------------------------------------------------------- pass 1 ------------------------------------------------------------------------------
Pass
{
NAME"Main Pass"
Tags
{
"LightMode" = "UniversalForward"
}
HLSLPROGRAM
#pragma vertex VS
#pragma fragment PS
PSInput VS(VSInput vsInput)
{
PSInput psInput;
psInput.positionH = TransformObjectToHClip(vsInput.positionL);
psInput.uv.xy = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
psInput.uv.zw = TRANSFORM_TEX(vsInput.uv, _TexNormal);
// 因为叉乘后的垂直方向有两个,vsInput.tangentL.w用于确定垂直方向
// unity_WorldTransformParams.w该值与scale有关,可能造成镜像,若scale有奇数个为负,则该值为-1;否则为1
float3 bitTangent = cross(normalize(vsInput.normalL), normalize(vsInput.tangentL.xyz)) * vsInput.tangentL.w * unity_WorldTransformParams.w;
float3x3 rotation = float3x3(vsInput.tangentL.xyz, bitTangent.xyz, vsInput.normalL);
psInput.viewDirT = mul(rotation, TransformWorldToObject(_WorldSpaceCameraPos - TransformObjectToWorld(vsInput.positionL)));
psInput.lightDirT = mul(rotation, TransformWorldToObject(GetMainLight().direction));
return psInput;
}
half4 PS(PSInput psInput) : SV_TARGET
{
const float3 viewDirT = normalize(psInput.viewDirT);
const float3 lightDirT = normalize(psInput.lightDirT);
const half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv.xy);
half4 packedNormal = SAMPLE_TEXTURE2D(_TexNormal, sampler_TexNormal, psInput.uv.zw);
half3 tangentNormal;
tangentNormal.xy = ((packedNormal * 2) - 1) * _NormalScale;
tangentNormal.z = sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
const half3 halfVector = normalize(viewDirT + lightDirT);
const half NoL = dot(tangentNormal, lightDirT) * 0.5 + 0.5;
const half NoH = saturate(dot(tangentNormal, halfVector));
const half3 diffuse = texAlbedo * _AlbedoColorTint * GetMainLight().color * NoL;
const half3 specular = GetMainLight().color * _SpecularColor * pow(NoH, _SpecularRange);
half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);
return half4(diffuse + specular + ambient, texAlbedo.a);
}
ENDHLSL
}
}
}
在世界空间计算光照
{
// ------------------------------------------------------------------------------------------- Properties ---------------------------------------------------------------------
Properties
{
_AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
_TexAlbedo("Albedo Tex", 2D) = "white"{}
_SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
_SpecularRange("Specular Range", Range(1, 200)) = 10
// // "bump" 是untiy内置的normal map,当没有提供Normal map时,untiy会内置的Normal map
_TexNormal("Normal Tex", 2D) = "bump"{}
_NormalScale("Normal Scale", float) = 1
}
// ------------------------------------------------------------------------------------------- SubShader ----------------------------------------------------------------------
SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalRenderPipeline"
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
CBUFFER_START(UnityPerMaterial)
half4 _AlbedoColorTint;
half4 _SpecularColor;
half _SpecularRange;
float _NormalScale;
float4 _TexNormal_ST;
float4 _TexAlbedo_ST;
CBUFFER_END
TEXTURE2D(_TexAlbedo);
TEXTURE2D(_TexNormal);
SAMPLER(sampler_TexAlbedo);
SAMPLER(sampler_TexNormal);
struct VSInput
{
float4 positionL : POSITION;
float4 uv : TEXCOORD0;
float4 tangentL : TANGENT;
float3 normalL : NORMAL;
};
struct PSInput
{
float4 positionH : SV_POSITION;
float4 uv : TEXCOORD0;
float4 T2W0: TEXCOORD1;
float4 T2W1 : TEXCOORD2;
float4 T2W2 : TEXCOORD3;
};
ENDHLSL
// ------------------------------------------------------------------------------------------- pass 1 ------------------------------------------------------------------------------
Pass
{
NAME"Main Pass"
Tags
{
"LightMode" = "UniversalForward"
}
HLSLPROGRAM
#pragma vertex VS
#pragma fragment PS
PSInput VS(VSInput vsInput)
{
PSInput psInput;
psInput.positionH = TransformObjectToHClip(vsInput.positionL);
psInput.uv.xy = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
psInput.uv.zw = TRANSFORM_TEX(vsInput.uv, _TexNormal);
float3 positionW = TransformObjectToWorld(vsInput.positionL).xyz;
float3 tangentW = TransformObjectToWorld(vsInput.tangentL).xyz;
float3 normalW = TransformObjectToWorld(vsInput.normalL);
// 因为叉乘后的垂直方向有两个,vsInput.tangentL.w用于确定垂直方向
// unity_WorldTransformParams.w该值与scale有关,可能造成镜像,若scale有奇数个为负,则该值为-1;否则为1
float3 bitTangentW = cross(tangentW, normalW) * vsInput.tangentL.w * unity_WorldTransformParams.w;
psInput.T2W0 = float4(tangentW.x, bitTangentW.x, normalW.x, positionW.x);
psInput.T2W1 = float4(tangentW.y, bitTangentW.y, normalW.y, positionW.y);
psInput.T2W2 = float4(tangentW.z, bitTangentW.z, normalW.z, positionW.z);
return psInput;
}
half4 PS(PSInput psInput) : SV_TARGET
{
float3 positionW = float3(psInput.T2W0.w, psInput.T2W1.w, psInput.T2W2.w);
float3 lightDirW = normalize(GetMainLight().direction);
float3 viewDirW = normalize(_WorldSpaceCameraPos - positionW);
half3 packedNormal = SAMPLE_TEXTURE2D(_TexNormal, sampler_TexNormal, psInput.uv.zw);
packedNormal.xy = (packedNormal * 2 - 1) * _NormalScale;
packedNormal.z = sqrt(1 - saturate(dot(packedNormal.xy, packedNormal.xy)));
half3 packedNormalW = normalize(half3(dot(psInput.T2W0.xyz, packedNormal), dot(psInput.T2W1.xyz, packedNormal), dot(psInput.T2W2.xyz, packedNormal)));
half3 halfVector = normalize(viewDirW + lightDirW);
half NoL = dot(packedNormalW, lightDirW) * 0.5 + 0.5;
half NoH = saturate(dot(packedNormalW, halfVector));
half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv.xy);
half3 diffuse = texAlbedo * _AlbedoColorTint * GetMainLight().color * NoL;
half3 specular = _SpecularColor * GetMainLight().color * pow(NoH, _SpecularRange);
half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);
return half4(diffuse + specular + ambient, texAlbedo.a);
}
ENDHLSL
}
}
}
Ramp Texture
-
思路:以Texture存储渐变值,再以half lambert作为ramp texture的uv点
-
需要将warp mode设为clamp,防止采样浮点数精度造成的问题
-
实现
{ // ------------------------------------------------------------------------------------------- Properties --------------------------------------------------------------------- Properties { _ColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1) _TexRamp("Albedo Tex", 2D) = "white"{} } // ------------------------------------------------------------------------------------------- SubShader ---------------------------------------------------------------------- SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" CBUFFER_START(UnityPerMaterial) half4 _ColorTint; float4 _TexRamp_ST; CBUFFER_END TEXTURE2D(_TexRamp); SAMPLER(sampler_TexRamp); struct VSInput { float4 positionL : POSITION; float3 normalL : NORMAL; }; struct PSInput { float4 positionH : SV_POSITION; float3 normalW : NORMAL; float3 positionW : TEXCOORD1; }; ENDHLSL // ------------------------------------------------------------------------------------------- Pass 1 ---------------------------------------------------------------------- Pass { NAME"Main Pass" Tags { "LightMode" = "UniversalForward" } HLSLPROGRAM #pragma vertex VS #pragma fragment PS PSInput VS(VSInput vsInput) { PSInput psInput; psInput.positionH = TransformObjectToHClip(vsInput.positionL); psInput.positionW = TransformObjectToWorld(vsInput.positionL); psInput.normalW = TransformObjectToWorldNormal(vsInput.normalL); return psInput; } float4 PS(PSInput psInput) : SV_TARGET { Light mainLight = GetMainLight(); half3 lightDirW = normalize(mainLight.direction); half3 normalW = normalize(psInput.normalW); half NoL = saturate(dot(normalW, lightDirW) * 0.5 + 0.5); half4 texDiffuse = SAMPLE_TEXTURE2D(_TexRamp, sampler_TexRamp, half2(NoL, NoL)); return texDiffuse * _ColorTint; } ENDHLSL } } }
Alpha Blend
-
blend公式:shader color * alpha + buffer color * (1 - alpha)
-
实现
{ // ------------------------------------------------------------------------------------------- Properties --------------------------------------------------------------------- Properties { _TexAlbedo("Albedo Tex", 2D) = "white" {} _ColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1) _TexAlpha("Alpha Tex", 2D) = "white" {} } // ------------------------------------------------------------------------------------------- SubShader --------------------------------------------------------------------- SubShader { Tags { "RenderPipeline" = "UniversalRenderPipeline" "RenderType" = "Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True" //取消贴花的影响 } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) float4 _ColorTint; float4 _TexAlbedo_ST; float4 _TexAlpha_ST; CBUFFER_END TEXTURE2D(_TexAlbedo); TEXTURE2D(_TexAlpha); SAMPLER(sampler_TexAlbedo); SAMPLER(sampler_TexAlpha); struct VSInput { float4 positionL : position; float4 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float4 uv : TEXCOORD0; }; ENDHLSL // ------------------------------------------------------------------------------------------- Main Pass --------------------------------------------------------------------- Pass { Tags { "LightMode" = "UniversalForward" } // shader color * alpha + buffer color * (1 - alpha) Blend SrcAlpha OneMinusSrcAlpha Zwrite Off HLSLPROGRAM #pragma vertex VS #pragma fragment PS PSInput VS(VSInput vsInput) { PSInput psInput; psInput.positionH = TransformObjectToHClip(vsInput.positionL); psInput.uv.xy = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); psInput.uv.zw = TRANSFORM_TEX(vsInput.uv, _TexAlpha); return psInput; } float4 PS(PSInput psInput) : SV_TARGET { half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv.xy) * _ColorTint; half texAlpha = SAMPLE_TEXTURE2D(_TexAlpha, sampler_TexAlpha, psInput.uv.zw).r; return float4(texAlbedo.rgb, texAlpha); } ENDHLSL } } }
-
效果
Alpha Test
-
必要设置
-
实现
{ // ------------------------------------------------------------------------------------------- Properties --------------------------------------------------------------------- Properties { _TexAlbedo("Albedo Tex", 2D) = "white" {} _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1) // "_Cutoff"名字固定 _Cutoff("Cut off", Range(0,1)) = 0 } // ------------------------------------------------------------------------------------------- Properties --------------------------------------------------------------------- SubShader { Tags { "RenderPipeline" = "UniversalRenderPipeline" "RenderType" = "TransparentCutout" "Queue" = "AlphaTest" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" CBUFFER_START(UnityPerMaterial) float4 _TexAlbedo_ST; float4 _AlbedoColorTint; float _Cutoff; CBUFFER_END TEXTURE2D(_TexAlbedo); SAMPLER(sampler_TexAlbedo); struct VSInput { float4 positionL : position; float2 uv : TEXCOORD0; float3 normalL : NORMAL; }; struct PSInput { float4 positionH : SV_POSITION; float3 normalW : NORMAL; float3 positionW : TEXCOORD1; float2 uv : TEXCOORD0; }; ENDHLSL // ------------------------------------------------------------------------------------------- Main Pass --------------------------------------------------------------------- Pass { Tags { "LightMode" = "UniversalForward" } HLSLPROGRAM #pragma vertex VS #pragma fragment PS PSInput VS(VSInput vsInput) { PSInput VSOutput; VSOutput.positionH = TransformObjectToHClip(vsInput.positionL); VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); VSOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL); VSOutput.positionW = TransformObjectToWorld(vsInput.positionL); return VSOutput; } float4 PS(PSInput psInput) : SV_TARGET { float4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint; half3 normalW = normalize(psInput.normalW); Light mainLight = GetMainLight(); half3 lightColor = mainLight.color; half3 lightDirW = mainLight.direction; float NoL = dot(normalW, lightDirW) * 0.5 + 0.5; // 利用clip()进行alpha test clip(texAlbedo.a - _Cutoff - 0.01); float3 diffuse = texAlbedo.rgb * _AlbedoColorTint * lightColor * NoL; return float4(diffuse, texAlbedo.a); } ENDHLSL } } }
多光源
-
URP中的多光源处理与Buildin里的不同。在Buildin中,在其中一个pass处理主光源,在其他pass中处理其他光源;而在URP中,只在一个pass中循环处理光源——收集所有光照信息,再传输给Pass,由开发者在Pass中决定使用指定光源进行光照,最后叠加光源计算的结果
-
优点:降低draw call
-
缺点:打破合批
-
思路:先计算其余光源的个数,再用其遍历全部灯光(通过GetAdditionalLight(i,WS_Pos)遍历每个光源的struct Light)得到光源方向、颜色、距离衰减、阴影衰减,最后累加
-
关键函数
- GetAdditionalLightsCount():返回额外灯光的个数
- Light GetAdditionalLight(uint i, float3 positionWS):返回struct Light
- i:额外灯光的id
- positionWS:世界空间下的顶点坐标
-
实现
{ // ------------------------------------------------------------------------------------------- Properties --------------------------------------------------------------------- Properties { _TexAlbedo("Albedo Tex", 2D) = "white" {} _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1) _SpecularColor("Specular Color", Color) = (1, 1, 1, 1) _Gloss("Gloss", Range(1, 200)) = 20 [KeywordEnum(OFF, ON)] _MULTIPLE_LIGHT("multiple light", float) = 1 } // ------------------------------------------------------------------------------------------- SubShader --------------------------------------------------------------------- SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" } LOD 100 HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" CBUFFER_START(UnityPerMaterial) half4 _AlbedoColorTint; float4 _TexAlbedo_ST; float _Gloss; float4 _SpecularColor; CBUFFER_END TEXTURE2D(_TexAlbedo); SAMPLER(sampler_TexAlbedo); struct VSInput { float4 positionL : POSITION; float3 normalL : NORMAL; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float3 positionW : TEXCOORD1; float3 normalW : NORMAL; float3 viewDirW : TEXCOORD2; float2 uv : TEXCOORD0; }; ENDHLSL // -------------------------------------------------------------------------------------------Main Pass --------------------------------------------------------------------- Pass { Tags { "LightMode" = "UniversalForward" } HLSLPROGRAM #pragma vertex VS #pragma fragment PS #pragma shader_feature _MULTIPLE_LIGHT_ON _MULTIPLE_LIGHT_OFF #pragma enable_d3d11_debug_symbols PSInput VS(VSInput vsInput) { PSInput VSOutput; VSOutput.positionH = TransformObjectToHClip(vsInput.positionL); VSOutput.positionW = TransformObjectToWorld(vsInput.positionL.xyz); VSOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL); VSOutput.viewDirW = GetCameraPositionWS() - VSOutput.positionW; VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); return VSOutput; } float4 PS(PSInput psInput) : SV_TARGET { half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint; Light mainLight = GetMainLight(); half3 mainLightColor = mainLight.color; half3 mainLightDir = normalize(mainLight.direction); float3 positionW = psInput.positionW; half3 normalW = normalize(psInput.normalW); half3 viewDirW = normalize(psInput.viewDirW); half3 halfVector = normalize(viewDirW + mainLightDir); half NoL = saturate(dot(normalW, mainLightDir) * 0.5 + 0.5); half NoH = saturate(dot(normalW, halfVector)); float3 diffuse = texAlbedo.rgb * mainLightColor * NoL; float3 specular = _SpecularColor * pow(NoH, _Gloss); // calc add lights float3 addColor = float3(0.f, 0.f, 0.f); #ifdef _MULTIPLE_LIGHT_ON // 求得其余灯光数量 int addLightCounts = GetAdditionalLightsCount(); for(int i = 0; i < addLightCounts; ++i) { // 求得某一个灯光数据 Light addLight = GetAdditionalLight(i, positionW); half3 addLightDirW = normalize(addLight.direction); half3 addLightHalfVector = normalize(viewDirW + addLightDirW); half addLightNoL = saturate(dot(normalW, addLightDirW) * 0.5 + 0.5); half addLightNoH = saturate(dot(normalW, addLightHalfVector)); float3 addLightDiffuse = texAlbedo.rgb * addLight.color * addLightNoL * addLight.distanceAttenuation * addLight.shadowAttenuation; float3 addLightSpecular = _SpecularColor * pow(addLightNoH, _Gloss); addColor += addLightDiffuse + addLightSpecular; } #else addColor = (0.f, 0.f, 0.f); #endif return float4(diffuse + specular + addColor, 1.f); } ENDHLSL } } }
-
效果
主光源阴影
-
重要函数
-
Light GetMainLight(float4 shadowCoord):计算阴影衰减
-
TransformWorldToShadowCoord(float3 positionWS):计算阴影坐标
-
-
实现
{ // ------------------------------------------------------------------------------------------- Properties --------------------------------------------------------------------- Properties { _TexAlbedo("Albedo Tex", 2D) = "white" {} _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1) _SpecularColor("Specular Color", Color) = (1, 1, 1, 1) _Gloss("Gloss", Range(10, 300)) = 20 } // ------------------------------------------------------------------------------------------- SubShader --------------------------------------------------------------------- SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" } LOD 100 HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" CBUFFER_START(UnityPerMaterial) half4 _AlbedoColorTint; float4 _TexAlbedo_ST; float _Gloss; float4 _SpecularColor; CBUFFER_END TEXTURE2D(_TexAlbedo); SAMPLER(sampler_TexAlbedo); struct VSInput { float4 positionL : POSITION; float3 normalL : NORMAL; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float3 positionW : TEXCOORD1; float3 normalW : NORMAL; float3 viewDirW : TEXCOORD2; float2 uv : TEXCOORD0; }; ENDHLSL // ------------------------------------------------------------------------------------------- Main Pass --------------------------------------------------------------------- Pass { Tags { "LightMode" = "UniversalForward" } HLSLPROGRAM #pragma vertex VS #pragma fragment PS #pragma multi_compile _ _MAIN_LIGHT_SHADOWS // 计算阴影衰减 #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE // 得到正确的阴影坐标 #pragma multi_compile _ _SHADOWS_SOFT //计算软阴影 PSInput VS(VSInput vsInput) { PSInput VSOuput; VSOuput.positionH = TransformObjectToHClip(vsInput.positionL); VSOuput.positionW = TransformObjectToWorld(vsInput.positionL); VSOuput.normalW = TransformObjectToWorldNormal(vsInput.normalL); VSOuput.viewDirW = GetCameraPositionWS() - VSOuput.positionW; VSOuput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); return VSOuput; } float4 PS(PSInput psInput) : SV_TARGET { half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint; Light mainLight = GetMainLight(TransformWorldToShadowCoord(psInput.positionW)); // 得到阴影坐标并计算阴影衰减 half3 lightColor = mainLight.color; half3 lightDirW = normalize(mainLight.direction); half3 normalW = normalize(psInput.normalW); half3 viewDirW = normalize(psInput.viewDirW); half3 halfVector = normalize(viewDirW + lightDirW); float NoL = saturate(dot(normalW, lightDirW) * 0.5 + 0.5); float NoH = saturate(dot(normalW, halfVector)); // // 需要乘以shadowAttenuation计算阴影衰减 half3 diffuse = texAlbedo * lightColor * mainLight.shadowAttenuation * NoL; half3 specular = _SpecularColor * pow(NoH, _Gloss) * mainLight.shadowAttenuation; return float4(diffuse + specular, 1.f); } ENDHLSL } // 阴影接收 UsePass "Universal Render Pipeline/Lit/ShadowCaster" } }
-
效果
多光源阴影
-
重要函数
-
Light GetAdditionalLight(uint i, float3 positionWS, half4 shadowMask)
可以看到想要计算额外光的阴影衰减就需要定义关键字"ADDITIONAL_LIGHT_CALCULATE_SHADOWS"
-
-
实现
{ // ------------------------------------------------------------------------------------------- Properties --------------------------------------------------------------------- Properties { _TexAlbedo("Albedo Tex", 2D) = "white" {} _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1) _SpecularColor("Specular Color", Color) = (1, 1, 1, 1) _Gloss("Gloss", Range(10, 300)) = 20 [KeywordEnum(OFF, ON)] _MULTIPLE_LIGHT("Muliptle Light", float) = 0 } // ------------------------------------------------------------------------------------------- SubShader --------------------------------------------------------------------- SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" } LOD 100 HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" CBUFFER_START(UnityPerMaterial) half4 _AlbedoColorTint; float4 _TexAlbedo_ST; float _Gloss; float4 _SpecularColor; CBUFFER_END TEXTURE2D(_TexAlbedo); SAMPLER(sampler_TexAlbedo); struct VSInput { float4 positionL : POSITION; float3 normalL : NORMAL; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float3 positionW : TEXCOORD1; float3 normalW : NORMAL; float3 viewDirW : TEXCOORD2; float2 uv : TEXCOORD0; }; ENDHLSL // ------------------------------------------------------------------------------------------- Main Pass --------------------------------------------------------------------- Pass { Tags { "LightMode" = "UniversalForward" } HLSLPROGRAM #pragma vertex VS #pragma fragment PS #pragma shader_feature _MULTIPLE_LIGHT_ON _MULTIPLE_LIGHT_OFF #pragma multi_compile __ _MAIN_LIGHT_SHADOWS // 计算阴影衰减 #pragma multi_compile __ _MAIN_LIGHT_SHADOWS_CASCADE // 得到正确的阴影坐标 #pragma multi_compile __ _SHADOWS_SOFT //计算软阴影 #pragma multi_compile __ ADDITIONAL_LIGHT_CALCULATE_SHADOWS // 计算额外光的阴影衰减和距离衰减 PSInput VS(VSInput vsInput) { PSInput VSOuput; VSOuput.positionH = TransformObjectToHClip(vsInput.positionL); VSOuput.positionW = TransformObjectToWorld(vsInput.positionL); VSOuput.normalW = TransformObjectToWorldNormal(vsInput.normalL); VSOuput.viewDirW = GetCameraPositionWS() - VSOuput.positionW; VSOuput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); return VSOuput; } float4 PS(PSInput psInput) : SV_TARGET { half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint; half4 shadowUV = TransformWorldToShadowCoord(psInput.positionW); Light mainLight = GetMainLight(shadowUV); // 得到阴影坐标并计算阴影衰减 half3 lightColor = mainLight.color; half3 lightDirW = normalize(mainLight.direction); float3 positionW = psInput.positionW; half3 normalW = normalize(psInput.normalW); half3 viewDirW = normalize(psInput.viewDirW); half3 halfVector = normalize(viewDirW + lightDirW); float NoL = saturate(dot(normalW, lightDirW) * 0.5 + 0.5); float NoH = saturate(dot(normalW, halfVector)); // // 需要乘以shadowAttenuation计算阴影衰减 half3 diffuse = texAlbedo * lightColor * mainLight.shadowAttenuation * NoL; half3 specular = _SpecularColor * pow(NoH, _Gloss) * mainLight.shadowAttenuation; float3 resultColor = diffuse + specular; #if defined _MULTIPLE_LIGHT_ON int addLightCounts = GetAdditionalLightsCount(); for(uint i = 0; i < addLightCounts; ++i) { Light addLight = GetAdditionalLight(i, positionW, half4(1.f, 1.f, 1.f, 1.f)); half3 addLightColor = addLight.color; half3 addLightDirW = normalize(addLight.direction); half3 addLightHalfVector = normalize(viewDirW + addLightDirW); half addLightNoL = saturate(dot(normalW, addLightDirW) * 0.5 + 0.5); half addLightNoH = saturate(dot(normalW, addLightHalfVector)); half3 addLightDiffuse = texAlbedo * addLightNoL * addLightColor * addLight.shadowAttenuation * addLight.distanceAttenuation; half3 addLightSpecular = _SpecularColor * pow(addLightNoH, _Gloss) * addLight.shadowAttenuation * addLight.distanceAttenuation; resultColor += addLightDiffuse + addLightSpecular; } #endif return float4(resultColor, 1.f); } ENDHLSL } // 阴影接收 UsePass "Universal Render Pipeline/Lit/ShadowCaster" } }
-
解决SRP Batcher不兼容
可以看到使用多光源投影会使得SRP Batcher无法工作-
关键点
- float3 ApplyShadowBias(float3 positionWS, float3 normalWS, float3 lightDirection):定义于"Shadow.hlsl"。用于得到阴影投射的裁剪空间坐标
- UNITY_REVERSED_Z:因为dx11/12的深度缓冲,1.0为近平面,0.0为远平面;而dx9/opengl的深度缓存,0.0为近平面,1.0远平面,所以这里需要翻转深度值
- 原因:提升深度缓冲的精度,减轻z值冲突和提高阴影质量
- _LightDirection:获取主光源和其余光源的方向
- float3 ApplyShadowBias(float3 positionWS, float3 normalWS, float3 lightDirection):定义于"Shadow.hlsl"。用于得到阴影投射的裁剪空间坐标
-
实现
{ // ------------------------------------------------------------------ Properties ------------------------------------------------------------------ Properties { _TexAlbedo("Albedo Tex", 2D) = "white" {} _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1) _SpecularColor("Specular Color", Color) = (1, 1, 1, 1) _Gloss("Gloss", Range(10, 300)) = 20 _Cutoff("Cutoff", Range(0,1)) = 1 [KeywordEnum(OFF, ON)] _MULTIPLE_LIGHT("Muliptle Light", float) = 0 [KeywordEnum(OFF, ON)] _CUT("CUT", float) = 0 } // ------------------------------------------------------------------ SubShader ------------------------------------------------------------------ SubShader { Tags { "RenderPipeline" = "UniversalPipeline" } LOD 100 HLSLINCLUDE #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" #pragma shader_feature_local _MULTIPLE_LIGHT_ON #pragma shader_feature_local _CUT_ON CBUFFER_START(UnityPerMaterial) half4 _AlbedoColorTint; float4 _TexAlbedo_ST; float _Gloss; float4 _SpecularColor; float _Cutoff; CBUFFER_END TEXTURE2D(_TexAlbedo); SAMPLER(sampler_TexAlbedo); struct VSInput { float4 positionL : POSITION; float3 normalL : NORMAL; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float3 positionW : TEXCOORD1; float3 normalW : NORMAL; float3 viewDirW : TEXCOORD2; float2 uv : TEXCOORD0; half4 shadowUV : TEXCOORD3; }; ENDHLSL // ------------------------------------------------------------------ Main Pass ------------------------------------------------------------------ Pass { Tags { "LightMode" = "UniversalForward" "RenderType" = "TransparentCutout" "Queue" = "AlphaTest" } Cull off HLSLPROGRAM #pragma vertex VS #pragma fragment PS #pragma multi_compile __ _MAIN_LIGHT_SHADOWS // 计算阴影衰减 #pragma multi_compile __ _MAIN_LIGHT_SHADOWS_CASCADE // 得到正确的阴影坐标 #pragma multi_compile __ _SHADOWS_SOFT //计算软阴影 #pragma multi_compile __ ADDITIONAL_LIGHT_CALCULATE_SHADOWS // 计算额外光的阴影衰减和距离衰减 #pragma multi_compile __ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS //计算阴影投射 PSInput VS(VSInput vsInput) { PSInput VSOutput; VSOutput.positionH = TransformObjectToHClip(vsInput.positionL); VSOutput.positionW = TransformObjectToWorld(vsInput.positionL); VSOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL); VSOutput.viewDirW = GetCameraPositionWS() - VSOutput.positionW; VSOutput.shadowUV = TransformWorldToShadowCoord(VSOutput.positionW); VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); return VSOutput; } float4 PS(PSInput psInput) : SV_TARGET { half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint; #ifdef _CUT_ON clip(texAlbedo.a - _Cutoff); #endif Light mainLight = GetMainLight(psInput.shadowUV); // 得到阴影坐标并计算阴影衰减 half3 lightColor = mainLight.color; half3 lightDirW = normalize(mainLight.direction); float3 positionW = psInput.positionW; half3 normalW = normalize(psInput.normalW); half3 viewDirW = normalize(psInput.viewDirW); half3 halfVector = normalize(viewDirW + lightDirW); float NoL = saturate(dot(normalW, lightDirW) * 0.5 + 0.5); float NoH = saturate(dot(normalW, halfVector)); // // 需要乘以shadowAttenuation计算阴影衰减 half3 diffuse = texAlbedo * lightColor * mainLight.shadowAttenuation * NoL; half3 specular = _SpecularColor * pow(NoH, _Gloss) * mainLight.shadowAttenuation; float3 resultColor = diffuse + specular; // calc add light #if defined _MULTIPLE_LIGHT_ON int addLightCounts = GetAdditionalLightsCount(); for(uint i = 0; i < addLightCounts; ++i) { Light addLight = GetAdditionalLight(i, positionW, half4(1.f, 1.f, 1.f, 1.f)); half3 addLightColor = addLight.color; half3 addLightDirW = normalize(addLight.direction); half3 addLightHalfVector = normalize(viewDirW + addLightDirW); half addLightNoL = saturate(dot(normalW, addLightDirW) * 0.5 + 0.5); half addLightNoH = saturate(dot(normalW, addLightHalfVector)); half3 addLightDiffuse = texAlbedo * addLightNoL * addLightColor * addLight.shadowAttenuation * addLight.distanceAttenuation; half3 addLightSpecular = _SpecularColor * pow(addLightNoH, _Gloss) * addLight.shadowAttenuation * addLight.distanceAttenuation; resultColor += addLightDiffuse + addLightSpecular; } #endif return float4(resultColor, 1.f); } ENDHLSL } //UsePass "Universal Render Pipeline/Lit/ShadowCaster" // ------------------------------------------------------------------ ShadowCaster Pass ------------------------------------------------------------------ Pass { Tags { "LightMode" = "ShadowCaster" } HLSLPROGRAM #pragma vertex VSShadow #pragma fragment PSShadow // 获取主光源和其余光源的方向(Unity自动完成赋值) half3 _LightDirection; PSInput VSShadow(VSInput vsInput) { PSInput VSOutput; VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); VSOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL); VSOutput.positionW = TransformObjectToWorld(vsInput.positionL); Light mainLight = GetMainLight(); VSOutput.positionH = TransformWorldToHClip(ApplyShadowBias(VSOutput.positionW, VSOutput.normalW, _LightDirection)); #if UNITY_REVERSED_Z VSOutput.positionH.z = min(VSOutput.positionH.z, VSOutput.positionH.w * UNITY_NEAR_CLIP_VALUE); #else VSOutput.positionH.z = max(VSOutput.positionH.z, VSOutput.positionH.w * UNITY_NEAR_CLIP_VALUE); #endif return VSOutput; } half4 PSShadow(PSInput psInput) : SV_TARGET { #ifdef _CUT_ON float alpha = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv).a; clip(alpha - _Cutoff); #endif return 0; } ENDHLSL } } }
-
FlipbBook
-
思路
-
列
frac(floor(_Time.y) / _flipbookCols):对time向下取整得到整数,并处以列数,这样就可以得到每列每个图像出现的时间点(如8列,即0,0.125,0.25,0.375,0.5,0.625,0.75,0.875),再加上第一列的即可psInput.uv.x / _flipbookCols
-
行
frac(floor(_Time.y / _flipbookCols) / _flipbookRows):因为这里需要考虑上一行是否走完,所以需要_Time.y / _flipbookCols,再除以总行数即可得到当前的行数
-
动画方向
因为unity的uv是从下到上,而目前实现的filpbook是从上到下,若不调整会造成动画是反向的,所以需要对flipbook的y进行oneminus(1 - y)
-
-
实现
{ // ------------------------------------------------------------------ Properties ------------------------------------------------------------------ Properties { _TexAlbedo("Alebdo Tex", 2D) = "white" {} _AlbedoTint("Albedo Color Tint", Color) = (1, 1, 1, 1) // filpbook的行列数 _flipbookCols("flipbook columns", Range(0,8)) = 8 _flipbookRows("flipbook rows", Range(0, 8)) = 8 // 帧率 _FrameRate("Frame Rate", Range(0, 144)) = 60 } // ------------------------------------------------------------------ SubShader ------------------------------------------------------------------ SubShader { Tags { "RenderPipeline" = "UniversalPipeline" // flipbook通常为透明纹理 "Queue" = "Transparent" "RenderType" = "Transparent" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) half4 _AlbedoTint; float4 _TexAlbedo_ST; half _flipbookCols; half _flipbookRows; half _FrameRate; CBUFFER_END TEXTURE2D(_TexAlbedo); SAMPLER(sampler_TexAlbedo); ENDHLSL // ------------------------------------------------------------------ Main Pass ------------------------------------------------------------------ Pass { Tags { "LightMode" = "UniversalForward" } ZWrite off Blend SrcAlpha OneMinusSrcAlpha HLSLPROGRAM #pragma vertex VS #pragma fragment PS struct VSInput { float4 positionL : position; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float2 uv : TEXCOORD0; }; PSInput VS(VSInput vsInput) { PSInput VSOutput; VSOutput.positionH = TransformObjectToHClip(vsInput.positionL); VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); return VSOutput; } float4 PS(PSInput psInput) : SV_TARGET { float2 flipbookUV; flipbookUV.x = psInput.uv.x / _flipbookCols + frac(floor(_Time.y * _FrameRate) / _flipbookCols); flipbookUV.y = psInput.uv.y / _flipbookRows + 1 - frac(floor(_Time.y * _FrameRate / _flipbookCols) / _flipbookRows); return SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, flipbookUV); } ENDHLSL } } }
注意:flipbook的纹理类型需要设为"Alpha is Transparency"
Billboard
-
思路:主要是确定新的坐标系。推荐一篇简单清晰的https://www.bilibili.com/read/cv6483887/?spm_id_from=333.999.0.0
-
实现
{ // ------------------------------------------------------------------ Properties ------------------------------------------------------------------ Properties { _TexAlbedo("Albedo Tex", 2D) = "white" {} _AlbedoTint("Albedo Color Tint", Color) = (1, 1, 1, 1) _FlipbookCols("Flipbook Columns", Range(0, 8)) = 8 _FlipbookRows("Flipbook Rows", Range(0, 8)) = 8 _FrameRate("Frame Rate", Range(0, 144)) = 60 // 是否固定z轴 [KeywordEnum(LOCK_Z, FREE_Z)] _Z_STAGE("Z_Stage", float) = 1 } // ------------------------------------------------------------------ SubShader ------------------------------------------------------------------ SubShader { Tags { "RenderPipeline" = "UniversalRenderPipeline" "RenderType" = "Transparent" "Queue" = "Transparent" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) half4 _AlbedoTint; float4 _TexAlbedo_ST; half _FlipbookCols; half _FlipbookRows; half _FrameRate; CBUFFER_END TEXTURE2D(_TexAlbedo); SAMPLER(sampler_TexAlbedo); ENDHLSL Pass { Tags { "LightMode" = "UniversalForward" } ZWrite off Blend SrcAlpha OneMinusSrcAlpha HLSLPROGRAM #pragma vertex VS #pragma fragment PS #pragma shader_feature_local _Z_STAGE_LOCK_Z struct VSInput { float4 positionL : position; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float2 uv : TEXCOORD0; }; PSInput VS(VSInput vsInput) { PSInput VSOutput; // 构建新的坐标系 // newZ // newZ:world space下(0,0,0)到相机坐标的朝向转换至模型空间 float3 newZ = TransformWorldToObject(GetCameraPositionWS()); // 是否锁定z轴 // 不锁定:始终朝向相机 // 锁定:z轴固定,无法移动 #ifdef _Z_STAGE_LOCK_Z // 以相机投影在水平面的的分量作为newZ newZ.y = 0; #endif newZ = normalize(newZ); // newX // 当newZ与oldY(0, 1, 0)不重叠:newX = cross(newZ, oldY) // 当newZ与oldY(0, 1, 0)完全重叠:newX = cross(newZ, oldZ(0, 0, 1))。因为完全重叠时,newX一定与oldZ垂直 float3 newX = abs(newZ.y) < 0.99 ? cross(newZ, half3(0.f, 1.f ,0.f)) : cross(newZ, half3(0.f, 0.f, 1.f)); newX = normalize(newX); // newY float3 newY = normalize(cross(newZ, newX)); float3x3 billboardMatrix = {newX, newY, newZ}; // 左乘相当于乘以矩阵的转置 float3 newPosition = mul(vsInput.positionL, billboardMatrix); VSOutput.positionH = TransformObjectToHClip(newPosition); VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); return VSOutput; } float4 PS(PSInput psInput) : SV_TARGET { float2 flipbookUV; flipbookUV.x = psInput.uv.x / _FlipbookCols + frac(floor(_Time.y * _FrameRate) / _FlipbookCols); flipbookUV.y = psInput.uv.y / _FlipbookRows + 1 - frac(floor(_Time.y * _FrameRate / _FlipbookCols) / _FlipbookRows); return SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, flipbookUV); } ENDHLSL } } }
viewport Color
-
在build in中,通过grad pass 获取屏幕图像;但在URP中则不同,需要通过URP提供的copy color得到一张名叫_CameraColorTexture的屏幕图像,随后使用"SAMPER()"采样图像
-
想要使用_CameraColorTexture,需要启用"Opaque Texture"
-
获取viewport uv
在以往Unity以opengl(入门精要)为准,齐次裁剪空间的近平面深度为near,远平面深度为far,深度差为fra - near;但目前untiy以DX11为准,齐次裁剪空间的近平面的深度为near,远平面深度为0,深度差为near。可以看出在DX11下,对Z轴进行反向(提高近远平面的精度)
在vertex shader中获得viewport uv:不能进行透视除法,因此viewport uv为:w * (xy / w * 0.5 + 0.5)。当然对于dx11/12来说,v的方向是相反的,因此需要进行oneminus
在pixel shader中获得viewport uv :psInput.positionH(SV_POSITION).xy / _ScreenParams.xy
-
实现
{ // ------------------------------------------------------------------ Properties ------------------------------------------------------------------ Properties { _TexNormal("Normal Tex", 2D) = "bump" {} _NormalScale("Normal Scale", Range(0, 5)) = 1 _Intensity("viewport UV Offset", Range(0, 1000)) = 100 // 在world space 还是tangent space计算normal [KeywordEnum(WORLD, TANGENT)] _NORMAL_STAGE("Normal Stage", float) = 1 } // ------------------------------------------------------------------ SubShader ------------------------------------------------------------------ SubShader { Tags { "RenderPipeline" = "UniversalPipeline" "Queue" = "Transparent" "RenderType" = "Transparent" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" CBUFFER_START(UnityPerMaterial) float4 _TexNormal_ST; half _NormalScale; half _Intensity; CBUFFER_END // 非本shader独有,因此不可以放于常量缓冲区 float4 _CameraColorTexture_TexelSize; TEXTURE2D(_TexNormal); SAMPLER(sampler_TexNormal); SAMPLER(_CameraColorTexture); ENDHLSL // ------------------------------------------------------------------ Main Pass ------------------------------------------------------------------ Pass { Tags { "LightMode" = "UniversalForward" } HLSLPROGRAM #pragma vertex VS #pragma fragment PS #pragma shader_feature_local _NORMAL_STAGE_WORLD struct VSInput { float4 positionL : position; float4 tangentL : TANGENT; float3 normalL : NORMAL; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float4 tangentW : TANGENT; float4 bitTangentW : TEXCOORD1; float4 normalW : NORMAL; float2 uv : TEXCOORD0; }; PSInput VS(VSInput vsInput) { PSInput vsOutput; vsOutput.positionH = TransformObjectToHClip(vsInput.positionL); vsOutput.normalW.xyz = TransformObjectToWorldNormal(vsInput.normalL); vsOutput.tangentW.xyz = TransformObjectToWorldDir(vsInput.tangentL); vsOutput.bitTangentW.xyz = cross(vsOutput.normalW, vsOutput.tangentW.xyz) * vsInput.tangentL.w * unity_WorldTransformParams.w; float3 positionW = TransformObjectToWorld(vsInput.positionL); vsOutput.tangentW.w = positionW.x; vsOutput.bitTangentW.w = positionW.y; vsOutput.normalW.w = positionW.z; vsOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexNormal); return vsOutput; } float4 PS(PSInput psInput) : SV_TARGET { half4 texNormal = SAMPLE_TEXTURE2D(_TexNormal, sampler_TexNormal, psInput.uv); float3 normalT = UnpackNormalScale(texNormal, _NormalScale); // 求得viewport的 uv float2 viewportUV = psInput.positionH.xy / _ScreenParams.xy; #ifdef _NORMAL_STAGE_WORLD // 在world space计算normal float3x3 matrixT2W = {psInput.tangentW.xyz, psInput.bitTangentW.xyz, psInput.normalW.xyz}; float3 normalW = mul(normalT, matrixT2W); // 对viewport uv进行offset float2 viewportBias = normalW.xy * _Intensity * _CameraColorTexture_TexelSize; #else float2 viewportBias = normalT.xy * _Intensity * _CameraColorTexture_TexelSize; #endif float4 glassColor = tex2D(_CameraColorTexture, viewportUV + viewportBias); return float4(glassColor.xyz, 1.f); } ENDHLSL } } }
此时大概率会发现在Scene窗口下渲染出的物体是纯黑色的,而在Game窗口下是正常的,个人猜测是因为在获取"_CameraColorTexture"时,半透明物体还没渲染成型,所以是纯黑的,也就是说"_CameraColorTexture"应该是在透明物体渲染后再采样场景
Viewport Depth
-
为了获取当前viewport的depth,需要获得一张名为_CameraDepthTexture的深度图并进行采样,这就需要启用"Depth Texture"
-
采样深度图后,使用float Linear01Depth(float depth, float4 zBufferParam)将depth转换至linear space
-
需要注意_CameraDepthTexture深度图,从近到远,值是从1到0(这是最原始的深度图,因为dx平台下进行了Reversed Z,所以深度是相反);但当转换为线性的01深度,从近到远,值是从01
-
思路
-
因为该护盾效果是根据viewport depth实现的,所以需要先获取viewport uv
// vs vsOutput.positionVP.xy = vsOutput.positionH.w * (vsOutput.positionH.xy / vsOutput.positionH.w * 0.5 + 0.5); vsOutput.positionVP.zw = vsOutput.positionH.zw; // ps psInput.positionVP.xy /= psInput.positionVP.w; // 透视除法 #ifdef UNITY_UV_STARTS_AT_TOP // 判断opengl,还是dx psInput.positionVP.y = 1 - psInput.positionVP.y; #endif
-
获取viewport depth(_CameraDepthTexture类似于UE的scene depth,采样时忽略半透明物体 )
SAMPLER(_CameraDepthTexture); float4 depthColor = tex2D(_CameraDepthTexture, psInput.positionVP.xy); float depthBuffer = Linear01Depth(depthColor, _ZBufferParams); // 转换为线性depth
-
为了实现互盾在模型上方(不被模型覆盖)的效果,且护盾与模型的交界处显得非常明显还需要获取model depth(此处modelDepth类似于pixel depth,采样时并没有忽略半透明物体)
float modelDepth = psInput.positionH.z; modelDepth = Linear01Depth(modelDepth, _ZBufferParams); // 转换为线性depth // 使用模型深度 - viewport depth后,再+一个控制深度大小的值。这样可以控制护盾的厚度 float edge = saturate(modelDepth - depthBuffer + _DepthSize) * 100 * _DepthOffset;
这里edge的思路就是用pixel depth - scene depth,得到的值肯定是负值,再加上_DepthSize使得部分较大的负值大于0,即可实现明显的交界效果
-
边缘光
edge有个缺点,只在和物体接触时,交界轮廓很明显,因此需要加上fresnel效果float fresnel = Fresnel(normalW, viewDirW, _FresnelExp) * _DepthOffset;
-
将edge 和 fresnel相加,作为最终结果的alpha
return float4(texAlbedo.xyz, edge + fresnel);
-
添加流光动画效果
float flow = saturate(pow(1 - abs(frac(psInput.positionW.y * 0.3 - _Time.y * 0.2) - 0.5), 10) * 0.3); float4 flowColor = flow * _Emission;
此时实现的重点在于frac(),可以用shader graph看看效果
-
-
实现
{ // ------------------------------------------------------------------ Properties ------------------------------------------------------------------ Properties { _TexAlbedo("Albedo Tex", 2D) = "white" {} [HDR]_AlbedoTint("Albedo Tint", Color) = (1, 1, 1, 1) _DepthOffset("Depth Offset", Range(0,100)) = 50 _DepthSize("Depth Size", Range(0, 0.001)) = 0.0005 _FresnelExp("Fresnel Exp", Range(0, 10)) = 5 [HDR]_FlowColor("Flow Color", Color) = (1, 1, 1, 1) _FlowSpeed("Flow Speed", Range(0, 1)) = 0.5 _FlowEdge("Flow Edge", Range(0, 1)) = 0.5 } // ------------------------------------------------------------------ Properties ------------------------------------------------------------------ SubShader { Tags { "RenderPipeline" = "UniversalRenderPipeline" "Queue" = "Transparent" "RenderType" = "Transparent" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Assets/Shader/MyUtil/MyUtil.hlsl" //个人定义的工具函数 CBUFFER_START(UnityPerMaterial) float4 _AlbedoTint; float4 _TexAlbedo_ST; half _DepthOffset; half _DepthSize; half _FlowEdge; half _FlowSpeed; float4 _FlowColor; half _FresnelExp; CBUFFER_END TEXTURE2D(_TexAlbedo); SAMPLER(sampler_TexAlbedo); // 采样viewport的深度图 SAMPLER(_CameraDepthTexture); ENDHLSL Pass { Tags { "LightMode" = "UniversalForward" } Blend SrcAlpha OneMinusSrcAlpha HLSLPROGRAM #pragma vertex VS #pragma fragment PS struct VSInput { float4 positionL : position; float4 normalL : NORMAL; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float4 positionVP : TEXCOORD2; float3 normalW : NORMAL; float2 uv : TEXCOORD0; float3 positionW : TEXCOORD1; }; PSInput VS(VSInput vsInput) { PSInput vsOutput; vsOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo); vsOutput.positionH = TransformObjectToHClip(vsInput.positionL); vsOutput.positionW = TransformObjectToWorld(vsInput.positionL).xyz; vsOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL).xyz; // calc viewport position,但xy不能进行透视除法, 因为viewport只关心xy,所以zw不变 vsOutput.positionVP.xy = vsOutput.positionH.xy * 0.5 + 0.5 * float2(vsOutput.positionH.w, vsOutput.positionH.w); vsOutput.positionVP.zw = vsOutput.positionH.zw; return vsOutput; } float4 PS(PSInput psInput) : SV_TARGET { half3 viewDirW = normalize(GetCameraPositionWS() - psInput.positionW); half3 normalW = normalize(psInput.normalW); float2 uv = psInput.uv; half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, uv) * _AlbedoTint; // calc viewport uv psInput.positionVP.xy /= psInput.positionVP.w; // 透视除法 #ifdef UNITY_UV_STARTS_AT_TOP // 判断opengl,还是dx psInput.positionVP.y = 1 - psInput.positionVP.y; #endif // Fresnel float fresnel = Fresnel(normalW, viewDirW, _FresnelExp) * _DepthOffset; // calc buffer depth float4 depthColor = tex2D(_CameraDepthTexture, psInput.positionVP.xy); float depthBuffer = Linear01Depth(depthColor, _ZBufferParams); // 转换为线性depth // calc 目标模型 depth float modelDepth = psInput.positionH.z; modelDepth = Linear01Depth(modelDepth, _ZBufferParams); // 转换为线性depth float edge = saturate(modelDepth - depthBuffer + _DepthSize) * 100 * _DepthOffset; // 扫光 float flow = saturate(pow(1 - abs(frac(psInput.positionW.y * 0.3 - _Time.y * _FlowSpeed) - _FlowEdge), 10) * 0.3); float4 flowColor = flow * _FlowColor; return float4(flowColor.rgb + texAlbedo.rgb, edge + fresnel + flowColor.a); } ENDHLSL } } }
-
效果
场景反射
-
在unity中常用的反射有三种:天空盒(sky box)、反射探针(light probe)、屏幕空间反射。在这里介绍前面两种的shader实现方式(实现方式是相同的),屏幕空间反射会放到新的一篇进行详细介绍
-
天空盒和反射探针的实现原理类似,天空盒是采样一张Texture;而反射探针是使用球谐函数对一张Texture进行编码(以球谐函数的方式存储,因为纹理开销较大)
-
天空盒
准备一个sky box的material,Shader选择"Cubemap",Cubemap Texture一定要使用HDRI格式的Texture,否则效果不理想依次点击"Window"->"Rendering"->"Lighting"->"Environment"->"Sky box Material",选择刚刚准备好的sky box material
-
反射探针
-
为什么需要反射探针?
当场景愈来愈复杂,天空盒给与的间接光效果是不理想的,有一种好用且开销很低的方式就是反射探针,如下图所示,每个黄色小点表示的就是反射探针,它存储了该点的环境光信息
-
什么是反射探针
小伙伴可能会认为反射探针其实就是一个小型的skybox,这个想法对但不完全对。因为为了达到理想的间接光效果,很有可能需要用到许许多多的反射探针,每个反射探针都存储一张Texture,那开销就太大了!所以反射探针对此进行了优化,也就是球谐函数(这个很复杂,可以看看这https://mp.weixin.qq.com/s/dW6Kz_jyS503QTtLnyK6og)
简单来说,球谐函数可以对环境光Texture进行编码,记录函数的参数,Bake时使用傅里叶展开还原(因为没有什么时傅里叶展开画不出来的,正余弦越多图像越精确)这一Texture。采用球谐函数的另一个理由是,环境光的精度要求不是那么高,无需保存很多参数,例如在Unity中,球谐函数只采用了九个球面正余弦函数,就可以满足低频环境光照的需求
-
创建反射探针
-
反射探针有一定的范围限制,只有在这个范围内的物体才会反射
-
在这里反射探针类型以"Baked"为例
灯光的"Mode"设为"Baked"
将需要烘培的物体设为"Static"(因为反射探针只反射静态物体)
对于需要烘培的物体,设置其"Reflection Probes"为"Blend Probe"
-
-
重要函数
-
SAMPLE_TEXTURECUBE_LOD:采样环境光
// 通过unity_SpecCube0 获取相应的cube map // URP 定义 TEXTURECUBE(unity_SpecCube0); SAMPLER(samplerunity_SpecCube0); // 获取cube map后对其进行采样 // lod:mipmap级别 // URP 定义 #define SAMPLE_TEXTURECUBE_LOD(textureName, samplerName, coord3, lod) PLATFORM_SAMPLE_TEXTURECUBE_LOD(textureName, samplerName, coord3, lod)
-
-
实现
Shader "Custom/Reflect" { Properties { // // 采样的Texture Mipmap Level(Unity中Mipmap有10个Level) [IntRange]_MipMapLevel("Skybox MipMap Level", Range(0, 9)) = 0 } SubShader { Tags { "Pipeline" = "UniversalPipeline" "RenderType" = "Opaque" } LOD 100 HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) int _MipMapLevel; CBUFFER_END struct VSInput { float4 positionL : POSITION; float4 normalL : NORMAL; }; struct PSInput { float4 positionH : SV_POSITION; float3 positionW : TEXCOORD0; float3 normalW : NORMAL; }; ENDHLSL Pass { Tags { "LightMode" = "UniversalForward" } HLSLPROGRAM #pragma vertex VS #pragma fragment PS PSInput VS(VSInput vsInput) { PSInput vsOutput; vsOutput.positionH = TransformObjectToHClip(vsInput.positionL.xyz); vsOutput.normalW = normalize(TransformObjectToWorldNormal(vsInput.normalL.xyz)); vsOutput.positionW = TransformObjectToWorld(vsInput.positionL.xyz); return vsOutput; } half4 PS(PSInput psInput) : SV_TARGET { half4 outputColor = 0.f; float3 viewDirW = normalize(GetCameraPositionWS() - psInput.positionW); float3 normalW = psInput.normalW; float3 reflectDirW = reflect(-viewDirW, normalW); half4 SkyBoxColor = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectDirW, _MipMapLevel); outputColor += SkyBoxColor; return outputColor; } ENDHLSL } } }
reference
https://www.bilibili.com/read/cv14791374/
https://space.bilibili.com/5863867
https://juejin.cn/post/7105038680091263007
https://mp.weixin.qq.com/s/dW6Kz_jyS503QTtLnyK6og