[Unity3D] reflection and refraction

1 Preface

Cubemap and Skybox introduce the method of generating cube texture and making skybox. This article will use cube texture for sampling to achieve reflection, Fresnel reflection and refraction effects. In addition, this article also uses GrabPass to grab the screen image, instead of the cube texture, as the refraction sampling texture.

Cube texture sampling principle: starting from the coordinate origin of the world coordinate system, emit a ray and intersect with a cube with a side length of 1 (its center is at the coordinate origin, and each surface is perpendicular to the corresponding coordinate axis), and the pixel at the intersection position is Sampled pixels. The cube texture sampling function is as follows, cubemap is the cube texture, worldVec is the sampling direction vector in the world coordinate system, and color is the sampled color.

// Cube texture sampling
fixed4 color = texCUBE(cubemap, worldVec)

For the complete resources of this article, see→Unity3D reflection and refraction.

2 reflection

For any point on the model, the reflection color calculation method is: first calculate the vector pointing to the point of the camera, then calculate the reflection vector according to the normal information of the point, then use the reflection vector to perform texture sampling in the cubemap, and finally use the sampled The color is mixed with the diffuse color.

Reflect. shader

Shader "MyShader/Reflection" { // reflection
Properties {
_Color("Color Tint", Color) = (1, 1, 1, 1) // object color
_ReflectColor("Reflection Color", Color) = (1, 1, 1, 1) // Color of reflected light
_ReflectAmount("Reflect Amount", Range(0, 1)) = 1 // Reflection scale (for interpolation between diffuse and reflective)
_Cubemap("Reflection Cubemap", Cube) = "_Skybox" {} // cube texture
}

SubShader {
Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}

Pass {
Tags { "LightMode" = "ForwardBase" }

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Color; // object color
fixed4 _ReflectColor; // The color of the reflected light
fixed _ReflectAmount; // Reflection ratio (for interpolation between diffuse reflection and reflection)
samplerCUBE _Cubemap; // cube texture

struct a2v {
float4 vertex : POSITION; // Model space vertex coordinates
float3 normal : NORMAL; // model space normal phase vector
};

struct v2f {
float4 pos : SV_POSITION; // clip space vertex coordinates
float3 worldPos : TEXCOORD0; // World space vertex coordinates
fixed3 worldNormal : TEXCOORD1; // vertex normal vector
fixed3 worldViewDir : TEXCOORD2; // view vector (vertex points to camera)
fixed3 worldRefl : TEXCOORD3; // reflection vector
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // Convert model space vertex coordinates to clip space, equivalent to: mul(UNITY_MATRIX_MVP, v.vertex)
o.worldNormal = UnityObjectToWorldNormal(v.normal); // Calculate the vertex normal vector in the world space (normalized)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // Calculate the coordinates of vertices in world space
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // Calculate the viewing vector in world space (the vertex points to the camera)
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); // Calculate the reflection vector of the observation vector
return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal); // normal vector
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // light vector (vertex points to light source)
fixed3 worldViewDir = normalize(i.worldViewDir); // observation vector (vertex points to camera)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // ambient light color
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // diffuse light color
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb; // reflected light color
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount); // interpolation between diffuse reflection light and reflection light color
return fixed4(color, 1.0);
}

ENDCG
}
}

FallBack "Reflective/VertexLit"
}

_ReflectAmount value is 1, the reflection effect is as follows:

_ReflectAmount value from 0 to 1 gradient reflection effect is as follows:

Explanation: Even without the room model, the 5 objects in the middle reflect the room environment, because the textures they use are derived from Cubemap sampling, and the Cubemap, once generated, has nothing to do with the environment.

3 Fresnel reflection

Fresnel reflection describes an optical phenomenon, that is, when light hits the surface of an object, part of it is reflected, and part of it enters the interior of the object, where it is refracted or scattered. There is a certain ratio relationship between the reflected light and the incident angle (the smaller the incident angle, the less reflected light; the larger the incident angle, the more reflected light; when the incident angle is large to a certain value, total reflection will occur, That is, there is no refraction phenomenon), this ratio can be calculated by the Fresnel equation. The currently widely used Fresnel approximation equations mainly include: Schlick Fresnel approximation equation, Empricial Fresnel approximation equation.

1) Schlick Fresnel approximation equation

Explanation: F0 is the reflection coefficient, which is used to control the strength of Fresnel reflection. The user can set it according to the material characteristics of the object. v and n are the incident vector and the normal vector respectively.

2) Empricial Fresnel approximation equation

Note: bias, scale, and power are undetermined parameters, which can be set by the user according to the material characteristics of the object. v, n are the incident vector and the normal vector, respectively.

FresnelReflect.shader

