经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » unity » 查看文章
【Unity3D】反射和折射
来源:cnblogs  作者:little_fat_sheep  时间:2023/8/9 9:14:28  对本文有异议

1 前言

? 立方体纹理(Cubemap)和天空盒子(Skybox)中介绍了生成立方体纹理和制作天空盒子的方法,本文将使用立方体纹理进行采样,实现反射、菲涅耳反射和折射效果。另外,本文还使用了 GrabPass 抓取屏幕图像,替代立方体纹理,作为折射的采样纹理。

? 立方体纹理采样原理:从世界坐标系的坐标原点出发,发射一条射线,与边长为 1 的立方体相交(其中心在坐标原点,并且每个面与对应坐标轴垂直),交点位置的像素即为采样的像素。立方体纹理采样函数如下,cubemap 为立方体纹理,worldVec 为世界坐标系中采样方向向量,color 为采样的颜色。

  1. // 立方体纹理采样
  2. fixed4 color = texCUBE(cubemap, worldVec)

? 本文完整资源见→Unity3D反射和折射

2 反射

? 对于模型上的任意一点,其反射颜色计算方法为:首先计算相机指向该点的向量,再根据该点的法线信息计算反射向量,接着使用反射向量在 cubemap 中进行纹理采样,最后使用采样后的颜色和漫反射颜色进行混合。

? Reflect.shader

  1. Shader "MyShader/Reflection" { // 反射
  2. Properties {
  3. _Color("Color Tint", Color) = (1, 1, 1, 1) // 物体颜色
  4. _ReflectColor("Reflection Color", Color) = (1, 1, 1, 1) // 反射光的颜色
  5. _ReflectAmount("Reflect Amount", Range(0, 1)) = 1 // 反射比例(用于漫反射和反射之间插值)
  6. _Cubemap("Reflection Cubemap", Cube) = "_Skybox" {} // 立方体纹理
  7. }
  8. SubShader{
  9. Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}
  10. Pass {
  11. Tags { "LightMode" = "ForwardBase" }
  12. CGPROGRAM
  13. #pragma vertex vert
  14. #pragma fragment frag
  15. #include "Lighting.cginc"
  16. fixed4 _Color; // 物体颜色
  17. fixed4 _ReflectColor; // 反射光的颜色
  18. fixed _ReflectAmount; // 反射比例(用于漫反射和反射之间插值)
  19. samplerCUBE _Cubemap; // 立方体纹理
  20. struct a2v {
  21. float4 vertex : POSITION; // 模型空间顶点坐标
  22. float3 normal : NORMAL; // 模型空间法相向量
  23. };
  24. struct v2f {
  25. float4 pos : SV_POSITION; // 裁剪空间顶点坐标
  26. float3 worldPos : TEXCOORD0; // 世界空间顶点坐标
  27. fixed3 worldNormal : TEXCOORD1; // 顶点法线向量
  28. fixed3 worldViewDir : TEXCOORD2; // 观察向量(顶点指向相机)
  29. fixed3 worldRefl : TEXCOORD3; // 反射向量
  30. };
  31. v2f vert(a2v v) {
  32. v2f o;
  33. o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
  34. o.worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量(已归一化)
  35. o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 计算世界空间中顶点坐标
  36. o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // 计算世界空间中观察向量(顶点指向相机)
  37. o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); // 计算观察向量的反射向量
  38. return o;
  39. }
  40. fixed4 frag(v2f i) : SV_Target {
  41. fixed3 worldNormal = normalize(i.worldNormal); // 法线向量
  42. fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 灯光向量(顶点指向光源)
  43. fixed3 worldViewDir = normalize(i.worldViewDir); // 观察向量(顶点指向相机)
  44. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光颜色
  45. fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // 漫反射光颜色
  46. fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb; // 反射光颜色
  47. fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount); // 漫反射光与反射光颜色进行插值
  48. return fixed4(color, 1.0);
  49. }
  50. ENDCG
  51. }
  52. }
  53. FallBack "Reflective/VertexLit"
  54. }

? _ReflectAmount 值为 1 反射效果如下:

img

? _ReflectAmount 值由 0 至 1 渐变反射效果如下:

img

? 说明:即使没有房间模型,中间的 5 个物体也会反射房间环境,这是因为它们使用的纹理源于 Cubemap 采样,而 Cubemap 一旦生成,就与环境无关。

