【Unity3D】地面网格特效

news2024/12/26 11:13:02

1 前言

        本文实现了地面网格特效,包含以下两种模式:

  • 实时模式:网格线宽度和间距随相机的高度实时变化;
  • 分段模式:将相机高度分段,网格线宽度和间距在每段中对应一个值。

        本文完整资源见→Unity3D地面网格特效。 

2 地面网格实现

        SceneController.cs

using System;
using UnityEngine;

public class SceneController : MonoBehaviour {
    private static SceneController instance; // 单例
    private Action cameraChangedHandler; // 相机状态改变处理器
    private Transform cam; // 相机

    public static SceneController Instance() { // 获取实例
        return instance;
    }

    public void AddHandler(Action handler) { // 添加处理器
        cameraChangedHandler += handler;
    }

    private void Awake() {
        instance = this;
        cam = Camera.main.transform;
    }

    private void Update() { // 更新场景(Scroll: 缩放场景, Ctrl+Drag: 平移场景, Alt+Drag: 旋转场景)
        float scroll = Input.GetAxis("Mouse ScrollWheel");
        ScaleScene(scroll);
        if ((Input.GetMouseButton(0))) {
            if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) {
                float hor = Input.GetAxis("Mouse X");
                float ver = Input.GetAxis("Mouse Y");
                MoveScene(hor, ver);
            }
            if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) {
                float hor = Input.GetAxis("Mouse X");
                float ver = Input.GetAxis("Mouse Y");
                RotateScene(hor, ver);
            }
        }
    }

    private void ScaleScene(float scroll) { // 缩放场景
        if (Mathf.Abs(scroll) > Mathf.Epsilon) {
            cam.position += cam.forward * scroll * 50;
            cameraChangedHandler?.Invoke();
        }
    }

    private void MoveScene(float hor, float ver) { // 平移场景
        if (Mathf.Abs(hor) > Mathf.Epsilon || Mathf.Abs(ver) > Mathf.Epsilon) {
            cam.position -= (cam.right * hor * 3 + cam.up * ver * 3);
            cameraChangedHandler?.Invoke();
        }
    }

    private void RotateScene(float hor, float ver) { // 旋转场景
        if (Mathf.Abs(hor) > Mathf.Epsilon || Mathf.Abs(ver) > Mathf.Epsilon) {
            cam.RotateAround(Vector3.zero, Vector3.up, hor * 3);
            cam.RotateAround(Vector3.zero, -cam.right, ver * 3);
            cameraChangedHandler?.Invoke();
        }
    }
}

        说明:SceneController 脚本组件挂在相机对象上,这里旋转中心是固定的,如果想设置为随相机焦点自动变化,可以参考 缩放、平移、旋转场景。

        GridPlane.cs

using UnityEngine;

public class GridPlane : MonoBehaviour {
    public GridType gridType = GridType.REALTIME; // 网格类型
    private const float lineGapFactor = 0.2f; // 线段间距因子(相机距离单位长度变化时线段间距的变化量)
    private const float lineWidthFactor = 0.01f; // 线段宽度因子(相机距离单位长度变化时线段宽度的变化量)
    private const int segmentFactor = 100; // 分段因子(每隔多远分一段)
    private Transform cam; // 相机
    private float camDist; // 相机距离
    private float lineGap = 1; // 线段间距
    private float lineWidth = 0.05f; // 线段宽度
    private Vector4 planeCenter = Vector4.zero; // 地面中心
    private Material material; // 网格材质

    private void Start() {
        SceneController.Instance().AddHandler(UpdateGrid);
        cam = Camera.main.transform;
        material = Resources.Load<Material>("GridPlaneMat");
        material.SetVector("_PlaneCenter", planeCenter);
        UpdateGrid();
    }

    private void UpdateGrid() { // 更新网格
        camDist = Mathf.Abs(cam.position.y - planeCenter.y);
        if (gridType == GridType.REALTIME) {
            RealtimeUpdateGrid();
        } else if (gridType == GridType.SEGMENTED) {
            SegmentedUpdateGrid();
        }
    }

    private void RealtimeUpdateGrid() { // 实时更新网格
        lineGap = camDist * lineGapFactor;
        lineWidth = camDist * lineWidthFactor;
        UpdateMatProperties();
    }

    private void SegmentedUpdateGrid() { // 分段更新网格
        int dist = (((int) camDist) / segmentFactor + 1) * segmentFactor;
        lineGap = dist * lineGapFactor;
        lineWidth = dist * lineWidthFactor;
        UpdateMatProperties();
    }

    private void UpdateMatProperties() { // 更新材质属性
        lineGap = Mathf.Max(lineGap, lineGapFactor);
        lineWidth = Mathf.Max(lineWidth, lineWidthFactor);
        material.SetFloat("_LineGap", lineGap);
        material.SetFloat("_LineWidth", lineWidth);
    }
}

