Unity镜子效果制作教程


本文提供全流程,中文翻译。

Chinar 坚持将简单的生活方式,带给世人!

(拥有更好的阅读体验 —— 高分辨率用户请根据需求调整网页缩放比例)


Chinar —— 心分享、心创新!

助力快速实现一个简单的镜面反射效果

为新手节省宝贵的时间,避免采坑!

Chinar 教程效果:



全文高清图片,点击即可放大观看 (很多人竟然不知道)


1

Create Mirror —— 创建镜子

本教程,无需自己找镜子Shader,只需2个脚本即可在Unity中创建一个简单的模拟镜面反射效果

1. 在场景中创建一个 Plane —— 用来作为镜子

2. 同时创建一个材质球 /Material —— 给到 Plane 上

3. 修改新创建的 Material 的 Shader 为 Unlit/Texture




2

Create Camera —— 创建一个新相机

1. 新建一个 Render Texture(我改名为 Plane 便于区分和理解)

2. 右键 层次列表/Hierarchy —— 创建一个新的 Camera

3. 将新建的 Render Texture(Plane)给新建的 Camera 组件中的 Target Texture

4. 给新建的 Camera相机,添加脚本 ChinarMirrorPlane

并将 Main Camera与 Plane 拖到 Inspector 面板中对应的属性里

5. 给新建的 Camera相机,添加脚本 ChinarMirror ,并将 Plane 拖至 Inspector 面板中

注意: 一定要修改 Plane 材质的属性为:



具体流程其实很简单,如下





两个脚本,都需要挂载到 Camera:

using UnityEngine;

/// <summary>
/// 镜子管理脚本 —— 挂在新建的Camera上
/// </summary>
[ExecuteInEditMode]
public class ChinarMirror : MonoBehaviour
{
public GameObject mirrorPlane; //镜子
public Camera mainCamera; //主摄像机
private Camera mirrorCamera; //镜像摄像机 private void Start()
{
mirrorCamera = GetComponent<Camera>();
} private void Update()
{
if (null == mirrorPlane || null == mirrorCamera || null == mainCamera) return;
Vector3 postionInMirrorSpace = mirrorPlane.transform.InverseTransformPoint(mainCamera.transform.position); //将主摄像机的世界坐标位置转换为镜子的局部坐标位置
postionInMirrorSpace.y = -postionInMirrorSpace.y; //一般y为镜面的法线方向
mirrorCamera.transform.position = mirrorPlane.transform.TransformPoint(postionInMirrorSpace); //转回到世界坐标系的位置
}
}
using UnityEngine;

