分类目录归档:未分类

Gerstner wave

Shader "Unlit/GerstnerWaveStandard"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        [Header(BaseShading)]
        _BaseMap ("Example Texture", 2D) = "white" { }
        [HDR]_BaseColor ("Base Colour", Color) = (0, 0.66, 0.73, 1)
        _WaterFogColor ("Water Fog Colour", Color) = (0, 0.66, 0.73, 1)
        _FogDensity ("Fog Density", range(0, 1)) = 0.1
        _NormalMap ("Normal Map", 2D) = "white" { }
        _NormalScale ("Normal Scale", Range(0, 1)) = 0.1
        _Shininess ("High Light Roughness", Range(0, 0.1)) = 0.01
        [Space(20)]
        [Header(Reflection)]
        _Skybox ("Skybox", Cube) = "white" { }
        [Header(Refractive)]
        _AirRefractiveIndex ("Air Refractive Index", Float) = 1.0
        _WaterRefractiveIndex ("Water Refractive Index", Float) = 1.333
        _FresnelPower ("Fresnel Power", Range(0.1, 50)) = 5
        _RefractionStrength ("Refraction Strength", Range(0, 1)) = 0.1
        
        [Space(20)]
        [Header(SSS)]
        _FrontSubsurfaceDistortion ("Front Subsurface Distortion", Range(0, 1)) = 0.5
        _BackSubsurfaceDistortion ("Back Subsurface Distortion", Range(0, 1)) = 0.5
        _FrontSSSIntensity ("Front SSS Intensity", float) = 0.2
        _HeightCorrection ("SSS Height Correction", float) = 6
        
        [Space(20)]
        [Header(Foam)]
        _FoamIntensity ("Foam Intensity", float) = 0.5
        _FoamNoiseTex ("Foam Noise", 2D) = "white" { }
        
        [Space(20)]
        [Header(Caustic)]
        _CausticIntensity ("Caustic Intensity", float) = 0.5
        _CausticTex ("Caustic Texture", 2D) = "white" { }
        _Caustics_Speed ("Caustics Speed,(x,y)&(z,w)", Vector) = (1, 1, -1, -1)
        
        [Space(20)]
        [Header(Waves)]
        _Speed ("Speed", float) = 0.2
        _Frequency ("Frequency", float) = 2
        _WaveA ("Wave A (dir, steepness, wavelength)", Vector) = (1, 0, 0.5, 10)
        _WaveB ("Wave B", Vector) = (0, 1, 0.25, 20)
        _WaveC ("Wave C", Vector) = (1, 1, 0.15, 10)
        _WaveD ("Wave D", Vector) = (0, 1, 0.25, 20)
        _WaveE ("Wave E", Vector) = (1, 1, 0.15, 10)
        _WaveF ("Wave F", Vector) = (0, 1, 0.25, 20)
        _WaveG ("Wave G", Vector) = (1, 1, 0.15, 10)
        _WaveH ("Wave H", Vector) = (0, 1, 0.25, 20)
        _WaveI ("Wave I", Vector) = (1, 1, 0.15, 10)
        _WaveJ ("Wave J", Vector) = (1, 1, 0.15, 10)
        _WaveK ("Wave K", Vector) = (1, 1, 0.15, 10)
        _WaveL ("Wave L", Vector) = (1, 1, 0.15, 10)
        [Space(20)]
        [Header(Tessellation)]
        _TessellationUniform ("Tessellation Uniform", Range(1, 64)) = 1
        _TessellationEdgeLength ("Tessellation Edge Length", Range(5, 100)) = 50
        [Toggle(_TESSELLATION_EDGE)]_TESSELLATION_EDGE ("TESSELLATION EDGE", float) = 0



        [Space(20)]
        [Header(Others)]
        _shadowAttenuation("shadowAttenuation",float)=0.2
        _CameraOpaqueTexture("_CameraOpaqueTexture",2D)="white"{}
    }
    SubShader
    {
        Tags { "RenderType" = "Transparent"  "RenderQueue" = "Transparent" }
        
        ZWrite Off
        ZTest On
        CGPROGRAM
        #include "Lighting.cginc"
        #include "unityCg.cginc"
        float4 _BaseMap_ST, _FoamNoiseTex_ST, _CausticTex_ST;
        float4 _BaseColor, _WaterFogColor, _Caustics_Speed;
        float4 _NormalMap_ST;
        float4 _WaveA, _WaveB, _WaveC, _WaveD, _WaveE, _WaveF, _WaveG, _WaveH, _WaveI, _WaveJ, _WaveK, _WaveL;
        float _Speed, _Frequency, _NormalScale, _AirRefractiveIndex, _WaterRefractiveIndex, _FresnelPower;
        float _RefractionStrength, _FogDensity, _Shininess, _FrontSubsurfaceDistortion, _BackSubsurfaceDistortion;
        float _FrontSSSIntensity, _HeightCorrection, _FoamIntensity, _CausticIntensity;
        ENDCG

        Pass
        {
            Name "Example"
            Tags { "LightMode" = "ForwardBase" }
            
            ZWrite off
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
           // #pragma multi_compile_fog

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"

            struct a2v
            {
                float4 positionOS: POSITION;
                float2 uv: TEXCOORD0;
                float3 normal: NORMAL;
                float4 color: COLOR;
            };

            struct v2f
            {
                float4 positionCS: SV_POSITION;
                float2 uv: TEXCOORD0;
                float3 normalWS: TEXCOORD1;
                float3 positionWS: TEXCOORD2;
                float3 tangentWS: TEXCOORD3;
                float4 scrPos: TEXCOORD4;
                float heightOS: TEXCOORD5;
                float fogFactor: TEXCOORD6;
                float4 color: COLOR;
            };

            samplerCUBE _Skybox;
            float4 _Skybox_ST;
            sampler2D _CameraOpaqueTexture;
            float4 _CameraOpaqueTexture_ST;
            sampler2D _FoamNoiseTex;
            float2 _FoamNoiseTex_ST;
            sampler2D _CausticTex;
            float4 _CausticTex_ST;
            float _Speed, _Frequency, _NormalScale, _AirRefractiveIndex, _WaterRefractiveIndex, _FresnelPower;
            float4 _WaveA, _WaveB, _WaveC, _WaveD, _WaveE, _WaveF, _WaveG, _WaveH, _WaveI, _WaveJ, _WaveK, _WaveL;
            float4 _NormalMap_ST;
            float _RefractionStrength, _FogDensity, _Shininess, _FrontSubsurfaceDistortion, _BackSubsurfaceDistortion;
            float4 _BaseColor, _WaterFogColor, _Caustics_Speed;
            float _shadowAttenuation;
            float _FrontSSSIntensity, _HeightCorrection, _FoamIntensity, _CausticIntensity;


            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _NormalMap;

            //-------------GenrateGerstnerWave-------------
            float3 GerstnerWave(
                float4 wave, float3 p, inout float3 tangent, inout float3 binormal
            )
            {
                float steepness = wave.z;
                float wavelength = wave.w;
                float k = 2 * UNITY_PI / wavelength;
                float c = sqrt(9.8 / k);
                float2 d = normalize(wave.xy) * _Frequency;
                float f = k * (dot(d, p.xz) - c * _Time.y * _Speed);
                float a = steepness / k;
        
                tangent += float3(
                    - d.x * d.x * (steepness * sin(f)),
                    d.x * (steepness * cos(f)),
                    - d.x * d.y * (steepness * sin(f))
                );
                binormal += float3(
                    - d.x * d.y * (steepness * sin(f)),
                    d.y * (steepness * cos(f)),
                    - d.y * d.y * (steepness * sin(f))
                );
                return float3(
                    d.x * (a * cos(f)),
                    a * sin(f),
                    d.y * (a * cos(f))
                );
            }
              
            //-----------CalculateFresnel------------------
            float CalculateFresnel(float3 viewDir, float3 normal)
            {
                float R_0 = (_AirRefractiveIndex - _WaterRefractiveIndex) / (_AirRefractiveIndex + _WaterRefractiveIndex);
                R_0 *= R_0;
                return R_0 + (1.0 - R_0) * pow((1.0 - saturate(dot(viewDir, normal))), _FresnelPower);
            }
            
            
            
            
            half3 Highlights(half3 positionWS, half roughness, half3 normalWS, half3 viewDirectionWS)
            {
                //Light mainLight = GetMainLight();
                half roughness2 = roughness * roughness;
                half3 halfDir = normalize(_WorldSpaceLightPos0.xyz + viewDirectionWS);
                half NoH = saturate(dot(normalize(normalWS), halfDir));
                half LoH = saturate(dot(_WorldSpaceLightPos0.xyz, halfDir));
                // GGX Distribution multiplied by combined approximation of Visibility and Fresnel
                half d = NoH * NoH * (roughness2 - 1) + 1.0001;
                half LoH2 = LoH * LoH;
                half specularTerm = roughness2 / ((d * d) * max(0.1, LoH2) * (roughness + 0.5) * 4);
                specularTerm = min(specularTerm, 10);
        
                return specularTerm * _LightColor0.rgb * _shadowAttenuation;
            }
            


            //-------------GetOddNegativeScale----------------
            float GetOddNegativeScale()
            {
                return unity_WorldTransformParams.w;
            }

            //------------CreateTangentToWorld---------------
            float3x3 CreateTangentToWorld(float3 normal, float3 tangent, float flipSign)
            {
            // For odd-negative scale transforms we need to flip the sign
            float sgn = flipSign * GetOddNegativeScale();
            float3 bitangent = cross(normal, tangent) * sgn;

            return float3x3(tangent, bitangent, normal);
            }
            
            //-----------TransformTangentToWorld----------
            float3 TransformTangentToWorld(float3 dirTS, float3x3 tangentToWorld)
            {
                // Note matrix is in row major convention with left multiplication as it is build on the fly
                return mul(dirTS, tangentToWorld);
            }
            float3 GetCameraPositionWS()
            {
                return _WorldSpaceCameraPos;
            }

            //-----------SampleSceneDepth--------------
            sampler2D _CameraDepthTexture;
            sampler2D sampler_CameraDepthTexture;
            float SampleSceneDepth(float2 uv)
            {
                return tex2D(_CameraDepthTexture, UnityStereoTransformScreenSpaceTex(uv)).r;
            }



            float SubsurfaceScattering(float3 viewDir, float3 lightDir, float3 normalDir,
            float frontSubsurfaceDistortion, float backSubsurfaceDistortion, float frontSSSIntensity, float thickness)
            {
            //分别计算正面和反面的次表面散射
            float3 frontLitDir = normalDir * frontSubsurfaceDistortion - lightDir;
            float3 backLitDir = normalDir * backSubsurfaceDistortion + lightDir;
            float frontsss = saturate(dot(viewDir, -frontLitDir));
            float backsss = saturate(dot(viewDir, -backLitDir));
        
            float result = saturate(frontsss * frontSSSIntensity + backsss) * thickness;
            return result;
            }
            /*
            //------------Light------------------
            struct Light
            {
                half3   direction;
                half3   color;
                half    distanceAttenuation;
                half    shadowAttenuation;
            };

            //-----------GetMainLight-------------
            Light GetMainLight()
            {
                Light light;
                light.direction = _WorldSpaceLightPos0.xyz;
                // unity_LightData.z is 1 when not culled by the culling mask, otherwise 0.
                light.distanceAttenuation = unity_LightData.z;
            #if defined(LIGHTMAP_ON) || defined(_MIXED_LIGHTING_SUBTRACTIVE)
                // unity_ProbesOcclusion.x is the mixed light probe occlusion data
                light.distanceAttenuation *= unity_ProbesOcclusion.x;
            #endif
                light.shadowAttenuation = 1.0;
                light.color = _MainLightColor.rgb;

                return light;
            }
            */

            v2f vert (a2v v)
            {
                v2f o;
                float3 tangent = float3(1, 0, 0);
                float3 binormal = float3(0, 0, 1);
                float3 p = v.positionOS;

                p += GerstnerWave(_WaveA, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveB, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveC, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveD, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveE, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveF, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveG, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveH, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveI, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveJ, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveK, v.positionOS.xyz, tangent, binormal);
                p += GerstnerWave(_WaveL, v.positionOS.xyz, tangent, binormal);


                o.heightOS = p.y;
                float3 normal = normalize(cross(binormal, tangent));
                UNITY_INITIALIZE_OUTPUT(v2f, o);
                o.positionCS = UnityObjectToClipPos(p);
                o.normalWS = UnityObjectToWorldNormal(v.normal);
                o.positionWS = mul(unity_ObjectToWorld,v.positionOS);
                o.scrPos = ComputeScreenPos(o.positionCS);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                //UNITY_TRANSFER_FOG(o,o.positionCS);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 normalWS = normalize(i.normalWS);
                //-------------normal----------
                float3x3 TtoW = CreateTangentToWorld(i.normalWS, i.tangentWS, 1);
                fixed4 normalTS = tex2D(_NormalMap,i.uv);
                normalTS.xyz = normalize(UnpackNormal(normalTS));
                normalWS = lerp(normalWS,normalize(TransformTangentToWorld(normalTS,TtoW)),_NormalScale);
                normalWS = normalize(normalWS);

                //------------reflection-----------
                float3 viewDirectionWS = normalize(GetCameraPositionWS() - i.positionWS.xyz);
                float3 reflectDir = reflect(-viewDirectionWS, normalWS);
                float4 reflectCol = texCUBE(_Skybox,reflectDir);
                //------------refraction------------
                float2 scrPos = i.scrPos / i.scrPos.w;
                float depth = SampleSceneDepth(scrPos);
                depth = LinearEyeDepth(depth);
                float surfaceDepth = i.scrPos.w;
                float depthDiffer = depth - surfaceDepth;
                float2 uvOffset = normalWS.xz * _RefractionStrength * saturate(depthDiffer);
                float2 offsetPos = scrPos + uvOffset;
                float offsetPosDepth = SampleSceneDepth(offsetPos);
                offsetPosDepth = LinearEyeDepth(offsetPosDepth);
                offsetPos = scrPos + uvOffset * step(surfaceDepth, offsetPosDepth);
                float4 refractCol = tex2D(_CameraOpaqueTexture,offsetPos);
                float depthFactor = depth / surfaceDepth;
                float3 underPos = (i.positionWS - GetCameraPositionWS()) * depthFactor + GetCameraPositionWS();
                float2 causticSampler = (underPos.xy + underPos.xz + underPos.yz) / 100 * _CausticTex_ST.xy;
                float4 caustic1 = tex2D(_CausticTex, causticSampler + _Caustics_Speed.xy * _Time.y/30);
                float4 caustic2 = tex2D(_CausticTex, causticSampler + _Caustics_Speed.zw * _Time.y/30); 
                float4 caustic = min(caustic1, caustic2);
                
                //------------fog-------------
                float offsetDepthDiffer = surfaceDepth > offsetPosDepth ? depthDiffer: offsetPosDepth - surfaceDepth;
                float fogFactor = saturate(1 - exp(-offsetDepthDiffer * _FogDensity / 10)) * _shadowAttenuation;
                float4 waterCol = lerp(_WaterFogColor, _BaseColor, fogFactor);
                refractCol = lerp(waterCol, waterCol * refractCol, saturate(fogFactor));
                refractCol += caustic * pow((1 - saturate(fogFactor)), 10);

                //------------specular----------
                float3 halfDir = normalize(viewDirectionWS + normalize(_WorldSpaceLightPos0.xyz));
                float3 specular = Highlights(i.positionWS, _Shininess, normalWS, viewDirectionWS);
                specular *= _shadowAttenuation;

                //------------SSS---------------
                float SSSValue = SubsurfaceScattering(viewDirectionWS, _WorldSpaceLightPos0.xyz, normalWS, _FrontSubsurfaceDistortion,
                _BackSubsurfaceDistortion, _FrontSSSIntensity, saturate(i.heightOS - _HeightCorrection));
                SSSValue *= _shadowAttenuation;
                float fresnel = CalculateFresnel(viewDirectionWS, normalWS);
                float4 scatterCol = lerp(refractCol, reflectCol, saturate(fresnel));
                
                float3 shading = scatterCol.rgb + specular + SSSValue * _LightColor0.rgb;

                //------------Foam------------------
                float foamOffset = tex2D(_FoamNoiseTex, i.uv * _FoamNoiseTex_ST.xy + _Time.y).x;
                shading = lerp(shading, float3(0.8, 0.8, 0.8), pow(saturate(_FoamIntensity * foamOffset -depthDiffer) * 2, 3) * saturate(depthDiffer));
                //shading = MixFog(shading.rgb, i.fogFactor);
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                //UNITY_APPLY_FOG(i.fogCoord, col);
                return (shading.rgb,1);
            }
            ENDCG
        }
    }
}

