Unity3d Shader篇(十六)— 模拟雪的Shader

news2024/9/30 17:22:29

文章目录

  • 前言
  • 一、什么是模拟雪的Shader?
    • 1. 雪Shader原理
    • 2. 雪Shader优缺点
        • 优点:
        • 缺点:
  • 二、使用步骤
    • 1. Shader 属性定义
    • 2. SubShader 设置
    • 3. 渲染 Pass
    • 4. 定义结构体和顶点着色器函数
    • 5. 片元着色器函数
    • 6. 控制雪大小的脚本
  • 三、效果
  • 四、总结


前言

在游戏开发中,模拟雪效果是营造寒冷气氛的重要组成部分之一。本文将介绍如何使用Unity Shader编写一个模拟雪的Shader,通过调整参数实现雪花的积雪效果。


一、什么是模拟雪的Shader?

1. 雪Shader原理

**纹理采样:**通过采样主纹理和法线贴图,获取雪花的颜色和法线信息。

**光照计算:**根据光照方向和视角,计算雪花表面的漫反射和环境光照,从而确定雪花的亮度和色彩。

法线贴图处理:使用法线贴图来模拟雪花表面的凹凸纹理,使得雪花在光照下产生变化的效果。

**参数控制:**通过Shader属性(Properties)和参数(_Snow、_SnowColor等)来控制雪花的外观和行为,实现雪花的动态效果和交互性。

总的来说,雪Shader利用计算机图形学的原理和技术,模拟出雪花在游戏场景中的外观和行为,从而增强了游戏的视觉体验和真实感

2. 雪Shader优缺点

优点:

视觉效果:雪Shader能够模拟出逼真的雪花效果,增加游戏场景的真实感和美观度。

**交互性:**通过Shader参数的调整,可以实现不同程度、不同方向的雪覆盖效果,使得游戏场景更具交互性和变化性。

**自定义性:**雪Shader的参数可以自定义调整,包括雪的颜色、密度、积雪等级等,以适应不同游戏场景的需求。

**性能:**通常情况下,合理设计的雪Shader能够在保证视觉效果的同时,保持较高的性能表现,不会对游戏的帧率造成过大影响。

缺点:

**复杂度:**编写和调试雪Shader需要一定的专业知识和经验,对于新手来说可能较为复杂。

**兼容性:**不同的平台和设备对Shader的兼容性可能有所不同,需要进行兼容性测试和调整。

**性能消耗:**如果雪Shader设计不当或者参数设置过于复杂,可能会对游戏性能产生一定的消耗,降低游戏的流畅度。

二、使用步骤

1. Shader 属性定义

// 定义属性
Properties {
	_MainTex("Texture", 2 D) = "white" {} // 主纹理
	_Diffuse("Color", Color) = (1, 1, 1, 1) // 漫反射颜色
	_BumpMap("Normal Map", 2 D) = "white" {} // 法线贴图
	_BumpScale("Bump Scale", float) = 1 // 法线贴图缩放
	_Outline("Outline", Range(0, 0.2)) = 0.1 // 轮廓宽度
	_OutlineColor("OutlineColor", Color) = (0, 0, 0, 0) // 轮廓颜色
	_Step("Step", Range(1, 30)) = 1 // 阶梯化步数
	_ToonEffect("ToonEffect", Range(0, 1)) = 0.5 // 卡通效果
	//_Snow("Snow Level", Range(0,1)) = 0.5  // 积雪等级
	_SnowColor("SnowColor", Color) = (1, 1, 1, 1) // 积雪颜色
	_SnowDir("SnowDir", Vector) = (0, 1, 0) // 积雪方向
}

这部分定义了Shader的属性,包括主纹理、漫反射颜色、法线贴图等,以及控制雪效果的参数,注释Snow Level是方便后续在c#代码中调整

2. SubShader 设置

SubShader
{
    Tags
    {
        "RenderType"="Opaque"  // 渲染类型为不透明
    }
    LOD 100  // 细节级别

    UsePass "Unlit/Cartoon/Outline"  // 使用轮廓Pass
}

在SubShader中设置了渲染类型为不透明,并使用了轮廓Pass来增加渲染效果。
使用UsePass复用了之前的轮廓pass
轮廓pass

3. 渲染 Pass

