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

11 篇文章 1 订阅
7 篇文章 0 订阅

先上效果图

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

立方体纹理

立方体纹理(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);
    }
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中,Shader是用来渲染场景的关键部分。在URP中,Shader的编写方式与传统的Shader编写方式略有不同,但其核心思想仍然是相同的。 在本文中,我们将从一个简单的Unlit Shader开始,介绍在URP中编写Shader的基本步骤。 1. 创建Shader 首先,我们需要创建一个新的Shader。在Project视图中右键点击Assets文件夹,选择Create > Shader > Unlit Shader。 2. 编写Shader 打开新创建的Shader文件,我们可以看到如下代码: ``` Shader "Unlit/MyUnlitShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } FallBack "Diffuse" } ``` 这个Shader使用Unity默认的Unlit Shader模板,并添加了一个_MainTex属性,该属性用于接收贴图,并在渲染过程中使用。 3. 添加SubShader 在URP中,我们需要为Shader添加一个SubShader。在SubShader中,我们可以定义一系列Pass,每个Pass都是对场景中不同物体的渲染。 修改代码如下: ``` Shader "Unlit/MyUnlitShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag () : SV_Target { fixed4 col = fixed4(1, 1, 1, 1); return col; } ENDCG } } FallBack "Diffuse" } ``` 在这个代码中,我们添加了一个新的SubShader,并为其添加了一个Pass。这个Pass是一个简单的颜色填充Pass,用于在没有贴图的情况下渲染物体。 4. 测试Shader 最后,我们需要将Shader应用到一个物体上并测试它的效果。在Scene视图中创建一个新的Cube,并将新创建的Shader应用到该Cube上。 现在,你应该能够看到在没有贴图的情况下,该Cube被渲染为白色。如果你将贴图赋给_MainTex属性,你应该能够看到该Cube被渲染为该贴图。 这就是从一个简单的Unlit Shader开始,在URP中编写Shader的基本步骤。通过深入学习Shader编写的过程,你将能够创建更高级、更复杂的Shader,并为你的游戏带来更加出色的视觉效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值