点关注不迷路,持续输出Unity
干货文章。
嗨,大家好,我是新发,之前写了一篇文章:《ShaderGraph使用教程与各种特效案例:Unity2020》。
昨天,又有个同学问了我一个问题:
昨天太忙了,今天趁下班后的业余时间,就来讲讲这个问题吧:
如何使用Unity ShaderGraph
实现在模型涂鸦的效果?
本文最终效果如下:
本文Demo
工程已上传到CodeChina
(最近GitHub
貌似有问题,经常连不上),感兴趣的同学可自行下载学习。
CodeChina
地址:https://codechina.csdn.net/linxinfa/UnityShaderGraphGraffiti
注意,我使用的Unity
版本是2020.2.7f1c1
,ShaderGraph
版本是Version 10.3.2 - March 01, 2021
,如果你使用的版本比我的版本低,则可能运行我的Demo
工程会有问题。
首先,我们思考一下,实现这个功能需要解决的问题:
1 我们怎么知道鼠标点击在模型上的具体位置?
2 我们怎么在这个位置画笔刷图案?
3 我们画的图案如何与模型的主贴图融合?
第一个问题,我们可以利用摄像机射线碰撞检测来获取鼠标点击到模型上的具体位置。
用到Camera.ScreenPointToRay
和Physics.Raycast
接口。
第二个问题,我们可以把鼠标画的点画在一张RenderTexture
上,
基于第一个问题答案,我们可以得到射线碰撞检测的RaycastHit
,而RaycastHit
有一个textureCoord
成员变量,这个就是uv
坐标,有了uv
值,就可以换算出在RenderTexture
图上的具体位置,再使用Graphics.DrawTexture
接口在RenderTexture
上画笔刷图案即可。
第三个问题,两张图片的融合,这就交给ShaderGraph
来处理吧。
所以,其实这个功能的核心不是ShaderGraph
,ShaderGraph
只是做最后的图片融合处理。
当然,这只是其中一种思路,如果各位大佬有更好的方法,欢迎指出。
首先,创建一个Render Texture
。
设置一下Render Texture
的尺寸和格式,尺寸的大小决定我们最后画图案的精度。
我们可以看到,Render Texture
默认是黑乎乎的一张图。
用photoshop
做两张图,一张纯黑色的方图(用于初始化填充Render Texture
),一张笔刷图,简单起见,笔刷图案我就用一个白点。
如下:
开始写代码,就一个脚本:DrawOn3D.cs
。代码的注释我写得比较清晰了,大家应该能看懂。
代码如下:
// DrawOn3D.cs
using UnityEngine;
///
/// 在3D模型上涂鸦
///
public class DrawOn3D : MonoBehaviour
{
///
/// 绘制的目标图片
///
public RenderTexture rt;
///
/// 笔刷
///
public Texture brushTexture;
///
/// 空白图
///
public Texture blankTexture;
///
/// 主摄像机
///
public Camera cam;
///
/// 模型
///
public Transform modelTransform;
void Start()
{
cam = Camera.main;
DrawBlank();
}
// 初始化RenderTexture
private void DrawBlank()
{
// 激活rt
RenderTexture.active = rt;
// 保存当前状态
GL.PushMatrix();
// 设置矩阵
GL.LoadPixelMatrix(0, rt.width, rt.height, 0);
// 绘制贴图
Rect rect = new Rect(0, 0, rt.width, rt.height);
Graphics.DrawTexture(rect, blankTexture);
// 弹出改变
GL.PopMatrix();
RenderTexture.active = null;
}
// 在RenderTexture的(x,y)坐标处画笔刷图案
private void Draw(int x, int y)
{
// 激活rt
RenderTexture.active = rt;
// 保存当前状态
GL.PushMatrix();
// 设置矩阵
GL.LoadPixelMatrix(0, rt.width, rt.height, 0);
// 绘制贴图
x -= (int)(brushTexture.width * 0.5f);
y -= (int)(brushTexture.height * 0.5f);
Rect rect = new Rect(x, y, brushTexture.width, brushTexture.height);
Graphics.DrawTexture(rect, brushTexture);
// 弹出改变
GL.PopMatrix();
RenderTexture.active = null;
}
private void Update()
{
if (Input.GetMouseButton(0))
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
// hit.textureCoord是碰撞点的uv值,uv值是从0到1的,所以要乘以宽高才能得到具体坐标点
var x = (int)(hit.textureCoord.x * rt.width);
// 注意,uv坐标系和Graphics坐标系的y轴方向相反
var y = (int)(rt.height - hit.textureCoord.y * rt.height);
Draw(x, y);
}
}
// 按左右方向键,旋转模型
if(Input.GetKey(KeyCode.LeftArrow))
{
modelTransform.Rotate(0, 360 * Time.deltaTime, 0);
}
else if (Input.GetKey(KeyCode.RightArrow))
{
modelTransform.Rotate(0, -360 * Time.deltaTime, 0);
}
}
}
创建一个PBR ShaderGraph
,实现模型主贴图和RenderTexture
的融合。
暴露出四个变量,方便在材质球中设置参数。
为了体现我的艺术天分,我找了个手模。
创建一个材质球HandMat
,使用上面做的ShaderGraph
,给材质球赋值贴图。
最后将材质赋给模型。
将DrawOn3D
脚本挂到Main Camera
上,并设置好参数。
Rt
:Render Texture
,用于画图案;
Brush Texture
:笔刷图案,一个白点;
Blank Texture
:一张纯黑色的空白图;
Cam
:主摄像机,用于做射线检测;
Module Transform
:用于旋转模型。
运行Unity
,测试效果如下:
可以通过材质球调节图案颜色和透明度。
写完了,现在是23:01,收拾睡觉,大家晚安。