URP卡通水体渲染

URP卡通水体渲染

很抱歉告诉大家,简书上不再更新我的个人 博 客,所有文章已经搬运到我的新 博 客

效果图

思路

复刻于:https://roystan.net/articles/toon-water.html

总体思路为:使用深度纹理来渐变水体颜色,使用噪声来营造漂浮的泡沫,使用法线纹理计算物体边缘来增加水面物体的泡沫。

需要注意的是,URP管线下,在Shader中的法线深度纹理是需要扩展URP的管线功能才会有的。

  1. 我们通过顶点在屏幕空间中的位置与深度值来计算水深。

                    //--1.处理深度法线纹理
                    // 采样法线深度纹理(xy/w将坐标从正交投影转换为透视投影)
                    half4 sample = SAMPLE_TEXTURE2D(_CameraDepthNormalsTexture, sampler_CameraDepthNormalsTexture, i.screenPosition.xy / i.screenPosition.w);
                    float depth;
                    float3 normal;
                    DecodeDepthNormal(sample, depth, normal);
                    //输出深度,从深度法线纹理中获取到的深度亮度值相比直接从深度纹理不够高,*1000以提升亮度
                    depth = depth * 1000;
                    
                    // 水的表面和它背后的物体之间的距离,以单位表示。
                    float depthDifference = depth - i.vertex.w;
                    return depthDifference;
    
  2. 使用得到的水深深度值,插值最浅水面颜色和最深水面颜色。

  3. 接下来来使用噪声纹理模拟漂浮的泡沫,其中,我们使用一个截止阈值来控制漂浮泡沫的多少。

                    //--3.通过噪声绘制泡沫
                    // 通过噪声纹理获取表面的波浪
                    float surfaceNoiseSample = SAMPLE_TEXTURE2D(_SurfaceNoise, sampler_SurfaceNoise, i.noiseUV).r;
                    return waterColor + surfaceNoiseSample;
    
  4. 利用深度纹理来插值漂浮泡沫的截止阈值,以此来模拟水面较浅的部分的泡沫

  5. 使用一张RG通道的法线纹理和Vector.XY来模拟泡沫的浮动轨迹,来让水面动起来

  6. 使用物体法线纹理与水面的法线纹理进行点击,来得到水体中物体与水平面的交界面。将该值叠加到控制泡沫的阈值来实现水面物体边缘的泡沫效果。

    1. 我们改进Shader默认的透明度混合方案,来优化水面的泡沫透明度。

      float4 alphaBlend(float4 top, float4 bottom)
      {
        float3 color = (top.rgb * top.a) + (bottom.rgb * (1 - top.a));
        float alpha = top.a + bottom.a * (1 - top.a);
      
        return float4(color, alpha);
      }
      
    2. 最后使用smoothstep函数来优化泡沫边缘的锯齿。

      #define SMOOTHSTEP_AA 0.01
      
      …
      
      float surfaceNoise = smoothstep(surfaceNoiseCutoff - SMOOTHSTEP_AA, surfaceNoiseCutoff + SMOOTHSTEP_AA, surfaceNoiseSample);
      

完整代码

