变换矩阵模型世界设模型的x轴y轴z轴在世界的方向是vX、vY、vX模型的世界坐标是(xM,yM,zM)。变换矩阵是vXx vYx vZx xMvXy vYy vZy yMvXz vYz vZz zM0 0 0 1面法向和顶点法向面法向是垂直面的向量。顶点法向是由顶点参与的面的法向加权平均通常是面积加权算出的向量。模型文件里存的是顶点法向因为面法向通过2个边叉乘就能算出。两种法线贴图世界空间和切线空间如果你写过切线空间法线贴图的着色器一定会被里面的矩阵操作弄到晕头转向。使用切线空间着色器你的灯光在世界空间要么把法线转到世界空间要么把灯光转到切线空间。如果法线贴图记录世界空间法线计算就简单很多。但是那种法线贴图是限定模型的一个胶囊体的法线贴图对一个立方体是完全错误的。切线空间法线贴图则可以用于任何模型。所以想写着色器简单就只能用模型专用法线贴图想用泛用的法线贴图写着色器就麻烦。切线空间首先切线空间是一个顶点在它参与的一个具体三角形里的概念。就是说一个顶点参与了3个三角形那么它有3个各自不同的切线空间。原点在顶点z轴是面法线x轴是切线y轴是副切线。切线方向是纹理U坐标增加的方向。副切线方向是纹理V坐标增加的方向。对于每个三角形可以通过顶点位置和UV坐标计算// 三角形的三个顶点v0, v1, v2 // 对应的UV坐标uv0, uv1, uv2 // 计算边向量 float3 edge1 v1 - v0; float3 edge2 v2 - v0; // 计算UV差值 float2 deltaUV1 uv1 - uv0; float2 deltaUV2 uv2 - uv0; //设 edge1 deltaUV1.u*T deltaUV1.v*B; edge2 deltaUV2.u*T deltaUV2.v*B; //其中T、B是切线、副切线三维向量 // 计算切线(T)和副切线(B) float f 1.0 / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y); float3 tangent f * (deltaUV2.y * edge1 - deltaUV1.y * edge2); float3 bitangent f * (-deltaUV2.x * edge1 deltaUV1.x * edge2); // 确保正交性Gram-Schmidt正交化 tangent normalize(tangent - normal * dot(normal, tangent));看不懂。我们可以想象blender里的uv展开这里的u和v方向朝右朝上把这两个方向映射回3D模型空间就是切线、副切线。不过注意模型三角面和3个顶点在贴图上组成的三角形不一定相似。我们可以把一个顶点拉走让2个三角形形状明显不同此时我们想知道左图的u方向映射到右边空间的向量。我想到可以用v0v1、v0v2作基底表示u向量得到基底坐标值(x,y)然后到三维空间还用(x,y)带入三维空间的v0v1、v0v2。Phong高光和Blinn Phong高光对比假设光和法向的夹角是a视线和法向的夹角是b则phong高光反射的夹角是abs(a-b)Blinn Phong高光反射的夹角是abs((a-b)/2)同样情况下Blinn Phong高光反射更亮。纹理采样、漫反射、高光反射、环境光几个成分是相乘还是相加的问题如果全相乘我们知道高光反射大部分区域是黑的乘其他成分也都是黑的高光反射应该只对光斑附近有影响所以高光反射。漫反射应该使纹理背光面变暗变淡向光面不受影响所以纹理*漫反射。如果纹理漫反射很明显是背光面是纹理向光面发白环境光呢乘环境光会变暗加环境光又会加上一层白雾。所以环境光不应该在最终的公式中单独成一项它可以给漫反射打底。或者说如果作为加法项应该乘贴图采样避免”白雾“效果。最终的公式是纹理采样*(漫反射环境光高光反射。渲染管线的流程前向渲染顶点坐标变换模型空间世界空间相机空间裁剪空间得到顶点在屏幕的坐标和深度光栅化决定每个三角形包含的像素三次叉乘算法着色根据颜色或采样贴图得到基础颜色然后计算漫反射、高光反射、环境光决定颜色深度模板测试深度测试透明度混合写入帧缓冲。延迟渲染顶点坐标变换模型空间世界空间相机空间裁剪空间得到顶点在屏幕的坐标和深度光栅化决定每个三角形包含的像素三次叉乘算法深度测试模板测试写入G Buffer像素的位置、法线着色写入帧缓冲。裁剪空间范围(-1,-1,-1)到(1,1,1).正交相机原点是远裁剪面、近裁剪面中间。顶点的相机空间(x/相机半宽,y/相机半高,(2z-N-F)/(N-F))光栅化不偷懒的情况下对视口里的每个三角形遍历每个像素使用三次叉乘看符号决定像素在不在三角形里。但很明显可以取三角形3个顶点x、y坐标的最小最大值得到包围盒只计算包围盒里的像素。计算颜色时颜色大于1怎么办根据是否启用了HDR(High Dynamic Range高动态范围如果启用了把颜色使用形如1-exp(-color)的公式从0-无穷大映射到0-1没有启用则大于1的直接等于1..shader(ShaderLab)脚本的结构参考文章2024-02-01 Unity Shader 开发入门4 —— ShaderLab 语法-CSDN博客官方文档ShaderLab - Unity 手册.shader是Unity定义的文件类型。它的基本结构为Shader 自定义着色器名字 { Properties { _变量名(面板显示名,类型)默认值{选项} ... //类型有2D贴图、Int、Float、Range(min,max)、Color、Vector四维向量 } SubShader { Tags{键1值1...}//标签有Queue、RenderType、DisableBatching、ForceNoShadowCasting、PreviewType等 渲染状态//有Cull (Back/Front/Off)、ZWrite(On/Off)、LOD xxx、Blend等 Pass { Name 通道名字//其他着色器可以通过UsePass 自定义着色器名称/通道名字使用这个通道 Tags{键1值1...}//标签有LightMode(常用ForwardBase、ShadowCaster YYYPROGRAM//YYY可选CG或HLSL YYY语言代码... ENDYYY } Pass { YYYPROGRAM//YYY可选CG或HLSL YYY语言代码... ENDYYY } } Fallback 备用着色器 }两种着色器语言对比CGHLSL全称C for graphicsHigh level shader language适配的渲染管线Builtin RPURP其他区别cg有fixed4数据类型hlsl没有Properties_MainTex(主贴图,2D)white{} _MyFloat(浮点型参数,Float)1 _MyRange(范围浮点型参数,Range(0,4))1 _MyColor(颜色参数,Color)(1,1,1,1) _MyVec(向量,Vector)(0,0,0,0) _MyInt(整形参数,Int)1 [Enum(UnityEngine.Rendering.CullMode)] _CullMode (Cull Mode, Float) 2SubShader TagsTags{ RenderTypeOpaque//Transparent、TransparentCutout树叶、Background、Overlay QueueGeometry//Background、Geometry、AlphaTest、Transparent、Overlay DisableBatchingTrue//False、LODFading ForceNoShadowCastingFalse//True IgnoreProjectorTrue }AlphaTest和Transparent的区别AlphaTest根据Alpha是否达到阈值要么显示要么不显示用于显示形状不规则的树叶树叶外面的区域都是低AlphaTransparent在物体和它后面的像素之间混合。Queue和RenderType是对应的QueueRenderTypeGeometryOpaqueTransparentTransparentBackgroundBackgroundAlphaTestTransparentCutoutOverlayOverlay渲染状态Cull Back//Off、Front、[_CullMode]材质面板控制 ZWrite On//Off ZTest Less//Greater、LEqual、GEqual、Equal、NotEqual、Always Blend SrcAlpha OneMinusSrcAlpha//One One、OneMinusDstColor One、DstColor Zero //DstColor SrcColor、One OneMinusSrcAlpha LOD 100不同Blend对比SrcAlpha OneMinusSrcAlphaOne One颜色相加OneMinusDstColor OneDstColor Zero颜色相乘DstColor SrcColor二倍颜色相乘Pass TagsTags{LightModeForwardBase //Always、ForwardAdd、Deferred、ShadowCaster、…… RequireOptionsSoftVegetation PassFlagsOnlyDirectional }GrabPassGrabPass { _BackgroundTexture } //把当前屏幕输出到_BackgroundTextureProperties和Pass变量对应Color,Vectorfloat4,half4,fixed4Range,Float,Intfloat, half, fixed2Dsampler2D顶点着色器和片元着色器着色器分为两个阶段顶点着色器Vertex shader和片元着色器Fragment shader。通过#pragma vertex xxx#pragma fragment yyy指定顶点着色器函数为xxx片元着色器函数为yyy。着色器脚本里还会定义两个结构体顶点着色器的输入结构体一般叫appdata、顶点着色器传递到片元着色器的结构体一般叫v2f。顶点着色器的输出类型和片元着色器的输入类型虽然一样但中间发生了从逐顶点到逐像素的插值frag的输入结构体数量等于像素数量远大于vert的输出结构体数量等于顶点数量。语义就是着色器脚本里全大写的量POSITION, TEXCOORD0等对应cpu或gpu里的寄存器。在顶点着色器的输入结构体的字段后面加:POSITION意为从cpu的这个寄存器取数据取出的是顶点在模型本地的坐标。其他语义类似。在顶点到片元结构体的字段后面加:XXX意为写入到gpu的这个寄存器。对TEXCOORD0的理解TEXCOORD里面存的是和模型顶点数量相同的float2。appdata里的:TEXCOORD0是从模型顶点数据取数据。而在v2f里的:TEXCOORD0的意思是在v2f结构体vertex-to-fragment中TEXCOORD0的含义与appdata中不同它不是模型数据而是插值寄存器用于在顶点着色器和片元着色器之间传递数据。struct v2f { float2 uv : TEXCOORD0; // ← 插值寄存器用于传递数据 };TEXCOORD0在这里是一个语义标签告诉GPU请把这段数据在光栅化时进行插值然后传递给片元着色器。在v2f结构中TEXCOORD0 是插值寄存器语义用于顶点→片元的数据传递GPU会在光栅化时自动插值可以传递任意数据不限于UV坐标数字编号只是标识符没有特殊含义系统值System value命名为SV_XXX的语义叫系统值是gpu拿去渲染用的数据。常见的有SV_POSITION是顶点在裁剪空间的坐标SV_TARGET是顶点的颜色值。顶点位置空间转换和向量比如法线空间转换不能用一个转换矩阵吗我们这样想模型空间比世界空间平移了(3,0,0)旋转、缩放都不变化那么一个向量它在这两个坐标系的方向是一样的但是一个点在两个坐标系的位置不一样。所以顶点位置空间转换矩阵是4x4向量空间转换矩阵是3x3。向量的空间转换相当于两个顶点空间转换后相减。编译预处理指令#pragma#pragma的作用很多无法用一句话概括。它的具体作用取决于后面的第一个参数。如上面指定顶点和片元着色器。对#pragma multi_compile的理解参考文章【unity shader变体之#pragma multi_compile 和 #pragma shader_feature - CSDN App】https://blog.csdn.net/qq_17347313/article/details/106872268?sharetypeblogdetailshareId106872268sharereferAPPsharesourceJesuisharefromlink和#define类似都是条件编译而#pragma multi_compile只是声明一组“候选”宏后面的宏也不一定启用需要shader.EnableKeyWord()或material.EnabledKeyWord()才相当于#define了这个宏。条件编译时也是用#if defined()。相当于用非编译预处理指令定义宏。LOD官方介绍ShaderLab为子着色器指定 LOD 值 - Unity 手册首先着色器LOD和模型LOD是两个概念。通过设置shader.maximumLOD在一列subshader里使用第一个遇到的不大于maximumLOD的subshader如果想使用尽量高品质的着色器就把LOD降序排列。它就是一个着色器选择器。HLSL的语法和APICBUFFER_START()和CBUFFER_END这是hlsl语言用的用来把Properties里不是贴图对所有顶点一样也就是uniform的参数搬运过来在程序里使用。Shader 自定义着色器 { Properties { _AAA(哈哈哈,Float)0 } SubShader { Pass { Name 通道1 HLSLPROGRAM CBUFFER_START(UnityPerMaterial) float _AAA; ENDHLSL } } Fallback 备用着色器 }VertexPositionInputs vertexInput GetVertexPositionInputs(input.positionOS.xyz);VertexPositionInputs是一个结构体里面包含顶点在各个空间下的坐标。GetVertexPositionInputs(float3 positionOS)用于从模型空间得到各个空间的坐标。内置代码按常用程度。变量类宏定义一般以_开头常量类宏定义全大写。UnityObjectToClipPos(float4 v)tex2D(贴图uv)uv范围是0-1对应贴图从左到右从下到上。当uv超过(0,1)时根据贴图的Wrap Mode决定是就采样0、1还是循环采样。那么下面这一段fixed4 coltex2D(_MainTex,i.vertex.xy/100);就把此材质的物体变成了一个“窗口”贴图以一定缩放平铺在屏幕里在模型显示的区域能看见贴图。float3 UnityObjectToWorldNormal(float3 norm)_WorldSpaceLightPos0从物体表面指向光源的方向。类型为float4分量w为0表示平行光1表示点光源。_LightColor0主光源颜色。需要包含Lighting.cginc_WorldSpaceCameraPos相机世界位置。UNITY_LIGHTMODEL_AMBIENT类型float4表示环境光的颜色。unity_ObjectToWorld模型转世界空间的矩阵。reflect(i,n)计算反射方向。float3 WorldSpaceViewDir(float4 v)float3 ObjSpaceViewDir(float4 v)float3 WorldSpaceLightDir(float4 v)flaot3 ObjSpaceLightDir(float4 v)模型空间顶点指向光源的方向。对平行光所有顶点相同。float3 UnityObjectToWorldDir(in float3 dir)float3 UnityWorldToObjectDir(float3 dir)UnityObjectToWorldNormal和UnityObjectToWorldDir的内部有什么区别主要在于如何处理非均匀缩放。特性UnityObjectToWorldNormalUnityObjectToWorldDir用途专门用于法线向量用于一般方向向量切线、视线、光源方向等缩放处理使用逆转置矩阵处理非均匀缩放使用普通旋转矩阵忽略缩放或假设均匀缩放数学原理乘以模型矩阵的逆转置乘以模型矩阵的旋转部分归一化自动重新归一化通常不重新归一化除非输入已归一化典型用途法线贴图、光照计算切线方向、向量运算_MainTex_TexelSize贴图像素尺寸。具体来说这个变量是一个四元数Vector4包含以下四个分量值x1 / 纹理宽度每个像素在UV空间的宽度y1 / 纹理高度每个像素在UV空间的高度z纹理宽度像素数量w纹理高度像素数量假设你的纹理分辨率是512×512像素那么_MainTex_TexelSize的值就是Vector4(1/512, 1/512, 512, 512)采样一个像素的上下左右像素half2 destUv half2(_MainTex_TexelSize.x, _MainTex_TexelSize.y); half spriteLeft tex2D(_MainTex, i.uv half2(destUv.x, 0)).a; half spriteRight tex2D(_MainTex, i.uv - half2(destUv.x, 0)).a; half spriteBottom tex2D(_MainTex, i.uv half2(0, destUv.y)).a; half spriteTop tex2D(_MainTex, i.uv - half2(0, destUv.y)).a;TRANSFORM_TEX(uv,tex)TRANSFORM_TEX(v.uv,_MainTex)应用贴图的拉伸、偏移。相当于v.uv.xy*_MainTex##_ST.xy_MainTex##_ST.zw_Time_Time是一个float4类型的变量包含四个分量x分量t/20时间除以20y分量t原始时间z分量t×2时间乘以2w分量t×3时间乘以3其中t是从当前场景加载开始所经过的时间以秒为单位着色器应该怎么学写着色器有几个特点没有打印功能难调试没有代码提示大量用到UnityCG.cginc、AutoLight.cginc的宏定义着色器代码调试直接输出查看return fixed4(result,result,result,result);实战逐顶点Phong这里没有加单独的漫反射颜色不想让参数太多。Shader 逐顶点Phong { Properties { _Color (Color, Color) (1,1,1,1) _SpeColor(高光颜色,Color)(1,1,1,1) _Gloss(高光范围,Range(0.1,10))5 _MainTex(MainTex,2D)white{} } SubShader { Tags { RenderTypeOpaque } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include UnityCG.cginc #include Lighting.cginc struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv:TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed3 spec:TEXCOORD0; fixed3 albedo:TEXCOORD2; float2 uv:TEXCOORD1; }; fixed4 _Color; fixed4 _SpeColor; float _Gloss; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); float3 worldNormalnormalize(UnityObjectToWorldNormal(v.normal)); float3 lightPosnormalize(_WorldSpaceLightPos0.xyz); fixed3 diffuse_LightColor0.rgb*_Color*max(0,dot(worldNormal,lightPos)); float3 lightDir-normalize(_WorldSpaceLightPos0.xyz); float3 reflDirreflect(lightDir,worldNormal); float3 worldVermul(unity_ObjectToWorld,v.vertex); float3 viewDirnormalize(_WorldSpaceCameraPos-worldVer); fixed4 spec_LightColor0*_SpeColor*pow(max(0,dot(reflDir,viewDir)),_Gloss); o.albedodiffuseUNITY_LIGHTMODEL_AMBIENT; o.specspec; o.uvv.uv; return o; } fixed4 frag (v2f i) : SV_Target { fixed3 colortex2D(_MainTex,i.uv)* i.albedo i.spec; return fixed4(color,1); } ENDCG } } }透明度随时间变化注意要支持透明必须写Tags{QueueTransparent}Blend SrcAlpha OneMinusSrcAlphaShader 透明度随时间变换 { Properties { _MainTex (Texture, 2D) white {} _RandomSeed(_MaxYUV, Range(0, 10000)) 0.0 } SubShader { Tags{ QueueTransparent } LOD 100 Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; UNITY_DEFINE_INSTANCED_PROP(float, _RandomSeed) v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { half randomSeed UNITY_ACCESS_INSTANCED_PROP(Props, _RandomSeed); // 获取随机种子值 fixed4 col tex2D(_MainTex, i.uv); col.w(_Time.y randomSeed) % 1; return col; } ENDCG } } }裁剪空间坐标z的含义用这个代码测试fixed4 frag (v2f i) : SV_Target { fixed coli.vertex.z; return col; }看起来相机处是1远离相机快速变成0.描边参考Quick Outline | Particles/Effects | Unity Asset Store它是由2个材质Mask和Fill组成Mask的队列值小于Fill比Fill先渲染。Fill负责把物体渲染成纯色且比真实形状大一些通过output.position UnityViewToClipPos(viewPosition viewNormal * -viewPosition.z * _OutlineWidth / 1000.0);把顶点向外移一些。而Mask先通过模板测试使用Stencil { Ref 1 Pass Replace }把模板缓冲区写成1然后Fill的模板设置Stencil { Ref 1 Comp NotEqual }凡是Mask渲染过的像素模板缓冲区是1Fill通不过测试不渲染显示之前的像素。这需要Mask在Fill之前渲染如果把Mask的队列改成大于3110它就在Fill后面渲染就会失效物体变成纯色。我没有搞懂的是Mask里没有顶点着色器没有输出到SV_Position怎么知道Mask覆盖的区域的漫反射逐顶点和逐像素的对比逐顶点v2f里记录颜色vert里计算漫反射颜色frag里乘颜色struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; fixed3 color:COLOR; }; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); fixed3 worldNormalnormalize(UnityObjectToWorldNormal(v.normal)); fixed3 worldLightnormalize(_WorldSpaceLightPos0.xyz); fixed3 diffuse_LightColor0.rgb*saturate(dot(worldNormal,worldLight)); o.colordiffuse; return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); col.rgbcol.rgb*i.color; return col; }逐像素v2f里记录法线vert里计算法线frag里计算漫反射颜色应用struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 worldNormal:TEXCOORD1; }; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); o.worldNormalUnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); fixed3 worldNormalnormalize(i.worldNormal); fixed3 worldLightnormalize(_WorldSpaceLightPos0.xyz); fixed3 diffuse_LightColor0.rgb*saturate(dot(worldNormal,worldLight)); col.rgb*diffuse; return col; }UnityURPToonLitShaderExample着色器有时候会显示在Failed to compile里重新导入后会不再显示Failed to compileUnityURPToonLitShaderExample着色器应用后关闭项目再打开妮露和甘雨的着色出现黑色把人物删掉重新导入应用Shader人物显示正常关闭项目重新打开后又出问题。妮露的头发的渲染由两个材料完成“前蝴蝶结”渲染大部分颜色“后发”渲染头发上的光泽大部分是黑的可以看到出问题的时候头发光泽还在推测原因是把“后发”这个材料覆盖在了“前蝴蝶结”上。解决方法后发材料勾选Alpha Clipping。这个选项的意思是把贴图里不透明度alpha值不大于Cutoff的部分设置为透明让其他材料渲染。关于下面的Cutoff为0时预览材质球跟之前一样大部分是黑的稍微调大一点黑色消失只剩下光泽再调大一点光泽也消失。但是调这个对场景里的效果没影响。但是甘雨的后背材料“肌”勾选Alpha Clipping没用。给衣服的材料“裙摆”勾选Alpha Clipping有用同时发现“裙摆”预览材质球的一些部分由黑色变透明所以是“裙摆”该设置透明的部分没有透明渲染成黑色盖在了后背的材料“肌”上。总之所有预览材质球里有这种黑色部分的材料都应该勾选Alpha Clipping。如果没有勾不勾选就没有区别。从顶点着色器到片元着色器的结构体里的一个变量没有写语义系统会自动加上这一段给着色器加阴影效果解决方法在Shader最后加Fallback DiffuseGPU用自定义的着色器渲染完会调用Diffuse着色器Diffuse着色器里面有LightModeShadowCaster的Pass用于投射阴影。投射阴影需要在shader脚本里定义LightModeShadowCaster的pass并在里面写相应代码。使用了Fallback Diffuse后如果自己写的脚本没有LightModeShadowCaster的pass就会使用Diffuse的。背面剔除背面剔除后从模型内部看不到模型。URP内置的Lit和Unlit着色器的背面剔除叫Render Face。自己写代码是声明一个[Enum(UnityEngine.Rendering.CullMode)]类型的变量再在Pass开头写Cull[变量名]某些人的衣服颜色不对着色器是SimpleURPToonLitExample(With Outline)和URP/Lit会有此问题。URP/Unlit没问题。更奇怪的是改变BlendShapes衣服会变色改成别的着色器如URP/Unlit好了。所以认为是着色器不完善细节无法深究。Instancing: Property unity_RenderingLayer shares the same constant buffer offset with unity_LODFade. Ignoring.UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool)移动相机时场景里的树明暗突变可能是这个报错的结果。后来我想起我导入了URP的例子删除例子场景里的东西变成淡蓝色重新渲染了。然后问题消失了。不过具体机制还是不懂。场景里没有阴影时可以检查哪些地方Project settings-Graphics-Scriptable render pipeline settings点进URP Asset的检查器在Lighting部分检查Cast Shadows在场景里的光源检查器的Shadows部分检查Shadow Type。物体的Mesh renderer或Skinned mesh renderer的Lighting部分的Cast shadows总之检查Asset和Component的设置。试图写接收阴影的着色器失败按照Unity中Shader阴影的接收_shader 阴影-CSDN博客写的没有看到其他物体的阴影在人物上造成明暗差异的效果。v2f中定义了SHADOW_COORDS()而非UNITY_SHADOW_COORDS()因为查阅AutoLight.cginc已经改成这个名字了。Shader 学习/贴图加漫反射//名称 { Properties { _MainTex (贴图, 2D) white {} [Enum(UnityEngine.Rendering.CullMode)]_CullMode(剔除模式,float)2 } SubShader { Tags { RenderTypeOpaque } LOD 100 Pass//一次渲染过程 { Cull [_CullMode]//关闭背面剔除Cull Back背面剔除 Tags{LightModeForwardBase} CGPROGRAM #pragma vertex vert//顶点着色器声明类型 名字 #pragma fragment frag//片元着色器声明类型 名字 #pragma multi_compile DIRECTIONAL POINT SHADOWS_SCREEN #include Lighting.cginc #include UnityCG.cginc #include AutoLight.cginc struct appdata//从CPU拿的数据结构名字可自定义 { float4 vertex : POSITION;//顶点在模型空间下的坐标vertex是变量名可自定义POSITION特定语义词 float2 uv : TEXCOORD0;//第一套uv float3 normal : NORMAL; }; struct v2f//从顶点着色器传递到片元着色器的数据结构名字可自定义 { float2 uv : TEXCOORD0;//储存器 float4 vertex : SV_POSITION; float3 normalInWorld:NORMAL; float4 worldPos :TEXCOORD2; SHADOW_COORDS(3) }; sampler2D _MainTex;//把Properties里的同名变量传进SubShader float4 _MainTex_ST;//贴图拉伸和偏移 v2f vert (appdata v)//顶点着色器 { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex);//应用贴图拉伸和偏移 o.normalInWorldmul((float3x3)unity_ObjectToWorld,v.normal); TRANSFER_SHADOW(o) o.worldPos mul(unity_ObjectToWorld,v.vertex); return o; } fixed4 frag (v2f i) : SV_Target//片元着色器。冒号后面是渲染目标 { //贴图采样 fixed4 col tex2D(_MainTex, i.uv);//fixed精度8位half精度16位 //下面是计算漫反射 fixed3 worldNormalnormalize(i.normalInWorld); //获得直射光的方向 fixed3 worldLightDirnormalize(_WorldSpaceLightPos0.xyz); //漫反射 fixed3 diffuseColor_LightColor0.rgb*col.rgb*(dot(worldNormal,worldLightDir)*0.50.5); //增加环境光颜色 fixed3 colorcol.rgb; color*UNITY_LIGHTMODEL_AMBIENT.xyz; colordiffuseColor; //接收阴影 // float attenuation1; // attenuation SHADOW_ATTENUATION(i); UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos); color*attenuation; return fixed4(color,1); } ENDCG }又尝试把UNITY_LIGHT_ATTENUATION输出的值返回UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos); color*attenuation; return fixed4(attenuation,attenuation,attenuation,1);人物显示为全白色也就是全1开头加入#pragma multi_compile_fwdbase则TRANSFER_SHADOW(o)的位置报错让SimpleURPToonLitOutlineExample着色器接收阴影接收阴影的原理使用一个世界坐标转阴影贴图坐标的函数HLSL里是TransformWorldToShadowCoord()得到模型顶点的阴影坐标把阴影坐标输入一个计算阴影衰减的函数HLSL里是MainLightRealtimeShadow(float4 shadowCoord)得到衰减值就是一个浮点数应用阴影衰减通常是把输出的颜色乘上衰减。SimpleURPToonLitOutlineExample里的修改方法1.开启关键字法1.选中要接收阴影的材质在右上角三个点开启Debug2.在Valid Keywords下加一个_MAIN_LIGHT_SHADOWS注意加关键字的时候可能在下面的Invalid Keywords加了一个元素不用管把关键字粘贴进去如果正确就会进入Valid Keywords里面。效果2.修改代码法1.在SimpleURPToonLitOutlineExample_Shared.hlsl的InitializeLightingData()函数里加上lightingData.shadowCoordTransformWorldToShadowCoord(input.positionWSAndFogFactor.xyz);加上之后这个函数变成ToonLightingData InitializeLightingData(Varyings input) { ToonLightingData lightingData; lightingData.positionWS input.positionWSAndFogFactor.xyz; lightingData.viewDirectionWS SafeNormalize(GetCameraPositionWS() - lightingData.positionWS); lightingData.normalWS normalize(input.normalWS); //interpolated normal is NOT unit vector, we need to normalize it lightingData.shadowCoordTransformWorldToShadowCoord(input.positionWSAndFogFactor.xyz); return lightingData; }2.在ShadeAllLights()函数里的GetMainLight输入lightingData.shadowCoordLight mainLight GetMainLight(lightingData.shadowCoord);相当于把#ifdef _MAIN_LIGHT_SHADOWS #endif里的代码拿出来了。关键函数InitializeLightingData()ShadeAllLights()ShadeSingleLight()C#调用后处理和设置材质如果Graphics.Blit(buffer, buffer2, material, 0);发现某些效果没有生效可能是某些properties没有按默认值需要类似material.SetFloat(_BlurSpread,blurSpread);设置相关属性。float4 depthNormal tex2D(_CameraDepthNormalsTexture,i.uv);zw是深度信息吗深度不就一个数字吗为什么需要2个分量unity为了精度把一个深度数字拆成2个float4存。法线是如何存的depthNormal.xy是单位向量的xy转换到0-1得到法线需要先转换到(-1,1)再根据长度1算出z。