Unity半透明物体+投射阴影+接受阴影

2023-11-06

【前置】

水流动效果+透明

物体实现半透明有透明度测试和透明度混合两种方式,不同方式接受和投射阴影的方式有所差别。

【透明度测试阴影效果图】

  • 正方体的阴影一部分打在了水平面上,一部分在白色平面上。由于透明度测试使得水平面的右半部分为完全透明,所以白色平面会接收到正方体的阴影。
  • 水平面用的纹理贴图本身是完全不透明的物体,为了避免在透明度测试中完全透明和完全不透明的情况,用了下面的一张透明度纹理来代替水平面的透明通道

【透明度测试阴影代码】

Shader "Custom/WaterFlow"
{
	Properties{
		_MainTex("MainTex",2D) = "white"{}//存放贴图
	    _Color("Color Tint",Color) = (1,1,1,1)//控制整体颜色
		_Specular("Specular",Color) = (1,1,1,1)//控制高光反射颜色
		_Gloss("Gloss",Range(1,100)) = 10//控制高光区域大小
		_Magnitude("Magnitude",Float) = 0.1//控制波动频率
		_Frequency("Frequency",Float) = 0.5//控制波动幅度,参考正弦波的频率幅度来理解
		_Speed("Speed", Float) = 0.01//控制流动速度
		_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5//透明度测试时使用的阈值
	    _AlphaTex("AlphaTex",2D)="white"{}//用一张透明纹理才代替贴图纹理的透明通道值
		                                  //也可以用贴图纹理本身的透明通道,看哪个效果好用哪个
	}

		SubShader{
		//指定透明度测试的渲染队列、该Shader不受投影器影响、该Shader要使用透明度测试、顶点动画不能批处理
		Tags{ "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" "DisableBatching" = "True" }

		Pass{//第一个Pass,让物体可以投射阴影,即把该物体加入到相机的深度纹理的计算中,
		  Tags{"LightMode"="ShadowCaster"}

		  CGPROGRAM
		 
         #pragma vertex vert
         #pragma fragment frag
         #pragma multi_compile_shadercaster
         #include "UnityCG.cginc"

		 sampler2D _AlphaTex;
	     float4 _AlphaTex_ST;
		 fixed _Cutoff;

		struct v2f {
		V2F_SHADOW_CASTER;
		float2 uv:TEXCOORD0;
	  };

	v2f vert(appdata_base v)
	{
		v2f o;
		TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
		o.uv= TRANSFORM_TEX(v.texcoord, _AlphaTex);
		return o;
	}

	float4 frag(v2f i) :SV_Target{
		//对不透明物体不需要下面两行代码,对使用透明度测试的物体,其完全透明部分不会有阴影,要将其剔除
		fixed4 alphaCol = tex2D(_AlphaTex,i.uv);
	    clip(alphaCol.a - _Cutoff);

		SHADOW_CASTER_FRAGMENT(i)
	}

		ENDCG

        }

		Pass{//第二个Pass,包含接收阴影的计算
		//指定前向渲染模式
		Tags{ "LightMode" = "ForwardBase" }

		CGPROGRAM

        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_fwdbase//保证在Shader中使用的光照衰减等光照变量可以被正确赋值

        #include "UnityCG.cginc"
        #include "Lighting.cginc"
        #include "AutoLight.cginc"//添加Unity的内置文件,包含计算阴影所用的宏

		//定义Properties中的变量
		sampler2D _MainTex;
	    float4 _MainTex_ST;//纹理的缩放和偏移值,TRANSFORM_TEX会调用
	    fixed4 _Color;
	    fixed4 _Specular;
	    float _Gloss;
	    float _Magnitude;
	    float _Frequency;
	    float _Speed;
	    fixed _Cutoff;
	    sampler2D _AlphaTex;
	    float4 _AlphaTex_ST;

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

	struct v2f {
		float4 pos:SV_POSITION;
		float4 uv:TEXCOORD0;//使用第一个插值寄存器,用一个uv存储两张纹理的纹理坐标
		float3 worldNormal:TEXCOORD1;//使用第二个插值寄存器
		float3 worldPos:TEXCOORD2;
		SHADOW_COORDS(3)//声明一个用于对阴影纹理采样的坐标,表示使用第4个插值寄存器
	};

	v2f vert(a2v v) {

		v2f o;

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

		float4 offset = float4(0, 0, 0, 0);//顶点偏移
		offset.y = sin(_Frequency *_Time.y + v.vertex.x + v.vertex.y + v.vertex.z)*_Magnitude;//顶线Y坐标随时间偏移
		o.pos = UnityObjectToClipPos(v.vertex + offset);//顶点从模型空间到裁剪空间
		o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);//传递UV坐标
		o.uv.xy += float2(0, _Time.y*_Speed);//纹理动画,水平方向上的移动

		o.uv.zw = TRANSFORM_TEX(v.texcoord, _AlphaTex);

		TRANSFER_SHADOW(o);//计算声明的阴影纹理坐标
		return o;
	}

	fixed4 frag(v2f i) :SV_Target{

		//计算漫反射
		fixed3 worldNormal = normalize(i.worldNormal);//世界空间下顶点法线
	    fixed3 worldLight = UnityWorldSpaceLightDir(i.worldPos);//世界空间下顶点处的入射光

	    fixed4 texColor = tex2D(_MainTex, i.uv.xy);
	    fixed4 alpha = tex2D(_AlphaTex, i.uv.zw);
	    texColor.a = alpha.a;//替换贴图纹理的透明通道
	    clip(texColor.a - _Cutoff);//将小于透明阈值的片元全部舍去,即完全透明

	    fixed3 albedo = texColor.rgb*_Color.rgb;//纹理采样获取漫反射颜色
	    fixed3 diffuse = _LightColor0.rgb * albedo * (dot(worldLight, worldNormal)*0.5 + 0.5);//半兰伯特模型计算漫反射

	    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;//环境光

	    //计算高光反射,Blinn-Phong模型
	    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));//观察方向
	    fixed3 halfDir = normalize(worldLight + viewDir);
	    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);

	    UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//获取光照衰减和阴影
		//fixed shadow = SHADOW_ATTENUATION(i);//获取阴影值

	    return fixed4(ambient + (diffuse + specular) * atten,1.0);//没有被舍弃的部分,相当于不透明物体
		
	}
		ENDCG
	  }
	}
	FallBack "Transparent/Cutout/VertexLit" //最好不要更改FallBack
}