/// <summary>
/// Plane管理脚本 —— 挂载新建的Camera上
/// </summary>
[ExecuteInEditMode] //编辑模式中执行
public class ChinarMirrorPlane : MonoBehaviour
{
public GameObject mirrorPlane; //镜子Plane
public bool estimateViewFrustum = true;
public bool setNearClipPlane = true; //是否设置近剪切平面
public float nearClipDistanceOffset = -0.01f; //近剪切平面的距离
private Camera mirrorCamera; //镜像摄像机
private Vector3 vn; //屏幕的法线
private float l; //到屏幕左边缘的距离
private float r; //到屏幕右边缘的距离
private float b; //到屏幕下边缘的距离
private float t; //到屏幕上边缘的距离
private float d; //从镜像摄像机到屏幕的距离
private float n; //镜像摄像机的近剪切面的距离
private float f; //镜像摄像机的远剪切面的距离
private Vector3 pa; //世界坐标系的左下角
private Vector3 pb; //世界坐标系的右下角
private Vector3 pc; //世界坐标系的左上角
private Vector3 pe; //镜像观察角度的世界坐标位置
private Vector3 va; //从镜像摄像机到左下角
private Vector3 vb; //从镜像摄像机到右下角
private Vector3 vc; //从镜像摄像机到左上角
private Vector3 vr; //屏幕的右侧旋转轴
private Vector3 vu; //屏幕的上侧旋转轴
private Matrix4x4 p = new Matrix4x4();
private Matrix4x4 rm = new Matrix4x4();
private Matrix4x4 tm = new Matrix4x4();
private Quaternion q = new Quaternion(); private void Start()
{
mirrorCamera = GetComponent<Camera>();
} private void Update()
{
if (null == mirrorPlane || null == mirrorCamera) return;
pa = mirrorPlane.transform.TransformPoint(new Vector3(-5.0f, 0.0f, -5.0f)); //世界坐标系的左下角
pb = mirrorPlane.transform.TransformPoint(new Vector3(5.0f, 0.0f, -5.0f)); //世界坐标系的右下角
pc = mirrorPlane.transform.TransformPoint(new Vector3(-5.0f, 0.0f, 5.0f)); //世界坐标系的左上角
pe = transform.position; //镜像观察角度的世界坐标位置
n = mirrorCamera.nearClipPlane; //镜像摄像机的近剪切面的距离
f = mirrorCamera.farClipPlane; //镜像摄像机的远剪切面的距离
va = pa - pe; //从镜像摄像机到左下角
vb = pb - pe; //从镜像摄像机到右下角
vc = pc - pe; //从镜像摄像机到左上角
vr = pb - pa; //屏幕的右侧旋转轴
vu = pc - pa; //屏幕的上侧旋转轴
if (Vector3.Dot(-Vector3.Cross(va, vc), vb) < 0.0f) //如果看向镜子的背面
{
vu = -vu;
pa = pc;
pb = pa + vr;
pc = pa + vu;
va = pa - pe;
vb = pb - pe;
vc = pc - pe;
}
vr.Normalize();
vu.Normalize();
vn = -Vector3.Cross(vr, vu); //两个向量的叉乘,最后在取负,因为Unity是使用左手坐标系
vn.Normalize();
d = -Vector3.Dot(va, vn);
if (setNearClipPlane)
{
n = d + nearClipDistanceOffset;
mirrorCamera.nearClipPlane = n;
}
l = Vector3.Dot(vr, va) * n / d;
r = Vector3.Dot(vr, vb) * n / d;
b = Vector3.Dot(vu, va) * n / d;
t = Vector3.Dot(vu, vc) * n / d; //投影矩阵
p[0, 0] = 2.0f * n / (r - l);
p[0, 1] = 0.0f;
p[0, 2] = (r + l) / (r - l);
p[0, 3] = 0.0f; p[1, 0] = 0.0f;
p[1, 1] = 2.0f * n / (t - b);
p[1, 2] = (t + b) / (t - b);
p[1, 3] = 0.0f; p[2, 0] = 0.0f;
p[2, 1] = 0.0f;
p[2, 2] = (f + n) / (n - f);
p[2, 3] = 2.0f * f * n / (n - f); p[3, 0] = 0.0f;
p[3, 1] = 0.0f;
p[3, 2] = -1.0f;
p[3, 3] = 0.0f; //旋转矩阵
rm[0, 0] = vr.x;
rm[0, 1] = vr.y;
rm[0, 2] = vr.z;
rm[0, 3] = 0.0f; rm[1, 0] = vu.x;
rm[1, 1] = vu.y;
rm[1, 2] = vu.z;
rm[1, 3] = 0.0f; rm[2, 0] = vn.x;
rm[2, 1] = vn.y;
rm[2, 2] = vn.z;
rm[2, 3] = 0.0f; rm[3, 0] = 0.0f;
rm[3, 1] = 0.0f;
rm[3, 2] = 0.0f;
rm[3, 3] = 1.0f; tm[0, 0] = 1.0f;
tm[0, 1] = 0.0f;
tm[0, 2] = 0.0f;
tm[0, 3] = -pe.x; tm[1, 0] = 0.0f;
tm[1, 1] = 1.0f;
tm[1, 2] = 0.0f;
tm[1, 3] = -pe.y; tm[2, 0] = 0.0f;
tm[2, 1] = 0.0f;
tm[2, 2] = 1.0f;
tm[2, 3] = -pe.z; tm[3, 0] = 0.0f;
tm[3, 1] = 0.0f;
tm[3, 2] = 0.0f;
tm[3, 3] = 1.0f; mirrorCamera.projectionMatrix = p; //矩阵组
mirrorCamera.worldToCameraMatrix = rm * tm;
if (!estimateViewFrustum) return;
q.SetLookRotation((0.5f * (pb + pc) - pe), vu); //旋转摄像机
mirrorCamera.transform.rotation = q; //聚焦到屏幕的中心点 //估值 —— 三目简写
mirrorCamera.fieldOfView = mirrorCamera.aspect >= 1.0 ? Mathf.Rad2Deg * Mathf.Atan(((pb - pa).magnitude + (pc - pa).magnitude) / va.magnitude) : Mathf.Rad2Deg / mirrorCamera.aspect * Mathf.Atan(((pb - pa).magnitude + (pc - pa).magnitude) / va.magnitude);
//在摄像机角度考虑,保证视锥足够宽
}
}

