体积渲染(4)——噪声贴图+OnRenderImage实现体积云

1、噪声贴图

之前的shader需要CPU一刻不停地计算噪声值,但如果用噪声贴图,那么噪声值就已经是提前计算好的,可以直接使用,节约性能。

由于它是有rgb值的,所以我们可以使用它的不同通道去生成不同的噪声

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MldY2VEd-1619172692063)(photo/ValueNoise.png)]

导入设置

  1. 不产生Mipmap
  2. 勾选 override for pc…因为我们想使用噪声图片上原始的值,所以要勾选,这样会在上面所示的平台中都使用MAX SIZE2048,即不压缩这张噪声图片。
  3. Format设置为RGBA 32 bit

2、动态添加shader

给相机添加clouds脚本

using UnityEngine;

//在非播放状态就可以看见云
[ExecuteInEditMode]
public class Clouds : MonoBehaviour
{
    public Shader CloudShader;
    public float MinHeight = 0.0f;
    public float MaxHeight = 0.0f;
    public float FadeDist = 2;
    public float Scale = 5;
    public float Steps = 50;
    public Texture ValueNoiseImage;
    public Transform Sun;

    Camera _Cam;

    Material _Material;

    //创建一个temporary material
    public Material Material
    {
        get
        {
            if(_Material == null && CloudShader != null)
            {
                _Material = new Material(CloudShader);
            }
            if(_Material != null &&CloudShader == null)
            {
                DestroyImmediate(_Material);
            }
            if (_Material != null && CloudShader != null && CloudShader != _Material.shader)
            {
                DestroyImmediate(_Material);
                _Material = new Material(CloudShader);
            }

            return _Material;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        if (_Material)
            DestroyImmediate(_Material);
        _Material = Material;
    }

    //因为我们有了材质但不能没有mesh,所以就将相机的视锥体作为我们的mesh
    //这个函数可以结合CustomGraphicsBlit去理解
    Matrix4x4 GetFrustumCorners()
    {
        Matrix4x4 frustumCorners = Matrix4x4.identity;
        Vector3[] fCorners = new Vector3[4];

        这个函数将相机在远截面的四个角的位置赋值给fCorners
        _Cam.CalculateFrustumCorners(new Rect(0, 0, 1, 1), _Cam.farClipPlane, Camera.MonoOrStereoscopicEye.Mono, fCorners);

        frustumCorners.SetRow(0, fCorners[1]); //topLeft
        frustumCorners.SetRow(1, fCorners[2]); //topRight
        frustumCorners.SetRow(2, fCorners[3]); //bottomRight
        frustumCorners.SetRow(3, fCorners[0]); //0代表左下角,左下角最后渲染,否则会有90度倾斜

        return frustumCorners;
    }
}

注意:

1、代码中创建的material只是Temporary material

2、Camera.CalculateFrustumCorners

public void CalculateFrustumCorners (Rect viewport, float z, Camera.MonoOrStereoscopicEye eye, Vector3[] outCorners);

参数

viewport 用于视锥体计算的标准化视口坐标。
z 从摄像机原点开始的 Z 深度(将在该位置计算四角)。
eye 要使用的摄像机眼投影矩阵。
outCorners 包含视锥体四角矢量的输出数组。不能为 null,且长度必须 >= 4。

描述

给定视口坐标,计算指向指定摄像机深度处视锥体四角的视图空间矢量。

用于有效地计算图像特效着色器中的像素在世界空间中的位置。

使用示例

using UnityEngine;

public class ExampleClass : MonoBehaviour
{
    void Update()
    {
        // this example shows the different camera frustums when using asymmetric projection matrices (like those used by OpenVR).

        var camera = GetComponent<Camera>();
        Vector3[] frustumCorners = new Vector3[4];
        camera.CalculateFrustumCorners(new Rect(0, 0, 1, 1), camera.farClipPlane, Camera.MonoOrStereoscopicEye.Mono, frustumCorners);

        for (int i = 0; i < 4; i++)
        {
            var worldSpaceCorner = camera.transform.TransformVector(frustumCorners[i]);
            Debug.DrawRay(camera.transform.position, worldSpaceCorner, Color.blue);
        }

        camera.CalculateFrustumCorners(new Rect(0, 0, 1, 1), camera.farClipPlane, Camera.MonoOrStereoscopicEye.Left, frustumCorners);

        for (int i = 0; i < 4; i++)
        {
            var worldSpaceCorner = camera.transform.TransformVector(frustumCorners[i]);
            Debug.DrawRay(camera.transform.position, worldSpaceCorner, Color.green);
        }

        camera.CalculateFrustumCorners(new Rect(0, 0, 1, 1), camera.farClipPlane, Camera.MonoOrStereoscopicEye.Right, frustumCorners);

        for (int i = 0; i < 4; i++)
        {
            var worldSpaceCorner = camera.transform.TransformVector(frustumCorners[i]);
            Debug.DrawRay(camera.transform.position, worldSpaceCorner, Color.red);
        }
    }
}

参数解析

1、Rect Viewport

Viewport 用于视锥体计算的标准化视口坐标。
左下角为(0,0),右上角为(1,1)。

2、MonoOrStereoscopicEye

描述

摄像机眼,对应于人的左眼或右眼(用于立体渲染),或不对应于眼睛(用于非立体渲染)。

单个摄像机可以在一帧中渲染左视图及右视图。因此,该枚举描述:1) 当 Camera.stereoActiveEye 在渲染回调(例如 Camera.OnRenderImage)期间返回时,摄像机当前正在渲染的眼睛;2) 当传入函数时,指示要对哪个眼睛进行操作。

默认值为 Camera.MonoOrStereoscopicEye.Left,因此,如果启用了立体渲染,则在渲染之外调用时,某些方法或属性可能会返回 Camera.MonoOrStereoscopicEye.Left

变量

Left 对应于左眼立体渲染的摄像机眼。
Right 对应于右眼立体渲染的摄像机眼。
Mono 对应于非立体渲染的摄像机眼。

3、图像效果处理 post-processing

在clouds脚本中添加如下函数:

 //云不是transparent吗?为什么这里用的是opaque?
    //因为这里需要把天空盒的颜色和我们的云进行混合,所以这个图像就并不是透明的了
    [ImageEffectOpaque]
    void OnRenderImage(RenderTexture source, RenderTexture destination) //这个函数用于update我们的图像
    {
        //如果 _Material 或 ValueNoiseImage 有缺失,那么就无后处理效果
        if(_Material == null || ValueNoiseImage == null)
        {
            //Blit=Bit Block Transfer 在CG中,用于将bitmap合并
            Graphics.Blit(source, destination);
            return;
        }

        if (_Cam == null)
            _Cam = GetComponent<Camera>();

        Material.SetTexture("_ValueNoise", ValueNoiseImage);
        if (Sun != null)
            Material.SetVector("_SunDir", -Sun.forward); //白天,相机朝向太阳的方向
        else
            Material.SetVector("_SunDir", Vector3.up); //夜晚,意味着相机不会hit anything

        Material.SetFloat("_MinHeight", MinHeight);
        Material.SetFloat("_MaxHeight", MaxHeight);
        Material.SetFloat("_FadeDist", FadeDist);
        Material.SetFloat("_Scale", Scale);
        Material.SetFloat("_Steps", Steps);

        //下面设置相机的动态部分
        Material.SetMatrix("_FrustumCornersWS", GetFrustumCorners());
        Material.SetMatrix("_CameraInvViewMatrix", _Cam.cameraToWorldMatrix);
        Material.SetVector("_CameraPosWS", _Cam.transform.position);

        //最后一个参数为pass
        CustomGraphicsBlit(source, destination, Material, 0);
    }

解析:

(1)、图像效果处理

在unity中使用图像效果处理非常简单,用一个脚本使用OnRenderImage 方法并将该方法挂载在camera上面,OnRenderImage 有两个参数,第一个是unity传进来的图像(当前渲染的),第二个是目标纹理

这里一般使用一个shader对每个像素进行处理使用Graphics.Blit ,可以渲染的目标纹理中也可以渲染到自己创建的render texture中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ul2PjExv-1619172692067)(photo/5c2dd09cd0346.png)]

OnRenderImage(RenderTexture src, RenderTexture dest)

src: 是当前渲染帧最后的图像,如果使用特性 ImageEffectOpaque 得到的是渲染不透明之后的帧图像

注意事项

一般使用 Graphics.Blit 渲染到目标纹理

1.如果不设置目标纹理,那么会直接渲染的back buffer(直接用于显示)

2.unity期望使用目标纹理的,所以我们Graphics.Blit 或者手动操作dest 应该是最后的操作

3.使用的shader 应该关闭一些操作 Cull Off ZWrite Off ZTest Always 这些应该是都有的

4.使用Graphics.SetRenderTarget.渲染到不同的目标中

5.如果使用纹理坐标来做一些计算的话注意不同平台的坐标可能不同平台相关信息

6.多个效果的话unity按照从脚本从上到下执行上一个的目标是下一个的source.如图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HmmOGadt-1619172692069)(photo/5c2dd0c33c431.png)]

(2)Graphics.Blit

public static void Blit (Texture source, RenderTexture dest);

public static void Blit (Texture source, RenderTexture dest, Material mat, int pass= -1);

public static void Blit (Texture source, Material mat, int pass= -1);

public static void Blit (Texture source, RenderTexture dest, Vector2 scale, Vector2 offset);

public static void Blit (Texture source, RenderTexture dest, int sourceDepthSlice, int destDepthSlice);

public static void Blit (Texture source, Material mat, int pass, int destDepthSlice);

public static void Blit (Texture source, RenderTexture dest, Vector2 scale, Vector2 offset, int sourceDepthSlice, int destDepthSlice);

参数

source 源纹理。
dest 目标
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值