当前位置:   article > 正文

shader 反射 水面_Unity Shader 水体渲染

unity里water shader反射不到

Unity Shader Water 真实感水体的制作心得

用了两周半的时间研究了一下基本的水体渲染。效果中并不涉及顶点的波移动,仅为法线贴图扰动,其中包括了海浪、平面反射、高光反射、折射等效果。适合用于PC端各类型游戏的水面效果,且源代码用的是最基本的顶点片元着色器和一些常见的shaderlab函数,也容易运用到其他引擎上。

此文是我一个学习笔记,分享出来希望大家一起学习,如果有大佬教导就更好了。

我在看了网上能找到的多部分水体的文章和论文后(文末会列出),结合一些实际游戏的效果图,其中代码会有所ctrlc/v和删减,实际效果还有许多欠缺,之后会继续学习。

首先要说我选择的是在世界空间下计算这些参数,即先在顶点着色器中计算切线空间到世界空间的变换矩阵,把他传递给片元着色器,再在片元着色器中把法线方向从切线空间变换到世界空间。 在计算中我会运用到这些基础向量:worldPos / lightDir / viewDir / halfDir / NdotL(法线点乘光源方向)/ NdotH(法线点乘半角向量)均为世界空间下 。

关于世界空间下法线的变换 详请翻阅《入门精要》P152。

这里贴上主要代码:

struct v2f

{

float4 pos : SV_POSITION;

float2 uv : TEXCOORD0;

float4 TtoW0:TEXCOORD2;

float4 TtoW1:TEXCOORD3;

float4 TtoW2:TEXCOORD4;

};

//顶点着色器的输出结构体v2f,包含了切线空间到世界空间的变换矩阵。

v2f vert (appdata v)

{

v2f o;

o.pos = UnityObjectToClipPos(v.vertex);

o.uv = TRANSFORM_TEX(v.uv, _MainTex);

float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;

float3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));

fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);

fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);

o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);

o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

return o;

}

顶点着色器中计算了世界空间下的顶点切线、副法线和法线的矢量表示。

//要用的向量

float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);

float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

float3 halfDir = normalize(lightDir + viewDir);

fixed3 tangentNormal1 = UnpackNormal(tex2D(_NormalTex , i.uv + offset)).rgb;

fixed3 tangentNormal2 = UnpackNormal(tex2D(_NormalTex , i.uv - offset)).rgb;

fixed3 tangentNormal = normalize(tangentNormal1 + tangentNormal2);

tangentNormal.xy *= _NormalScale;

tangentNormal.z = sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

float3 worldNormal = normalize(half3(dot(i.TtoW0.xyz, tangentNormal), dot(i.TtoW1.xyz, tangentNormal), dot(i.TtoW2.xyz, tangentNormal)));

float NdotH = max(0,dot(halfDir , worldNormal)); //BlinnPhong

float NdotL = max(0,dot(worldNormal , lightDir)); // 漫反射

这里的offset是一个法线扰动值,之后会讲解到。计算LightDir / viewDir 使用了UnityCG.cgine中常用的帮助函数,了解请翻阅《入门精要》P108,详细请查找源码 。使用前需要在前面声明:

#include "UnityCG.cginc"

我们现在基本的向量都计算完了,有了这些向量我们就可以开始逐个计算各个元素了。我们先计算了A(ambient)D(diffuse)S(specular),

fixed3 diffuse = _LightColor0.rgb*col*saturate(dot(worldNormal , lightDir)) ;

fixed3 specular = pow( NdotH , _Specular * 128.0) * _Gloss;

float3 ambient = col*UNITY_LIGHTMODEL_AMBIENT.xyz;

(_LightColor0是directional light的颜色)

使用_LightColor0 和UNITY_LIGHTMODEL_AMBIENT需要在前面声明

#include "Lighting.cginc"

这是最基本的光照模型,我们可以看到以下效果。

///

之后我们加入法线偏移,这样水面就会动起来,原理是用一张法线贴图根据内置的_Time函数计算出一个可动的float2类型的偏移值,再在法线从切线空间转到世界空间的时候进行uv偏移,达到水面流动的效果。

//法线扰动

float4 offsetColor = (tex2D(_NormalTex, i.uv

+ float2(_WaveXSpeed*_Time.x,0))

+ tex2D(_NormalTex, float2(i.uv.y,i.uv.x)

+ float2(_WaveYSpeed*_Time.x,0)))/2;

// float4 waveOffset = tex2D(_NormalTex ,i.uv + wave_offset);