【透明度混合阴影效果图】 

  • 正方体的阴影在水平面上,水平面的阴影在白色平面上,这正是我们想看到的结果
  • 由于使用透明度混合的物体关闭了深度写入,因此不会参与到相机的深度纹理和光源的阴影纹理映射中,进而在屏幕空间的阴影图中没有该半透明物体。所以,需要强制将其当做完全不透明物体来投射阴影,通过指定FallBack为"VertexLit"来实现,VertexLit中有ShadowCaster的Pass
  • 设置材质的渲染队列
  • 设置相机的

【透明度混合阴影代码】 



Shader "Custom/WaterFlow"
{
	Properties{
		_MainTex("MainTex",2D) = "white"{}//存放贴图
		_Color("Color Tint",Color) = (1,1,1,1)//控制整体颜色
		_Specular("Specular",Color) =(1,1,1,1)//控制高光反射颜色
		_Gloss("Gloss",Range(1,100))=10//控制高光区域大小
		_Magnitude("Magnitude",Float) = 0.1//控制波动频率
		_Frequency("Frequency",Float) = 0.5//控制波动幅度,参考正弦波的频率幅度来理解
		_Speed("Speed", Float) = 0.01//控制流动速度
		_AlphaScale("Alpha Scale",Range(0,1)) = 0.65//透明度混合中的透明度系数

	}

		SubShader{	

		Pass{
		//指定前向渲染模式
		Tags{"LightMode" = "ForwardBase" "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "DisableBatching" = "True" }

		ZWrite Off//关闭深度读写
		Blend SrcAlpha OneMinusSrcAlpha//开启混合模式
		Cull Off//关闭剔除功能

		CGPROGRAM

		#pragma vertex vert
		#pragma fragment frag
        #pragma multi_compile_fwdbase//保证在Shader中使用的光照衰减等光照变量可以被正确赋值

		#include "UnityCG.cginc"
        #include "Lighting.cginc"
        #include "AutoLight.cginc"//添加Unity的内置文件,包含计算阴影所用的宏
        
		//定义Properties中的变量
		sampler2D _MainTex;
		float4 _MainTex_ST;//纹理的缩放和偏移值,TRANSFORM_TEX会调用
		fixed4 _Color;
		fixed4 _Specular;
		float _Gloss;
		float _Magnitude;
		float _Frequency;
		float _Speed;
		float _AlphaScale;

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

		struct v2f {
			float4 pos:SV_POSITION;
			float2 uv:TEXCOORD0;//使用第一个插值寄存器
			float3 worldNormal:TEXCOORD1;//使用第二个插值寄存器
			float3 worldPos:TEXCOORD2;
			SHADOW_COORDS(3)//声明一个用于对阴影纹理采样的坐标,表示使用第4个插值寄存器
		};

		v2f vert(a2v v) {
			v2f o;

			o.worldNormal = UnityObjectToWorldNormal(v.normal);//世界空间下顶点法线
			o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

			float4 offset = float4(0, 0, 0, 0);//顶点偏移
			offset.y = sin(_Frequency *_Time.y+ v.vertex.x+ v.vertex.y+ v.vertex.z)*_Magnitude;//顶线Y坐标随时间偏移
			o.pos = UnityObjectToClipPos(v.vertex + offset);//顶点从模型空间到裁剪空间
			o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);//传递UV坐标
			o.uv += float2(0, _Time.y*_Speed);//纹理动画,水平方向上的移动

			TRANSFER_SHADOW(o);//计算声明阴影纹理坐标
			return o;
		}

		fixed4 frag(v2f i) :SV_Target{

			//计算漫反射
			fixed3 worldNormal = normalize(i.worldNormal);//世界空间下顶点法线
			fixed3 worldLight = UnityWorldSpaceLightDir(i.worldPos);//世界空间下顶点处的入射光
			fixed4 texColor = tex2D(_MainTex, i.uv);
			fixed3 albedo = texColor.rgb*_Color.rgb;//纹理采样获取漫反射颜色
			fixed3 diffuse = _LightColor0.rgb * albedo * (dot(worldLight, worldNormal)*0.5 + 0.5);//半兰伯特模型计算漫反射

			fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;//环境光

			//计算高光反射,Blinn-Phong模型
			fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));//观察方向
			fixed3 halfDir = normalize(worldLight + viewDir);
			fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);
			
			UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//获取光照衰减和阴影
			//fixed shadow = SHADOW_ATTENUATION(i);//获取阴影值

			return fixed4(ambient + (diffuse + specular) * atten,texColor.a*_AlphaScale);//改变透明通道的值
		}

		ENDCG
		    }
		 }
	FallBack "VertexLit"//最好不要更改Fallback
}