Pass 
{
	CGPROGRAM
    #pragma vertex vert 
    #pragma fragment frag
    #pragma multi_compile __ SNOW_ON // 根据宏定义编译多个版本的着色器代码
    #include "UnityCG.cginc" // Unity 内置 CG 函数库
	#include "Lighting.cginc" // 光照函数库


	sampler2D _MainTex; // 主纹理采样器
	float4 _MainTex_ST; // 主纹理缩放和偏移参数
	fixed4 _Diffuse; // 漫反射颜色
	float _Step; // 计算漫反射阶梯
	float _ToonEffect; // 卡通效果参数
	sampler2D _BumpMap; // 法线贴图采样器
	float4 _BumpMap_ST; // 法线贴图缩放和偏移参数
	float _BumpScale; // 法线贴图缩放因子
	//积雪
	float _Snow; // 积雪级别参数
	float4 _SnowColor; // 积雪颜色
	float4 _SnowDir; // 积雪方向
} 

这里开始了渲染 Pass 部分。在这里,我们使用了 CGPROGRAM 指令来声明顶点着色器和片元着色器函数。#pragma vertex vert#pragma fragment frag 分别指定了顶点着色器函数和片元着色器函数的名称。

然后,我们包含了 UnityCG.cgincLighting.cginc,它们提供了许多有用的函数和宏,用于简化编写 Shader。

4. 定义结构体和顶点着色器函数

// 定义结构体:从顶点到片元的数据传递
struct v2f {
	float4 vertex: SV_POSITION; // 顶点位置
	float4 uv: TEXCOORD0; // 纹理坐标
	float4 TtoW0: TEXCOORD1; // 切线空间转世界空间矩阵
	float4 TtoW1: TEXCOORD2; // 切线空间转世界空间矩阵
	float4 TtoW2: TEXCOORD3; // 切线空间转世界空间矩阵
};

// 顶点着色器函数
v2f vert(appdata_tan v) {
	v2f o;
	o.vertex = UnityObjectToClipPos(v.vertex); // 顶点转剪裁空间
	o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); // 主纹理坐标
	o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap); // 法线贴图坐标

	fixed3 worldPos = mul(unity_ObjectToWorld, v.vertex); // 世界空间位置
	fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); // 世界空间法线
	fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); // 世界空间切线
	fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; // 世界空间副切线

	o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x); // 切线空间转世界空间矩阵
	o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y); // 切线空间转世界空间矩阵
	o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z); // 切线空间转世界空间矩阵

	return o;
}

这段代码是顶点着色器中的函数,其主要作用是将顶点从对象空间转换到剪裁空间,并计算顶点的纹理坐标、世界空间位置以及世界空间法线、切线、副切线方向,并将这些数据保存在顶点结构体中,以便后续的像素着色器使用

5. 片元着色器函数

// 片元着色器
fixed4 frag(v2f i): SV_Target 
{
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT; // 环境光

	fixed4 albedo = tex2D(_MainTex, i.uv); // 主纹理采样

	float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w); // 世界空间位置

	fixed3 lightDir = UnityWorldSpaceLightDir(worldPos); // 光照方向
	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos)); // 视线方向

	//求法线
	fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); // 法线贴图采样
	fixed3 tangentNormal = UnpackNormal(packedNormal); // 切线空间法线解码
	tangentNormal.xy *= _BumpScale; // 法线贴图缩放
	fixed3 worldNormal = normalize(float3(dot(i.TtoW0.xyz, tangentNormal), dot(i.TtoW1.xyz, tangentNormal),
		dot(i.TtoW2.xyz, tangentNormal))); // 世界空间法线

	float difLight = dot(lightDir, worldNormal) * 0.5 + 0.5; // 漫反射光照
	difLight = smoothstep(0, 1, difLight); // 漫反射光照平滑处理
	float toon = floor(difLight * _Step) / _Step; // 计算漫反射阶梯
	difLight = lerp(difLight, toon, _ToonEffect); // 考虑卡通效果
	fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * difLight; // 漫反射颜色

	fixed4 color = fixed4(ambient + diffuse, 1); // 最终颜色

	#if SNOW_ON
	if (dot(worldNormal, _SnowDir.xyz) > lerp(1, -1, _Snow)) // 判断是否处于积雪区域
	{
		color.rgb = _SnowColor.rgb; // 使用积雪颜色
	} else {
		color.rgb = color.rgb; // 使用漫反射颜色
	}
	#endif


	return color; // 返回最终颜色
}

这段片元着色器代码负责计算每个像素的颜色。它首先从主纹理中采样颜色,并根据光照方向和视线方向计算漫反射光照。同时,它也考虑了法线贴图,将切线空间法线转换为世界空间法线,并应用漫反射效果。最后,如果开启了积雪效果,它会根据像素的法线与积雪方向的夹角来决定是否应用积雪颜色。

