简约水面
场景准备:
水底和水面的示例物体
天空球
和天空球一样的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 100Pass{Name "Water"//Blend SrcAlpha OneMinusSrcAlphaHLSLPROGRAM// 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_ENDTEXTURE2D (_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:扭曲状态下的深度。 |片段深度|大一点,说明该像素看不到该片段。该值<0float2 opaqueUV;//true表示深度图采样到的depth加上该片段的高度小于0,即没有在水面之上//用depthWater好点if(depthWater < 0){opaqueUV = screenUV;}else{opaqueUV = distortUV;depthWater = depthDistortWater;}//根据扭曲后的UV采样抓屏纹理,水上的使用未扭曲的screenUV,水下的使用扭曲后的distortUVhalf4 opaqueTex = SAMPLE_TEXTURE2D(_CameraOpaqueTexture, sampler_CameraOpaqueTexture, opaqueUV);//根据深度采样渐变纹理,水上的使用未扭曲的depthWater,水下的使用扭曲后的depthDistortWaterhalf4 waterColor = SAMPLE_TEXTURE2D(_RampTex, sampler_RampTex, half2(depthWater * _Depth, 0));//----------------泡沫----------------//根据深度给予贴图一个范围,贴图颜色如果在范围内,就返回白色,最后乘上_FoamColorhalf 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 + Vhalf3 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;//Fogc.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 FrontHLSLPROGRAM#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_ENDVaryings 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 100Pass{Name "Cartoon"Stencil{Ref [_Ref]Comp AlwaysPass 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_ENDTEXTURE2D (_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 FrontHLSLPROGRAM#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_ENDVaryings 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 OnZTest LEqualColorMask 0Cull[_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}}
}