关于一般水体渲染的技术总结

水体渲染一半的技术节点在于波纹、水体边缘与折射。

波纹笔者现已知的方法有 凹凸纹理贴图、正弦波、FFT这三种,其中最为简单的是纹理贴图只要对其法线进行采样,然后制作UV动画,在制作UV动画的时候,有一个小trick,同一张Normal图可以采样两次,然后分别做主动与扰动。其次是正弦波,正弦波更多的是进行顶点动画,改变定点数据的值来得到,一般的伪代码为 WaveValue = Sin(_Time.y*_Speed+v.vertex.x*_Frequency)*_Amplitude.。期间可以叠加一些常见的sin波或者cos波来进行扰动,他的特点是平滑圆润,适合表达像池塘一样平静的水面。而正弦波的进化版则是Gerstner波,Gerstner波是将水平位置进行挤压,使得波峰变尖,波谷变宽,适合模拟海洋,该公式在《GPU Gems 1》出现过

Gerstner公式

而FTT海洋则是我接下来要研究的对象。之后会另出一篇文章来详解。

水体边缘检测,也就是海浪。海浪我之前有做过两种,一种是描出水体与山体的接触边缘,利用通道区分海浪与海面,相当于做一个mask然后再mask上进行叠加noise图造成扰动效果。另外一种是用深度检测,直接检测出边缘,类似边缘检测的方法。顺带边缘检测一般有几种方法,最基础的方法是用Sobel算子对屏幕图像进行边缘检测,但这种方法会得到许多不希望得到的边缘线而且挺受光源的影响。还有一种卷积检测的方法是Roberts算子,本质就是计算左上角核右下角的差值,乘上右上角和左下角的插值,作为评估依据。取对角方向的深度或者法线值,比较他们之间的插值,如果超过某个阈值,就认为有一条边。而Sobel检测算子是利用相邻像素之间的差值用梯度表示,梯度的绝对值越大,则越有可能是边缘处。这种方法叫做基于图像处理的轮廓渲染。无论怎么改变算子还是其他的,这种方法的劣势是一些深度和法线变化很小的轮廓无法被检出,其他的算法还有

