阴影实现 - ShadowMap方式
最终效果
1) 两个阴影重叠的时候,正常
2) 在斜坡上,阴影正常
3) 在平地上遇到障碍物,阴影正常
4) 在平台边缘,镂空的地方,阴影正常
5) 没有做阴影衰减
6) 阴影抗锯齿没做(pcf)
原理
ShadowMap阴影是一种实时阴影,其原理其实也很简单,就2步:1、投射阴影,2、接收阴影
1) 投射阴影,这一步主要是记录下不会产生阴影的物体的相关信息,这些信息会被保存在深度图(ShadowMap)中。
那怎么知道哪些物体不会产生阴影呢?在光源位置放一个与光同方向的相机,相机能看到的地方就是不会产生阴影的;相机看不到的地方,就是被挡住了的,会产生阴影。
2) 接收阴影,这一步就是把阴影显示出来。原理是,通过比较物体的深度值和ShadowMap中的深度值,如果是大于就是物体被挡住了,会有阴影。
实现
投射阴影用shader
Shader "My/Shadow/MyShadowCaster" { SubShader { Tags { "RenderType" = "Opaque" } Pass { ZWrite On ZTest LEqual Cull Front CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct a2v { float4 vertex : POSITION; }; struct v2f { float4 pos : SV_POSITION; }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); //模型空间转换为裁剪空间 return o; } fixed4 frag(v2f i) : SV_Target { float depth = i.pos.z / i.pos.w; //w不为1时: 保证z在[-1, 1]范围内
#if defined(SHADER_TARGET_GLSL) depth = depth * 0.5 + 0.5; #elif defined(UNITY_REVERSED_Z) //DX11, DX12等这样的平台上, depth从1到0(解决浮点数精度和分布造成的Z-Fight闪烁问题) depth = 1 - depth; #endif return EncodeFloatRGBA(depth); } ENDCG } } }
相机生成ShadowMap逻辑
using UnityEngine; [RequireComponent(typeof(Camera))] public class ShadowMapCamera : MonoBehaviour { public Transform m_DirLight; public Shader m_ShadowCaster; //负责将阴影信息投射到shadowMap public Transform m_Player; //跟随玩家 private Vector3 m_LastPlayerPos; [Range(0, 1)] public float m_ShadowStrength = 0; [Range(0, 2)] public float m_ShadowBias = 0; private Camera m_LightCamera; private RenderTexture m_ShadowMapTexture; private void Awake() { m_LightCamera = GetComponent<Camera>(); if (null == m_ShadowCaster) m_ShadowCaster = Shader.Find("My/Shadow/MyShadowCaster"); } void Start() { ResetCamera(m_LightCamera); CreateShadowMapTexture(); m_LightCamera.transform.rotation = m_DirLight.rotation; //假定运行过程中光角度不变 Shader.SetGlobalFloat("_shadowBias", m_ShadowBias); Shader.SetGlobalFloat("_shadowStrength", m_ShadowStrength); UpdateLightCameraVPMatrix(); } private void UpdateLightCameraVPMatrix() { Matrix4x4 projMatrix = GL.GetGPUProjectionMatrix(m_LightCamera.projectionMatrix, false); Matrix4x4 vpMatrix = projMatrix * m_LightCamera.worldToCameraMatrix; //矩阵左乘 Shader.SetGlobalMatrix("_worldToShadow", vpMatrix); } void Update() { if (m_Player) { var diff = m_Player.position - m_LastPlayerPos; if (diff.sqrMagnitude > 0) { m_LastPlayerPos = m_Player.position; m_LightCamera.transform.position = m_LastPlayerPos; UpdateLightCameraVPMatrix(); } } m_LightCamera.RenderWithShader(m_ShadowCaster, ""); //使用阴影投射shader渲染视野范围内的所有物体 } private void ResetCamera(Camera lightCamera) { lightCamera.backgroundColor = Color.white; lightCamera.clearFlags = CameraClearFlags.SolidColor; lightCamera.orthographic = true; lightCamera.orthographicSize = 6f; lightCamera.nearClipPlane = -10; lightCamera.farClipPlane = 10f; lightCamera.enabled = false; lightCamera.allowMSAA = false; lightCamera.allowHDR = false; lightCamera.cullingMask = -1; } private void UpdateShadowMapTexture() { if (null != m_ShadowMapTexture) { m_LightCamera.targetTexture = null; RenderTexture.ReleaseTemporary(m_ShadowMapTexture); m_ShadowMapTexture = null; } CreateShadowMapTexture(); } private void CreateShadowMapTexture() { m_ShadowMapTexture = new RenderTexture(1024, 1024, 16, RenderTextureFormat.RGFloat); m_ShadowMapTexture.name = "Shadowmap_RGFloat"; m_ShadowMapTexture.hideFlags = HideFlags.DontSave; m_ShadowMapTexture.wrapMode = TextureWrapMode.Clamp; m_ShadowMapTexture.filterMode = FilterMode.Bilinear; m_LightCamera.targetTexture = m_ShadowMapTexture; Shader.SetGlobalTexture("_MyShadowMapTexture", m_ShadowMapTexture); } }
加了接收阴影逻辑的shader
Shader "My/Shadow/DiffusePerPixel_ShadowReceive" { Properties { _MainTex("Main Tex", 2D) = "white" {} _Color("Tint", Color) = (1, 1, 1, 1) //贴图颜色色调 } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } //设置了LightMode=ForwardBase时, 内置变量_WorldSpaceLightPos0才被赋值 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; //顶点法线 float2 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float4 worldPos : TEXCOORD2; float4 shadowCoord : TEXCOORD3; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; //********** 用于阴影Receive float4x4 _worldToShadow; sampler2D _MyShadowMapTexture; //shadowMap贴图 float _shadowStrength; //阴影强度 float _shadowBias; //Shadow Acne问题需要 //********** v2f vert(appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.shadowCoord = mul(_worldToShadow, o.worldPos); //LightCamera的VP_Matrix return o; } float hardShadow(float4 shadowCoord) { float2 uv = shadowCoord.xy / shadowCoord.w; uv = uv * 0.5 + 0.5; float depth = shadowCoord.z / shadowCoord.w; //w不为1时: 保证z在[-1, 1]范围内 #if defined(SHADER_TARGET_GLSL) depth = depth * 0.5 + 0.5; #elif defined(UNITY_REVERSED_Z) depth = 1 - depth; #endif float4 c = tex2D(_MyShadowMapTexture, uv); float shadowMapDepth = DecodeFloatRGBA(c); return (shadowMapDepth + _shadowBias) < depth ? _shadowStrength : 1; //shadowMap中的z值更小, 说明物体被挡住了, 在阴影中 } fixed4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); //主光源方向 fixed4 c = tex2D(_MainTex, i.uv); fixed3 albedo = c.rgb * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; //环境光 fixed lambert = max(0, dot(worldNormal, worldLightDir)); //漫反射计算公式 fixed3 diffuse = _LightColor0.rgb * albedo * lambert; fixed atten = 1.0; //这边没处理衰减, 暂时固定1 float shadow = 1.0; shadow = hardShadow(i.shadowCoord); return fixed4(ambient + diffuse * shadow * atten, 1.0); } ENDCG } } }
相关设置
相关链接
实时渲染-阴影渲染实现(Unity) - 知乎 (zhihu.com)
Unity实时阴影实现——Shadow Mapping - 知乎 (zhihu.com)
shadowMapDepth
分类:
shader / 阴影
, shader
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~