URP案例(较重要)
简约水面
场景准备:
水底和水面的示例物体
天空球
和天空球一样的Cubemap
组成部分
深度颜色
水下扭曲
泡沫
高光
反射
焦散
代码部分
git hub地址:
有注释,就不写了详细过程了
C#
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace URP { public class WaterColor : MonoBehaviour { public Gradient Ramp; public Texture2D RampTexture; void OnValidate() { RampTexture = new Texture2D(256, 1); RampTexture.wrapMode = TextureWrapMode.Clamp; RampTexture.filterMode = FilterMode.Bilinear; int n = RampTexture.width; Color[] colors = new Color[n]; for (int i = 0; i < n; i++) colors[i] = Ramp.Evaluate((float)i / n); RampTexture.SetPixels(colors); RampTexture.Apply(); Material mtl = GetComponent<MeshRenderer>().sharedMaterial; mtl.SetTexture("_RampTex", RampTexture); } } }
Shader
Shader "MyURP/Water" { Properties { [Header(Main)] _Depth("Depth", Range(0,0.1)) = 0 _Speed("Speed", Range(0, 1)) = 1 _Lightness("Lightness", float) = 1 [Header(Foam)] _FoamTex("Foam Tex", 2D) = "white" {} _FoamRange("Foam Range", Range(0, 15)) = 1 _FoamSize("Foam Size", Range(0, 3)) = 1 _FoamColor("Foam Color", color) = (1,1,1,1) [Header(Distort)] _Distort("Distort", Range(0,0.1)) = 0 _NormalTex("Normal Tex", 2D) = "white" {} [Header(Specular)] _SpecularColor("Specular Color", color) = (1,1,1,1) _Specular("Specular", float) = 1 _Smoothness("Smoothness", float) = 1 [Header(Reflection)] _ReflectionCube("Reflection Cube", cube) = "white" {} _ReflectionPow("Reflection Pow", float) = 1 [Header(Caustic)] _CausticTex("CausticTex", 2D) = "white" {} _CausticInstensity("Caustic Instensity", float) = 1 } SubShader { Tags { "Queue"="Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" } LOD 100 Pass { Name "Water" //Blend SrcAlpha OneMinusSrcAlpha HLSLPROGRAM // Required to compile gles 2.0 with standard srp library #pragma prefer_hlslcc gles #pragma exclude_renderers d3d11_9x #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #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.core/ShaderLibrary/UnityInstancing.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float4 uv : TEXCOORD0; float4 normalUV : TEXCOORD1; float fogCoord : TEXCOORD2; float3 positionWS : TEXCOORD3; float3 positionVS : TEXCOORD4; }; CBUFFER_START(UnityPerMaterial) half _Speed; half _Depth; half _Lightness; half4 _FoamColor; half _FoamRange; half _FoamSize; float4 _FoamTex_ST; half _Distort; float4 _NormalTex_ST; half4 _SpecularColor; half _Specular; half _Smoothness; half _ReflectionPow; float4 _CausticTex_ST; half _CausticInstensity; CBUFFER_END TEXTURE2D (_FoamTex);SAMPLER(sampler_FoamTex); TEXTURE2D (_NormalTex);SAMPLER(sampler_NormalTex); TEXTURECUBE (_ReflectionCube);SAMPLER(sampler_ReflectionCube); TEXTURE2D (_CausticTex);SAMPLER(sampler_CausticTex); TEXTURE2D (_RampTex);SAMPLER(sampler_RampTex); TEXTURE2D (_CameraDepthTexture);SAMPLER(sampler_CameraDepthTexture); TEXTURE2D (_CameraOpaqueTexture);SAMPLER(sampler_CameraOpaqueTexture); Varyings vert(Attributes v) { Varyings o = (Varyings)0; o.positionWS = TransformObjectToWorld(v.positionOS.xyz); o.positionVS = TransformWorldToView(o.positionWS); o.positionCS = TransformWViewToHClip(o.positionVS); float speed = _Time.y * _Speed; o.uv.xy = o.positionWS.xz * _FoamTex_ST.xy + speed; o.uv.zw = v.uv; o.normalUV.xy = TRANSFORM_TEX(v.uv, _NormalTex) + speed * float2(-1.07, 1.07); o.normalUV.zw = TRANSFORM_TEX(v.uv, _NormalTex) + speed; o.fogCoord = ComputeFogFactor(o.positionCS.z); return o; } half4 frag(Varyings i) : SV_Target { half4 c = 0; //水下的扭曲+深度过度颜色 //使用两个方向的法线贴图,做出波纹起伏的效果 //顶点着色器中之所以是1.07倍,是为了防止重叠时突然有一帧很亮 float3 normalUV01 = SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, i.normalUV.xy).xyz; float3 normalUV02 = SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, i.normalUV.zw).xyz; float3 normalUV = normalUV01 * normalUV02; //屏幕坐标 = 该片段屏幕坐标(0~1) / 屏幕像素(1920*1080) float2 screenUV = i.positionCS.xy / _ScreenParams.xy; //偏移坐标就是在屏幕坐标之上稍微偏一点 float2 distortUV = screenUV + normalUV01.xy * _Distort; half depthTex = SAMPLE_TEXTURE2D(_CameraDepthTexture, sampler_CameraDepthTexture, screenUV).x; half depth = LinearEyeDepth(depthTex, _ZBufferParams); half depthWater = depth + i.positionVS.z; depthTex = SAMPLE_TEXTURE2D(_CameraDepthTexture, sampler_CameraDepthTexture, distortUV).x; depth = LinearEyeDepth(depthTex, _ZBufferParams); half depthDistortWater = depth + i.positionVS.z; //depth:深度图上该点到相机近裁剪面的距离。大于0 //i.positionVS.z:VS空间下的深度。小于0 //depthWater:未扭曲状态下的深度。 |depth|大一点,该位置更深,在水面之下。该值>0 //depthDistortWater:扭曲状态下的深度。 |片段深度|大一点,说明该像素看不到该片段。该值<0 float2 opaqueUV; //true表示深度图采样到的depth加上该片段的高度小于0,即没有在水面之上 //用depthWater好点 if(depthWater < 0) { opaqueUV = screenUV; } else { opaqueUV = distortUV; depthWater = depthDistortWater; } //根据扭曲后的UV采样抓屏纹理,水上的使用未扭曲的screenUV,水下的使用扭曲后的distortUV half4 opaqueTex = SAMPLE_TEXTURE2D(_CameraOpaqueTexture, sampler_CameraOpaqueTexture, opaqueUV); //根据深度采样渐变纹理,水上的使用未扭曲的depthWater,水下的使用扭曲后的depthDistortWater half4 waterColor = SAMPLE_TEXTURE2D(_RampTex, sampler_RampTex, half2(depthWater * _Depth, 0)); //----------------泡沫---------------- //根据深度给予贴图一个范围,贴图颜色如果在范围内,就返回白色,最后乘上_FoamColor half foamTex = SAMPLE_TEXTURE2D(_FoamTex, sampler_FoamTex, i.uv.xy).x; foamTex = pow(abs(foamTex), _FoamSize); half foamRange = depthWater * _FoamRange; half foam = step(foamRange, foamTex); half4 foamColor = _FoamColor * foam; //----------------高光---------------- //Specular = SpecularColor * Ks * pow(NdotH, Smoothness) half3 N = lerp(half3(0,1,0), normalUV, 0.8); //H = L + V half3 V = normalize(_WorldSpaceCameraPos.xyz - i.positionWS); half3 H = normalize(GetMainLight().direction + V); half NdotH = saturate(dot(N, H)); half4 specular = _SpecularColor * _Specular * pow(NdotH, _Smoothness); //----------------反射---------------- //就是根据反射,出射角获取颜色 half3 reflectUV = reflect(-V, N); half4 reflectTex = SAMPLE_TEXTURECUBE(_ReflectionCube, sampler_ReflectionCube, reflectUV); half fresnel = 1 - saturate(dot(half3(0,1,0), V)); half4 reflect = reflectTex * pow(fresnel, _ReflectionPow); //----------------焦散---------------- //原理详见深度贴花, float4 depthVS = 1; depthVS.xy = i.positionVS.xy * depth / -i.positionVS.z; depthVS.z = depth; float3 depthWS = mul(unity_CameraToWorld, depthVS).xyz; //和水面法线类似,但水面是tex01 * tex02,而这里为了让焦散动起来,使用了min(tex01, tex02) float2 causticUV01 = depthWS.xz * _CausticTex_ST.xy + depthWS.y * 0.05 + _Time.y * _Speed; float2 causticUV02 = depthWS.xz * _CausticTex_ST.xy + depthWS.y * 0.08 + _Time.y * _Speed * float2(-1.07, 1.07); half4 causticTex01 = SAMPLE_TEXTURE2D(_CausticTex, sampler_CausticTex, causticUV01); half4 causticTex02 = SAMPLE_TEXTURE2D(_CausticTex, sampler_CausticTex, causticUV02); half4 caustic = min(causticTex01, causticTex02) * _CausticInstensity; c += waterColor * _Lightness; c += specular * reflect; c += foamColor; c *= opaqueTex + caustic * _Lightness; //Fog c.rgb = MixFog(c.rgb, i.fogCoord); c.a = 0.5; return c; } ENDHLSL } } }
卡通渲染
准备模型和贴图
(这是已经做了两个阶段的截图...)
Shader编写部分
1.描边Pass
做法很简单,就是将顶点往法线方向移动
描边Pass渲染时,会去渲染主Pass渲染过的地方,这样就造成了重复渲染
因此使用模板测试
主Pass部分:默认写入,并保持通过
描边Pass部分:不相等时才通过
问题一:近看很粗,远看很细
解决方法:将粗细乘上使用相机到顶点的距离
问题二:偏移方向不对
问题产生原因:可以从图中顶点的三个法线方向看出来,是有棱有角的,因此在偏移时,就是往比较硬的方向偏移的
解决办法一:将模型的法线属性变成“计算”并把光滑度拉满,这种做法就是在合并法线
但会产生新的问题,就是法线被修改了,我们后续无法再使用正确的法线方向了,因此光照部分会出问题
解决办法二:在模型制作时,将平均过的法线值储存到切线中,但我不会,幸好有代码侧的
解决办法三:使用C#代码,将平均过的法线值储存到切线中,直接选中模型跑Editor就行了
模型平均法线写入切线数据
public class PlugTangentTools { [MenuItem("Tools/模型平均法线写入切线数据")] public static void WirteAverageNormalToTangentToos() { MeshFilter[] meshFilters = Selection.activeGameObject.GetComponentsInChildren<MeshFilter>(); foreach (var meshFilter in meshFilters) { Mesh mesh = meshFilter.sharedMesh; WirteAverageNormalToTangent(mesh); } SkinnedMeshRenderer[] skinMeshRenders = Selection.activeGameObject.GetComponentsInChildren<SkinnedMeshRenderer>(); foreach (var skinMeshRender in skinMeshRenders) { Mesh mesh = skinMeshRender.sharedMesh; WirteAverageNormalToTangent(mesh); } } private static void WirteAverageNormalToTangent(Mesh mesh) { var averageNormalHash = new Dictionary<Vector3, Vector3>(); for (var j = 0; j < mesh.vertexCount; j++) { if (!averageNormalHash.ContainsKey(mesh.vertices[j])) { averageNormalHash.Add(mesh.vertices[j], mesh.normals[j]); } else { averageNormalHash[mesh.vertices[j]] = (averageNormalHash[mesh.vertices[j]] + mesh.normals[j]).normalized; } } var averageNormals = new Vector3[mesh.vertexCount]; for (var j = 0; j < mesh.vertexCount; j++) { averageNormals[j] = averageNormalHash[mesh.vertices[j]]; } var tangents = new Vector4[mesh.vertexCount]; for (var j = 0; j < mesh.vertexCount; j++) { tangents[j] = new Vector4(averageNormals[j].x, averageNormals[j].y, averageNormals[j].z, 0); } mesh.tangents = tangents; } }
问题四:模板测试使用唯一值的话,会让两个物体重叠部分产生不了描边
解决办法:使用C#脚本,在Start中定义_Ref
优化部分一:想自定义描边粗细和描边颜色(原神就有用到这个)
使用顶点色.rgb存储描边颜色,使用顶点色.a存储描边粗细
Shader代码部分,仅描边Pass
Outline
Pass { Name "Outline" Stencil { Ref [_Ref] Comp NotEqual } Tags { "LightMode"="Outline" } Cull Front HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float3 tangentOS : TANGENT; float4 color : COLOR; }; struct Varyings { float4 positionCS : SV_POSITION; float fogCoord : TEXCOORD0; float4 color : TEXCOORD1; }; CBUFFER_START(UnityPerMaterial) half _Outline; CBUFFER_END Varyings vert(Attributes v) { Varyings o = (Varyings)0; float3 positionWS = TransformObjectToWorld(v.positionOS); float distance = length(_WorldSpaceCameraPos - positionWS); float3 positionOS = v.positionOS.xyz; //0.使用C#脚本,将平均后的法线值存到切线中 //1.根据_Outline * 0.01可以自定义描边粗细 //2.distance可以让描边在远近距离粗细一样 //3.顶点色的Alpha值可以用来存储想要的粗细 positionOS += normalize(v.tangentOS) * _Outline * 0.01 * distance * v.color.a; o.color = v.color; o.positionCS = TransformObjectToHClip(positionOS); o.fogCoord = ComputeFogFactor(o.positionCS.z); return o; } half4 frag(Varyings i) : SV_Target { //顶点色的RGB值可以用来存储描边颜色 return MixFog(i.color, i.fogCoord).x; } ENDHLSL }
优化二:
由于模板测试的存在,模型的渲染顺序不同,可能会导致描边显示的不同:
有的时候是这样:描边Pass渲染时,底下的Body模型还没写入模板值,这样就可以渲染出来了
有的时候是这样:描边Pass渲染时,底下Body模型已经渲染了,且写入了模板值,这样就不会渲染了
解决办法:我们使用URP_Renderer自带的,规定Pass渲染顺序的功能
即,最底下的Add Renderer Feature,写上Pass的名字,并规定该Pass在什么时候渲
这样,我们就可以在所有主Pass渲染完之后,再去渲染描边Pass,Outline就是我们决定了渲染顺序的Pass。
可以看这篇文章,说得差不多【01】从零开始的卡通渲染-描边篇 - 技术专栏 - Unity官方开发者社区
阴影部分
使用Step,阶梯函数式阴影
但这样有个问题,_Step 0 时,就是全0
1时,就是全1
2时,一半是0.5,一半是1
3时,就是0.33-0.66-1
...
无法自定义,比如想要0.5-0.75-1就不行了
因此,使用采样渐变纹理,这样就能使用自定义软边,区域等
顺便把阴影接收也做了
高光部分
使用半角向量,并把值约束一下
菲涅尔外发光
和高光一样,把值约束一下
顶点部分
就是简单的一些赋值
Shader
除Shader之外,美术贴图方面更加重要
赛璐璐风格Shader
Shader "MyURP/Cartoon" { Properties { _BaseColor("Base Color",color) = (1,1,1,1) _BaseMap("Base Map", 2D) = "white" {} [Header(Outline)] _Outline("Outline", Range(0,1)) = 1 _Ref("Ref", float) = 0 [Header(Color)] _ShadowRampTex("Shadow Ramp Map", 2D) = "white" {} [Header(Specular)] _Specular("Instensity(x)Range(y)Smooth(z)", vector) = (0,0,0,0) [Header(Fresnel)] _Fresnel("Instensity(x)Range(y)Smooth(z)", vector) = (1,1,0,0) _FresnelColor("Fresnel Color", color) = (1,1,1,1) } SubShader { Tags { "Queue"="Geometry" "RenderType" = "Opaque" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" } LOD 100 Pass { Name "Cartoon" Stencil { Ref [_Ref] Comp Always Pass Replace } Tags { "LightMode"="UniversalForward" } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile _ _SHADOWS_SOFT #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #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.core/ShaderLibrary/UnityInstancing.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float fogCoord : TEXCOORD1; float3 normalWS : TEXCOORD2; float3 viewWS : TEXCOORD3; float3 positionWS : TEXCOORD4; }; CBUFFER_START(UnityPerMaterial) half4 _BaseColor; float4 _BaseMap_ST; float4 _ShadowRampTex_ST; half4 _Specular; half4 _Fresnel; half4 _FresnelColor; CBUFFER_END TEXTURE2D (_BaseMap);SAMPLER(sampler_BaseMap); TEXTURE2D (_ShadowRampTex);SAMPLER(sampler_ShadowRampTex); Varyings vert(Attributes v) { Varyings o = (Varyings)0; o.positionWS = TransformObjectToWorld(v.positionOS.xyz); o.viewWS = normalize(_WorldSpaceCameraPos - o.positionWS); o.positionCS = TransformObjectToHClip(v.positionOS.xyz); o.normalWS = TransformObjectToWorldNormal(v.normalOS); o.uv = TRANSFORM_TEX(v.uv, _BaseMap); o.fogCoord = ComputeFogFactor(o.positionCS.z); return o; } half4 frag(Varyings i) : SV_Target { half4 c; half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv); c = baseMap * _BaseColor; //使用Lambert求出片段0~1的明暗 //然后再使用Step做出硬边的明暗 Light mainLight = GetMainLight(TransformWorldToShadowCoord(i.positionWS)); half3 L = mainLight.direction; half3 N = normalize(i.normalWS); half NdotL = dot(N, L) * 0.5 + 0.5; half ramp = SAMPLE_TEXTURE2D(_ShadowRampTex, sampler_ShadowRampTex, half2(1-NdotL, 0)); ramp *= mainLight.shadowAttenuation * 0.5 + 0.5; c = lerp(c * ramp, c, ramp); //高光 half3 V = i.viewWS; half3 H = normalize(L + V); half NdotH = dot(N, H); half specular = _Specular.x * pow(NdotH, _Specular.y); specular = smoothstep(0.5, 0.5 + _Specular.z, specular); c += specular; //外发光 half NdotV = 1 - saturate(dot(N, V)); half fresnal = _Fresnel.x * pow(NdotV, _Fresnel.y); fresnal = smoothstep(0.5, 0.5 + _Fresnel.z, fresnal); c += _FresnelColor * fresnal; c.rgb = MixFog(c.rgb, i.fogCoord); return c; } ENDHLSL } Pass { Name "Outline" Stencil { Ref [_Ref] Comp NotEqual } Tags { "LightMode"="Outline" } Cull Front HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float3 tangentOS : TANGENT; float4 color : COLOR; }; struct Varyings { float4 positionCS : SV_POSITION; float fogCoord : TEXCOORD0; float4 color : TEXCOORD1; }; CBUFFER_START(UnityPerMaterial) half _Outline; CBUFFER_END Varyings vert(Attributes v) { Varyings o = (Varyings)0; float3 positionWS = TransformObjectToWorld(v.positionOS); float distance = length(_WorldSpaceCameraPos - positionWS); float3 positionOS = v.positionOS.xyz; //0.使用C#脚本,将平均后的法线值存到切线中 //1.根据_Outline * 0.01可以自定义描边粗细 //2.distance可以让描边在远近距离粗细一样 //3.顶点色的Alpha值可以用来存储想要的粗细 positionOS += normalize(v.tangentOS) * _Outline * 0.01 * distance; o.color = v.color; o.positionCS = TransformObjectToHClip(positionOS); o.fogCoord = ComputeFogFactor(o.positionCS.z); return o; } half4 frag(Varyings i) : SV_Target { //顶点色的RGB值可以用来存储描边颜色 return MixFog(i.color, i.fogCoord).x; } ENDHLSL } Pass { Name "ShadowCaster" Tags{"LightMode" = "ShadowCaster"} ZWrite On ZTest LEqual ColorMask 0 Cull[_Cull] HLSLPROGRAM #pragma exclude_renderers gles gles3 glcore #pragma target 4.5 // ------------------------------------- // Material Keywords #pragma shader_feature_local_fragment _ALPHATEST_ON #pragma shader_feature_local_fragment _GLOSSINESS_FROM_BASE_ALPHA //-------------------------------------- // GPU Instancing #pragma multi_compile_instancing #pragma multi_compile _ DOTS_INSTANCING_ON // ------------------------------------- // Universal Pipeline keywords // This is used during shadow map generation to differentiate between directional and punctual light shadows, as they use different formulas to apply Normal Bias #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW #pragma vertex ShadowPassVertex #pragma fragment ShadowPassFragment #include "Packages/com.unity.render-pipelines.universal/Shaders/SimpleLitInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl" ENDHLSL } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)