1.基于观察角度和表面法线的轮廓线渲染;这种方法是用视角方向和表面法线的点乘结果得到轮廓信息。可以在一个PASS中得到渲染结果,但局限性很大。

2.过程式几何轮廓渲染;这种方法的核心是用两个PASS渲染,一个剔除正面渲染背面然后沿着法线向外拓展形成轮廓线,还有一个PASS就正常渲染。

3.基于轮廓边检测的轮廓线渲染,这个方法,我们只需要检查和这条边相邻的两个三角面片是否满足 (n0·v>0)≠(n1·v>0),n0与n1分别表示两个相邻三角面片的法向,v是从视角到该边上任意一个顶点的方向。这个条件主要是检车两个相邻的三角面片是否一个正面一个背面。我们可以在几何着色器中完成这个操作,缺点是实现复杂,而且会出现动画连贯性的问题,由于逐帧单独提取轮廓,所以在帧与帧之间会出现条约性。

之前所作的海浪则是通过深度图,需要在摄像机开启深度渲染,基本思路是用摄像机距离地形的距离减去摄像机距离平面的距离,取绝对值,越接近0则越可能是边缘。但有些低端设备不支持深度图渲染,所以用的时候要比较慎用。而且在开启深度纹理的时候需要在FallBack设置为Diffuse才能够体现深度。

