39.高级纹理-渲染纹理-玻璃效果-基础实现
39.1 知识点
知识回顾
玻璃效果基本原理
在渲染玻璃效果之前,先捕获当前屏幕内容并保存到一张渲染纹理当中。在之后的 Shader 处理中利用该渲染纹理进行采样,参与最终的颜色计算,从而实现各种玻璃效果。
玻璃效果实现使用的新知识点
- 捕获屏幕内容的 GrabPass。
- 使用
ComputeGrabScreenPos
方法将裁剪坐标转换为屏幕坐标。 - 捕获纹理进行采样时,进行一些偏移计算,模拟折射效果。
渲染标签中的渲染队列
- 基本语法
Tags { "Queue" = "标签值" }
- 常用Unity预先定义好的渲染队列标签值
Background背景:队列号为 1000,用于最早被渲染的物体,如天空盒或背景。
Tags { "Queue" = "Background" }
Geometry几何:队列号为 2000,不透明的几何体通常使用该队列,当没有声明渲染队列时,Unity会默认使用这个队列。
Tags { "Queue" = "Geometry" }
AlphaTest透明测试:队列号为 2450,用于有透明通道且需要 Alpha 测试的几何体。当所有Geometry队列实体绘制完后再绘制AlphaTest队列,效率更高。
Tags { "Queue" = "AlphaTest" }
Transparent透明:队列号为 3000,该队列中几何体按照由远到近的顺序进行绘制,半透明物体的渲染队列,所有进行透明混合的几何体都应该使用该队列,如玻璃材质、粒子特效。
Tags { "Queue" = "Transparent" }
Overlay覆盖:队列号为 4000,用是放在最后渲染的队列,于叠加渲染的效果,比如镜头光晕等。
Tags { "Queue" = "Overlay" }
自定义队列:基于Unity预先定义好的这些渲染队列标签来进行加减运算来定义自己的渲染队列,例如:
Tags { "Queue" = "Geometry+1" } // 队列号为 2001 Tags { "Queue" = "Transparent-1" } // 队列号为 2999
自定义队列在一些特殊情况下,特别有用。比如 一些水的渲染 想要在不透明物体之后,半透明物体之前进行渲染,就可以自定义
如何让玻璃效果对象滞后渲染
我们上节课知道,在实现玻璃效果之前,需要先捕获当前屏幕内容并保存到一张渲染纹理当中。那么要保证玻璃效果对象后面的内容正确渲染,我们必须保证玻璃对象能够滞后渲染。
想要让一个对象滞后渲染,那么通过我们学习过的知识,自然的联想到了渲染标签 Tags 中的渲染队列 Queue。
因此对于玻璃效果对象,虽然它本质上是一个不透明物体,但是我们完全可以将它的渲染队列设置为 Transparent(透明的),保证它晚于背景队列、几何队列、透明测试队列之后再进行渲染。这时我们捕获的屏幕内容,将包含这些更早渲染的内容信息,便可以利用 GrabPass 捕获到相对正确的内容了。
基础玻璃效果实现
主要步骤
新建一个 Shader,取名
Lesson39_GlassBase
,复制反射基础实现Lesson26_ReflectBase
中的代码。修改相关代码:
- 修改属性代码(同时修改 CG 中的属性映射):
- 加入主纹理属性(用于处理物体本身颜色)
- 加入立方体纹理属性(用于处理反射)
- 将反射率改为折射程度(用于控制折射程度,0 表示完全不折射(相当于完全反射),1 表示完全折射(相当于完全透明))
- 修改渲染队列为
Transparent
,但 RenderType 渲染类型不修改,因为它本质上还是一个不透明物体。以后使用着色器替换功能时,可以在被正常渲染。 - 加入
GrabPass
,抓取屏幕图像并存储渲染纹理。 - 修改
v2f
结构体:- 加入相对屏幕坐标
float4
类型成员。 - 加入
uv
,用于采样物体颜色纹理。
- 加入相对屏幕坐标
- 修改顶点着色器:
- 使用
ComputeGrabScreenPos
方法,计算结构体中相对屏幕坐标。 - 计算纹理的缩放偏移。
- 使用
- 修改片元着色器:
- 用
uv
采样主纹理颜色。 - 将屏幕坐标转为裁剪坐标(0~1 范围内),对捕获纹理进行采样。
- 用反射在立方体纹理中进行采样,用结果乘以主纹理颜色,进行颜色叠加。
- 用折射程度参与最终的颜色计算,折射程度值的变化决定了最终表现效果在完全反射到完全折射之间的过渡。
- 用
- 修改属性代码(同时修改 CG 中的属性映射):
新建一个Shader取名Lesson39_GlassBase,复制反射基础实现Lesson26_ReflectBase中的代码
Shader "Unlit/Lesson39_GlassBase"
{
Properties
{
//立方体纹理
_Cube("Cubemap", Cube) = ""{}
//反射率
_Reflectivity("Reflectivity", Range(0,1)) = 1
}
SubShader
{
Tags
{
"RenderType"="Opaque" // 设置渲染类型为不透明(Opaque)
"Queue"="Geometry" // 设置渲染队列为Geometry,通常用于普通几何物体
}
Pass
{
// 设置LightMode标签,指定光照模式为ForwardBase
Tags
{
"LightMode"="ForwardBase" // 指定使用正向渲染中的基本光照模式
}
CGPROGRAM
#pragma vertex vert // 指定顶点着色器函数为vert
#pragma fragment frag // 指定片元着色器函数为frag
// 包含Unity内置的CG库和光照相关的库
#include "UnityCG.cginc"
#include "Lighting.cginc"
// 立方体纹理属性
samplerCUBE _Cube;
// 反射率属性
float _Reflectivity;
struct v2f
{
float4 pos:SV_POSITION; //裁剪空间下的顶点坐标
//世界空间下的反射向量
//我们将把反射向量的计算放在顶点着色器函数中 节约性能 表现效果也不会太差 肉眼几乎分辨不出来
float3 worldRefl:TEXCOORD0;
};
v2f vert(appdata_base appdata_base)
{
v2f v2f;
//顶点坐标转换
v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
//计算反射光向量
//1.计算世界空间下法线向量
float3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);
//2.世界空间下的顶点坐标
fixed3 worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
//3.计算视角方向 内部是用摄像机位置 - 世界坐标位置 计算反射向量时要取反
fixed3 worldViewDir = UnityWorldSpaceViewDir(worldPos);
//4.计算反射向量
v2f.worldRefl = reflect(-worldViewDir, worldNormal);
return v2f;
}
fixed4 frag(v2f v2f):SV_TARGET
{
//对立方体纹理利用对应的反射向量进行采样
fixed4 cubemapColor = texCUBE(_Cube, v2f.worldRefl);
//用采样颜色 * 反射率 决定最终的颜色效果
return cubemapColor * _Reflectivity;
}
ENDCG
}
}
}
修改属性代码(同时修改CG中的属性映射),加入主纹理属性(用于处理物体本身颜色),加入立方体纹理属性(用于处理反射),将反射率改为折射程度(用于控制折射程度 0表示完全不折射-相当于完全反射,1表示完全折射-相当于完全透明)
Properties
{
//主纹理
_MainTex("MainTex", 2D) = ""{}
//立方体纹理
_Cube("Cubemap", Cube) = ""{}
//折射程度 0~1 0代表完全反射(完全不折射)1代表完全折射(透明效果 相当于光全部进入了内部)
_RefractAmount("RefractAmount", Range(0,1)) = 1
}
sampler2D _MainTex;
float4 _MainTex_ST;
samplerCUBE _Cube;
float _RefractAmount;
修改渲染队列为Transparent,但是RenderType渲染类型不修改,因为它本质上还是一个不透明物体,目的是以后使用着色器替换功能时,可以在被正常渲染
//将渲染队列改为透明的 目的是让玻璃对象 滞后渲染
//能够捕获到之前正确的屏幕图像
Tags
{
"RenderType"="Opaque" "Queue"="Transparent"
}
加入GrabPass,抓取屏幕图像存储渲染纹理,同时要声明对应的属性映射
//使用它来捕获当前屏幕内容 并存储到默认的渲染纹理变量中
GrabPass {}
//GrabPass默认存储的纹理变量 这个是规则
sampler2D _GrabTexture;
修改v2f结构体加入相对屏幕坐标float4类型成员和用于采样物体颜色纹理的uv
struct v2f
{
//裁剪空间下的顶点坐标
float4 pos:SV_POSITION;
//用于存储从屏幕图像中采样的坐标(顶点相对于屏幕的位置)
float4 grabPos:TEXCOORD0;
//用于在颜色纹理中采样的UV坐标
float2 uv:TEXCOORD1;
//世界空间下的反射向量
//我们将把反射向量的计算放在顶点着色器函数中 节约性能 表现效果也不会太差 肉眼几乎分辨不出来
float3 worldRefl:TEXCOORD2;
};
修改顶点函数, 使用ComputeGrabScreenPos方法,计算结构体中相对屏幕坐标。同时计算纹理的缩放偏移
v2f vert(appdata_base appdata_base)
{
v2f v2f;
//顶点坐标转换
v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
//屏幕坐标转换相关的内容 注意传进去的是裁剪空间下的坐标
v2f.grabPos = ComputeGrabScreenPos(v2f.pos);
//uv坐标计算相关的内容
v2f.uv.xy = appdata_base.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//计算反射光向量
//1.计算世界空间下法线向量
float3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);
//2.世界空间下的顶点坐标
fixed3 worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
//3.计算视角方向 内部是用摄像机位置 - 世界坐标位置
fixed3 worldViewDir = UnityWorldSpaceViewDir(worldPos);
//4.计算反射向量
v2f.worldRefl = reflect(-worldViewDir, worldNormal);
return v2f;
}
修改片元函数,用uv采样主纹理颜色,用反射在立方体纹理中进行采样,用结果乘以主纹理颜色,进行颜色叠加。将屏幕坐标转为裁剪坐标01范围内,对捕获纹理进行采样。 用折射程度参与最终的颜色计算,折射程度值的变化决定了最终表现效果在 完全反射完全折射 之间变化
fixed4 frag(v2f v2f):SV_TARGET
{
//反射颜色相关的计算 会叠加主纹理颜色
//对颜色纹理进行采样
fixed4 mainTex = tex2D(_MainTex, v2f.uv);
//将反射颜色和主纹理颜色进行叠加
fixed4 reflColor = texCUBE(_Cube, v2f.worldRefl) * mainTex;
//折射相关的颜色
//其实就是从我们抓取的 屏幕渲染纹理中进行采样 参与计算
//抓取纹理中的颜色信息 相当于是这个玻璃对象后面的颜色
//利用透视除法 将屏幕坐标转换到 0~1范围内 然后再进行采样
fixed2 screenUV = v2f.grabPos.xy / v2f.grabPos.w;
//从捕获的渲染纹理中进行采样 获取后面的颜色
// 在 _RefractAmount 接近 1 时,玻璃材质会逐渐变得透明,展示出后方的图像,实现玻璃折射的视觉效果。
fixed4 grabColor = tex2D(_GrabTexture, screenUV);
//折射程度 0~1 0代表完全反射(完全不折射)1代表完全折射(透明效果 相当于光全部进入了内部)
float4 color = reflColor * (1 - _RefractAmount) + grabColor * _RefractAmount;
return color;
}
创建shader赋值,可以看到玻璃效果,当折射程度为0完全反射,折射程度为1完全透明
自定义折射效果
目前的问题
目前我们利用屏幕坐标在抓取纹理中采样并没有处理偏移,因此呈现出来的效果像是在透明和半透明之间切换。我们可以自定义一些简单的偏移计算规则,让最终的采样位置发生偏移,从而模拟折射效果。
想要有折射效果,可以在片元函数采样grabColor之前进行xy屏幕坐标的偏移。规则是自己定的,看起来正确即可。比如结合折射程度计算,当有一定折射但又不是完全折射时,向右偏移在采样,达到折射的效果
在片元函数中自定义偏移规则:
fixed4 frag(v2f v2f):SV_TARGET
{
//反射颜色相关的计算 会叠加主纹理颜色
//对颜色纹理进行采样
fixed4 mainTex = tex2D(_MainTex, v2f.uv);
//将反射颜色和主纹理颜色进行叠加
fixed4 reflColor = texCUBE(_Cube, v2f.worldRefl) * mainTex;
//折射相关的颜色
//其实就是从我们抓取的 屏幕渲染纹理中进行采样 参与计算
//抓取纹理中的颜色信息 相当于是这个玻璃对象后面的颜色
//想要有折射效果 可以在采样grabColor之前 进行xy屏幕坐标的偏移
//可以自己定规则
//比如折射程度为1时 相当于没有偏移 折射程度越小 偏移越大
float2 offset = 1 - _RefractAmount;
//xy偏移一个位置 自定义的 这样当有一定折射但又不是完全折射时 向右偏移
v2f.grabPos.xy = v2f.grabPos.xy - offset / 100;
//利用透视除法 将屏幕坐标转换到 0~1范围内 然后再进行采样
fixed2 screenUV = v2f.grabPos.xy / v2f.grabPos.w;
//从捕获的渲染纹理中进行采样 获取后面的颜色
// 在 _RefractAmount 接近 1 时,玻璃材质会逐渐变得透明,展示出后方的图像,实现玻璃折射的视觉效果。
fixed4 grabColor = tex2D(_GrabTexture, screenUV);
//折射程度 0~1 0代表完全反射(完全不折射)1代表完全折射(透明效果 相当于光全部进入了内部)
float4 color = reflColor * (1 - _RefractAmount) + grabColor * _RefractAmount;
return color;
}
39.2 知识点代码
Lesson39_高级纹理_渲染纹理_玻璃效果_基础实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson39_高级纹理_渲染纹理_玻璃效果_基础实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾
//1.玻璃效果基本原理
// 在渲染玻璃效果之前,先捕获当前屏幕内容并保存到一张渲染纹理当中,
// 在之后的Shader处理中利用该渲染纹理进行采样,参与最终的颜色计算,实现
// 各种玻璃效果。
//2.玻璃效果实现使用的新知识点
// 捕获屏幕内容的GrabPass;
// 裁剪坐标转屏幕坐标的ComputeGrabScreenPos方法
// 捕获纹理进行采样时,进行一些偏移计算,模拟折射效果
//3.渲染标签中的 渲染队列
// Tags{ "Queue" = "标签值" }
// 常用Unity预先定义好的渲染队列标签值:
// 3-1.Background(背景)(队列号:1000)
// 最早被渲染的物体的队列,一般用来渲染天空盒或者背景
// Tags{ "Queue" = "Background" }
// 3-2.Geometry(几何)(队列号:2000)
// 不透明的几何体通常使用该队列,当没有声明渲染队列时,Unity会默认使用这个队列
// Tags{ "Queue" = "Geometry" }
// 3-3.AlphaTest(透明测试)(队列号:2450)
// 有透明通道的,需要进行Alpha测试的几何体会使用该队列
// 当所有Geometry队列实体绘制完后再绘制AlphaTest队列,效率更高
// Tags{ "Queue" = "AlphaTest" }
// 3-4.Transparent(透明的)(队列号:3000)
// 该队列中几何体按照由远到近的顺序进行绘制,半透明物体的渲染队列,所有进行透明混合的几何体都应该使用该队列
// 比如:玻璃材质,粒子特效等
// Tags{ "Queue" = "Transparent" }
// 3-5.Overlay(覆盖)(队列号:4000)
// 用是放在最后渲染的队列,于叠加渲染的效果,比如镜头光晕等
// Tags{ "Queue" = "Overlay" }
// 3-6.自定义队列
// 基于Unity预先定义好的这些渲染队列标签来进行加减运算来定义自己的渲染队列
// 比如:
// Tags{ "Queue" = "Geometry+1" } 代表的队列号就是 2001
// Tags{ "Queue" = "Transparent-1" } 代表的队列号就是 2999
// 自定义队列在一些特殊情况下,特别有用
// 比如 一些水的渲染 想要在不透明物体之后,半透明物体之前进行渲染,就可以自定义
#endregion
#region 知识点一 如何让玻璃效果对象滞后渲染
//我们上节课知道
//在实现玻璃效果之前,需要先捕获当前屏幕内容并保存到一张渲染纹理当中
//那么要保证玻璃效果对象后面的内容正确渲染,我们必须保证玻璃对象能够滞后渲染
//想要让一个对象滞后渲染,那么通过我们学习过的知识,自然的联想到了
//渲染标签Tags中的 渲染队列Queue
//因此对于玻璃效果对象,虽然它本质上是一个不透明物体
//但是我们完全可以将它的渲染队列设置为 Transparent(透明的)
//保证它晚于 背景队列、几何队列、透明测试队列 之后再进行渲染
//这时我们捕获的屏幕内容,将包含这些更早渲染的内容信息
//便可以利用GrabPass捕获到相对正确的内容了
#endregion
#region 知识点二 基础玻璃效果实现
//1.新建一个Shader取名Lesson39_GlassBase,复制反射基础实现Lesson26_ReflectBase中的代码
//2.修改相关代码
// 2-1:修改属性代码(同时修改CG中的属性映射)
// 加入主纹理属性(用于处理物体本身颜色)
// 加入立方体纹理属性(用于处理反射)
// 将反射率改为折射程度(用于控制折射程度 0表示完全不折射-相当于完全反射,1表示完全折射-相当于完全透明)
// 2-2:修改渲染队列为Transparent,
// 但是RenderType渲染类型不修改,因为它本质上还是一个不透明物体
// 以后使用着色器替换功能时,可以在被正常渲染
// 2-3:加入GrabPass,抓取屏幕图像存储渲染纹理
// 2-4:修改v2f结构体
// 加入相对屏幕坐标float4类型成员
// 加入uv,用于采样物体颜色纹理
// 2-5:修改顶点着色器
// 使用ComputeGrabScreenPos方法,计算结构体中相对屏幕坐标
// 计算纹理的缩放偏移
// 2-6:修改片元着色器
// 用uv采样主纹理颜色
// 将屏幕坐标转为裁剪坐标0~1范围内,对捕获纹理进行采样
// 用反射在立方体纹理中进行采样,用结果乘以主纹理颜色,进行颜色叠加
// 用折射程度参与最终的颜色计算,折射程度值的变化决定了最终表现效果在 完全反射~完全折射 之间变化
#endregion
#region 知识点三 自定义折射效果
//目前我们利用屏幕坐标在抓取纹理中采样并没有处理偏移
//因此呈现出来的效果像是在透明和半透明之前切换
//我们可以自定义一些简单的偏移计算规则
//让最终的采样位置发生偏移,模拟折射效果
#endregion
}
}
Lesson39_GlassBase.shader
Shader "Unlit/Lesson39_GlassBase"
{
Properties
{
//主纹理
_MainTex("MainTex", 2D) = ""{}
//立方体纹理
_Cube("Cubemap", Cube) = ""{}
//折射程度 0~1 0代表完全反射(完全不折射)1代表完全折射(透明效果 相当于光全部进入了内部)
_RefractAmount("RefractAmount", Range(0,1)) = 1
}
SubShader
{
//将渲染队列改为透明的 目的是让玻璃对象 滞后渲染
//能够捕获到之前正确的屏幕图像
Tags
{
"RenderType"="Opaque" "Queue"="Transparent"
}
//使用它来捕获当前屏幕内容 并存储到默认的渲染纹理变量中
GrabPass {}
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
samplerCUBE _Cube;
float _RefractAmount;
//GrabPass默认存储的纹理变量 这个是规则
sampler2D _GrabTexture;
struct v2f
{
//裁剪空间下的顶点坐标
float4 pos:SV_POSITION;
//用于存储从屏幕图像中采样的坐标(顶点相对于屏幕的位置)
float4 grabPos:TEXCOORD0;
//用于在颜色纹理中采样的UV坐标
float2 uv:TEXCOORD1;
//世界空间下的反射向量
//我们将把反射向量的计算放在顶点着色器函数中 节约性能 表现效果也不会太差 肉眼几乎分辨不出来
float3 worldRefl:TEXCOORD2;
};
v2f vert(appdata_base appdata_base)
{
v2f v2f;
//顶点坐标转换
v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
//屏幕坐标转换相关的内容 注意传进去的是裁剪空间下的坐标
v2f.grabPos = ComputeGrabScreenPos(v2f.pos);
//uv坐标计算相关的内容
v2f.uv.xy = appdata_base.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//计算反射光向量
//1.计算世界空间下法线向量
float3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);
//2.世界空间下的顶点坐标
fixed3 worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
//3.计算视角方向 内部是用摄像机位置 - 世界坐标位置
fixed3 worldViewDir = UnityWorldSpaceViewDir(worldPos);
//4.计算反射向量
v2f.worldRefl = reflect(-worldViewDir, worldNormal);
return v2f;
}
fixed4 frag(v2f v2f):SV_TARGET
{
//反射颜色相关的计算 会叠加主纹理颜色
//对颜色纹理进行采样
fixed4 mainTex = tex2D(_MainTex, v2f.uv);
//将反射颜色和主纹理颜色进行叠加
fixed4 reflColor = texCUBE(_Cube, v2f.worldRefl) * mainTex;
//折射相关的颜色
//其实就是从我们抓取的 屏幕渲染纹理中进行采样 参与计算
//抓取纹理中的颜色信息 相当于是这个玻璃对象后面的颜色
//想要有折射效果 可以在采样grabColor之前 进行xy屏幕坐标的偏移
//可以自己定规则
//比如折射程度为1时 相当于没有偏移 折射程度越小 偏移越大
float2 offset = 1 - _RefractAmount;
//xy偏移一个位置 自定义的 这样当有一定折射但又不是完全折射时 向右偏移
v2f.grabPos.xy = v2f.grabPos.xy - offset / 100;
//利用透视除法 将屏幕坐标转换到 0~1范围内 然后再进行采样
fixed2 screenUV = v2f.grabPos.xy / v2f.grabPos.w;
//从捕获的渲染纹理中进行采样 获取后面的颜色
// 在 _RefractAmount 接近 1 时,玻璃材质会逐渐变得透明,展示出后方的图像,实现玻璃折射的视觉效果。
fixed4 grabColor = tex2D(_GrabTexture, screenUV);
//折射程度 0~1 0代表完全反射(完全不折射)1代表完全折射(透明效果 相当于光全部进入了内部)
float4 color = reflColor * (1 - _RefractAmount) + grabColor * _RefractAmount;
return color;
}
ENDCG
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com