6. 控制雪大小的脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Snow : MonoBehaviour
{
    private const string SnowOn = "SNOW_ON"; // 积雪关键字
    private const string SnowLevel = "_Snow"; // 积雪级别参数

    private bool isShow = true; // 是否显示积雪
    private float timer; // 计时器

    // Start is called before the first frame update
    void Start()
    {
        Shader.EnableKeyword(SnowOn); // 启用积雪关键字
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(KeyCode.A)) // 如果按下 A 键
        {
            if (isShow) // 如果正在显示积雪
            {
                timer += Time.deltaTime; // 计时器增加
                if (timer > 5) // 如果计时器超过 5 秒
                {
                    isShow = false; // 停止显示积雪
                    timer = 0; // 重置计时器
                }

                // 设置全局积雪级别参数
                Shader.SetGlobalFloat(SnowLevel, timer / 25);
            }
        }
        else if (Input.GetKey(KeyCode.D)) // 如果按下 D 键
        {
            isShow = true; // 开启显示积雪
            Shader.SetGlobalFloat(SnowLevel, 0f); // 设置全局积雪级别参数为 0
        }
    }
}

三、效果

在这里插入图片描述

四、总结

在本文中,我们介绍了如何使用Unity Shader编写一个模拟雪效果的Shader。首先,我们了解了雪Shader的原理,包括纹理采样、光照计算、法线贴图处理以及参数控制。然后,我们详细介绍了雪Shader的优缺点,其中优点包括视觉效果逼真、交互性强、自定义性高以及性能表现良好,缺点则包括编写复杂、兼容性问题和性能消耗等方面。

接着,我们按照使用步骤分为Shader属性定义、SubShader设置、渲染Pass、定义结构体和顶点着色器函数以及片元着色器函数等几个部分进行了详细的讲解。在每个部分,我们展示了相关代码,并解释了其作用和实现原理。

最后,我们展示了雪Shader的效果图,并通过视觉呈现了其在游戏场景中的应用效果。

综上所述,本文提供了一个基于Unity Shader的雪效果实现方案,并通过详细的步骤和代码解析,帮助大家理解了如何编写和使用这样一个Shader,从而为游戏开发中营造寒冷气氛提供了一种可行的技术方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1540685.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI大模型学习:理论基石、优化之道与应用革新

✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…

数组三(冒泡排序、二分查找)

目录 冒泡排序算法 冒泡排序的基础算法 冒泡排序的优化算法 二分法查找 冒泡排序算法 冒泡排序是最常用的排序算法,在笔试中也非常常见,能手写出冒泡排序算法可以说是 基本的素养。 冒泡排序的基础算法 冒泡排序算法重复地走访过要排序的数列&#…

GUROBI的数据结构

为了在GUROBI中能够更加高效地建模,Python API内置了三种特殊的数据结构,方便根据下标来查找数据。注意在使用这三种数据结构之前需要 import gurobipy as gp multidict 一、普通字典dict()的用法 小结:普通字典dict()只有一个返回值&…

Java代码基础算法练习-数位交换-2024.03.23·