接下来是折射

WaterDepth=SceneDepth(Eye)-Screen Position

DepthFade
Movement
Water Color
Water Refraction
Foam
Combine_Color Output

RayMarching’s Post Work

在学习关于Ray Marching的过程中,我发现对于Ray Marching的理解可以通过ShaderToy来加深一下。

Ray Marching原理

如上图所属,我们的摄像机需要获取4组数据,UV、摄像机位置(CameraPos)、视线(ViewDir)、缩放,然后根据这四组数据进行输出光线,这个光线是个向量,然后经过一个黑箱操作(这里指Ray Marching)输出一组数据,我们在这组数据上进行材质的添加与系列操作,最终输出到我们的片元着色器/像素着色器(fragment shader/pixel shader)。而我们需要进行的操作就是如何进行黑箱计算。

Ray Marching中的黑箱

如上我们具体所做的就是上面图中的操作,不断的使用光线步进来检测物体

第一次步进

第一次步进接触到最靠近相机的物体表面,我们在这打个标记,表示第一次步进,然后依次为圆心再对其他的光线进行步进

第二次步进

第二次步进我们在次发散出去的光又一次接触到了离此处最近的物体表面也就是黄圈的半径,我们在此再次打个点进行我们下一步的步进。然后周而复始达到最远的点。

最终的步进

如此我们就能勾勒出一个大体的形状.我们在这一部的代码可以如下