public enum GridType { // 网格类型
    REALTIME, // 实时模式(网格随相机高度实时变化)
    SEGMENTED // 分段模式(网格随相机高度分段变化)
}

        说明:GridPlane 脚本组件挂在地面对象上。

        GridPlane.shader

Shader "MyShader/GridPlane"  { // 路径上的节点移动特效
    Properties {
        _PlaneColor("Plane Color", Color) = (1, 1, 1, 1) // 地面颜色
        _LineColor("Line Color", Color) = (1, 1, 1, 1) // 线条颜色
        _LineGap("Line Gap", Int) = 1 // 线段间距
        _LineWidth("Line Width", Range(0, 1)) = 0.1 // 线段宽度
        _PlaneCenter("Plane Center", Vector) = (0, 0, 0, 0) // 地面中心
    }

    SubShader {
        Pass {
            cull off
            CGPROGRAM
 
            #include "UnityCG.cginc"
            #pragma vertex vert
            #pragma fragment frag

            float4 _PlaneColor; // 地面颜色
            float4 _LineColor; // 线条颜色
            int _LineGap; // 线段间距
            float _LineWidth; // 线段宽度
            float4 _PlaneCenter; // 地面中心

            struct v2f {
                float4 pos : SV_POSITION; // 裁剪空间顶点坐标
                float2 worldPos : TEXCOORD0; // 世界空间顶点坐标(只包含xz)
            };
 
            v2f vert(float4 vertex: POSITION) {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, vertex)
                o.worldPos = mul(unity_ObjectToWorld, vertex).xz; // 将模型空间顶点坐标变换到世界空间
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                float2 vec = abs(i.worldPos - _PlaneCenter.xz);
                float2 mod = fmod(vec, _LineGap);
                float2 xz = min(mod, _LineGap - mod);
                float dist = min(xz.x, xz.y);
                float factor = 1 - smoothstep(0, _LineWidth, dist);
                fixed4 color = lerp(_PlaneColor, _LineColor, factor);
                return fixed4(color.xyz, 1);
            }

            ENDCG
        }
    }
}

        说明:在 Assets 窗口新建 Resources 目录,接着在 Resources 目录下面创建材质,重命名为 GridPlaneMat,将 GridPlane.shader 与 GridPlaneMat 材质绑定。

3 运行效果

        1)实时模式

        2)分段模式

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

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

相关文章

C++ —— 类与对象(上)

前言 由于C在C语言的基础上移植了新的编程理念&#xff0c;所以我们先回顾一下C语言所遵循的旧的理念。一般来说&#xff0c;计算机语言要处理两个概念——数据和算法。数据是程序使用和处理的信息&#xff0c;而算法是程序使用的方法。C语言与当前最主流的语言一样&#xff0…

关于 Qt中的QString内容存在\u0000使用QChart(0x00)消除 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/131860574 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

Maven 基础之简介,基础配置

Maven 基本概念 Maven 是基于项目对象模型&#xff08;Project Object Model&#xff09;&#xff0c;可以通过一小段描述信息来管理项目的构建&#xff0c;报告和文档的软件项目管理工具。 Maven 主要有 2 个功能&#xff1a;「项目构建」和「依赖管理」。 &#x1f58b; 说…

Godot 4 源码分析 - 增加管道通信

学习研究Godot 4&#xff0c;很爽&#xff0c;虽然很庞杂&#xff0c;但相对于自己的水平来说&#xff0c;很强大&#xff0c;尤其是vulkan这块直接打包可用&#xff0c;省得自己从头琢磨。 一点一点地消化、优化与完善&#xff0c;最终才能成为自己的。 这段时间就在Godot的…

POSIX线程编程

死在山野的风里&#xff0c;活在自由的梦里 本专栏参考教材是四川轻化工大学陈年老师的linux实验指导手册&#xff08;含学习通的一些程序笔记&#xff09;。 POSIX线程编程 1.线程是什么2.创建线程创建一个用户级的线程&#xff0c;实现在线程中更改进程&#xff08;主线程&a…

Bean小结

Bean是Spring框架中最核心的两个概念之一&#xff08;另一个是面向切面编程AOP&#xff09;。 Bean 定义 Spring 官方文档对 bean 的解释是&#xff1a; In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC contain…

从Vue2到Vue3【三】——Composition API(第三章)

系列文章目录 内容链接从Vue2到Vue3【零】Vue3简介从Vue2到Vue3【一】Composition API&#xff08;第一章&#xff09;从Vue2到Vue3【二】Composition API&#xff08;第二章&#xff09;从Vue2到Vue3【三】Composition API&#xff08;第三章&#xff09;从Vue2到Vue3【四】C…

webrtc QOS方法二.4(flexfec 实现可优化点)