Shader "MyShader/FresnelReflect" { // Fresnel reflection
Properties {
_Color("Color Tint", Color) = (1, 1, 1, 1) // object color
_FresnelScale("Fresnel Scale", Range(0, 1)) = 0.5 // Fresnel reflection coefficient scaling value
_Cubemap("Reflection Cubemap", Cube) = "_Skybox" {} // cube texture
}

SubShader {
Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}

Pass {
Tags { "LightMode" = "ForwardBase" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Color; // object color
fixed _FresnelScale; // Fresnel reflection coefficient scaling value
samplerCUBE _Cubemap; // cube texture

struct a2v {
float4 vertex : POSITION; // Model space vertex coordinates
float3 normal : NORMAL; // model space normal phase vector
};

struct v2f {
float4 pos : SV_POSITION; // clip space vertex coordinates
float3 worldPos : TEXCOORD0; // World space vertex coordinates
fixed3 worldNormal : TEXCOORD1; // vertex normal vector
fixed3 worldViewDir : TEXCOORD2; // view vector (vertex points to camera)
fixed3 worldRefl : TEXCOORD3; // reflection vector
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // Convert model space vertex coordinates to clip space, equivalent to: mul(UNITY_MATRIX_MVP, v.vertex)
o.worldNormal = UnityObjectToWorldNormal(v.normal); // Calculate the vertex normal vector in the world space (normalized)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // Calculate the coordinates of vertices in world space
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // Calculate the viewing vector in world space (the vertex points to the camera)
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); // Calculate the reflection vector of the observation vector
return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal); // normal vector
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // light vector (vertex points to light source)
fixed3 worldViewDir = normalize(i.worldViewDir); // observation vector (vertex points to camera)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // ambient light color
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // diffuse light color
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb; // reflected light color
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5); // Fresnel reflection coefficient
fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)); // interpolation between diffuse light and reflected light color
return fixed4(color, 1.0);
}

ENDCG
}
}

FallBack "Reflective/VertexLit"
}

The Fresnel reflection effect is as follows:

Explanation: Even without the room model, the 5 objects in the middle reflect the room environment, because the textures they use are derived from Cubemap sampling, and the Cubemap, once generated, has nothing to do with the environment.

4 Refraction

For any point on the model, the refraction color calculation method is (only one refraction is considered, two refractions will occur in the real world): first calculate the vector of the camera pointing to the point, and then according to the normal information and the refractive index ratio of the point Calculate the refraction vector, then use the refraction vector to sample the texture in the cubemap, and finally use the sampled color and the diffuse color to blend.

Refract. shader

Shader "MyShader/Refraction" { // refraction
Properties {
_Color("Color Tint", Color) = (1, 1, 1, 1) // object color
_RefractColor("Refraction Color", Color) = (1, 1, 1, 1) // color of refracted light
_RefractAmount("Refraction Amount", Range(0, 1)) = 1 // Refraction scale (for interpolation between diffuse and refraction)
_RefractRatio("Refraction Ratio", Range(0.1, 1)) = 0.5 // Refraction ratio (refractive index of incident medium/refractive index of refracted medium)
_Cubemap("Refraction Cubemap", Cube) = "_Skybox" {} // cube texture
}

SubShader {
Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}

Pass {
Tags { "LightMode" = "ForwardBase" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Color; // object color
fixed4 _RefractColor; // The color of refracted light
float _RefractAmount; // Refraction ratio (for interpolation between diffuse reflection and refraction)
fixed _RefractRatio; // Refractive ratio (refractive index of incident medium/refractive index of refractive medium)
samplerCUBE _Cubemap; // cube texture

struct a2v {
float4 vertex : POSITION; // Model space vertex coordinates
float3 normal : NORMAL; // model space normal phase vector
};

struct v2f {
float4 pos : SV_POSITION; // clip space vertex coordinates
float3 worldPos : TEXCOORD0; // World space vertex coordinates
fixed3 worldNormal : TEXCOORD1; // vertex normal vector
fixed3 worldViewDir : TEXCOORD2; // view vector (vertex points to camera)
fixed3 worldRefr : TEXCOORD3; // refraction vector
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // Convert model space vertex coordinates to clip space, equivalent to: mul(UNITY_MATRIX_MVP, v.vertex)
o.worldNormal = UnityObjectToWorldNormal(v.normal); // Calculate the vertex normal vector in the world space (normalized)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // Calculate the coordinates of vertices in world space
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // Calculate the viewing vector in world space (the vertex points to the camera)
o.worldRefr = refract(-normalize(o.worldViewDir), o.worldNormal, _RefractRatio); // Calculate the refraction vector of the viewing vector
return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal); // normal vector
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // light vector (vertex points to light source)
fixed3 worldViewDir = normalize(i.worldViewDir); // observation vector (vertex points to camera)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // ambient light color
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // diffuse light color
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb; // refracted light color
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount); // interpolation between diffuse reflection light and refraction light color
return fixed4(color, 1.0);
}

ENDCG
}
}

FallBack "Reflective/VertexLit"
}

The _RefractAmount value is 1 and the refraction effect is as follows:

The _RefractAmount value is from 0 to 1. The gradient refraction effect is as follows:

Explanation: In the real world, when light is shot from the air into a translucent object and then out into the air, there will be two refractions, but Unity Shader renders pixel by pixel, and each pixel is rendered independently (convenient for GPU parallel computing, improving Rendering efficiency), when the light is refracted at the exit point of the object, the position and normal information of the incident point cannot be obtained, so the double refraction effect cannot be simulated.

5 Refraction based on GrabPass

In Section 4, refraction effects are realized based on Cubemap sampling, and in this section, refraction effects are realized based on GrabPass screen sampling.

GrabPass is used to obtain screen textures, and there are two forms:

// 1. In the subsequent pass, access the screen image through _GrabTexture. This method consumes more performance. Unity performs a screen image capture operation for each object that uses GrabPass
GrabPass {}
// 2. Access the screen image through TextureName in the subsequent Pass. This method has better performance. Unity will only perform a screen image capture operation for the first object using the TextureName texture in each frame. This texture can also be used in other Accessed in Pass
GrabPass { "TextureName" }

GrabPass is usually used to render transparent objects. Although the code does not contain mixed instructions, we still need to set the rendering queue of the object to a transparent queue (ie “Queue”=”Transparent”). Only in this way can we ensure that when rendering the object, all opaque objects have been drawn on the screen, so as to obtain the correct screen image.

GrabRefract.shader

Shader "MyShader/GrabRefract" { // Refraction based on GrabPass texture sampling
Properties {
_Color("Color Tint", Color) = (1, 1, 1, 1) // object color
_RefractColor("Refraction Color", Color) = (1, 1, 1, 1) // color of refracted light
_RefractAmount("Refraction Amount", Range(0, 1)) = 1 // Refraction scale (for interpolation between diffuse and refraction)
_RefractRatio("Refraction Ratio", Range(0.1, 1)) = 0.5 // Refraction ratio (refractive index of incident medium/refractive index of refracted medium)
}

SubShader {
// The render queue must be set to transparent to ensure that all opaque objects are rendered on the screen before this object
Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }
// Grab the screen image and store it in _RefractionTex as the texture for refraction sampling
GrabPass { "_RefractionTex" }

Pass {
Tags { "LightMode" = "ForwardBase" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Color; // object color
fixed4 _RefractColor; // The color of refracted light
float _RefractAmount; // Refraction ratio (for interpolation between diffuse reflection and refraction)
fixed _RefractRatio; // Refractive ratio (refractive index of incident medium/refractive index of refractive medium)
sampler2D _RefractionTex; // The screen image captured by GrabPass, used as the texture of refraction sampling

struct a2v {
float4 vertex : POSITION; // Model space vertex coordinates
float3 normal : NORMAL; // model space normal phase vector
};

struct v2f {
float4 pos : SV_POSITION; // clip space vertex coordinates
float4 scrPos : TEXCOORD0; // Screen space vertex coordinates (uv coordinates sampled by _RefractionTex)
float3 worldPos : TEXCOORD1; // World space vertex coordinates
fixed3 worldNormal : TEXCOORD2; // vertex normal vector
fixed3 worldViewDir : TEXCOORD3; // view vector (vertex points to camera)
fixed3 worldRefr : TEXCOORD4; // refraction vector
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // Convert model space vertex coordinates to clip space, equivalent to: mul(UNITY_MATRIX_MVP, v.vertex)
o.scrPos = ComputeGrabScreenPos(o.pos); // Screen space vertex coordinates (uv coordinates sampled by _RefractionTex)
o.worldNormal = UnityObjectToWorldNormal(v.normal); // Calculate the vertex normal vector in the world space (normalized)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // Calculate the coordinates of vertices in world space
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // Calculate the viewing vector in world space (the vertex points to the camera)
o.worldRefr = refract(-normalize(o.worldViewDir), o.worldNormal, _RefractRatio); // Calculate the refraction vector of the viewing vector
return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal); // normal vector
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // light vector (vertex points to light source)
fixed3 worldViewDir = normalize(i.worldViewDir); // observation vector (vertex points to camera)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // ambient light color
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // diffuse light color
float offset = 1 - dot(-worldViewDir, normalize(i.worldRefr)); // refraction offset
float2 cameraViewDir = normalize(mul(unity_MatrixV, float4(worldViewDir, 0)).xy); // Observation vector coordinates in the observation coordinate system
i.scrPos.xy = i.scrPos.xy + cameraViewDir * offset; // Offset screen coordinates corresponding to vertices (screen texture sampling coordinates)
fixed3 refraction = tex2D(_RefractionTex, i.scrPos.xy / i.scrPos.w).rgb; // refracted light color
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount); // interpolation between diffuse reflection light and refraction light color
return fixed4(color, 1.0);
}

ENDCG
}
}

FallBack "Reflective/VertexLit"
}

The _RefractAmount value is 1 and the refraction effect is as follows:

The _RefractAmount value is from 0 to 1. The gradient refraction effect is as follows: