Snow Cover with Multiple Blend Shader Practice.


Shader development prototypes according to the demand of other art teams.

Planar mirror reflection

Feature List

  • Floor reflection shader that seems like planar mirror reflection.
  • UV based fake depth fade of reflection added.

Source code here

Click to go Git-hub

Code Snippet below

float node_8816 = (sceneUVs.r+_ReflectionCoordinater);
                float2 node_2055 = (float2(sceneUVs.r,sceneUVs.g)+(float2(node_8816,node_8816)*(float2(_normal_var.r,_normal_var.g)*_NormalJitter)));
                half4 _ReflectionTex_var = tex2D(_ReflectionTex,node_2055);
                fixed3 FlatMirror = ((_ReflectionTex_var.rgb+(pow((1.0 - lerp( _ReflectionTex_var.rgb.g, _ReflectionTex_var.rgb.r, _ChannelSwap )),exp2(_ReflectionBrightSubScale))*_ReflectionBrightSubScale))*_ReflectionsIntensity);
                half Fresnel = dot(pow(1.0-max(0,dot(normalDirection, viewDirection)),exp2((1.0 - _ReflectionFresnel))),lightDirection);
                float3 node_9903 = (FlatMirror*Fresnel);
                half3 FlatMirrorBlended = saturate(max(_MainTex_var.rgb,node_9903));
                half3 node_9232 = FlatMirrorBlended;
                half3 Albedo = _MainTex_var.rgb;
                float node_6753 = (i.uv0.g+(_ReflectionFadeOffset*0.5+0.0)).r;
                half3 FlatMirrorFade = lerp(node_9232,Albedo,node_6753);
                float3 diffuseColor = FlatMirrorFade;


Purpose of implementation.

I was used screen space light texture technique (matcap or light-cap) since from 2006 in years.
And then my new stuff of new matcap technique (I called dual matcap technique) since by 2015 in years When I developed android mobile game in Korea.
Basically matcap technique is So famous tweak shader lighting technique for mobile games.But I curious Could I use to dual side of matcap?
So here is results.

Debug Sphere space capture for parabolic Map.

Complete code here.

Shader "ASE_DualMatcap"
		_FrontParaboloid ("_FrontParaboloid", 2D) = "white" { }
		_RearParaboloid ("_RearParaboloid", 2D) = "white" { }
		_Albedo("Albedo", 2D) = "white" {}
		_Float0("Float 0", Range( 0 , 2)) = 1
		[HideInInspector] _texcoord( "", 2D ) = "white" {}
		Tags { "RenderType"="Opaque" "LightMode" = "ForwardBase" }
		LOD 100
		Cull Off

			#pragma target 3.0 
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			struct appdata
				float4 vertex : POSITION;
				float3 normal : NORMAL;	
				float4 texcoord : TEXCOORD0;
				float4 texcoord1 : TEXCOORD1;
			struct v2f
				float4 vertex : SV_POSITION;
				float4 texcoord : TEXCOORD0;
				float3 worldNormal : TEXCOORD1;

			uniform fixed4 _Color;
			uniform sampler2D _FrontParaboloid;
			uniform sampler2D _RearParaboloid;
			uniform sampler2D _Albedo;
			uniform float4 _Albedo_ST;
			uniform float _Float0;
			v2f vert ( appdata v )
				v2f o;
				o.texcoord.xy = v.texcoord.xy; = v.texcoord1.xy;
				// ase common template code +=  float3(0,0,0) ;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.worldNormal = normalize(mul((float3x3)unity_ObjectToWorld, v.normal));
				return o;

			inline fixed4 texParaboloidXFliped(sampler2D front, sampler2D back, float3 refl)
				if (refl.x > 0)
					float2 frontUv;
					frontUv.x = refl.z / (refl.x + 1);
					frontUv.y = refl.y / (refl.x + 1);
					frontUv = (frontUv * .5f) + 0.5f;
					frontUv.x = 1 - frontUv.x;

					return tex2D(front, frontUv);
					float2 backUv;
					backUv.x = refl.z / (1 - refl.x);
					backUv.x *= -1;
					backUv.y = refl.y / (1 - refl.x);
					backUv = (backUv * .5f) + 0.5f;
					backUv.x = backUv.x;
					return tex2D(back, backUv);
			fixed4 frag (v2f i ) : SV_Target
				fixed4 parabolicOut;
				// ase common template code
				float2 uv_Albedo = i.texcoord.xy * _Albedo_ST.xy +;
				parabolicOut = ( float4(1,1,1,0) * ( tex2D( _Albedo, uv_Albedo ) * _Float0 ) );
				//return myColorVar;
				return parabolicOut * texParaboloidXFliped(_FrontParaboloid, _RearParaboloid, i.worldNormal);;
	CustomEditor "ASEMaterialInspector"

Cartoon Style Splash FX practice.

Purpose of Practice

参考游戏 : RIM2

Shader 的实现。使用不同的两张波纹(Ripple)纹理,各自设置不同的速度使波纹效果更自然。
为了让Mesh的波纹呈现向外围渐渐消失的效果,使用Vertex Color。


为了在Shader使波纹的边缘自然地Fade-out,Vertex color 要用 mask。

  • 先用黑色填满整体后,在开着Soft-Selection的状态下选择里面的Vertex填满红色。
  • 用Vertex tool的Blur功能把整体修改得自然。
  • Mesh的内部也需要添加自然波纹效果的话,内部也设置成黑色便可。
Dual ripple foam texture.
Channel R
Channel B

Shader Snippet

该着色器是使用“ Amplify Shader Editor”创建的。
This shader was created using the Amplify Shader Editor.

Shader "Water Ripple Shader"
		_Cutoff( "Mask Clip Value", Float ) = 0.5
		_RippleFoam("RippleFoam", 2D) = "white" {}
		_MainFoamSpeed("Main Foam Speed", Float) = 0.1
		_SecondFoamSpeed("Second Foam Speed", Float) = 0.1
		_MainFoamScale("Main Foam Scale", Float) = 2
		[HideInInspector] __dirty( "", Int ) = 1

		Tags{ "RenderType" = "TransparentCutout"  "Queue" = "AlphaTest+0" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True" "IsEmissive" = "true"  }
		Cull Back
		#include "UnityShaderVariables.cginc"
		#pragma target 3.5
		#pragma multi_compile_instancing
		#pragma only_renderers d3d11 glcore gles gles3 metal 
		#pragma surface surf Unlit keepalpha noshadow novertexlights nolightmap  nodynlightmap nodirlightmap nometa noforwardadd vertex:vertexDataFunc 
		struct Input
			float2 vertexToFrag32;
			float2 vertexToFrag33;
			float4 vertexColor : COLOR;

		uniform float _MainFoamSpeed;
		uniform float _MainFoamScale;
		uniform sampler2D _RippleFoam;
		uniform float _SecondFoamSpeed;
		uniform float _Cutoff = 0.5;

		void vertexDataFunc( inout appdata_full v, out Input o )
			float temp_output_30_0 = ( _MainFoamSpeed * 0.15 );
			float2 appendResult31 = (float2(temp_output_30_0 , temp_output_30_0));
			float2 panner8 = ( ( appendResult31 * _Time.y ).x * float2( 0,1 ) + v.texcoord.xy);
			float2 temp_output_22_0 = ( panner8 * _MainFoamScale );
			o.vertexToFrag32 = temp_output_22_0;
			float temp_output_15_0 = ( _SecondFoamSpeed * 0.15 );
			float2 appendResult18 = (float2(temp_output_15_0 , temp_output_15_0));
			o.vertexToFrag33 = ( temp_output_22_0 + ( appendResult18 * _Time.y ) );

		inline fixed4 LightingUnlit( SurfaceOutput s, half3 lightDir, half atten )
			return fixed4 ( 0, 0, 0, s.Alpha );

		void surf( Input i , inout SurfaceOutput o )
			o.Emission = float4(1,1,1,0).rgb;
			o.Alpha = 1;
			float lerpResult40 = lerp( 0.5 , ( tex2D( _RippleFoam, i.vertexToFrag32 ).g + tex2D( _RippleFoam, i.vertexToFrag33 ).r ) , i.vertexColor.r);
			clip( lerpResult40 - _Cutoff );

	CustomEditor "ASEMaterialInspector"

[experimental]LightMap shader tweaking.

The purpose if implementation.

  • Open exposure value for artist When they want to adjustment to baked-indirect merged pixel result.

GI信息存储在Unity3D中的Baked Indirect中,使用Meta Pass来确定相邻的颜色值。评估最终结果的视觉效果时,比起真实值,控制更人为的颜色值的Color Bleeding强度,为了接近美术想要的最终品质,修改Unity3D提供的LightMap最终计算方式的过程。在创作早期原型时,使用Amplify Shader Editor等来快速实现,并与美术协商,以便将其添加到内部项目的lightmap计算中。

GI information saved in Baked Indirect of Unity3D is determined by using Meta Pass for adjacent color values.
When evaluating the visual effects of the final result, we control the Color Bleeding intensity of the adjacent color values more artificially than the realistic ones. Shows the process.
When building an initial prototype, you can quickly implement it using the Amplify Shader Editor, etc., and work with the art team to add it to the lightmap calculations for your internal project.

Light-Probe Data manager implementation

purpose of implementations.

  • Possible to adjustment of individual probe SH out data results.
  • Very useful interface design for artists.

Light Probe data具有在lightAsset中以二进制形式存储的特性。
此外,将根据最后一个场景中的信息Replace所有合并的场景的Light Probe信息。

Light Probe data has the property of being stored as binary in lightAsset.
It will also replace the light probe information of all merged scenes based on the information of the last scene.
The basic way to solve this problem is to save as TextAsset and manage each chunk separately.
It also implements an editor extension that allows the art team to modify the detailed SH values.

Source Code here

Virtual back lighting method test

Purpose of implementations.

  • Avoiding increasing draw-call.
    half2 RoL = max(0, half2(dot(ReflectionVector, DirectionalLightDirection), dot(ReflectionVector,;
    half2 PhongSpecular = PhongApprox(Roughness, RoL);
	half3 Directional = (Shadow * NoL) * DirectionalLightColor * (DiffuseColor + SpecularColor * PhongSpecular.x);
    half NoL2 = max(0, dot(normalWorld,;
	Directional += NoL2 * _LightColor1 * (DiffuseColor + SpecularColor * PhongSpecular.y);
    half3 halfDir = normalize(DirectionalLightDirection + s.viewdir);
    half RoL = max(0, dot(ReflectionVector, DirectionalLightDirection));
    half RoLDual = max(0, dot(ReflectionVector , -s.viewdir * halfDir));
    half3 Directional = (Shadow * NoL) * DirectionalLightColor * (DiffuseColor + SpecularColor * (PhongApprox(Roughness,RoL)));
    #if defined(USE_BACKLIGHT_SPECUAR)
        Directional = (Shadow * NoL) * DirectionalLightColor * (DiffuseColor + SpecularColor * (PhongApprox(Roughness,RoL) + (PhongApprox(Roughness, RoLDual))));

Development of three-dimensional pupil shader.

Purpose of implementations.

  • One draw call rendering by one mesh.
  • Supporting customization of eyeball pupil.
  • Simple refraction FX.
Refer to desired result of eyeball.

preview of result.


我为美术提供的最终目标是Horizon Zero Dawn。

在实现过程中,我们合并了两个Draw-call,最大限度地降低了精度,消除了所有的GGX运算,只使用Normalized Blinn-Phong Specular实现。

ALU在处理折射时有所增加,但删除了依赖于PBR的某些部分,使得整体ALU比正常的PBR少了一些,实现了CustomUV,从而减少了Pixel Shader Stage计算的部分。

现在,我们使用Replection Cubemap来实现油膜的光泽度,而在下一个优化版本中,将更改为Dual Parabolic Reflection,以进一步优化它。

创作原型时,使用Amplify Shader Editor来实现了它。

实现了Eye Refraction function, Mode写为Create的话之后在代码中会以函数形式来记录。


An important part of my development of the pupils was to pinpoint exactly what the final requirements of the art team were.

The first two meshes were used by the art team voluntarily, but the two draw-calls were consumed and there was no refraction, so we simply discussed with the art team and then used two specular One is calculated from the position of the actual solar light source and the other is calculated from the point of view of the camera, that is, the camera point of view.

I also had to find the overall feeling of completeness, and my final goal for the art team was Horizon Zero Dawn.

In the implementation process, we merged two draw-calls into one, and we reduced the precision as much as possible, eliminated all GGX operations, and implemented only with Normalized Blinn-Phong Specular.

The ALU increased slightly when refracting, but by removing some of the PBR dependent parts, the overall ALU was reduced compared to the normal PBR, and the CustomUV implementation reduced some of the computation on the Pixel Shader Stage.Currently, the reflection cube map is used to realize the gloss of the retina. In the next optimized version, it will be changed to Dual Parabolic Reflection to optimize it further.

通常,Cube-Map reflection主要用于角色选择窗口。
在实际游戏中,通过仅选择视觉上不明显的部分来删除命令,并制作一个Shader for LOD MATERIAL。当前尽量不把Sub shader划分为多个。

It is a good idea to develop prototypes with simple textures so that the art team knows their implementation intent.

At this point, I discussed further with the art team to discuss how to improve the pupil lighting process and to continue to develop.

Normally Cube-Map reflection is only used in the character selection window.

In real gameplay, we’ll only remove parts that aren’t visually obvious and remove commands and create another shader for LOD MATERIAL.We are not dividing the currently available Sub shader into several.

Energy conserved specular blinn-phong.

Energy conserved specular blinn-phong implementation record

Elements of Implementations

  • Energy conserved diffuse with specular.
  • Physically based Fresnel.
  • Environments Reflectance.
  • Ambient lighting adjustment controller.

Energy conserved diffuse with specular.

about energy conserved specular.

Method of Simple energy calculations of shader.
Applied Energy Conservation of blinn-phong specular.
//                      Lighting Functions                                   //
half3 LightingLambert(half3 lightColor, half3 lightDir, half3 normal)
    half NdotL = saturate(dot(normal, lightDir));
    return (1.0 / PI ) * lightColor * NdotL;
    return lightColor * NdotL;

half3 LightingSpecular(half3 lightColor, half3 lightDir, half3 normal, half3 viewDir, half4 specular, half smoothness)
    float3 halfVec = SafeNormalize(float3(lightDir) + float3(viewDir));
    half NdotH = saturate(dot(normal, halfVec)); //specualrDot
    half modifier = 0;
//PI is already defined location from "Packages/com.unity.render-pipelines.core/ShaderLibrary/Macros.hlsl"
    half norm = (smoothness + 2 ) / (2 * PI ); //refer document by ""
    modifier = pow(NdotH, smoothness) * norm;
    modifier = pow(NdotH, smoothness);

    half norm = (smoothness + 2)*(smoothness + 4 ) / ((8 * PI )*(2-(smoothness/2) + smoothness)); //refer document by ""
    modifier = pow(NdotH, smoothness) * norm;
    modifier = pow(NdotH, smoothness);
    half3 specularReflection = specular.rgb * modifier;
    return lightColor * specularReflection;

half3 LightingPhysicallyBased(BRDFData brdfData, half3 lightColor, half3 lightDirectionWS, half lightAttenuation, half3 normalWS, half3 viewDirectionWS)
    half NdotL = saturate(dot(normalWS, lightDirectionWS));
    half3 radiance = lightColor * (lightAttenuation * NdotL);
    return DirectBDRF(brdfData, normalWS, lightDirectionWS, viewDirectionWS) * radiance;

half3 LightingPhysicallyBased(BRDFData brdfData, Light light, half3 normalWS, half3 viewDirectionWS)
    return LightingPhysicallyBased(brdfData, light.color, light.direction, light.distanceAttenuation * light.shadowAttenuation, normalWS, viewDirectionWS);

half3 VertexLighting(float3 positionWS, half3 normalWS)
    half3 vertexLightColor = half3(0.0, 0.0, 0.0);

    uint lightsCount = GetAdditionalLightsCount();
    for (uint lightIndex = 0u; lightIndex < lightsCount; ++lightIndex)
        Light light = GetAdditionalLight(lightIndex, positionWS);
        half3 lightColor = light.color * light.distanceAttenuation;
        vertexLightColor += LightingLambert(lightColor, light.direction, normalWS);

    return vertexLightColor;

Energy conserved experimental code block.

Simulate of Specular Reflectance

Applied Ambient Contrast
half3 GlossyEnvironmentReflectionExt(half3 reflectVector, half smoothness, half occlusion)
            #if !defined(_ENVIRONMENTREFLECTIONS_OFF)
                half mip = smoothness;
                half4 encodedIrradiance = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectVector, mip);
            #if !defined(UNITY_USE_NATIVE_HDR)
                half3 irradiance = DecodeHDREnvironment(encodedIrradiance, unity_SpecCube0_HDR);
                half3 irradiance = encodedIrradiance.rbg;
                return irradiance * occlusion;
            #endif // GLOSSY_REFLECTIONS
                return _GlossyEnvironmentColor.rgb * occlusion;
half4 UniversalFragmentBlinnPhongExtend(InputData inputData, half3 reflectVector,  half3 diffuse, half4 specularGloss, half smoothness, half reflectionRoughness,  half3 emission, half occlusion,half alpha)
                Light mainLight = GetMainLight(inputData.shadowCoord);
                MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, half4(0, 0, 0, 0));
                half3 attenuatedLightColor = mainLight.color * (mainLight.distanceAttenuation * mainLight.shadowAttenuation);
                half3 diffuseColor = 1;
                diffuseColor = inputData.bakedGI + LightingLambertExt(attenuatedLightColor, mainLight.direction, inputData.normalWS);
                diffuseColor *=occlusion;
                half3 specularColor = LightingSpecularExtend(attenuatedLightColor, mainLight.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);
                diffuseColor +=GlossyEnvironmentReflectionExt(reflectVector, reflectionRoughness, occlusion);
            #ifdef _ADDITIONAL_LIGHTS
                uint pixelLightCount = GetAdditionalLightsCount();
                for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
                    Light light = GetAdditionalLight(lightIndex, inputData.positionWS);
                    half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
                    diffuseColor += LightingLambertExt(attenuatedLightColor, light.direction, inputData.normalWS);
                    specularColor += LightingSpecularExtend(attenuatedLightColor, light.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);
            #ifdef _ADDITIONAL_LIGHTS_VERTEX
                diffuseColor += inputData.vertexLighting;
                half3 finalColor = diffuseColor * diffuse + emission;
            #if defined(_SPECGLOSSMAP) || defined(_SPECULAR_COLOR)
                finalColor += specularColor;
                return half4(finalColor, alpha);

Specular Reflectance experimental code block.


Purpose of implementation.

  • Fully support to mobile devices such as opengl-es 2.x to 3.x.
  • Under limited of instruction counts 180.
  • Under limited texture fetching counts 6.
  • 完全支持移动设备,例如opengl-es 2.x至3.x.
  • 指令数限制下为180。
  • 在有限的纹理抓取下计数为6。

The game I’m developing now implements a point light that doesn’t work at all in the rendering core.

In order to achieve the permeation effect, the LUT of the Ramp method was used to express the depth of ice chunks inherently, and the Glitz phenomenon caused by scattering of ice or snow surface was implemented to give the user a visually interesting feeling.

目前正在开发的游戏在rendering core里Point light完全不能启用。



选择简单的方法在GetMainLightShadowParams() 函数中定义,在特定shader中放置关键字时,可以降低阴影浓度以进行处理。

In addition, the shadow density is weaker than normal objects because it is usually a transparent material of ice. In general, the shadow intensity of directional light is mostly 1, and all other objects are affected.I chose a simple method and defined it inside the GetMainLightShadowParams () function so that if I put keywords inside a specific shader, it handles it by lowering the shadow density.

In-dependency snow shader

Development of ice effect that can be calculated sufficiently with mobile hardware performance. This is mainly used for developing dungeon battle scenes.
Dungeon combat scenes are loaded independently and remove special effects related to weather changes or other environmental changes like other open scenes.
The overall performance was measured and the effect was expressed boldly.


//Parralex thickness shader here to start.
				float2 uv = input.uv.xy * float2( 1,1 ) + float2( 0,0 );
				float2 uv_BottomDepthMap = input.uv.xy * _BottomDepthMap_ST.xy +;

				float3 tanToWorld0 = float3( WorldSpaceTangent.x, WorldSpaceBiTangent.x, WorldSpaceNormal.x );
				float3 tanToWorld1 = float3( WorldSpaceTangent.y, WorldSpaceBiTangent.y, WorldSpaceNormal.y );
				float3 tanToWorld2 = float3( WorldSpaceTangent.z, WorldSpaceBiTangent.z, WorldSpaceNormal.z );
				float3 ViewDirTS =  tanToWorld0 * WorldSpaceViewDirection.x + tanToWorld1 * WorldSpaceViewDirection.y  + tanToWorld2 * WorldSpaceViewDirection.z;
				ViewDirTS = normalize(ViewDirTS); //Parallex ice ground viewDir according to tangent
				float4 mask_Var = tex2D(_Mask , uv);
				float2 plx_bottom = ( ( mask_Var.b - 1 ) * ( ViewDirTS.xy / ViewDirTS.z ) * OutterDepthScale ) + uv;
				float2 uv_MiddleDepthMap = input.uv.xy * _MiddleDepthMap_ST.xy +;
				float2 plx_middle = ( ( mask_Var.g - 1 ) * ( ViewDirTS.xy / ViewDirTS.z ) * _MiddleDepthScale ) + uv;
				float4 combinedMiddleBottomAlbedo = lerp( ( tex2D( _BottomMap, plx_bottom ) * _BottomDark ) , ( tex2D( _MiddleMap, plx_middle ) * _MidDark ) , tex2D( _Mask, plx_middle ).r);
				float4 albedoCombined = lerp( combinedMiddleBottomAlbedo , half4(0,0,0,1) , mask_Var.r);
				float2 uv_normalTop = input.uv.xy * _normalTop_ST.xy +;
				float2 uv_RoughnessMap = input.uv.xy * _RoughnessMap_ST.xy +;
				float4 roughness_Var = tex2D( _RoughnessMap, uv_RoughnessMap );
				float3 normalCombined = lerp( float3(0,0,1) , UnpackNormalScale( tex2D( _normalTop, uv_normalTop ), 1.0f ) , ( mask_Var.g * roughness_Var ).rgb);