zoukankan      html  css  js  c++  java
  • 立体渲染 Volumetric Rendering

    基础概念

    在3D游戏引擎中,球体、立方体以及所有其它复杂的集合体都是由三角面片组成的。引擎只会渲染物体的表面,比如球体,半透明物体等.整个世界由各种空壳构成.

    立体渲染(Volumetric Rendering)的基本概念:模拟光线在物体内部的传送,从而实现更震撼也更真实的视觉效果。
    片段着色器最后返回的对象,是从特定角度看过去特定位置的颜色。
    这种方式计算的颜色是完全随意的,因此返回的内容可以不必匹配几何体的真实渲染情况。
    下图展示了一个3D立方体的例子。当片段着色器检测到立方体表面的颜色时,模拟光线传送,使得结果如同一个球体

    vol2

    立体射线投射 Volumetric Raycasting

    用一个函数判断光线与自定义的几何体相交的问题,限制较大,只能模拟简单几何体比如球,圆柱等。

    固定步长立体光线追踪 Volumetric Raymarching with Constant Step

    不依赖相交函数的,基于迭代的,可以模拟任意几何体
    一步一步的检测光线是否已经投射到红色球体

    vol3

    bool raymarchHit (float3 position, float3 direction)
    {
            for (int i = 0; i < STEPS; i++)
            {
                    if ( sphereHit(position) )
                            return true;
    
                    position += direction * STEP_SIZE;
            }
    
            return false;
    }
    
    bool sphereHit (float3 p)
    {
        return distance(p,_Centre) < _Radius;
    }

    距离辅助的光线追踪 Distance Aided Raymarching

    固定步长的光线追踪非常低效,需要一种方法估算在遇到几何体之前需要走多远,
    比如之前的sphereHit函数,不是返回bool值,而是距离球面的距离

    float sphereDistance (float3 p)
    {
        return distance(p,_Centre) - _Radius;
    }

    该函数就是一个有向距离函数(signed distance function),正数在几何体外,负数在几何体上,0在几何体表面
    距离辅助的光线追踪实现代码:

    fixed4 raymarch (float3 position, float3 direction)
    {
            for (int i = 0; i < STEPS; i++)
            {
                    float distance = sphereDistance(position);
                    if (distance < MIN_DISTANCE)
                            return i / (float) STEPS;
    
                    position += distance * direction;
            }
            return 0;
    }

    在一个比较复杂的场景运行的效果如下

    STEPS 最大步数,需要根据图像形状调整
    MIN_DISTANCE 不能是0,给一个比较合适的误差值0.01左右

    SDF Signed Distance Fields(Functions) 有向距离场(函数) 组合

    可以用组合的方式做出比较复杂的图形,例如那个很出名的蜗牛
    简单来说 min返回并集,max返回交集
    可以用类似Alpha混合的方式做多个形状的混合 as1 + (1-a)s2
    还有很多种别的合并方式,如光滑合并

    float sdf_smin(float a, float b, float k = 32)
    {
        float res = exp(-k*a) + exp(-k*b);
        return -log(max(0.0001,res)) / k;
    }

    法线预估

    Íñigo Quílez的方法是对周围其它点的距离场进行取样,来估算局部表面的曲率

    float3 normal (float3 p)
    {
        const float eps = 0.01; 
        return normalize
        ( float3 (
           map(p + float3(eps, 0, 0) ) - map(p - float3(eps, 0, 0)),
           map(p + float3(0, eps, 0) ) - map(p - float3(0, eps, 0)),
           map(p + float3(0, 0, eps) ) - map(p - float3(0, 0, eps))
        ) );
    }

    更多

    ShaderToy中有很多效果很好的例子
    MERCURY团队创建的hg_sdf库,有很多元物件与操作

    一个简单的实例Unity Shader

    效果如下,在Cube内绘制了一个球

    // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
    
    Shader "Unlit/VolumetricText"
    {
     Properties
     {
      _BaseColor ("Base Color", Color) = (1,1,1,1)
            _SphereColor ("Sphere Color", Color) = (1,0,0,1)
            _SphereCentre("Sphere Centre",Vector) = (0,0,0)
            _ShpereRange ("Sphere Range", Range(0.1,2)) = 0.8
     }
     SubShader
     {
      Tags { "RenderType"="Opaque" "LightMode" = "ForwardBase" }
      LOD 100
    
      Pass
      {
       CGPROGRAM
                #include "Lighting.cginc"
    
       #pragma vertex vert
       #pragma fragment frag
    
       #include "UnityCG.cginc"
    
       struct appdata
       {
        float4 vertex : POSITION;    
       };
    
       struct v2f
       {
        float4 vertex : SV_POSITION;
                    float3 wPos : TEXCOORD0; //世界坐标
       };
    
                float4 _BaseColor;
                float4 _SphereColor;
                float3 _SphereCentre;
                fixed _ShpereRange;
    
                //有向距离函数
                float SphereDistance(float3 p)
                {
                    return distance(p, _SphereCentre) - _ShpereRange;
                }
    
                //光线追踪
                fixed Raymarch(float3 position, float3 direction)
                {
                    float STEPS = 10;
                    float MIN_DISTANCE = 0.01;
    
                    for (int i = 0; i < STEPS; i++)
                    {
                        float distance = SphereDistance(position);
                        if (distance < MIN_DISTANCE)
                            return i / (float)STEPS;
    
                        position += distance * direction;
                    }
                    return 0;
                }
    
                //法线模拟_简单球形测试
                float3 NormalEstimation_Sphere(float3 p)
                {
                    return normalize(p - _SphereCentre);
                }
    
       v2f vert (appdata v)
       {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
                    o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
        return o;
       }
    
       fixed4 frag (v2f i) : SV_Target
       {
        fixed4 col = _BaseColor;
                    float3 viewDirection = normalize(i.wPos - _WorldSpaceCameraPos);
                    fixed rayHit = Raymarch(i.wPos, viewDirection);
                    if (rayHit >= 0.01)
                        col = _SphereColor;
    
                    //丢弃不在形状内的,测试,不在的改为白色
                    clip(col.a - 0.01);
    
                    fixed3 normal = NormalEstimation_Sphere(i.wPos);
    
                    //简单处理下光照
                    fixed3 lightDir = _WorldSpaceLightPos0.xyz; // Light direction
                    fixed3 lightCol = _LightColor0.rgb; // Light color
    
                    fixed NdotL = max(dot(normal, lightDir), 0);
                    col.rgb = col * lightCol * NdotL;
        return col;
       }
       ENDCG
      }
     }
    }

    参考网页

    Unity教程|立体渲染
    Unity3D体积烟雾制作思路分享
    梯度下降法
    comprehensive guide to volume rendering

  • 相关阅读:
    SA练习题总结-篇一
    树上距离(树形DP)
    Codeforces Round #633(Div.2) E. Perfect Triples
    Codeforces Round #633 (Div. 2) D.Edge Weight Assignment
    问题 B: FZB(树形DP+边记忆化)
    【Matlab】自学笔记——基础知识篇
    【Python科学计算】Numpy——ndarry
    退役总结
    [树的遍历]树的遍历(PTA)
    [stl]集合相似度(PTA)
  • 原文地址:https://www.cnblogs.com/Hichy/p/9584068.html
Copyright © 2011-2022 走看看