float raymarching(vec3 ro, vec3 rd){
	float dO=0.;
        
        for(int i=0; i<MAX_STEPS;i++){
        	vec3 p = ro + rd*dO;
            float dS = GetDist(p);
            dO +=dS;
            if (dO>MAX_DIST || dS<SURF_DIST) break;
        
        
        }
        
    return dO;
}

在这个函数之前,我们需要再写一段函数来获得步进距离的函数。

获得步进距离

首先我们先获得两个距离,一个是相机到地面的距离,一个是相机到物体表面的距离,在这里物体是一个球,相机到地面的距离可以通过cameraPos.y-plane.y来获得,在这里相机到地面的距离就是相机的y坐标。相机到球体表面的距离是相机坐标减去球心坐标取模,再减去球的半径。我们为了确保光线能够安全的达到物体表面,我们需要将相机与任意物体的距离作为初始的步进距离,在这里我们用相机到地面的距离。

安全步进

所以此处的步进方式应该如下

    如此步进便能安全有效的达到物体的表面。此处代码为

float GetDist(vec3 p){
	vec4 s = vec4 (0, 1, 6, 1);
    
    float sphereDist = length(p-s.xyz)-s.w;
    float planeDist=p.y;
    
    float d = min(sphereDist,planeDist);
    return d;
    


}

之后添加灯光并获取法线就能进行简单的模型编写。

完整代码如下:

#define MAX_STEPS 100
#define MAX_DIST 100.
#define SURF_DIST .01


float GetDist(vec3 p){
	vec4 s = vec4 (0, 1, 6, 1);
    
    float sphereDist = length(p-s.xyz)-s.w;
    float planeDist=p.y;
    
    float d = min(sphereDist,planeDist);
    return d;
    


}


float raymarching(vec3 ro, vec3 rd){
	float dO=0.;
        
        for(int i=0; i<MAX_STEPS;i++){
        	vec3 p = ro + rd*dO;
            float dS = GetDist(p);
            dO +=dS;
            if (dO>MAX_DIST || dS<SURF_DIST) break;
        
        
        }
        
    return dO;



}

vec3 GetNormal(vec3 p){
	float d = GetDist(p);
    vec2 e = vec2(.01,0);
    
    vec3 n = d- vec3(
        GetDist(p-e.xyy),
        GetDist(p-e.yxy),
        GetDist(p-e.yyx));
        return normalize(n);
}

float GetLight(vec3 p){
    vec3 lightPos = vec3(0, 5, 6);
    lightPos.xy += vec2(sin(iTime),cos(iTime))*2.;
    vec3 l = normalize(lightPos-p);
    vec3 n = GetNormal(p);
    
    float dif = clamp(dot(n,l),0.,1.);
    
    float d =  raymarching(p+n*SURF_DIST*2.,l);
    if(d<length(lightPos-p)) dif *=.1;
    
    return dif;


}



void mainImage( out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = (fragCoord-0.5*iResolution.xy)/iResolution.y;
    
    vec3 col = vec3(0);
    
    vec3 ro =vec3(0,1,0);
    vec3 rd = normalize(vec3(uv.x,uv.y,1));
    
	float d=raymarching(ro,rd);
    
    vec3 p = ro + rd * d;
    
    float dif = GetLight(p);
    col = vec3(dif);
    
   
    
    fragColor = vec4(col,1.0);



}

Unity-SmipleWater

这次我们将运用之前所学的内容进行简单的水体编辑,先上效果图。

完成后的效果图

这次的水体其实非常的简单,主要是通过UV运动与顶点运动所实现的,为了节省美术资源,我们就用了两张图像资源,一张包含渐变、泡沫的Foam图,一张法线图。

Foam
Foam
Normal
Normal

我们所使用的资源就是这两个。首先我们先完成海水的渐变效果,在这里我们需要用到Foam的R通道,如果要完成更加自由的组合我们可以用photoshop来进行编辑或者设置两个颜色与一张灰度图来进行比较,这两种方法都会在日常的工作中运用得到。但这里我们只用Foam图的R通道进行实现渐变。

fixed degree=tex2D(_Foma,i.uv).r;
fixed3 albedo=lerp(_ShalowColor,_DeepColor,degree);

如上所写,我们将_Foam进行采样然后将他的R通道的值赋予degree,之后将albedo也就是反射率进行线性插值,便可以得到如下效果

简单的渐变与法线

与法线贴图配合就能实现上图的效果。这部分的代码比较重要,建议写的比较有条理些,毕竟框架蛮重要的。