一、冗余报文和媒体报文组织结构优化点 以单帧10个媒体报文&#xff0c;冗余度20%为例。这里webrtc输出要有10个媒体包2个冗余包。webrtc输出的报文序列如下&#xff1a; 代码实现如下&#xff1a; UlpfecGenerator::AddPacketAndGenerateFec&#xff1a;攒够足够的帧 Forwar…

使用springboot进行后端开发100问

properties和yaml文件怎么互转 安装插件 properties文件和yaml文件区别 properties 文件通过“.”和“”赋值&#xff0c;值前不加空格&#xff0c;yaml通过“:”赋值&#xff0c;值前面加一个空格&#xff1b;yaml文件缩进用空格&#xff1b; properties只支持键值对&#x…

C++实现LRU(逐句讲解)

使用双向链表解决此问题&#xff0c;因为双向链表可以很容易的获取到头结点和尾结点。题目要求 get 和 put 要在O(1)的时间复杂度下运行&#xff0c;很显然要用set或map。根据题意&#xff0c;应使用map。 unordered_map<int,Node*> cache; map->first为Node中的key&a…

查看RabbitMQ日志---trace插件的使用

我的RabbitMQ是安装在docker里面的 所以我以下的方法都是根据这个路径去操作的 如果RabbitMQ安装在其他地方 请自行百度 1. 显示正在运行的RabbitMQ容器的名称或ID&#xff1a; docker ps这将启动所有正在运行的 Docker 容器&#xff0c;并包含 RabbitMQ 容器的信息。 使用…

【Docker】Docker的数据管理

目录 一、Docker 的数据管理1.1数据卷1.2 数据卷容器1.3端口映射1.4容器互联&#xff08;使用centos镜像&#xff09; 二、Docker镜像的创建2.1基于现有镜像创建2.2&#xff0e;基于本地模板创建2.3 基于Dockerfile 创建联合文件系统&#xff08;UnionFS&#xff09;镜像加载原…

【云原生】Docker网络及Cgroup资源控制

一、Docker网络 1.docker网络实现原理 Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c;Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为Container-IP&#xff0c;同时Docker网桥是每个容器的默认网关。…

【Linux网络】 网络套接字(二)socket编程_UDP网络程序

目录 socket 编程接口socket 常见的APIsockaddr结构 UDP网络程序简单例子服务端代码编写服务端创建套接字服务端绑定运行服务器测试启动服务端 客户端代码编写客户端创建套接字启动客户端本地测试INADDR_ANY服务端接收信息发回到客户端如何进行网络测试 socket 编程接口 socke…

【机器学习】吃瓜教程 | 西瓜书 + 南瓜书 (1)

文章目录 一、绪论1、什么是机器学习&#xff1f;2、基本术语3、假设空间4、归纳偏好5、发展历程 二、模型评估与选择A、一种训练集一种算法2.1 经验误差 与 过拟合2.2 评估方法a) 留出法b) 交叉验证法c) 自助法d) 调参与最终模型 2.3 性能度量a) 错误率与精度b) 查准率、查全率…

RT-Thread快速入门-内核移植

1RT-Thread快速入门-内核移植 RT-Thread 快速入门系列前面的文章介绍了内核相关的知识&#xff0c;以及内核提供的接口函数和如何使用。 本篇文章主要介绍如何将 RT-Thread 内核移植到某个硬件平台之上。移植分为两部分&#xff1a; CPU 架构移植 BSP 移植 也就是将 RT-Th…

MySQL第二课表的增删插改

&#x1f49b; 后端进行的表的操作增删查改 现在是建了一个成绩表&#xff0c;注意哈。 decimal(2,1). 2是M表示有两个有效数字长度&#xff0c;1是D的长度&#xff0c;即小数点后有一位(10分制) &#x1f493;开始 1.增加&#xff1a; insert into 表名 values(值&#xff0…

安装VS Code 和 MiKTeX开发环境

下载&#xff1a; Getting MiKTeX 然后以管理员方式运行安装。 配置VS Code 之后配置VS Code&#xff0c;选择扩展&#xff08;两个位置都可以&#xff09;&#xff0c;然后搜索Latex&#xff1a; 然后打开设置&#xff1a; 这样就打开了setting.json文件&#xff0c; 然后…

SQL注入之Oracle环境搭建

SQL注入之Oracle环境搭建 前言 Oracle Database&#xff0c;又名Oracle RDBMS&#xff0c;或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。可以说Oracle数据库系统是世界上流行的关系数据库管理系统&#xff0c;系统可移植性…

gitlab上传新项目全过程

gitlab上传新项目全过程 一、前期准备1.1 gitlab配置1.2 gitlab安装1.3 需要在gitlab上新建一个空项目 二、本地操作2.1 gitlab上传新项目全过程2.2 gitlab将远程项目拉取到本地全过程 三、常见问题及解决四、常用命令4.1 代码更新提交命令4.2 其他指令 一、前期准备 1.1 gitl…