Shader "URP/ToonWater"
{
    Properties
    {
        // 当水面最浅时,水的颜色
        _DepthGradientShallow ("Depth Gradient Shallow", Color) = (0.325, 0.807, 0.971, 0.725)
        
        // 当水面最深的时候,水的颜色
        _DepthGradientDeep ("Depth Gradient Deep", Color) = (0.086, 0.407, 1, 0.749)
        
        // 水面下的最大深度,低于该值水面颜色不在发送变换。
        _DepthMaxDistance ("Depth Maximum Distance", Float) = 1
        
        // 渲染物体相交于表面所产生的泡沫颜色。
        _FoamColor ("Foam Color", Color) = (1, 1, 1, 1)
        
        // 用来产生波浪的噪声纹理。
        _SurfaceNoise ("Surface Noise", 2D) = "white" { }
        
        // 用于控制噪音滚动速度
        _SurfaceNoiseScroll ("Surface Noise Scroll Amount", Vector) = (0.03, 0.03, 0, 0)
        
        // 截止阈值,用于控制漂浮泡沫数量
        _SurfaceNoiseCutoff ("Surface Noise Cutoff", Range(0, 1)) = 0.777
        
        // 这个纹理的红色和绿色通道用来抵消噪声纹理,从而在波中产生失真。
        _SurfaceDistortion ("Surface Distortion", 2D) = "white" { }
        
        // 用这个值乘以失真。
        _SurfaceDistortionAmount ("Surface Distortion Amount", Range(0, 1)) = 0.27
        
        // 控制水面以下的距离将有助于渲染泡沫。
        _FoamMaxDistance ("Foam Maximum Distance", Float) = 0.4
        _FoamMinDistance ("Foam Minimum Distance", Float) = 0.04
        
        ///_FoamDistance ("Foam Distance", Float) = 0.4
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
        
        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        
        CBUFFER_START(UnityPerMaterial)
        
        float4 _BaseMap_ST;
        
        float4 _DepthGradientShallow;
        float4 _DepthGradientDeep;
        float4 _FoamColor;
        
        float _DepthMaxDistance;
        float _FoamMaxDistance;
        float _FoamMinDistance;
        
        float _SurfaceNoiseCutoff;
        float _SurfaceDistortionAmount;
        
        float2 _SurfaceNoiseScroll;
        
        /// float _FoamDistance;
        CBUFFER_END
        ENDHLSL
        
        Pass
        {
            Tags { "LightMode" = "UniversalForward" "Queue" = "Transparent" }
            Blend SrcAlpha OneMinusSrcAlpha
            HLSLPROGRAM
            
            #define SMOOTHSTEP_AA 0.01
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            
            struct appdata
            {
                float4 vertex: POSITION;
                float4 uv: TEXCOORD0;
                float3 normal: NORMAL;
            };
            
            struct v2f
            {
                float4 vertex: SV_POSITION;
                float2 noiseUV: TEXCOORD0;
                float2 distortUV: TEXCOORD1;
                float4 screenPosition: TEXCOORD2;
                float3 viewNormal: NORMAL;
            };
            
            
            TEXTURE2D(_SurfaceDistortion);
            SAMPLER(sampler_SurfaceDistortion);
            float4 _SurfaceNoise_ST;
            // 水波噪声纹理
            TEXTURE2D(_SurfaceNoise);
            SAMPLER(sampler_SurfaceNoise);
            float4 _SurfaceDistortion_ST;
            
            // 声明深度法线纹理,注意该名称是指定的
            TEXTURE2D(_CameraDepthNormalsTexture);
            SAMPLER(sampler_CameraDepthNormalsTexture);
            
            v2f vert(appdata v)
            {
                v2f o;
                // 初始化变量
                ZERO_INITIALIZE(v2f, o);
                
                VertexPositionInputs vertexPositionInputs = GetVertexPositionInputs(v.vertex.xyz);
                o.vertex = vertexPositionInputs.positionCS;
                // 计算顶点在着色器中的屏幕空间位置
                o.screenPosition = ComputeScreenPos(o.vertex);
                // 泡沫噪声纹理
                o.noiseUV = TRANSFORM_TEX(v.uv, _SurfaceNoise);
                // 漂浮失真纹理
                o.distortUV = TRANSFORM_TEX(v.uv, _SurfaceDistortion);
                
                // 水面在视角空间的法线纹理
                // o.viewNormal = COMPUTE_VIEW_NORMAL;
                o.viewNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
                return o;
            }
            
            // 解码深度
            inline float DecodeFloatRG(float2 enc)
            {
                float2 kDecodeDot = float2(1.0, 1 / 255.0);
                return dot(enc, kDecodeDot);
            }
            
            // 解码法线
            float3 DecodeNormal(float4 enc)
            {
                float kScale = 1.7777;
                float3 nn = enc.xyz * float3(2 * kScale, 2 * kScale, 0) + float3(-kScale, -kScale, 1);
                float g = 2.0 / dot(nn.xyz, nn.xyz);
                float3 n;
                n.xy = g * nn.xy;
                n.z = g - 1;
                return n;
            }
            
            inline  void  DecodeDepthNormal(float4 enc, out float depth, out float3 normal)
            {
                depth = DecodeFloatRG(enc.zw);
                normal = DecodeNormal(enc);
            }
            
            //混合两种颜色使用相同的算法,我们的着色器使用混合屏幕。这通常被称为“普通混合”,类似于Photoshop等软件如何混合两个图层。
            float4 alphaBlend(float4 top, float4 bottom)
            {
                float3 color = (top.rgb * top.a) + (bottom.rgb * (1 - top.a));
                float alpha = top.a + bottom.a * (1 - top.a);
                
                return float4(color, alpha);
            }
            
            half4 frag(v2f i): SV_Target
            {
                //--1.处理深度法线纹理
                // 采样法线深度纹理(xy/w将坐标从正交投影转换为透视投影)
                half4 sample = SAMPLE_TEXTURE2D(_CameraDepthNormalsTexture, sampler_CameraDepthNormalsTexture, i.screenPosition.xy / i.screenPosition.w);
                float depth;
                float3 normal;
                DecodeDepthNormal(sample, depth, normal);
                //输出深度,从深度法线纹理中获取到的深度亮度值相比直接从深度纹理不够高,*1000以提升亮度
                depth = depth * 1000;
                
                // 水的表面和它背后的物体之间的距离,以单位表示。
                float depthDifference = depth - i.vertex.w;
                //return depthDifference;
                
                //--2.绘制颜色
                // 根据深度插值水的颜色。
                float waterDepthDifference01 = saturate(depthDifference / _DepthMaxDistance);
                float4 waterColor = lerp(_DepthGradientShallow, _DepthGradientDeep, waterDepthDifference01);
                // return waterColor;
                
                //--3.通过噪声绘制泡沫
                // 通过噪声纹理获取表面的波浪
                // float surfaceNoiseSample = SAMPLE_TEXTURE2D(_SurfaceNoise, sampler_SurfaceNoise, i.noiseUV).r;
                // return waterColor + surfaceNoiseSample;
                
                //--6.通过偏移来做泡沫漂浮动画
                /// float2 noiseUV = float2(i.noiseUV.x + _Time.y * _SurfaceNoiseScroll.x, i.noiseUV.y + _Time.y * _SurfaceNoiseScroll.y);
                /// float surfaceNoiseSample = SAMPLE_TEXTURE2D(_SurfaceNoise, sampler_SurfaceNoise, noiseUV).r;
                
                //--7.通过失真纹理加强泡沫左右漂浮效果
                float2 distortSample = (SAMPLE_TEXTURE2D(_SurfaceDistortion, sampler_SurfaceDistortion, i.distortUV).xy * 2 - 1) * _SurfaceDistortionAmount;
                // 通过噪声纹理获取表面的波浪
                float2 noiseUV = float2((i.noiseUV.x + _Time.y * _SurfaceNoiseScroll.x) + distortSample.x, (i.noiseUV.y + _Time.y * _SurfaceNoiseScroll.y) + distortSample.y);
                float surfaceNoiseSample = SAMPLE_TEXTURE2D(_SurfaceNoise, sampler_SurfaceNoise, noiseUV).r;
                
                //--4.通过阈值来控制泡沫数量
                /// 通过阈值来控制漂浮泡沫数量
                ///  float surfaceNoise = surfaceNoiseSample > _SurfaceNoiseCutoff ? 1: 0;
                ///  return waterColor + surfaceNoise;
                
                //--6.通过比对法线来实现物体周围的泡沫
                // 比对所有物体的法线与水平面法线来获取物体的边缘
                float3 normalDot = saturate(dot(normal, i.viewNormal));
                //return float4(normal,1);
                float foamDistance = lerp(_FoamMaxDistance, _FoamMinDistance, normalDot.x);
                float foamDepthDifference01 = saturate(depthDifference / foamDistance);
                
                //--5.通过深度阈值来控制边缘泡沫
                // 通过深度来控制泡沫数量,来绘制边缘泡沫
                ///float foamDepthDifference01 = saturate(depthDifference / _FoamDistance);
                float surfaceNoiseCutoff = foamDepthDifference01 * _SurfaceNoiseCutoff;
                // 通过阈值来控制漂浮泡沫数量
                ///float surfaceNoise = surfaceNoiseSample > surfaceNoiseCutoff ? 1: 0;
                ///return waterColor + surfaceNoise;
                
                //--8.优化抗锯齿
                float surfaceNoise = smoothstep(surfaceNoiseCutoff - SMOOTHSTEP_AA, surfaceNoiseCutoff + SMOOTHSTEP_AA, surfaceNoiseSample);
                
                //--7.使用自定义混合方式改进泡沫颜色
                float4 surfaceNoiseColor = _FoamColor;
                surfaceNoiseColor.a *= surfaceNoise;
                return alphaBlend(surfaceNoiseColor, waterColor);
            }
            ENDHLSL
            
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容

  • 通过本篇教程你将学到如何做风格化水体的渲染,包含的知识点有如何使用天空立方体贴图作反射,如何巧用噪声贴图作纹理扰动...
    程序猿TODO阅读 900评论 0 1
  • 深度法线纹理 前言 在Built-in管线下我们对深度纹理与深度法线纹理获取方式是直接通过设置摄像机和Shader...
    BacteriumFox阅读 5,480评论 0 2
  • 本文参考自教程,加上自己的一点心得体会。 首先,根据水面和场景的深度差异划分为浅水和深水,浅水和深水各取两个极值,...
    异次元的归来阅读 619评论 0 0
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,454评论 16 22
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,534评论 0 11