Unity引擎制作玻璃球+玻璃杯
大家好,我是阿赵。
之前做海面效果的时候,没做反射和折射的效果,因为我觉得过于复杂的效果没有太大的实际作用。这方面的效果,我就做了现在这个例子来补充一下。
在这个demo场景里面,我建了一个面片,然后贴了一张室内场景的贴图。然后建了一个球放在面片前面。
一、 反射
首先我要给这个球加一个反射的效果。熟悉我的朋友应该都知道,我出于性能的考虑,是不会做真反射的。而我选择的反射方案,一般就是Matcap。
Matcap之前我已经专门写了2篇文章介绍过,有兴趣的朋友可以往前翻一下。这里简略的说一下就算了。Matcap的原理简单,就是用物体的世界法线方向转到View坐标系,然后把这个当做UV去采样一张Matcap贴图。所以Matcap实际上比采样CubeMap消耗还小,而且效果也比较的逼真。
Matcap的实现很简单,在ASE里面的连线是这样的:
把这个Matcap的颜色输出:
我赋予了一张Matcap贴图,然后稍微调了一点强度,看看效果:
可以看出,现在球体上已经有一些反射的效果了。
二、 折射
折射,有多种做法,比较常见的一种,是用CubeMap做一个假环境,然后通过对CubeMap的采样过程进行扭曲,做到折射的效果。
但我这次并不打算这样做折射,因为CubeMap折射的是假的环境,是不会变化的,我这里做一个稍微真实折射,通过GrabTexture,捕捉屏幕图像,然后再通过对GrabTexture的采样UV进行扭曲,达到折射的效果。
在ASE里面的连线大概是这样的:
需要注意的有以下这些点:
1、 GrabTexture一定要自定义贴图名称
GrabTexture的获取有2种方式,一种是不指定贴图名称,另一种是指定贴图名称。
这两种方式从结果看,效果是一模一样的。但如果不指定贴图名称,如果同一帧里面有多个材质球需要采样,就会捕捉多次屏幕,造成巨大的性能消耗。
如果指定了贴图名称,那么就可以大量的减少采样的次数了。GrabTexture捕捉屏幕画面,本身就是一个比较大的消耗,其实能不用最好是不用的。不过一些自带扭曲的效果,如果要实现,要么用后处理的方式做,要么用GrabTexture做,实际上后处理是把一帧的渲染结果当做RenderTexture拿到,再对这张RenderTexture进行处理,消耗也同样不小的。
2、 获取屏幕坐标的方法
由于dx和OpenGL的UV坐标方向不一样,所以要获得GrabTexture对应顶点的屏幕坐标,是需要分别处理的,ASE里面的GrabScreenPosition节点,已经做了这样的处理:
inline float4 ASE_ComputeGrabScreenPos( float4 pos ){#if UNITY_UV_STARTS_AT_TOPfloat scale = -1.0;#elsefloat scale = 1.0;#endiffloat4 o = pos;o.y = pos.w * 0.5f;o.y = ( pos.y - o.y ) * _ProjectionParams.x * scale + o.y;return o;
}
如果不想自己判断是不是UNITY_UV_STARTS_AT_TOP,也可以直接用UnityCG.cginc内置的方法ComputeScreenPos来获取这个坐标。
3、 通过Lerp来过渡
首先来明确一个问题,我们现在不是根据折射率来计算真实的折射,而是通过采样一帧的画面,进行扭曲模拟的折射效果。而我们能使用的扭曲画面的参数,我这里是使用了法线方向。根据法线方向的变化,对GrabTexture的采样UV进行偏移。
既然是这样,那么我们要获得一个世界空间的法线方向和视角的关系,来确定扭曲的强度。这个东西之前我们用过很多了,就是NDotV了:
根据这个的变化,来采样GrabTexture,然后再和正常的GrabTexture屏幕坐标,通过一个强度值,做一个Lerp的变化:
这样就能获得一个反射的颜色,如果单独输出这个反射颜色
球的效果会是这样的:
如果叠加上Matcap的反射效果:
球体的效果会变成这样:
三、 边缘
从模拟的效果看,现在的球体已经有一定的玻璃的特征了,但似乎并没有什么厚度,更像是一个肥皂泡泡。
为了解决这个问题,我尝试给他加一个边缘光。边缘光的算法其实就是之前做海面效果的菲涅尔了。
然后把这个边缘光也叠加上去:
调整一下菲涅尔的最大最小值,可以得到这样的效果:
四、 整体颜色
现在的玻璃默认是白色的,因为我们叠加的效果都是反射折射和边缘光,还没有给固有色指定。所以我添加一个mainColor作为固有色。也可以通过贴图采样作为固有色,都可以,这里就简单给一个颜色算了。
所以调整一下颜色,就可以得到:
或者:
五、 应用于其他的模型
在球体上面实现了这个效果了,接下来把它应用到别的形状的模型,看看效果对不对:
这里做了个酒杯,里面有半杯液体。
由于这个杯子整体来看每个面的法线方向都不一样,可以看出,整体的折射的模拟效果还是比较的正确的。然后由于不同的材质球可以调不同的折射强度,所以液体的折射和被子的折射可以调成不一样,看起来效果会更真实一点。
六、 源码
由于这个Shader是用ASE编的,所以我也提供一下生成的代码,各位有兴趣可以去ASE里面看看连线的情况:
// Made with Amplify Shader Editor
// Available at the Unity Asset Store - http://u3d.as/y3X
Shader "ballRefract"
{Properties{_mainColor("mainColor", Color) = (1,1,1,1)_matcapTex("matcapTex", 2D) = "white" {}_matcapStrength("matcapStrength", Range( 0 , 1)) = 0_refractStrength("refractStrength", Range( 0 , 1)) = 0_fresnelVal("fresnelVal", Range( 0 , 1)) = 1_fresnelMax("fresnelMax", Range( 0 , 1)) = 1_fresnelMin("fresnelMin", Range( 0 , 1)) = 0}SubShader{Tags { "RenderType"="Opaque" "Queue"="Transparent" }LOD 100CGINCLUDE#pragma target 3.0ENDCGBlend OffAlphaToMask OffCull BackColorMask RGBAZWrite OnZTest LEqualOffset 0 , 0GrabPass{ }Pass{Name "Unlit"Tags { "LightMode"="ForwardBase" }CGPROGRAM#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)#define ASE_DECLARE_SCREENSPACE_TEXTURE(tex) UNITY_DECLARE_SCREENSPACE_TEXTURE(tex);#else#define ASE_DECLARE_SCREENSPACE_TEXTURE(tex) UNITY_DECLARE_SCREENSPACE_TEXTURE(tex)#endif#ifndef UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX//only defining to not throw compilation error over Unity 5.5#define UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input)#endif#pragma vertex vert#pragma fragment frag#pragma multi_compile_instancing#include "UnityCG.cginc"#include "UnityShaderVariables.cginc"#define ASE_NEEDS_FRAG_WORLD_POSITIONstruct appdata{float4 vertex : POSITION;float4 color : COLOR;float3 ase_normal : NORMAL;UNITY_VERTEX_INPUT_INSTANCE_ID};struct v2f{float4 vertex : SV_POSITION;#ifdef ASE_NEEDS_FRAG_WORLD_POSITIONfloat3 worldPos : TEXCOORD0;#endiffloat4 ase_texcoord1 : TEXCOORD1;float4 ase_texcoord2 : TEXCOORD2;UNITY_VERTEX_INPUT_INSTANCE_IDUNITY_VERTEX_OUTPUT_STEREO};uniform float4 _mainColor;uniform sampler2D _matcapTex;uniform float _matcapStrength;ASE_DECLARE_SCREENSPACE_TEXTURE( _GrabTexture )uniform float _refractStrength;uniform float _fresnelMin;uniform float _fresnelMax;uniform float _fresnelVal;inline float4 ASE_ComputeGrabScreenPos( float4 pos ){#if UNITY_UV_STARTS_AT_TOPfloat scale = -1.0;#elsefloat scale = 1.0;#endiffloat4 o = pos;o.y = pos.w * 0.5f;o.y = ( pos.y - o.y ) * _ProjectionParams.x * scale + o.y;return o;}v2f vert ( appdata v ){v2f o;UNITY_SETUP_INSTANCE_ID(v);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);UNITY_TRANSFER_INSTANCE_ID(v, o);float3 ase_worldNormal = UnityObjectToWorldNormal(v.ase_normal);o.ase_texcoord1.xyz = ase_worldNormal;float4 ase_clipPos = UnityObjectToClipPos(v.vertex);float4 screenPos = ComputeScreenPos(ase_clipPos);o.ase_texcoord2 = screenPos;//setting value to unused interpolator channels and avoid initialization warningso.ase_texcoord1.w = 0;float3 vertexValue = float3(0, 0, 0);#if ASE_ABSOLUTE_VERTEX_POSvertexValue = v.vertex.xyz;#endifvertexValue = vertexValue;#if ASE_ABSOLUTE_VERTEX_POSv.vertex.xyz = vertexValue;#elsev.vertex.xyz += vertexValue;#endifo.vertex = UnityObjectToClipPos(v.vertex);#ifdef ASE_NEEDS_FRAG_WORLD_POSITIONo.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;#endifreturn o;}fixed4 frag (v2f i ) : SV_Target{UNITY_SETUP_INSTANCE_ID(i);UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);fixed4 finalColor;#ifdef ASE_NEEDS_FRAG_WORLD_POSITIONfloat3 WorldPosition = i.worldPos;#endiffloat3 ase_worldNormal = i.ase_texcoord1.xyz;float4 matcap24 = ( tex2D( _matcapTex, (mul( float4( ase_worldNormal , 0.0 ), UNITY_MATRIX_V ).xyz*0.5 + 0.5).xy ) * _matcapStrength );float4 screenPos = i.ase_texcoord2;float4 ase_grabScreenPos = ASE_ComputeGrabScreenPos( screenPos );float4 ase_grabScreenPosNorm = ase_grabScreenPos / ase_grabScreenPos.w;float2 appendResult3 = (float2(ase_grabScreenPosNorm.r , ase_grabScreenPosNorm.g));float3 ase_worldViewDir = UnityWorldSpaceViewDir(WorldPosition);ase_worldViewDir = normalize(ase_worldViewDir);float dotResult8 = dot( ase_worldNormal , ase_worldViewDir );float NDotV25 = dotResult8;float2 temp_cast_3 = (NDotV25).xx;float2 lerpResult5 = lerp( appendResult3 , temp_cast_3 , _refractStrength);float4 screenColor1 = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_GrabTexture,lerpResult5);float4 refract32 = screenColor1;float smoothstepResult9 = smoothstep( _fresnelMin , _fresnelMax , NDotV25);float Fresnel29 = ( ( 1.0 - smoothstepResult9 ) * _fresnelVal );finalColor = ( _mainColor * ( matcap24 + refract32 + Fresnel29 ) );return finalColor;}ENDCG}}CustomEditor "ASEMaterialInspector"}
/*ASEBEGIN
Version=18500
1920;0;1920;1019;3260.221;1877.513;3.859619;True;True
Node;AmplifyShaderEditor.CommentaryNode;28;-2376.601,15.2;Inherit;False;754.8627;439;Comment;4;7;6;8;25;NDotV;1,1,1,1;0;0
Node;AmplifyShaderEditor.ViewDirInputsCoordNode;7;-2312.601,266.2;Inherit;False;World;False;0;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
Node;AmplifyShaderEditor.WorldNormalVector;6;-2326.601,65.2;Inherit;False;False;1;0;FLOAT3;0,0,1;False;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
Node;AmplifyShaderEditor.CommentaryNode;16;-1784.797,-866.7996;Inherit;False;1347.15;473.2724;Comment;9;24;23;22;20;21;19;38;18;17;matcap;1,1,1,1;0;0
Node;AmplifyShaderEditor.DotProductOpNode;8;-2038.601,175.2;Inherit;False;2;0;FLOAT3;0,0,0;False;1;FLOAT3;0,0,0;False;1;FLOAT;0
Node;AmplifyShaderEditor.WorldNormalVector;18;-1734.797,-816.7996;Inherit;False;False;1;0;FLOAT3;0,0,1;False;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
Node;AmplifyShaderEditor.RegisterLocalVarNode;25;-1845.738,162.044;Inherit;True;NDotV;-1;True;1;0;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.CommentaryNode;34;-3346.037,-815.1047;Inherit;False;1148.658;457.9377;Comment;7;2;3;26;4;5;1;32;refract;1,1,1,1;0;0
Node;AmplifyShaderEditor.CommentaryNode;30;-2542.042,578.4539;Inherit;False;1269.011;491.3077;Comment;8;10;11;27;9;13;12;14;29;Fresnel;1,1,1,1;0;0
Node;AmplifyShaderEditor.ViewMatrixNode;17;-1690.02,-599.9698;Inherit;False;0;1;FLOAT4x4;0
Node;AmplifyShaderEditor.RangedFloatNode;10;-2492.042,755.5684;Inherit;False;Property;_fresnelMin;fresnelMin;6;0;Create;True;0;0;False;0;False;0;0;0;1;0;1;FLOAT;0
Node;AmplifyShaderEditor.RangedFloatNode;11;-2488.042,888.5684;Inherit;False;Property;_fresnelMax;fresnelMax;5;0;Create;True;0;0;False;0;False;1;1;0;1;0;1;FLOAT;0
Node;AmplifyShaderEditor.GetLocalVarNode;27;-2354.074,628.4539;Inherit;False;25;NDotV;1;0;OBJECT;;False;1;FLOAT;0
Node;AmplifyShaderEditor.SimpleMultiplyOpNode;19;-1543.883,-676.3995;Inherit;False;2;2;0;FLOAT3;0,0,0;False;1;FLOAT4x4;0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1;False;1;FLOAT3;0
Node;AmplifyShaderEditor.GrabScreenPosition;2;-3296.037,-765.1047;Inherit;False;0;0;5;FLOAT4;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.RangedFloatNode;38;-1598.579,-491.8766;Inherit;False;Constant;_Float0;Float 0;7;0;Create;True;0;0;False;0;False;0.5;0;0;0;0;1;FLOAT;0
Node;AmplifyShaderEditor.SmoothstepOpNode;9;-2123.853,749.8372;Inherit;False;3;0;FLOAT;0;False;1;FLOAT;0;False;2;FLOAT;1;False;1;FLOAT;0
Node;AmplifyShaderEditor.DynamicAppendNode;3;-3002.037,-729.1048;Inherit;False;FLOAT2;4;0;FLOAT;0;False;1;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;0;False;1;FLOAT2;0
Node;AmplifyShaderEditor.RangedFloatNode;4;-3170.409,-473.167;Inherit;False;Property;_refractStrength;refractStrength;3;0;Create;True;0;0;False;0;False;0;0;0;1;0;1;FLOAT;0
Node;AmplifyShaderEditor.ScaleAndOffsetNode;20;-1404.423,-612.3016;Inherit;False;3;0;FLOAT3;0,0,0;False;1;FLOAT;0.5;False;2;FLOAT;0.5;False;1;FLOAT3;0
Node;AmplifyShaderEditor.GetLocalVarNode;26;-3093.938,-599.0139;Inherit;False;25;NDotV;1;0;OBJECT;;False;1;FLOAT;0
Node;AmplifyShaderEditor.SamplerNode;21;-1140.799,-747.8926;Inherit;True;Property;_matcapTex;matcapTex;1;0;Create;True;0;0;False;0;False;-1;None;461d573efcdd854449e8d60d7851cb85;True;0;False;white;Auto;False;Object;-1;Auto;Texture2D;8;0;SAMPLER2D;;False;1;FLOAT2;0,0;False;2;FLOAT;0;False;3;FLOAT2;0,0;False;4;FLOAT2;0,0;False;5;FLOAT;1;False;6;FLOAT;0;False;7;SAMPLERSTATE;;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.OneMinusNode;12;-1908.118,772.1724;Inherit;False;1;0;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.RangedFloatNode;13;-2110.825,953.7616;Inherit;False;Property;_fresnelVal;fresnelVal;4;0;Create;True;0;0;False;0;False;1;1;0;1;0;1;FLOAT;0
Node;AmplifyShaderEditor.LerpOp;5;-2821.126,-595.4236;Inherit;False;3;0;FLOAT2;0,0;False;1;FLOAT2;0,0;False;2;FLOAT;0;False;1;FLOAT2;0
Node;AmplifyShaderEditor.RangedFloatNode;22;-1188.587,-525.996;Inherit;False;Property;_matcapStrength;matcapStrength;2;0;Create;True;0;0;False;0;False;0;0.484;0;1;0;1;FLOAT;0
Node;AmplifyShaderEditor.SimpleMultiplyOpNode;23;-799.8513,-661.761;Inherit;False;2;2;0;COLOR;0,0,0,0;False;1;FLOAT;0;False;1;COLOR;0
Node;AmplifyShaderEditor.SimpleMultiplyOpNode;14;-1726.323,856.4717;Inherit;False;2;2;0;FLOAT;0;False;1;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.ScreenColorNode;1;-2640.674,-692.4675;Inherit;False;Global;_GrabScreen0;Grab Screen 0;0;0;Create;True;0;0;False;0;False;Object;-1;False;False;1;0;FLOAT2;0,0;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.RegisterLocalVarNode;29;-1497.031,822.4349;Inherit;True;Fresnel;-1;True;1;0;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.RegisterLocalVarNode;32;-2421.379,-648.5054;Inherit;False;refract;-1;True;1;0;COLOR;0,0,0,0;False;1;COLOR;0
Node;AmplifyShaderEditor.RegisterLocalVarNode;24;-642.5197,-647.4543;Inherit;False;matcap;-1;True;1;0;COLOR;0,0,0,0;False;1;COLOR;0
Node;AmplifyShaderEditor.GetLocalVarNode;31;-388.149,69.38911;Inherit;False;29;Fresnel;1;0;OBJECT;;False;1;FLOAT;0
Node;AmplifyShaderEditor.GetLocalVarNode;33;-369.1212,4.012573;Inherit;False;32;refract;1;0;OBJECT;;False;1;COLOR;0
Node;AmplifyShaderEditor.GetLocalVarNode;35;-369.3356,-79.31943;Inherit;False;24;matcap;1;0;OBJECT;;False;1;COLOR;0
Node;AmplifyShaderEditor.ColorNode;36;-334.0762,-270.2781;Inherit;False;Property;_mainColor;mainColor;0;0;Create;True;0;0;False;0;False;1,1,1,1;0,0,0,0;True;0;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.SimpleAddOpNode;15;-140.1225,-8.462204;Inherit;False;3;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0
Node;AmplifyShaderEditor.SimpleMultiplyOpNode;37;-13.75513,-124.8451;Inherit;False;2;2;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;1;COLOR;0
Node;AmplifyShaderEditor.TemplateMultiPassMasterNode;0;173.9,-159.1;Float;False;True;-1;2;ASEMaterialInspector;100;1;ballRefract;0770190933193b94aaa3065e307002fa;True;Unlit;0;0;Unlit;2;True;0;1;False;-1;0;False;-1;0;1;False;-1;0;False;-1;True;0;False;-1;0;False;-1;False;False;False;False;False;False;True;0;False;-1;True;0;False;-1;True;True;True;True;True;0;False;-1;False;False;False;True;False;255;False;-1;255;False;-1;255;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;True;1;False;-1;True;3;False;-1;True;True;0;False;-1;0;False;-1;True;2;RenderType=Opaque=RenderType;Queue=Transparent=Queue=0;True;2;0;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;1;LightMode=ForwardBase;False;0;;0;0;Standard;1;Vertex Position,InvertActionOnDeselection;1;0;1;True;False;;False;0
WireConnection;8;0;6;0
WireConnection;8;1;7;0
WireConnection;25;0;8;0
WireConnection;19;0;18;0
WireConnection;19;1;17;0
WireConnection;9;0;27;0
WireConnection;9;1;10;0
WireConnection;9;2;11;0
WireConnection;3;0;2;1
WireConnection;3;1;2;2
WireConnection;20;0;19;0
WireConnection;20;1;38;0
WireConnection;20;2;38;0
WireConnection;21;1;20;0
WireConnection;12;0;9;0
WireConnection;5;0;3;0
WireConnection;5;1;26;0
WireConnection;5;2;4;0
WireConnection;23;0;21;0
WireConnection;23;1;22;0
WireConnection;14;0;12;0
WireConnection;14;1;13;0
WireConnection;1;0;5;0
WireConnection;29;0;14;0
WireConnection;32;0;1;0
WireConnection;24;0;23;0
WireConnection;15;0;35;0
WireConnection;15;1;33;0
WireConnection;15;2;31;0
WireConnection;37;0;36;0
WireConnection;37;1;15;0
WireConnection;0;0;37;0
ASEEND*/
//CHKSM=B4E7B803ED14C858BF513B4EAA46C6658043AB88