3 菲涅耳反射

? 菲涅耳反射描述了一种光学现象,即当光线射到物体表面时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射角度存在一定的比率关系(入射角越小,反射的光越少;入射角越大,反射的光越多;当入射角大到某个值时,会发生全反射,即没有折射现象),这个比值可以通过菲涅耳等式计算得到。当前应用比较广泛的菲涅耳近似等式主要有:Schlick 菲涅耳近似等式、Empricial 菲涅耳近似等式。

? 1)Schlick 菲涅耳近似等式

img

? 说明:F0 为反射系数,用于控制菲涅耳反射的强度,用户可以根据物体材质特性进行设定,v、n 分别为入射向量和法线向量。

? 2)Empricial 菲涅耳近似等式

img

? 说明:bias、scale、power 都是待定参数,用户可以根据物体材质特性进行设定,v、n 分别为入射向量和法线向量。

? FresnelReflect.shader

  1. Shader "MyShader/FresnelReflect" { // 菲涅耳反射
  2. Properties {
  3. _Color("Color Tint", Color) = (1, 1, 1, 1) // 物体颜色
  4. _FresnelScale("Fresnel Scale", Range(0, 1)) = 0.5 // 菲涅耳反射系数缩放值
  5. _Cubemap("Reflection Cubemap", Cube) = "_Skybox" {} // 立方体纹理
  6. }
  7. SubShader{
  8. Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}
  9. Pass {
  10. Tags { "LightMode" = "ForwardBase" }
  11. CGPROGRAM
  12. #pragma vertex vert
  13. #pragma fragment frag
  14. #include "Lighting.cginc"
  15. fixed4 _Color; // 物体颜色
  16. fixed _FresnelScale; // 菲涅耳反射系数缩放值
  17. samplerCUBE _Cubemap; // 立方体纹理
  18. struct a2v {
  19. float4 vertex : POSITION; // 模型空间顶点坐标
  20. float3 normal : NORMAL; // 模型空间法相向量
  21. };
  22. struct v2f {
  23. float4 pos : SV_POSITION; // 裁剪空间顶点坐标
  24. float3 worldPos : TEXCOORD0; // 世界空间顶点坐标
  25. fixed3 worldNormal : TEXCOORD1; // 顶点法线向量
  26. fixed3 worldViewDir : TEXCOORD2; // 观察向量(顶点指向相机)
  27. fixed3 worldRefl : TEXCOORD3; // 反射向量
  28. };
  29. v2f vert(a2v v) {
  30. v2f o;
  31. o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
  32. o.worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量(已归一化)
  33. o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 计算世界空间中顶点坐标
  34. o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // 计算世界空间中观察向量(顶点指向相机)
  35. o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); // 计算观察向量的反射向量
  36. return o;
  37. }
  38. fixed4 frag(v2f i) : SV_Target {
  39. fixed3 worldNormal = normalize(i.worldNormal); // 法线向量
  40. fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 灯光向量(顶点指向光源)
  41. fixed3 worldViewDir = normalize(i.worldViewDir); // 观察向量(顶点指向相机)
  42. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光颜色
  43. fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // 漫反射光颜色
  44. fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb; // 反射光颜色
  45. fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5); // 菲涅耳反射系数
  46. fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)); // 漫反射光与反射光颜色进行插值
  47. return fixed4(color, 1.0);
  48. }
  49. ENDCG
  50. }
  51. }
  52. FallBack "Reflective/VertexLit"
  53. }

? 菲涅耳反射效果如下:

img

? 说明:即使没有房间模型,中间的 5 个物体也会反射房间环境,这是因为它们使用的纹理源于 Cubemap 采样,而 Cubemap 一旦生成,就与环境无关。

4 折射

? 对于模型上的任意一点,其折射颜色计算方法为(仅考虑 1 次折射,现实世界会发生 2 次折射):首先计算相机指向该点的向量,再根据该点的法线信息和折射率比值计算折射向量,接着使用折射向量在 cubemap 中进行纹理采样,最后使用采样后的颜色和漫反射颜色进行混合。

