Unity3D 屏幕空间雪场景Shader渲染
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
CSDN视频网址:http://edu.csdn.net/lecturer/144
游戏中会出现各种各样的场景,比如雪场景,草地场景,城市场景等,这些场景通常的做法是通过美术利用Max工具建模实现的,在这里我们可以使用Shader去渲染,这样可以减少美术的工作量并且能优化效率。先给读者展示如下所示:

正常的场景效果,下面再给你看一副利用Shader的雪场景效果:
这两幅场景是相同的,唯一的区别是第二幅采用了雪效果场景,其他的纹理没有做任何改变,第二幅使用的就是屏幕空间的场景渲染,效果非常不错,下面给读者介绍一下它的实现原理:
理论很简单假设一旦渲染像素的正常面朝上(地面,屋顶等),则应该绘制雪。如果像素的正常面向任何其他方向,那么在雪纹理和原始纹理之间也应该有一个平缓的过渡 。
在实现该Shader之前需要做一些设置,首先将Rendering Path设置为Deferred (延迟渲染),如果将其设置成forward Rendering(前向渲染)在使用Shader时会出现问题。
在代码中将Camera.depthTextureMode设置成DepthNormals是为了将允许我们读取屏幕深度和法线。实现第二幅图效果只需要一个脚本和一个Shader文件就可以。下面实现屏幕渲染的脚本,代码如下所示:
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class ScreenSpaceSnow : MonoBehaviour
{
public Texture2D SnowTexture;
public Color SnowColor = Color.white;
public float SnowTextureScale = 0.1f;
[Range(0, 1)]
public float BottomThreshold = 0f;
[Range(0, 1)]
public float TopThreshold = 1f;
private Material _material;
void OnEnable()
{
// dynamically create a material that will use our shader
_material = new Material(Shader.Find("TKoU/ScreenSpaceSnow"));
// tell the camera to render depth and normals
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
}
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
// set shader properties
_material.SetMatrix("_CamToWorld", GetComponent<Camera>().cameraToWorldMatrix);
_material.SetColor("_SnowColor", SnowColor);
_material.SetFloat("_BottomThreshold", BottomThreshold);
_material.SetFloat("_TopThreshold", TopThreshold);
_material.SetTexture("_SnowTex", SnowTexture);
_material.SetFloat("_SnowTexScale", SnowTextureScale);
// execute the shader on input texture (src) and write to output (dest)
Graphics.Blit(src, dest, _material);
}
}
接下来实现Shader代码编写,我们需要把建筑物表面向上的铺上雪,所有法线朝上的表面都将覆盖雪。相机已经设置了生成深度法线贴图,所以现在直接获取即可。代码如下:
sampler2D _CameraDepthNormalsTexture;
查看Unity官方文档可以了解该命名的意义:
深度贴图可以作为一个着色器的全局着色器属性进行采样。通过声明名为_CameraDepthTexture的采样器,就能够采样相机的主深度纹理。
_CameraDepthTexture总是引用相机的主深度贴图。
获取法线的代码函数:
half4 frag (v2f i) : SV_Target
{
half3 normal;
float depth;
DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), depth, normal);
normal = mul( (float3x3)_CamToWorld, normal);
return half4(normal, 1);
}
Unity文档解释深度和法线的数据都打包为16位。这里需要像代码那样调用DecodeDepthNormal方法进行解包。
这个方法检索的是相机空间的法线。也就是说,如果旋转屏幕相机,那么法线朝向也会改变。脚本中将法线乘以_CamToWorld 矩阵就是为了避免这种情况。它会将法线从相机空间转换为世界空间,这样就不再依赖于相机的透视。
为了让着色器正确编译就必须返回一些东西,所以上面的代码设置了返回语句。这样也便于预览结果以确认计算是否正确。
暂时渲染为RGB图像。在Unity中,Y轴是默认向上的。图中绿色部分表示Y坐标轴的值,现在将其转换为雪量的因子。
half4 frag (v2f i) : SV_Target
{
half3 normal;
float depth;
DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), depth, normal);
normal = mul( (float3x3)_CamToWorld, normal);
half snowAmount = normal.g;
half scale = (_BottomThreshold + 1 - _TopThreshold) / 1 + 1;
snowAmount = saturate( (snowAmount - _BottomThreshold) * scale);
return half4(snowAmount, snowAmount, snowAmount, 1);
}
这里会用到绿色分量。接下来配置积雪覆盖区域顶部和底部的阀值,以便于调整场景的积雪量。
接下来介绍纹理,如果没有纹理,雪看起来会不真实。最难的部分就是将2D纹理(屏幕空间)应用到3D物体上。一种方法是获取像素的世界坐标,然后将世界坐标的X和Z值作为纹理坐标。
half4 frag (v2f i) : SV_Target
{
half3 normal;
float depth;
DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), depth, normal);
normal = mul( (float3x3)_CamToWorld, normal);
// find out snow amount
half snowAmount = normal.g;
half scale = (_BottomThreshold + 1 - _TopThreshold) / 1 + 1;
snowAmount = saturate( (snowAmount - _BottomThreshold) * scale);
// find out snow color
float2 p11_22 = float2(unity_CameraProjection._11, unity_CameraProjection._22);
float3 vpos = float3( (i.uv * 2 - 1) / p11_22, -1) * depth;
float4 wpos = mul(_CamToWorld, float4(vpos, 1));
wpos += float4(_WorldSpaceCameraPos, 0) / _ProjectionParams.z;
half3 snowColor = tex2D(_SnowTex, wpos.xz * _SnowTexScale * _ProjectionParams.z) * _SnowColor;
return half4(snowColor, 1);
}
这里涉及到一些数学知识,您只需知道vpos是视口坐标,wpos是由视口坐标与_CamToWorld矩阵相乘而得到的世界坐标,并且它通过除以远平面的位置(_ProjectionParams.z)来转换为有效的世界坐标。最后使用XZ坐标乘以可配置参数_SnowTexScale和远平面,来计算雪的颜色并获取适当的值。
下面将积雪与场景进行合并:
half4 frag (v2f i) : SV_Target
{
half3 normal;
float depth;
DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), depth, normal);
normal = mul( (float3x3)_CamToWorld, normal);
// find out snow amount
half snowAmount = normal.g;
half scale = (_BottomThreshold + 1 - _TopThreshold) / 1 + 1;
snowAmount = saturate( (snowAmount - _BottomThreshold) * scale);
// find out snow color
float2 p11_22 = float2(unity_CameraProjection._11, unity_CameraProjection._22);
float3 vpos = float3( (i.uv * 2 - 1) / p11_22, -1) * depth;
float4 wpos = mul(_CamToWorld, float4(vpos, 1));
wpos += float4(_WorldSpaceCameraPos, 0) / _ProjectionParams.z;
wpos *= _SnowTexScale * _ProjectionParams.z;
half3 snowColor = tex2D(_SnowTex, wpos.xz) * _SnowColor;
// get color and lerp to snow texture
half4 col = tex2D(_MainTex, i.uv);
return lerp(col, half4 (snowColor,1.0f), snowAmount);
}
上述代码获取原始颜色,并使用snowAmount进行插值渐变为snowColor 。
最后一步:将_TopThreshold设为0.6:
全屏效果见下图:
完整的代码下载地址:链接:http://pan.baidu.com/s/1qYTs0cG 密码:uw1k
Unity3D 屏幕空间雪场景Shader渲染的更多相关文章
- Unity3d 屏幕空间人体皮肤知觉渲染&次表面散射Screen-Space Perceptual Rendering & Subsurface Scattering of Human Skin
之前的人皮渲染相关 前篇1:unity3d Human skin real time rendering 真实模拟人皮实时渲染 前篇2:unity3d Human skin real time ren ...
- Unity3d 着色器语法(Shader)
Shader "name" { [Properties] Subshaders [Fallback] } 定义了一个着色器.着色器拥有一个 Properties 的列表.着色器包含 ...
- Three.js粒子特效,shader渲染初探(一篇非常详细的介绍)
Three.js粒子特效,shader渲染初探 转载来源:https://juejin.im/post/5b0ace63f265da0db479270a 这大概是个序 关于Three.js,网上有不多 ...
- 在Unity中实现屏幕空间阴影(1)
接着上篇文章,我们实现了SSR效果. 其中的在屏幕空间进行光线追踪的方法是通用的.借此我们再实现一种屏幕空间的效果,即屏幕空间阴影. 文中的图片来自Catlike coding http://catl ...
- 基于屏幕空间的实时全局光照(Real-time Global Illumination Based On Screen Space)
目录 Reflective Shadow Maps(RSM) RSM 的重要性采样 RSM 的应用与缺陷 Screen Space Ambient Occulsion(SSAO) SSAO Blur ...
- Unity3D ShaderLab立方体图的法线渲染
Unity3D ShaderLab立方体图的法线渲染 某些情况下,我们希望立方体图的材质球上产生法线效果,来更多的表现细节,比如菱形花纹的玻璃,冰块的表面. 在帧数的协调下,我们可以通过input结构 ...
- Unity3d 动态加载场景物件与缓存池的使用
聊聊Unity3d动态加载场景物件那些事儿. 众所周知,在策划或美术设计完游戏场景地图后,一个场景中可能会存在成千上万个小的物件,比如石头,木箱子,油桶,栅栏等等等等,这些物件并不是游戏中的道具,仅仅 ...
- three.js引擎基础知识—摄像机、场景及渲染器
一.three.js采用右手坐标系: x轴正方向向右,y轴正方向向上,z轴由屏幕从里向外,如下图右: 二.3D编程三要素:场景.渲染器.摄像机 1.场景:创建的物品和模型都需放入场景中 threejs ...
- unity3D 涂涂乐使用shader实现上色效果
unity3D 涂涂乐使用shader实现上色效果 之前我博文里面发过一个简单的通过截图方式来实现的模型上色方法,但是那个方法不合适商用,因为你需要对的很准确才可以把贴图完美截取下来,只要你手抖了一下 ...
随机推荐
- 20145321 《Java程序设计》第9周学习总结
20145321 <Java程序设计>第9周学习总结 教材学习内容总结 第十六章 整合数据库 16.1 JDBC 1.JDBC简介: JDBC是Java联机数据库的标准规范,它定义一组标准 ...
- 20145331 《Java程序设计》第6周学习总结
20145331 <Java程序设计>第6周学习总结 教材学习内容总结 第十章 输入/输出 10.1.1串流 •Java将输入/输出抽象化为串流,数据有来源及目的地,衔 ...
- strcpy、sprintf、memcpy的区别
char*strcpy(char *dest, const char *src); 其对字符串进行操作,完成从源字符串到目的字符串的拷贝,当源字符串的大小大于目的字符串的最大存储空间后,执行该操作会出 ...
- [翻译]理解CSS模块方法
在这个前端发展日新月异的世界,能够找到有所影响的概念相当困难,而将其准确无误的传达,让人们愿意尝试,更是难上加难. 拿CSS来看,在我们写CSS时,工具侧最大的变化,也就是CSS处理器的使用,如:可能 ...
- maven项目引入本地包,不使用中央仓库
1. dependendy引入 <dependency> <groupId>com.taobao</groupId> <artifactId>taoba ...
- LeetCode——Median of Two Sorted Arrays
Question There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median o ...
- Graph_Master(连通分量_H_Trajan+拓扑序dp)
Graph_Master_连通分量_H 题目描述: 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条 ...
- CentOS 7 Fail2ban防暴力破解
1.安装 yum install epel-release -y yum install fail2ban fail2ban-systemd -y 2.配置 //新建配置 vim /etc/fail2 ...
- Kubernetes 部署失败的 10 个最普遍原因
[原文].后面我们可能还会看到一个 OOMKilled 错误. 我们的应用正在挂掉?为什么? 首先我们查看应用日志.假定你发送应用日志到 stdout(事实上你也应该这么做),你可以使用 kubect ...
- 在 Linux 服务器上搭建和配置 Hadoop 集群
实验条件:3台centos服务器,jdk版本1.8.0,Hadoop 版本2.8.0 注:hadoop安装和搭建过程中都是在用户lb的home目录下,master的主机名为host98,slave的主 ...