half2 offset = UnpackNormal(offsetColor).xy * _NormalRefract;//法线偏移程度可控之后offset被用于这里

fixed3 tangentNormal1 = UnpackNormal(tex2D(_NormalTex , i.uv + offset)).rgb;

fixed3 tangentNormal2 = UnpackNormal(tex2D(_NormalTex , i.uv - offset)).rgb;

这样水面就动起来了。

///

接着我开始做根据水面深度来采样一张渐变贴图,达到水底是深色的,岸边是浅色的这一效果。具体文章出自

博客上讲的很清楚,我这里补充一下更细致的概念。

1.直接获得unity相机内置的屏幕深度图:链接详解

sampler2D _CameraDepthTexture;

2.用ComputeScreenPos函数返回一个float4的值,输入的参数时经过MVP变换的后在裁剪空间的顶点坐标,透视投影中z的分量范围是[-Near,Far],w值为[Near,Far];正交投影中z为[-1,1],w恒为1。

o.proj = ComputeScreenPos(o.pos); //返回齐次坐标系下的屏幕坐标值

COMPUTE_EYEDEPTH(o.proj.z); //计算深度

4.SAMPLE_DEPTH_TEXTURE_PROJ为unity提供的宏,LinearEyeDepth负责把深度纹理的采样结果转换到视角空间下的深度值。

float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture , i.proj);

half m_depth = LinearEyeDepth(depth);

half deltaDepth = m_depth - i.proj.z;

阶段性展示

5.采样渐变贴图

fixed4 gradientColor = tex2D(_GradientTex , float2(sin(min(_Range.y , deltaDepth)/_Range.y),1));

这样一来加上高光等效果就上档次了。

///

接着我们把海浪做了,海浪也是借鉴于那篇文章,不过有所减少。

//海浪

float3 n = tex2D(_NoiseTex , i.uv).rgb;

fixed3 w = tex2D(_WaveTex , float2 ( sin( _Time.y+ min(_Range.x, deltaDepth)/_Range.x) , 1) ).rgb;

float rz = 1 - (min(_Range.z , deltaDepth) / _Range.z );

最后输出颜色时加上w*rz就可以产生这个效果。

///

再加入反射,这里我用了平面反射。po一个链接中有目前常见的所有反射效果

sampler2D _ReflectionTex;

这里的用proj的xy除以proj的z来得到视口空间的坐标。具体原理在上面的链接中,讲的很好!概括来说就是新建一个相机渲染反射的图像,然后我们把渲染好的图像进行偏移。

fixed3 reflectionCol = tex2D(_ReflectionTex, i.proj.xy/i.proj.w).rgb ;

看一下效果

之后是折射,就是抓屏。我在最终效果中没有加入这个,因为我们前面生成的渐变色已经很好的把水下的样子渲染出来了,而且效果不错。不过如果是岸边比较浅的话还是用抓屏更好一点。直接上代码。

GrabPass{"_GrabTex"}

//得到对应被抓取屏幕图像的采样坐标

o.scrPos = ComputeGrabScreenPos(o.pos);

fixed3 refractColor = (tex2D( _GrabTex, i.scrPos.xy/i.scrPos.w).rgb );

效果图

///

好了有了折射有了反射 我们就可以快乐的菲涅耳了。

不过我实验了很多菲涅耳公式的简单变形式,效果并不佳。而且用折射和反射的菲涅耳效果更不好,所以我最终抛弃了折射,直接用光照模型结果和反射做了菲涅耳,说实话效果同样一般,但正在继续改进中。

fixed fresnel = pow((1 - (dot(worldNormal,viewDir))),5);

float3 finalCol = diffuse * gradientColor.rgb + specular + ambient;

fixed3 f = lerp(finalCol,reflectionCol,saturate(fresnel))*atten;

最终输出,Alpha用的是一个float值采样的深度值。

float Alpha = min(_Range.w, deltaDepth)/_Range.w; //透明度

return float4((f + w * rz), Alpha);

源文件中还加了法线融合和焦散的初始代码,但具体的并没有完美实现就不放效果了。

我想加入水波纹的交互但我不太会,有人有学习链接请评论出来,谢谢!

这是现阶段的输出效果。

以上是我二十天所学习到的水体渲染 学习笔记和总结。之后我会把有用的链接放在底下。

2020.8.10

工程

提取码:1963

如果大佬看到觉得哪里不好,改了的话一定告诉我。

这篇为学习笔记,如果代码雷同,那确实是我copy的(菜。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/108456
推荐阅读
相关标签
  

闽ICP备14008679号