? Refract.shader

  1. Shader "MyShader/Refraction" { // 折射
  2. Properties {
  3. _Color("Color Tint", Color) = (1, 1, 1, 1) // 物体颜色
  4. _RefractColor("Refraction Color", Color) = (1, 1, 1, 1) // 折射光的颜色
  5. _RefractAmount("Refraction Amount", Range(0, 1)) = 1 // 折射比例(用于漫反射和折射之间插值)
  6. _RefractRatio("Refraction Ratio", Range(0.1, 1)) = 0.5 // 折射比(入射介质折射率/折射介质折射率)
  7. _Cubemap("Refraction Cubemap", Cube) = "_Skybox" {} // 立方体纹理
  8. }
  9. SubShader{
  10. Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}
  11. Pass {
  12. Tags { "LightMode" = "ForwardBase" }
  13. CGPROGRAM
  14. #pragma vertex vert
  15. #pragma fragment frag
  16. #include "Lighting.cginc"
  17. fixed4 _Color; // 物体颜色
  18. fixed4 _RefractColor; // 折射光的颜色
  19. float _RefractAmount; // 折射比例(用于漫反射和折射之间插值)
  20. fixed _RefractRatio; // 折射比(入射介质折射率/折射介质折射率)
  21. samplerCUBE _Cubemap; // 立方体纹理
  22. struct a2v {
  23. float4 vertex : POSITION; // 模型空间顶点坐标
  24. float3 normal : NORMAL; // 模型空间法相向量
  25. };
  26. struct v2f {
  27. float4 pos : SV_POSITION; // 裁剪空间顶点坐标
  28. float3 worldPos : TEXCOORD0; // 世界空间顶点坐标
  29. fixed3 worldNormal : TEXCOORD1; // 顶点法线向量
  30. fixed3 worldViewDir : TEXCOORD2; // 观察向量(顶点指向相机)
  31. fixed3 worldRefr : TEXCOORD3; // 折射向量
  32. };
  33. v2f vert(a2v v) {
  34. v2f o;
  35. o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
  36. o.worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量(已归一化)
  37. o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 计算世界空间中顶点坐标
  38. o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // 计算世界空间中观察向量(顶点指向相机)
  39. o.worldRefr = refract(-normalize(o.worldViewDir), o.worldNormal, _RefractRatio); // 计算观察向量的折射向量
  40. return o;
  41. }
  42. fixed4 frag(v2f i) : SV_Target {
  43. fixed3 worldNormal = normalize(i.worldNormal); // 法线向量
  44. fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 灯光向量(顶点指向光源)
  45. fixed3 worldViewDir = normalize(i.worldViewDir); // 观察向量(顶点指向相机)
  46. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光颜色
  47. fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // 漫反射光颜色
  48. fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb; // 折射光颜色
  49. fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount); // 漫反射光与折射光颜色进行插值
  50. return fixed4(color, 1.0);
  51. }
  52. ENDCG
  53. }
  54. }
  55. FallBack "Reflective/VertexLit"
  56. }

? _RefractAmount 值为 1 折射效果如下:

img

? _RefractAmount 值由 0 至 1 渐变折射效果如下:

img

? 说明:现实世界中,光线从空气射入半透明物体,再射出到空气中,会发生 2 次折射,但是 Unity Shader 是逐像素渲染,每个像素都是独立渲染的(便于 GPU 并行计算,提高渲染效率),光线在物体出射点发生折射时,无法获取到入射点的位置及法线信息,因此无法模拟二次折射效果。

5 基于 GrabPass 的折射

? 第 4 节中基于 Cubemap 采样实现折射特效,本节基于 GrabPass 屏幕采样实现折射特效。

? GrabPass 用于获取屏幕纹理,有以下两种形式:

  1. // 1. 后续的Pass中通过_GrabTexture访问屏幕图像, 该方式较耗性能, Unity为每个使用了GrabPass的物体进行一次抓取屏幕图像操作
  2. GrabPass {}
  3. // 2. 后续的Pass中通过TextureName访问屏幕图像, 该方式性能较好, Unity只会在每一帧为第一个使用TextureName纹理的物体执行一次抓取屏幕图像操作, 这个纹理也可以在其他Pass中被访问
  4. GrabPass { "TextureName" }

? GrabPass 通常用于渲染透明物体,尽管代码里并不包含混合指令,但我们仍需要把物体的渲染队列设置为透明队列(即 "Queue"="Transparent")。这样才可以保证当渲染该物体时,所有不透明物体都已经被绘制在屏幕上,从而获取正确的屏幕图像。