Shader "Custom/SeaWater"
{
    Properties
    {
        [Space(20)]
        [Header(Color)]
        _ShalowColor("Shalow Color",Color)=(1,1,1,1)
        _DeepColor("Deep Color",Color)=(1,1,1,1)
        
        
        [Space(20)]
        [Header(Texture)]
        _Foam("Foam",2D)="white"{}
        _WaterNormal("Water Normal",2D)="Bump"{}
        _NormalScale("Normal Scale",Float)=1.0

        [Space(20)]
        [Header(Specular)]
        _SpecularColor("Specular Color",Color)=(1,1,1,1)
        _SpecularScale("Specular Scale",Range(8,256))=20
    }
    SubShader
    {
        Pass{
            Tags { "RenderType"="Opaque" }
            CGPROGRAM
            #pragma vertex vert 
            #pragma fragment frag 
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"

            fixed4 _ShalowColor;
            fixed4 _DeepColor;
            fixed4 _SpecularColor;
            float _SpecularScale;

            sampler2D _Foam;
            float4 _Foam_ST;
            sampler2D _WaterNormal;
            float4 _WaterNormal_ST;

            float _NormalScale;

            struct a2v{
                float4 vertex:POSITION;
                float4 texcoord:TEXCOORD0;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
            };

            struct v2f{
                float4 pos:SV_POSITION;
                float4 uv:TEXCOORD0;
                float3 lightDir:TEXCOORD1;
                float3 viewDir:TEXCOORD2;
            };

            v2f vert(a2v v){
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);

                o.uv.xy = v.texcoord.xy * _Foam_ST.xy + _Foam_ST.zw;
			    o.uv.zw = v.texcoord.xy * _WaterNormal_ST.xy + _WaterNormal_ST.zw;

                TANGENT_SPACE_ROTATION;

                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

                return o;
            }

            fixed4 frag(v2f i):SV_Target{
                float3 tangentLightDir=normalize(i.lightDir);
                float3 tangentViewDir=normalize(i.viewDir);
                fixed4 packedNormal = tex2D(_WaterNormal, i.uv.zw);
			    fixed3 tangentNormal=UnpackNormal(packedNormal);
                tangentNormal.xy *=_NormalScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

                fixed degree=tex2D(_Foam,i.uv.xy).r;
                fixed3 albedo=lerp(_ShalowColor,_DeepColor,degree);
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));

                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tangentNormal, halfDir)), _SpecularScale);
				
			    return fixed4(ambient+diffuse + specular, 1.0);
            
            }
            ENDCG
        }
    }

    FallBack "Diffuse"
}

我们先将框架搭完了之后就是传统的搭积木写代码而已。法线贴图的主要思路可以参考网上的代码,这部分资料有很多,我比较推荐冯乐乐的《Shader 入门精要》,里面阐述的比较详细。

接着我们进行最关键的一部分代码的书写,水面波纹的运动。毕竟我们的运动主要由法线动画所体现,核心代码如下

half2 panner1 = ( _Time.y * _WaveParams.xy + i.uv);
half2 panner2 = ( _Time.y * _WaveParams.zw + i.uv);
half3 worldNormal = BlendNormals(UnpackNormal(tex2D( _WaterNormal, panner1)) , UnpackNormal(tex2D(_WaterNormal, panner2)));
worldNormal = lerp(half3(0, 0, 1), worldNormal, _NormalScale);

我们在这里新定义了一个参数_WaveParams(x,y,z,w),这个参数我们UE4中经常用到是4位的向量,我们在这里可以定义一下,海浪参数(x:海浪范围,y:海浪偏移,z:海浪扰动,w:浪花泡沫扰动),这里我要啰嗦一句,在进行法线贴图的时候,法线其实是储存在切线空间下的,我当要使用法线在世界中的坐标的时候需要进行一部分转换。具体可以看这篇文章。关于为何要将法线存储在切线文章,可以参考法线贴图这篇文章。具体的关键代码如下:

v2f vert (appdata_full v)
{
    ...
    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
    fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
    fixed tangentSign = v.tangent.w * unity_WorldTransformParams.w;
    fixed3 worldBinormal = cross(worldNormal, worldTangent) * tangentSign;
    o.TW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, o.worldPos.x);
    o.TW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, o.worldPos.y);
    o.TW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, o.worldPos.z);
    ...
    return o;
}
fixed4 frag (v2f i) : SV_Target
{
    ...
    worldNormal = normalize(fixed3(dot(i.TW0.xyz, worldNormal), dot(i.TW1.xyz, worldNormal), dot(i.TW2.xyz, worldNormal)));
    ...
}
uv动画
uv动画

制作高光的时候我这里选择的Blinn-Phong高光模型,关于Blinn-Phong模型我们这里不再赘述,网上有很多资料,本博客也有。

fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tangentNormal, halfDir)), _SpecularScale);

我们在打框架的时候已经将Blinn-Phong模型加了进去,效果图就不放了,接下来我们要加入Fresnel效果,关于Fresnel有一篇很著名的博文EveryThingHasFresnel大家如果有空可以去看一看,Fresnel有一个比较类似的公式

Fresnel Schlick公式

其中F0意味着Fresnel系数,由我们自己定义。其他的都是由我们在shader中获取。还有一个比较广泛的公式是fresnel Empricial如下

Fresnel Empricial公式

其中Blas、Scale、Power是可控项,这个公式具有更高的可控性,大家可以玩玩,挺有意思的。

但,我们这里一个都不用,主要是具体介绍下Fresnel公式,我们这里用一个trick来实现fresnel,就是用自发光来代替不同角度下的颜色。

fixed3 rim = pow(1-saturate(dot(tangentNormal,tangentViewDir)),_RimPower)*_LightColor0.rgb * _RimIntensity;
对比图

接着是我们另外一个难点,就是海浪的绘制,海浪的绘制有两个方法,一是用通道图画出边缘。优点是可控性强,性能消耗低。缺点是边缘质量很依赖贴图大小和精度,一旦场景改变需要重制贴图;二是使用深度图直接检测出交接边缘。优点是精度较高,无需美术反复修改贴图。缺点是性能消耗略大(有些低端移动设备不支持渲染深度图或者默认不开启渲染深度图)。这里采用深度图做法。深度图我们需要在Camera中开启深度渲染,这一步只需要再脚本中写入

Camera.main.depthTextureMode = DepthTextureMode.Depth;
开启深度渲染的摄像机组件

在Shader中我们首先要先声明深度纹理

uniform sampler2D _CamerDepthTexture;

获取屏幕位置

o.screenPos = ComputeScreenPos(o.vertex);

计算边缘区域