任务描述: 输入一个三位整数,将其个位和百位交换后输出 任务要求: package march0317_0331;import java.util.Scanner;public class m240323 {public static void main(String[] args) {Scanner scanner new Scanner(System.in);System.out…

PointNet++论文复现(二)【最远点采样-球查询-采样和分组 代码详解】

最远点采样-球查询-采样和分组-代码详解 专栏持续更新中!关注博主查看后续部分! 最远点采样、球查询等位于 pointnet2_utils.py 定义 点云坐标归一化 点云坐标归一化是一种预处理步骤,用于将点云数据标准化到一个统一的尺度,通常是在一个特定的范围内,比如 [-1, 1] 或…

服务器运行一段时间后

自己记录一下。 一、查看目录占用情况 df -h 命令查看磁盘空间 du -ah --max-depth=1 / 查看根目录下各个文件占用情况 二、mysql日志清空 这个日志是可以清空的 echo > /usr/local/mysql/data/syzl-db2.log #将文件清空 说明: 这个文件这么大是因为,开启 …

[ C++ ] STL---反向迭代器的模拟实现

目录 前言: 反向迭代器简介 list反向迭代器的模拟实现 反向迭代器的模拟实现(适配器模式) SGI版本STL反向迭代器源码 STL库中解引用操作与出口设计 适配list的反向迭代器 适配vector的反向迭代器 前言: 反向迭代器是一种特殊类型的迭代器&#xf…

C语言函数和数组

目录 一.数组 一.一维数组: 1.一维数组的创建: 2.一维数组的初始化: 3.一维数组的使用 4.一维数组在内存中的存储: 二.二维数组: 三.数组越界: 四.数组作为函数参数: 二.函数 一.函数是什么&…

Redis I/O多路复用

I/O多路复用 Redis的I/o多路复用中,将多个连接放到I/O复用程序中,这个复用程序具体是什么,是Redis的主线程吗 在Redis的I/O多路复用机制中,“复用程序”实际上指的是操作系统提供的系统调用接口,如Linux下的epoll、sel…

Unity 学习日记 8.2D物理引擎

1.2D刚体的属性和方法 2.碰撞器

探索 Flutter 中的 NavigationRail:使用详解

1. 介绍 在 Flutter 中,NavigationRail 是一个垂直的导航栏组件,用于在应用程序中提供导航功能。它通常用于更大屏幕空间的设备,如平板电脑和桌面应用程序。NavigationRail 提供了一种直观的方式来浏览应用程序的不同部分,并允许…

【并发编程】锁相关公平锁和非公平锁?可重入锁锁的升级乐观锁和悲观锁版本号机制CAS 算法乐观锁有哪些问题?

目录 ​编辑 锁相关 公平锁和非公平锁? 可重入锁 锁的升级 乐观锁和悲观锁 版本号机制 CAS 算法 乐观锁有哪些问题? 锁相关 公平锁和非公平锁? 公平锁 : 锁被释放之后,先申请的线程先得到锁。性能较差一些,因…

第六届“传智杯”决赛 流水账 | 珂学家

前言 整体评价 有幸参加了第六届的传智杯决赛(A组),因为这个比赛是牛客协办,所以就写在这里。 作为Java选手,比赛中其实吃亏了,主要是T2吃了一发TLE,T4吃了一发莫名其妙的MLE。 顺便吐槽下T3,自测反馈WA…

局域网内的手机、平板、电脑的文件共享

在日常工作生活中,经常需要将文件在手机、平板、电脑间传输,以下介绍三种较为便捷的方法: 1.LocalSend 该软件是免费开源的,可以在局域网内的任意手机、平板、电脑间传递文件,并且任意一方都可以作为“发送方”和“接…

Windows11 安装confluence 7.4.0

Windows11安装confluence:7.4.0 1.打开终端管理员(管理员权限的PowerShell)2.按顺序执行以下命令,安装confluence服务3.浏览器(如Microsoft Edge) 打开 http://127.0.0.1:8100/ 配置confluence4.图示 本文是Windows11 安装confluence 7.4.0的步骤 本文参考 1.打开终端管理员(管…

⾃定义类型:结构体

目录 1. 结构体类型的声明 1.1 结构体回顾 1.1.1 结构的声明 1.1.2 结构体变量的创建和初始化 1.2 结构的特殊声明 1.3 结构的⾃引⽤ 2. 结构体内存对⻬ 2.1 对⻬规则 2.2 为什么存在内存对⻬? 2.3 修改默认对⻬数 3. 结构体传参 4. 结构体实现位段 4.1 什么是位段…

tcp 协议详解

什么是 TCP 协议 TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制。TCP 是一个传输层的协议。 如下图: 我们接下来在讲解 TCP/IP 协议栈的下三层时都会先解决这两个问题: 报头与有效载荷如何…

基于Springboot的艺体培训机构业务管理系统(有报告)。Javaee项目,springboot项目。

演示视频: 基于Springboot的艺体培训机构业务管理系统(有报告)。Javaee项目,springboot项目。 项目介绍: 采用M(model)V(view)C(controller)三层…

数据结构——树与二叉树

目录 树与二叉树 1.树的定义 2.树的有关术语 3.二叉树(BinaryTree) 二叉树的性质: 特殊的二叉树 满二叉树: 完全二叉树 二叉树的存储结构 顺序存储结构 链式存储结构 二叉树以及对应接口的实现 1.二叉树架构搭建 2…

docker将本地镜像pull到阿里云和registry

目录 一、上次到阿里云服务器 1、制作一个带有vim功能的Ubuntu镜像 2、在阿里云上面创建镜像仓库 3、从阿里云仓库中上传和拉取镜像 二、上传镜像到本地私有库registry 1、下载镜像docker registry 2、运行私有库registry,相当于本地有个私有docker hub。 3…