? GrabRefract.shader

  1. Shader "MyShader/GrabRefract" { // 基于GrabPass纹理采样的折射
  2. Properties {
  3. _Color("Color Tint", Color) = (1, 1, 1, 1) // 物体颜色
  4. _RefractColor("Refraction Color", Color) = (1, 1, 1, 1) // 折射光的颜色
  5. _RefractAmount("Refraction Amount", Range(0, 1)) = 1 // 折射比例(用于漫反射和折射之间插值)
  6. _RefractRatio("Refraction Ratio", Range(0.1, 1)) = 0.5 // 折射比(入射介质折射率/折射介质折射率)
  7. }
  8. SubShader{
  9. // 渲染队列必须设置为 transparent, 确保所有不透明物体都在该对象之前已经渲染在屏幕上
  10. Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }
  11. // 抓取屏幕图像并存储在_RefractionTex中, 作为折射采样的纹理
  12. GrabPass { "_RefractionTex" }
  13. Pass {
  14. Tags { "LightMode" = "ForwardBase" }
  15. CGPROGRAM
  16. #pragma vertex vert
  17. #pragma fragment frag
  18. #include "Lighting.cginc"
  19. fixed4 _Color; // 物体颜色
  20. fixed4 _RefractColor; // 折射光的颜色
  21. float _RefractAmount; // 折射比例(用于漫反射和折射之间插值)
  22. fixed _RefractRatio; // 折射比(入射介质折射率/折射介质折射率)
  23. sampler2D _RefractionTex; // GrabPass抓取的屏幕图像, 作为折射采样的纹理
  24. struct a2v {
  25. float4 vertex : POSITION; // 模型空间顶点坐标
  26. float3 normal : NORMAL; // 模型空间法相向量
  27. };
  28. struct v2f {
  29. float4 pos : SV_POSITION; // 裁剪空间顶点坐标
  30. float4 scrPos : TEXCOORD0; // 屏幕空间顶点坐标(_RefractionTex采样的uv坐标)
  31. float3 worldPos : TEXCOORD1; // 世界空间顶点坐标
  32. fixed3 worldNormal : TEXCOORD2; // 顶点法线向量
  33. fixed3 worldViewDir : TEXCOORD3; // 观察向量(顶点指向相机)
  34. fixed3 worldRefr : TEXCOORD4; // 折射向量
  35. };
  36. v2f vert(a2v v) {
  37. v2f o;
  38. o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
  39. o.scrPos = ComputeGrabScreenPos(o.pos); // 屏幕空间顶点坐标(_RefractionTex采样的uv坐标)
  40. o.worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量(已归一化)
  41. o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 计算世界空间中顶点坐标
  42. o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // 计算世界空间中观察向量(顶点指向相机)
  43. o.worldRefr = refract(-normalize(o.worldViewDir), o.worldNormal, _RefractRatio); // 计算观察向量的折射向量
  44. return o;
  45. }
  46. fixed4 frag(v2f i) : SV_Target {
  47. fixed3 worldNormal = normalize(i.worldNormal); // 法线向量
  48. fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 灯光向量(顶点指向光源)
  49. fixed3 worldViewDir = normalize(i.worldViewDir); // 观察向量(顶点指向相机)
  50. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光颜色
  51. fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // 漫反射光颜色
  52. float offset = 1 - dot(-worldViewDir, normalize(i.worldRefr)); // 折射偏移
  53. float2 cameraViewDir = normalize(mul(unity_MatrixV, float4(worldViewDir, 0)).xy); // 观察坐标系下观察向量坐标
  54. i.scrPos.xy = i.scrPos.xy + cameraViewDir * offset; // 顶点对应的偏移后的屏幕坐标(屏幕纹理采样坐标)
  55. fixed3 refraction = tex2D(_RefractionTex, i.scrPos.xy / i.scrPos.w).rgb; // 折射光颜色
  56. fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount); // 漫反射光与折射光颜色进行插值
  57. return fixed4(color, 1.0);
  58. }
  59. ENDCG
  60. }
  61. }
  62. FallBack "Reflective/VertexLit"
  63. }

? _RefractAmount 值为 1 折射效果如下:

img

? _RefractAmount 值由 0 至 1 渐变折射效果如下:

img

? 声明:本文转自【Unity3D】反射和折射

原文链接:https://www.cnblogs.com/zhyan8/p/17615592.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号