【不足之处】

上面透明度混合中水面接受了阴影,但投射的阴影有点虚假,只投射了很小一部分。下面左图是屏幕空间的阴影图,可以看到只有一部分;右图是几乎完全透明时的阴影。

将材质的渲染队列设置为,这时投射阴影没问题,但不能接受阴影。下面左图是屏幕空间的阴影图,可以看到中间部分没有正方体的投影;右图是几乎完全透明时的阴影。

从视觉效果来看前者好一些。

【参考】

《Unity Shader入门精要》

https://blog.csdn.net/linjf520/article/details/90929765

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unity半透明物体+投射阴影+接受阴影 的相关文章

  • 神经网络学习小记录45——Keras常用学习率下降方式汇总

    神经网络学习小记录45 Keras常用学习率下降方式汇总 2020年5月19日更新 前言 为什么要调控学习率 下降方式汇总 1 阶层性下降 2 指数型下降 3 余弦退火衰减 4 余弦退火衰减更新版 2020年5月19日更新 增加了论文中的余
  • JVM性能调优实战

    JVM调优调什么 JVM 调优是一个系统而又复杂的过程 但我们知道 在大多数情况下 我们基本不用去调整 JVM 内存分配 因为一些初始化的参数已经可以保证应用服务正常稳定地工作了 而且一般情况下 就算出现了 也是架构师级别的去处理 实际上
  • c++ to_string、stoi()、atoi()使用

    1 to string 包含在 include
  • SQL连续登录4天及以上的用户

    需求 连续登录4天及以上的用户 有一个表login test 求出连续登录4天及以上的用户 方法一 排序窗口函数 row number select row number over partition by user id order by
  • 探讨linux进程的三种时间(real time, system cpu time, user cpu time)的实现

    APUE 3 9节中关于系统调用read给出了不同大小的缓冲区会导致读取效率的差异 这里stevens用三种时间表示读取文件过程所花费的时间 这三种时间分别为真实 时钟时间 real clock time 系统cpu时间 system cp
  • 前端知识学习

    以下是一个使用Angular 13实现的示例代码 实现了当div出现滚动条时给div底部加上阴影效果 并随着滚动条的拖动保留阴影效果 当滚动条拉到最后时 阴影效果消失 首先 在你的组件的HTML模板中添加以下代码 div class con
  • 最适合初学者使用的react框架—UMI.js

    介绍 最近接触到了一个新的react框架 大大省略了我开发的时间 而且学起来和上手都挺容易的 但论坛上关于这个框架的介绍并不多 我就大概介绍一下这个框架并写一些使用心得 1 什么是umi 它是由dva的开发者云谦编写的一个新的React开发
  • 以太坊存储分析(整合)

    分析背景及概述 更加深入地了解以太坊的内部存储机制 更好地实现自己的区块链 存储是一个不能缺少的模块 但是存储又不是单一的数据保存 它涉及到了以太坊核心的编码 数据结构 中间还有缓存的部分 最后才通过leveldb 一种key value数
  • 若依tab权限问题

    普通的按钮直接用v hasPermi就可以了 但是这个有个问题 这个东西相当于v show 而像el tab pane就需要v if才能把标签头隐藏 所以需要以下代码 v if checkPermi xxx xxx xxx import c
  • Leetcode算法题:旋转数组问题(全)总结 思路+题解+代码

    旋转数组问题总结 标签 二分查找 翻转数组 笔者在刷LeetCode时多次遇到旋转数组问题 将其各类问题加以总结 从简入深整理出关于旋转数组的全部问题题解思路和代码 希望对读者有所帮助 题目 189 旋转数组 给定一个数组 将数组中的元素向
  • RuoYi -- 字典的前端调用

    前言 记录时间 2022 5 18 内容 字典可以实现枚举联动 前端怎么写去调用呢 我参考company之前项目里的 联赛赛事管理 部分 整理如下 主要包含html js的更新 备注 由于记录时并非边操作边记录 而是抽取往期代码加上自己的理
  • qt之页面布局

    QTlayout概述 Qt的布局管理系统提供了强大的机制来自动排列窗口中的所有部件 确保它们有效地使用空间 Qt包含了一组布局管理类 从而在应用程序的用户界面中对部件进行布局 比如QLayout的几个子类 这里将它们称作布局管理器 所有QW
  • 变差函数拟合(球状、指数、高斯)例题分析matlab

    解决问题的代码 学习笔记 clear clc n 20 h 0 2 f x 1 3 exp 2 x x linspace 0 n h n 第一问 y0 f x 原数据 y f x 0 1 rand size x 加入高斯噪音的数据 Exva
  • 解决报错:Cannot read properties of undefined (reading read ) at FileReader.reader.onload

    当我们使用xlsx插件实现excel导入功能时发现控制台报错 Cannot read properties of undefined reading read at FileReader reader onload 查询后发现是xlsx版本
  • 接口多实现类的动态调用

    一个接口在不同场景 需要有不同的实现类 实现动态调用 定义接口 创建接口实现类 定义支付方式接口 支付方式接口 public interface PaymentService 扫码支付 param transaction return th
  • python 执行js 提示window未定义_execjs执行js出现window对象未定义时的解决

    最近在开始学习js逆向 里面很重要的一个方法就是把js代码扣下来用python模拟执行 但是发现js里面有window对象时用execjs执行 当使用node js环境时会出现window对象未定义的情况 记录下在网上找到的解决方案 1 当
  • 程序员是如何思考的?

    1975 年 弗雷德里克 布鲁克斯 Frederick Brooks 出版了软件行业的名著 人月神话 他给出了一个统计结果 优秀程序员的开发效率是普通程序员的 10 倍 40 多年过去了 这个数字得到了行业的普遍认同 成为优秀程序员是很多程

随机推荐