3

Main Camera —— 主相机脚本(方便看到测试效果)

为了方便看到运行后的镜面效果, Chinar 在这里提供了一个第三人称的脚本

用于转镜头,看不同方位

需要挂载到主相机上,并将层次列表中的 Plane 拖到 Pivot 上

using UnityEngine;

/// <summary>
/// 主相机脚本 —— 挂载到主相机上,并 层次列表中的 Plane 拖到 pivot
/// </summary>
public class ChinarCamera : MonoBehaviour
{
public Transform pivot;
public Vector3 pivotOffset = Vector3.zero;
public Transform target;
public float distance = 10.0f;
public float minDistance = 2f;
public float maxDistance = 15f;
public float zoomSpeed = 1f;
public float xSpeed = 250.0f;
public float ySpeed = 120.0f;
public bool allowYTilt = true;
public float yMinLimit = -90f;
public float yMaxLimit = 90f;
private float x = 0.0f;
private float y = 0.0f;
private float targetX = 0f;
private float targetY = 0f;
private float targetDistance = 0f;
private float xVelocity = 1f;
private float yVelocity = 1f;
private float zoomVelocity = 1f; private void Start()
{
var angles = transform.eulerAngles;
targetX = x = angles.x;
targetY = y = ClampAngle(angles.y, yMinLimit, yMaxLimit);
targetDistance = distance;
} private void LateUpdate()
{
if (!pivot) return;
var scroll = Input.GetAxis("Mouse ScrollWheel"); if (scroll > 0.0f) targetDistance -= zoomSpeed;
else if (scroll < 0.0f)
targetDistance += zoomSpeed;
targetDistance = Mathf.Clamp(targetDistance, minDistance, maxDistance);
if (Input.GetMouseButton(1) || (Input.GetMouseButton(0) && (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))))
{
targetX += Input.GetAxis("Mouse X") * xSpeed * 0.02f;
if (allowYTilt)
{
targetY -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;
targetY = ClampAngle(targetY, yMinLimit, yMaxLimit);
}
}
x = Mathf.SmoothDampAngle(x, targetX, ref xVelocity, 0.3f);
y = allowYTilt ? Mathf.SmoothDampAngle(y, targetY, ref yVelocity, 0.3f) : targetY;
Quaternion rotation = Quaternion.Euler(y, x, 0);
distance = Mathf.SmoothDamp(distance, targetDistance, ref zoomVelocity, 0.5f);
Vector3 position = rotation * new Vector3(0.0f, 0.0f, -distance) + pivot.position + pivotOffset;
transform.rotation = rotation;
transform.position = position;
} private static float ClampAngle(float angle, float min, float max)
{
if (angle < -360) angle += 360;
if (angle > 360) angle -= 360;
return Mathf.Clamp(angle, min, max);
}
}

4

Create Cube —— 创建一个立方体

为了看镜子的效果

在场景中创建一个 Cube —— 用来作为参照对象

然后点击运行后,即可看到镜子效果已经完成






5

Indistinct —— 显示效果不清晰

如果发现,镜子的显示效果并不清晰

这是因为我们创建的 Render Texture 时使用的是默认的分辨率 256*256

修改成较高的分辨率即可,这里我修改为:1024*1024 (可视情况自己设定)

注意:分辨率设置越高,是越耗性能的





至此:镜子的制作教程结束


6

Project —— 项目文件

项目文件为 unitypackage 文件包:

下载导入 Unity 即可使用



点击下载 —— 项目资源


支持

May Be —— 搞开发,总有一天要做的事!

拥有自己的服务器,无需再找攻略!

Chinar 提供一站式教程,闭眼式创建!

为新手节省宝贵时间,避免采坑!

先点击领取 —— 阿里全产品优惠券 (享受最低优惠)



