UnityShader入门精要笔记2——使用Cubemap实现镜面反射和折射

先上效果图

在这里插入图片描述
(拖到最下面直接看代码)

立方体纹理

立方体纹理(Cubemap)由6张图像组成,对这个纹理进行采样时,需要提供一个向量,它会从立方体中间出发,当它向外延伸时就会与立方体的6个纹理之一相交,而采样结果就是由该交点(蓝点)计算而来【candycat】

在这里插入图片描述

让着色器使用立方体纹理

(当然,unity自带的着色器中,也有一些能够直接使用立方体纹理,例如Legacy Shaders - Reflective里面的那几个)
在这里插入图片描述

  1. 首先新建一个Unity Shader,把原有代码全部删除,然后给shader起个名字。为Shader添加一个Properties 语义块,声明我们需要的属性。
Shader "RefShader" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色
		_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1) //反射颜色
		_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1 //反射光大小
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {} //立方体纹理
		//对于2D,3D,Cube这3钟纹理类型,
		//它们的默认值是通过一个字符串后跟一个花括号来指定的。
		//其中,字符串要么是空的,要么是内置的纹理名称。
	}
}

这个 _Cubemap就是稍后我们要进行采样的立方体纹理。

  1. 为Shader添加SubShader和Pass,并设置标签“光照类型”(LightMode)为“向前渲染”(ForwardBase)
Shader "RefShader" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色
		_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1) //反射颜色
		_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1 //反射光大小
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {} //立方体纹理
	}
	SubShader {
		Pass {
			//设置正确的标签
			Tags { "LightMode"="ForwardBase" }
		}
	}
}
  1. 使用CG/HLSL语言来编写顶点/片元着色器
Shader "RefShader" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色
		_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1) //反射颜色
		_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1 //反射光大小
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {} //立方体纹理
	}
	SubShader {
		Pass {
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			struct a2v {
				float4 vertex : POSITION; 
				float3 normal : NORMAL;
			};
			
			struct v2f {
				//SV_POSITION语义告诉Unity,pos里包含了顶点在裁剪空间中的位置信息
				//这也是顶点着色器最重要的一个工作:将顶点座标从模型空间转换到裁剪空间
				float4 pos : SV_POSITION;
				//TEXCOORD0语义表示worldNormal变量占用了TEXCOORD0插值寄存器
				//每个插值寄存器可以存储4个浮点值(float)
				float3 worldNormal : TEXCOORD0; //世界空间下的顶点法线向量
				float3 worldPos : TEXCOORD1; //世界空间下的顶点座标
				float3 worldViewDir : TEXCOORD2; //世界空间下的观察方向,也就是从顶点到摄像机的方向
				float3 worldRef1 : TEXCOORD3; //镜面反射光的方向
				SHADOW_COORDS(4) //用于处理阴影的宏
			};
			
			v2f vert(a2v v) {
			}
			
			fixed4 frag(v2f i) : SV_Target {
			}

			ENDCG
		}
	}
}

现在,我们可以在顶点着色器中计算我们需要的信息了

	v2f vert(a2v v)
	{
		//申明返回值v2f
		v2f o;
		
		//这是顶点着色器最重要的一个任务,将顶点座标从模型空间转换到裁剪空间
		//UnityObjectToClipPos函数接受一个模型空间的座标,返回该座标在裁剪空间的座标
		o.pos = UnityObjectToClipPos(v.vertex);
		
		//UnityObjectToWorldNormal函数接受一个模型空间的法线向量,将其转换到世界空间中并返回
		o.worldNormal = UnityObjectToWorldNormal(v.normal);
		
		//unity_ObjectToWorld是unity模型空间到世界空间的变换矩阵
		//对v.vertex进行左乘,使其变换到世界空间下
		o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
		
		//UnityWorldSpaceViewDir函数接受一个世界空间中的顶点位置
		//返回世界空间中从该点到摄像机的观察方向
		o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
	
		//反射光
		o.worldRef1 = reflect(-o.worldViewDir, o.worldNormal);
		
		//用于处理阴影的宏
		TRANSFER_SHADOW(o);
	
		return o;
	}

其中,反射光的计算是这样子来的:
reflect函数需要输入两个值,第一个参数是入射向量l,第二个是法向量n,经过计算 [r^ = l^ - 2 ( n^ · l^ ) n^ ] ( v^表示单位向量)reflect函数返回反射方向r

在这里插入图片描述
在这里插入图片描述

shader:

Shader "RefShader" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1)
		_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
	}
	
	SubShader {
		Pass {
			Tags { "LightMode"="ForwardBase" }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"
			#include "AutoLight.cginc"

			fixed4 _Color;
			fixed4 _ReflectColor;
			float _ReflectAmount;
			samplerCUBE _Cubemap;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float3 worldViewDir : TEXCOORD2;
				float3 worldRef1 : TEXCOORD3;
				SHADOW_COORDS(4)
			};

			v2f vert(a2v v)
			{
				v2f o;

				o.pos = UnityObjectToClipPos(v.vertex);

				o.worldNormal = UnityObjectToWorldNormal(v.normal);

				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

				o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);

				//反射光
				o.worldRef1 = reflect(-o.worldViewDir, o.worldNormal);

				TRANSFER_SHADOW(o);

				return o;
			}

			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 worldViewDir = normalize(i.worldViewDir);
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 reflection = texCUBE(_Cubemap, i.worldRef1).rgb * _ReflectColor.rgb;
				//texCUBE(samplerCUBE, float3|half3|min10float3|min16float3)
				//texCUBE(samplerCUBE, float3|half3|min10float3|min16float3, float3|half3|min10float3|min16float3, float3|half3|min10float3|min16float3)
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

				fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
				return fixed4(color, 1.0);
			}
			
			ENDCG
		}
	}

	Fallback "Specular"
}

C#脚本:

    RenderTexture cubemap;
    Camera refCamera;
    // Use this for initialization
    void Start () {
        gameObject.AddComponent<Camera>();
        cubemap = new RenderTexture(128, 128, 16)
        {
            dimension = UnityEngine.Rendering.TextureDimension.Cube
        };
        GetComponent<Renderer>().material.SetTexture("_Cubemap", cubemap);
        refCamera = GetComponent<Camera>();
    }

    // Update is called once per frame
    void Update () {
        refCamera.RenderToCubemap(cubemap);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章