half4 screenPos = float4( i.screenPos.xyz , i.screenPos.w);
half eyeDepth = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture,UNITY_PROJ_COORD( screenPos ))));
half eyeDepthSubScreenPos = abs( eyeDepth - screenPos.w );
half depthMask = 1-eyeDepthSubScreenPos + _FoamDepth;

计算边缘区域的逻辑其实非常简单,用摄像机距离地形的距离减去摄像机距离平面的距离,然后取得绝对值。该绝对值约接近为0则越可能是其边缘,添加FoamDepth是为了控制边缘区域的范围大小

他的绿色部分就是我们所需要的值,该值的求解就是黄色的线减去红色的线,可以非常容易理解通过深度图是如何进行取得接触边缘的的。

在这里需要强调一点的是当开启深度纹理的时候需要在shader的FallBack设置正确的”Diffuse”能够体现深度的设置。这点很重要,不然Shader不会报错,但依旧的不到正确的结果。

有了边缘区域接下来就是要让边缘显示为泡沫形状。最简单的做法就是使用泡沫通道乘以遮罩然后对水颜色和泡沫颜色进行插值。参考代码如下:

half3 foam = tex2D(_Foam,i.uv);
float temp_output = ( saturate( (foam.g * depthMask - _FoamFactor) );
diffuse = lerp( diffuse , _FoamColor, temp_output);

为了使边缘不规则,我们可以增加噪声图或者直接新型采样,depthMask *=water.g 这部分的water可以是

half3 water = tex2D(_Foam,i.uv.xy/_Foam_ST.xy);

精简下代码就是

depthMask *=tex2D(_Foam,i.uv.xy/_Foam_ST.xy).g;

此时我们就得到了初步的岸边的效果。

我们同样对水面进行UV的动画处理

half3 foam1 = tex2D(_Foam,i.uv + worldNormal.xy*_FoamOffset.w);
half3 foam2 = tex2D(_Foam, _Time.y * _FoamOffset.xy + i.uv + worldNormal.xy*_FoamOffset.w);

现在细节只是一层不动的颜色贴图,在这基础上添加扰动,让它更像海水的状态:

alf3 foam1 = tex2D(_Foam,i.uv + worldNormal.xy*_FoamOffset.w);
half3 foam2 = tex2D(_Foam, _Time.y * _FoamOffset.xy + i.uv + worldNormal.xy*_FoamOffset.w);

Foamoffset.w是扰动因子然后修改diffuse的颜色与细节颜色混合。

half4 detail = tex2D(_Foam,i.uv/_Foam_ST.xy).b * _DetailColor;
diffuse.rgb = fixed3(diffuse.rgb * (NdotV + detail.rgb) * 0.5);

现在细节只是一层不动的颜色贴图,在这基础上添加扰动,让它更像海水的状态:

half2 detailpanner = (i.uv/_Foam_ST.xy + worldNormal.xy*_WaterWave);
half4 detail = tex2D(_Foam,i.uv/_Foam_ST.xy).b * _DetailColor;

接下来我们需要进行顶点动画的制作,我们首先将原来的Plane替换成几万顶点的Mesh。顶点动画的代码之前的文章有过叙述。

float time = _Time.y * _Speed;
float waveValue = sin(time + v.vertex.x *_Frequency)* _Amplitude;
v.vertex.xyz = float3(v.vertex.x, v.vertex.y + waveValue, v.vertex.z);

speed是移动速度,Frequency是频率,Amplitude是幅度。

最后我们将标签改为Transparent进行透明度渲染。

half alpha = saturate(eyeDepthSubScreenPos-_AlphaWidth);
fixed4 col = fixed4( diffuse + specular + rim ,alpha);

整体代码如下:

Shader "Custom/SeaWater"
{
    Properties
    {
        [Space(20)]
        [Header(Color)]
        _ShalowColor("Shalow Color",Color)=(1,1,1,1)
        _DeepColor("Deep Color",Color)=(1,1,1,1)
        _FoamColor("Foam Color",Color)=(1,1,1,1)
        _DetailColor("Detail Color",Color)=(1,1,1,1)
        
        
        [Space(20)]
        [Header(Texture)]
        _Foam("Foam",2D)="white"{}
        _WaterNormal("Water Normal",2D)="Bump"{}
        _NormalScale("Normal Scale",Float)=1.0

        [Space(20)]
        [Header(Specular)]
        _SpecularColor("Specular Color",Color)=(1,1,1,1)
        _SpecularScale("Specular Scale",Float)=1.0
        _Gloss("Gloss",Float)=20

        [Space(20)]
        [Header(Animation)]
        _WaveParams("Wave Params",Vector)=(0,0,0,0)
        _FoamOffset("Foam Offset",Vector)=(0,0,0,0)
        _WaterWave("Water Wave",Float)=1.0
        _WaveXSpeed("Wave Horizontal Speed",Range(-0.1,0.1))=0.01
		_WaveYSpeed("Wave Vertical Speed",Range(-0.1,0.1))=0.01
		_Amplitude("Amplitude",Float)=1.0
		_Frequency("Frequency",Float)=1.0
		_Speed("Speed",Float)=1.0
        
        [Space(20)]
        [Header(Emission)]
        _RimPower("Rim Power",Float)=1.0
        _RimIntensity("RimIntensity",Float)=1.0

        [Space(20)]
        [Header(Foam)]
        _FoamDepth("Foam Depth",Float)=1.0
        _FoamFactor("Foam Factor",Float)=1.0

        [Space(20)]
        [Header(Alpha)]
        _AlphaWidth("AlphaWidth",Float)=1.0


    }
    SubShader
    {
        Pass{
            Tags { "LightMode"="ForwardBase" "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
            ZWrite On 
			Blend SrcAlpha OneMinusSrcAlpha
            
            CGPROGRAM
            #pragma vertex vert 
            #pragma fragment frag 
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"

            fixed4 _ShalowColor;
            fixed4 _DeepColor;
            fixed4 _SpecularColor;
            fixed4 _FoamColor;
            fixed4 _DetailColor;
            float _SpecularScale;
            float _Gloss;

            sampler2D _Foam;
            float4 _Foam_ST;
            sampler2D _WaterNormal;
            float4 _WaterNormal_ST;

            float _NormalScale;

            float4 _WaveParams;
            fixed4 _FoamOffset;
            float _WaterWave;
            fixed _WaveXSpeed;
			fixed _WaveYSpeed;
            float _Amplitude;
			float _Frequency;
			float _Speed;
            float _AlphaWidth;


            float _RimPower;
            float _RimIntensity;

            float _FoamDepth;
            float _FoamFactor;
            uniform sampler2D _CameraDepthTexture;



            struct a2v{
                float4 vertex:POSITION;
                float4 texcoord:TEXCOORD0;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
            };

            struct v2f{
                float4 pos:SV_POSITION;
                float4 uv:TEXCOORD0;
                float3 lightDir:TEXCOORD1;
                float3 viewDir:TEXCOORD2;
                float4 screenPos:TEXCOORD3;
            };

            v2f vert(a2v v){
                v2f o;
                
                float time=_Time.y*_Speed;
				float waveValue=sin(time+v.vertex.x*_Frequency)*_Amplitude;
				v.vertex.xyz=float3(v.vertex.x,v.vertex.y+waveValue,v.vertex.z);
                
                o.pos=UnityObjectToClipPos(v.vertex);

                o.uv.xy = v.texcoord.xy * _Foam_ST.xy + _Foam_ST.zw;
			    o.uv.zw = v.texcoord.xy * _WaterNormal_ST.xy + _WaterNormal_ST.zw;

                TANGENT_SPACE_ROTATION;

                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
                
                o.screenPos = ComputeScreenPos(o.pos);

                return o;
            }

            fixed4 frag(v2f i):SV_Target{
                float3 tangentLightDir=normalize(i.lightDir);
                float3 tangentViewDir=normalize(i.viewDir);

                half2 panner1 = ( _Time.y * _WaveParams.xy + i.uv.zw);
                half2 panner2 = ( _Time.y * _WaveParams.zw + i.uv.zw);
                half3 tangentNormal = BlendNormals(UnpackNormal(tex2D( _WaterNormal, panner1)) , UnpackNormal(tex2D(_WaterNormal, panner2)));
                tangentNormal = lerp(half3(0, 0, 1), tangentNormal, _NormalScale);
                tangentNormal.xy *=_NormalScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
                
                half4 screenPos = float4( i.screenPos.xyz , i.screenPos.w);
                half eyeDepth = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture,UNITY_PROJ_COORD( screenPos ))));
                half eyeDepthSubScreenPos = abs( eyeDepth - screenPos.w );
                half depthMask = 1-eyeDepthSubScreenPos + _FoamDepth;
                depthMask *=tex2D(_Foam,i.uv.xy/_Foam_ST.xy).g;

                half3 foam1 = tex2D(_Foam,i.uv.xy+tangentNormal.xy*_FoamOffset.w);
				half3 foam2 = tex2D(_Foam, _Time.y * _FoamOffset.xy + i.uv.xy+tangentNormal.xy*_FoamOffset.w);
                float temp_output = ( saturate( (foam1.g + foam2.g ) * depthMask - _FoamFactor));
                

                fixed degree=tex2D(_Foam,i.uv.xy).r;
                fixed3 albedo=lerp(_ShalowColor,_DeepColor,degree);

                half2 detailpanner=i.uv.xy/_Foam_ST.xy+tangentNormal.xy*_WaterWave;
				half4 detail=tex2D(_Foam,i.uv.xy/detailpanner).b*_DetailColor;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 rim = pow(1-saturate(dot(tangentNormal,tangentViewDir)),_RimPower)*_LightColor0.rgb * _RimIntensity;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
                diffuse = lerp( diffuse , _FoamColor, temp_output);
                diffuse.rgb=fixed3(diffuse.rgb*(dot(tangentNormal,tangentViewDir)+detail.rgb)*0.5);
                //half alpha = saturate(eyeDepthSubScreenPos-_AlphaWidth);
                
                
                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
                fixed specularMask = tex2D(_WaterNormal, i.uv.zw).r * _SpecularScale;
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss)*specularMask;
				
			    return fixed4(ambient+diffuse+specular+rim, _AlphaWidth);
            
            }
            ENDCG
        }
    }

    FallBack "Diffuse/Specular/Transparent"
}

整体的效果到这就结束了,Shader的思路就是搭积木,选好模型,然后不断添加细节。在这基础之上可以添加交互功能,具体的方法我是参考这篇Blgo。可以得到基础的效果。另外关于海面波光的表现形式有多种做法,这里用了将法线贴图作为遮罩进行遮罩高光来实现具体效果,还有一种方法我很喜欢的效果,再风之旅人的GDC上提过,他们用在了沙漠上,但在海水上也很有效果。风之旅人的沙漠实现方法。这篇文章的实现思路也很值得学习。