1 —— 云服务器超全购买流程 (新手必备!)



2 —— 阿里ECS云服务器自定义配置 - 购买教程(新手必备!)



3—— Windows 服务器配置、运行、建站一条龙 !



4 —— Linux 服务器配置、运行、建站一条龙 !




" role="presentation">

技术交流群:806091680 ! Chinar 欢迎你的加入


END

本博客为非营利性个人原创,除部分有明确署名的作品外,所刊登的所有作品的著作权均为本人所拥有,本人保留所有法定权利。违者必究


对于需要复制、转载、链接和传播博客文章或内容的,请及时和本博主进行联系,留言,Email: ichinar@icloud.com


对于经本博主明确授权和许可使用文章及内容的,使用时请注明文章或内容出处并注明网址

Unity镜子效果的实现(无需镜子Shader)的更多相关文章

  1. Unity——ShaderLab实现玻璃和镜子效果

    在这一篇中会实现会介绍折射和反射,以及菲尼尔反射:并且实现镜子和玻璃效果: 这里和之前不同的地方在于取样的是一张CubeMap: demo里的cubemap使用的一样,相机所在位置拍出来的周围环境图: ...

  2. Unity喷墨效果Shader实现

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  3. Unity加载模块深度解析(Shader)

    作者:张鑫链接:https://zhuanlan.zhihu.com/p/21949663来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 接上一篇 加载模块深度解析(二 ...

  4. 【Unity Shaders】Diffuse Shading——向Surface Shader添加properties

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  5. unity描边效果

    这里总结了几种在unity实现描边效果的方法,首先准备一个模型导入在unity中,使用默认shader,上传一张原始图,以便后面实现功能效果的对比 一.边缘光,这里参照官方的一个SurfaceShad ...

  6. 【Unity Shaders】Transparency —— 透明的cutoff shader

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  7. 【Unity Shaders】Diffuse Shading——在Surface Shader中使用properties

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  8. Unity 的“Vertex Lit Rendering path“中 shader Pass 的注意事项

    "MADFINGER/Environment/Unlit (Supports Lightmap)"是 ShadowGun 示例中最简单的 shader 了,如下: // Unlit ...

  9. unity加载ab后,场景shader不起效问题(物件表现黑色)

    需要把unity自带的shader,加入到默认列表

随机推荐

  1. Linux Shell数值比较和字符串比较及相关

    说明: 1. 把字符串当成整型进行比较,由于abcd等字符对不上0123当程序尝试去转成二进制时无法完成转换,所以用于数值比较的运算不能用于字符串比较:但是把整型当成字符串进行比较,0123这些数值完 ...

  2. 查看mysql版本

    方法一:show variables like 'version'; 方法二:select version();

  3. memory prefix mini mono multi out _m 5

      1● mini 小 迷你   2● mono 单一 ,单   3● multi 多

  4. UVa 11210 - Chinese Mahjong 模拟, 枚举 难度: 0

    题目 https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&a ...

  5. Player Settings 导出设置

    Player Settings is where you define various parameters (platform specific) for the final game that y ...

  6. 首次编译Java小程序

    public class helloworld { public static void main(string[] args) { system.out.println("hello wo ...

  7. C++基础知识:操作符重载

    1.C++标准库: C++标准库并不是C++语言的一部分C++标准库是由C++语言编写而成的类库和函数的集合C++标准库中定义的类和对象都位于std命名空间中C++标准库的头文件都不带.h后缀C++标 ...

  8. 深入理解java虚拟机---java虚拟机内存管理(五)

    1.深入理解java虚拟机 总图: 1.线程共享区: 2.线程独占区: 1.程序计数器 理解为当前线程锁执行的字节码的行号指示器,程序计数器没有内存异常错误.

  9. ubuntu14.04 解析不了域名—ubuntu的DNS配置

    问题描述: 电脑系统为ubuntu14.04,连上无线后,火狐浏览器打开www.baidu.com,提示找不到服务器,以及终端ping www.baidu.com,提示unkown host,但是浏览 ...

  10. 理解AXI Quad Serial Peripheral Interface(SPI) IP核

    reference :   PG153-AXI Quad SPI v3.2 LogiCORE IP Product Guide.pdf 在使用MicroBlaze过程中,调用了此IP,所以有必须仔细学 ...