精华内容
下载资源
问答
  • java反射作用与意义

    千次阅读 2019-03-04 01:07:13
    1、反射的应用场合:在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息. 2、反射的作用:通过反射可以使程序代码访问装载到JVM 中的类的内部信息 获取已装载类的成员...

    1、反射的应用场合:在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息.

    2、反射的作用:通过反射可以使程序代码访问装载到JVM 中的类的内部信息

    获取已装载类的成员变量信息

    获取已装载类的方法

    获取已装载类的构造方法信息

    反射的缺点

    性能问题

    使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。

    使用反射会模糊程序内部逻辑

    程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。

    展开全文
  • Java——反射意义及优缺点

    千次阅读 多人点赞 2018-12-15 17:26:54
    意义: 1.增加程序的灵活性,避免将程序写死到代码里。 例:定义了一个接口,实现这个接口的类有20个,程序里用到了这个实现类的地方有好多地方,如果不使用配置文件手写的话,代码的改动量很大,因为每个地方都要...

    意义:

    1.增加程序的灵活性,避免将程序写死到代码里。

    例:定义了一个接口,实现这个接口的类有20个,程序里用到了这个实现类的地方有好多地方,如果不使用配置文件手写的话,代码的改动量很大,因为每个地方都要改而且不容易定位,如果你在编写之前先将接口与实现类的写在配置文件里,下次只需改配置文件,利用反射(java API已经封装好了,直接用就可以用 Class.newInstance())就可完成。

    2.代码简洁,提高代码的复用率,外部调用方便

    package cn.yonyong.reflection.testdemo;
    
    interface Fruit { //水果接口
      public void eat() ; //吃水果
    }
    
    class Apple implements Fruit{ //定义苹果
      public void eat() {
        System.out.println("**吃苹果。"); 
      } 
    }
    
    class Orange implements Fruit{
      public void eat() {
        System.out.println("**吃橘子。"); 
      }
    }
    
    class Factory{
      public static Fruit getInstance(String className){
        Fruit fruit = null ;
        try{
          fruit = (Fruit) Class.forName(className).newInstance() ;
        }catch(Exception e ){
          e.printStackTrace() ;
        }
        return fruit ;
      }
    }
    
    public class FactoryDemo{
      public static void main(String args[]){
      //通过工厂类取得接口实例,传入完整的包.类名称
        Fruit f = Factory.getInstance("cn.yonyong.reflection.testdemo.Apple") ;
        if(f!=null){ //判断是否取得接口实例
          f.eat() ;
        }
      }
    }

    如果不用反射,那么我们如果再加一个西瓜类,就得在Factory里判断,每添加一个类都要修改一次Factory,但用了反射只用在调用的时候传入完整的类名就可完成。结果:用反射,修改一处代码;不用反射,修改两处代码。

    3.对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法

     

     

    反射的缺点

    性能问题

    1.使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。

    2.反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。

    使用反射会模糊程序内部逻辑

    程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。

    安全限制

    使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了

    内部暴露

    由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

     

    Java反射可以访问和修改私有成员变量,那封装成private还有意义么?

    既然小偷可以访问和搬走私有成员家具,那封装成防盗门还有意义么?这是一样的道理,并且Java从应用层给我们提供了安全管理机制——安全管理器,每个Java应用都可以拥有自己的安全管理器,它会在运行阶段检查需要保护的资源的访问权限及其它规定的操作权限,保护系统免受恶意操作攻击,以达到系统的安全策略。所以其实反射在使用时,内部有安全控制,如果安全设置禁止了这些,那么反射机制就无法访问私有成员。

     

    反射是否真的会让你的程序性能降低?

    1.反射大概比直接调用慢50~100倍,但是需要你在执行100万遍的时候才会有所感觉

    2.判断一个函数的性能,你需要把这个函数执行100万遍甚至1000万遍

    3.如果你只是偶尔调用一下反射,请忘记反射带来的性能影响

    4.如果你需要大量调用反射,请考虑缓存。

    5.你的编程的思想才是限制你程序性能的最主要的因素

    展开全文
  • 反射的作用及意义

    2020-11-05 08:51:28
    1.反射的定义: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为java语言的反射...

    1.反射的定义:

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为java语言的反射机制。

    反射的应用场合:在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息.

    在运行阶段使用,不能写死;工厂模式,动态生成对象;框架底层;运行过程中修改jar包中的一些内容(由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。

    另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。)

    2.反射的功能:

    在运行时判定任意一个对象所属的类;
    在运行时构造任意一个类的对象;
    在运行时判定任意一个类所具有的成员变量和方法;
    在运行时调用任意一个对象的方法;
    生成动态代理。

    3.主要用途:

    反射最重要的用途就是开发各种通用框架。
    例子:
    1)比如在spring中,我们将所有的类Bean交给spring容器管理,无论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类的信息,spring根据这些信息,需要创建那些Bean,spring就动态的创建这些类。还有在struts2的struts.xml中配置action,也是通过反射调用的action。

    2)还有在我们创建数据库链接时,这句代码Class tc = Class.forName(“com.java.dbtest.TestConnection”)就是告诉JVM去加载这个类,而加载的过程是在程序执行过程中动态加载的。通过类的全类名让jvm在服务器中找到并加载这个类,而如果是使用别的数据库,那就要换一个类了,如果是传统写死的方法创建,就要修改原来类的代码,而对于反射,则只是传入的参数就变成另一个了而已,可以通过修改配置文件,而不是直接修改代码。

    3)再比如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。只是如果这个类还没有,获取时会获取不到,但不会导致编译错误,更不会导致程序的崩溃。

    4)还有当我们在使用 IDE(如 Eclipse\IDEA)时,当我们输入一个队长或者类并向调用它的属性和方法时,一按 (“.”)点号,编译器就会自动列出她的属性或方法,这里就会用到反射。

    4.基本反射功能的实现(反射相关的类一般都在java.lang.relfect包里):

    getName()获得类的完整名字

    getPackage()获取此类所属的包

    getSuperclass()获得此类的父类对应的Class对象

    getField(String name)获得类的指定属性

    getMethods()获得类的public类型的方法

    getMethod (String name,Class [] args)获得类的指定方法

    每个Method对象对应一个方法,获得Method对象后,可以调用其invoke() 来调用对应方法

    Object invoke(Object obj,Object [] args):obj代表当前方法所属的对象的名字,args代表当前方法的参数列表,返回值Object是当前方法的返回值,即执行当前方法的结果。

    1、获得Class对象

    使用Class类的forName静态方法

    直接获取某一个对象的class

    调用某个对象的getClass()方法

    2、判断是否为某个类的实例

    用instanceof关键字来判断是否为某个类的实例

    3、创建实例

    使用Class对象的newInstance()方法来创建Class对象对应类的实例。

    先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。

    4、获取方法

    getDeclaredMethods()

    5、获取构造器信息

    getDeclaredMethods()

    getMethods()

    getMethod()

    6、获取类的成员变量(字段)信息

    getFiled: 访问公有的成员变量

    getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量

    getFileds和getDeclaredFields用法

    7、调用方法

    invoke()

    8、利用反射创建数组

    Array.newInstance()

    5.反射的缺点:

    1)使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。

    2)使用反射会模糊程序内部逻辑

    3)程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。

    展开全文
  • 反射

    千次阅读 2018-06-21 21:09:40
    对环境进行采样。使用反射探测器。创建粗糙和平滑的镜子。...有些时候,你需要对自反射做些工作环境贴图目前,我们的着色器通过组合物体表面上的环境光、漫反射光和镜面反射光来对片段进行着色。这至少对...

    • 对环境进行采样。
    • 使用反射探测器。
    • 创建粗糙和平滑的镜子。
    • 执行盒体投影立方体贴图的采样。
    •  反射探测器之间的混合。


    这是关于渲染基础的系列教程的第八部分。在这个系列教程的前面部分我们添加了对阴影的支持。这一部分会介绍间接反射。

    这个系列教程是使用Unity5.4.0f3开发的。

    Unity 渲染教程(八):反射

    有些时候,你需要对自反射做些工作


    环境贴图

    目前,我们的着色器通过组合物体表面上的环境光、漫反射光和镜面反射光来对片段进行着色。这至少对于比较阴暗的表面,会产生看上去比较真实的图像但是对于自发光的表面会看起来不太对。

    一些闪亮的表面比如说是镜子,特别是金属材质的时候。一个完美的镜子会反射所有的光。这意味着根本没有漫反射。只有镜面反射的存在。所以,让我们把我们的材质变成一个镜子材质,通过设置“金属”属性为1和”光滑度“属性为0.95。同时把这个材质的颜色设置为白色。

    Unity 渲染教程(八):反射

    一个发光的白色金属球

    这么做以后,得到的结果是几乎完全黑色的表面,即使它的颜色是白色的。我们只看到一个小亮点,这是光源照射到物体直接反生发射到我们视野中的部分。而其他所有光被反射在不同的方向。如果你要将“平滑度“属性增加到1,那么高亮会消失。

    这看起来不像是真正的镜子。镜子不是黑色,它们会发射东西!在这种情况下,它应该发射天空盒,显示一个蓝色的天空与灰色的地面。


    间接的镜面高光

    我们的球变成了黑色,因为我们只包括了直接光照。为了反射环境,我们还必须包括间接光照。具体来说,间接光照用于镜面反射。在CreateIndirectLight函数中,我们配置了Unity的UnityIndirect结构。到目前为止,我们已将其镜面分量设置为零。这就是为什么球会变成黑色!

    将场景的环境亮度设置为零,以便我们可以专注于反射部分。将我们的材质再次变成比较阴暗的非金属,平滑度为0.5。然后将间接镜面高光颜色更改为一个更加明显的颜色,比如说是红色。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    UnityIndirect CreateIndirectLight (Interpolators i) {
        UnityIndirect indirectLight;
        indirectLight.diffuse = 0;
        indirectLight.specular = 0;
     
        #if defined(VERTEXLIGHT_ON)
            indirectLight.diffuse = i.vertexLightColor;
        #endif
     
        #if defined(FORWARD_BASE_PASS)
            indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
            indirectLight.specular = float3(1, 0, 0);
        #endif
     
        return indirectLight;
    }

     

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    间接高光颜色分别是黑色和红色的对比图,光滑度是0.5

    球体这次使用的是一个红色的色调。在这种情况下,红色的深浅正好是反射率的指示。因此,我们的球体会从它的中心那块反射一些环境光到我们的视野之中。显然,它更多地会在它的边缘的地方发生反射。这是因为当视角变得更浅的时候,每个表面都会变得更具有反射性。在掠射角的情况下,大多数光被反射,一切物体都变成了镜子。这被称为菲涅耳反射。我们使用UNITY_BRDF_PBS版本来为我们进行计算。

    表面越平滑,菲涅尔反射越强。当使用非常高的平滑度的时候,红色环变得非常明显。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    光滑度0.15和0.95的对比图

    因为反射来自间接光,所以它与直接光源无关。这样的话,反射也与该光源的阴影无关。因此,菲涅耳反射在球体的被遮蔽的边缘中变得非常明显。

    在金属的情况下,间接反射占主导地位。不再是一个黑色的球体,我们现在得到一个红色的球体。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    光滑度0.15、0.50和 0.95的金属材质对比图

     

    对环境进行采样

    为了反射出实际的环境,我们必须对天空盒的立方体贴图进行采样。它在UnityShaderVariables中定义为unity_SpecCube0。这个变量的类型取决于在HSLSupport中确定的目标平台。

    使用三维向量对立方体贴图进行采样,这可以指定采样的方向。我们可以使用UNITY_SAMPLE_TEXCUBE宏来处理类型上差异。让我们在开始的时候只使用法线向量作为采样的方向。

    1
    2
    3
    4
    5
    #if defined(FORWARD_BASE_PASS)
        indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
        float3 envSample = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.normal);
        indirectLight.specular = envSample;
    #endif


    Unity 渲染教程(八):反射

    对环境进行采样

    天空盒出现在我们的球体上了,但是这太亮了。这是因为立方体贴图里面包含高动态范围颜色,这允许它包含大于1的亮度值。我们必须将样本从高动态光照渲染格式转换为RGB。

    UnityCG包含一个DecodeHDR函数,我们可以使用它。高动态光照渲染数据存储在四个通道之中,使用的是RGBM格式。所以我们必须采样一个float4值,然后进行转换。

    1
    2
    3
    indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
    float4 envSample = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.normal);
    indirectLight.specular = DecodeHDR(envSample, unity_SpecCube0_HDR);

     

    Unity 渲染教程(八):反射

    高动态光照渲染数据解码以后的效果


    DecodeHDR是什么样子?

    RGBM格式包含RGB三个通道,加上包含幅度因子的M通道。最终的RGB值是通过将它们乘以Unity 渲染教程(八):反射来计算。在这个公式里面,x是标量,y是指数,分别存储在解码指令的前两个分量中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Decodes HDR textures
    // handles dLDR, RGBM formats
    inline half3 DecodeHDR (half4 data, half4 decodeInstructions) {
        // If Linear mode is not supported we can skip exponent part
        #if defined(UNITY_NO_LINEAR_COLORSPACE)
            return (decodeInstructions.x * data.a) * data.rgb;
        #else
            return (decodeInstructions.x * pow(data.a, decodeInstructions.y)) *
                data.rgb;
        #endif
    }

     

    包含幅度因子的M通道的转换是必须的,这是因为当存储在纹理中的时候,会限制使用8位值来表示一个0到1范围。因此,x指令将其放大,y指令使其非线性,比如像是伽马空间那样。



    跟踪反射

    我们得到了正确的颜色,但是我们没有看到实际的反射。这是因为我们使用球体的法线来对环境进行采样,所以投影不依赖于视图方向。所以它就像在一个球体上涂上了环境贴图一样。

    为了产生实际反射,我们必须得到从相机到物体表面的方向,并使用表面法线来反射它。我们可以使用reflect函数,就像我们在这个系列的第4部分所做的那样。在这种情况下,我们需要视图方向,因此将其作为参数添加到CreateIndirectLight。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {
        
         
        #if defined(FORWARD_BASE_PASS)
            indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
            float3 reflectionDir = reflect(-viewDir, i.normal);
            float4 envSample = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectionDir);
            indirectLight.specular = DecodeHDR(envSample, unity_SpecCube0_HDR);
        #endif
     
        return indirectLight;
    }
     
     
    float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
        
     
        return UNITY_BRDF_PBS(
            albedo, specularTint,
            oneMinusReflectivity, _Smoothness,
            i.normal, viewDir,
            CreateLight(i), CreateIndirectLight(i, viewDir)
        );
    }


    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    使用法线和反射方向之间的对比


    使用一个反射探测器
    让物体能反射天空盒很好,但如果能反射实际场景几何的话就更好了。所以让我们创建一个简单的建筑。我使用一个旋转后的四边形作为一个地板,并在它的上面放置了几个立方体柱,以及一些立方体梁。球体位于建筑物的中心。

    Unity 渲染教程(八):反射

    有一些东西等待反射

    要看到建筑物的反射,我们必须先捕获它。这是通过反射探测器完成的,你可以通过GameObject / Light / Reflection Probe进行添加。创建一个反射探测器并将其放置在与我们的球体相同的位置。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    一个默认的反射探测器

    场景视图显示场景中有具有圆形小控件的反射探测器的存在。它的外观取决于场景视图的配置。如果Gizmo阻碍我们的球体的视野,那就让我们关闭它。你可以通过打开场景视图工具栏中的Gizmo下拉菜单,向下滚动到ReflectionProbe,然后单击其图标来执行这个操作。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    关闭反射探测器的gizmo

    反射探测器通过渲染一个立方体贴图来捕获环境信息。这意味着它要渲染整个场景六次,每次绘制立方体贴图的一面。在默认情况下,它的类型设置为”烘焙“。在这个模式下,立方体贴图由编辑器生成,并包含在构建中。这些立方体贴图里面仅包括静态几何。因此,我们的建筑必须是静态的,然后才能渲染到立方体贴图之中。

    或者,我们可以将反射探测器的类型更改为“实时“。这种类型的探测器会在运行的时候实时渲染,你可以选择在何时渲染以及渲染的频率。还有一个自定义模式,给你完全的控制权。

    虽然实时类型的探测器是最灵活的,但是当频繁更新的时候,它们也是最昂贵的。此外,实时类型的探测器不在编辑模式下更新,而烘焙类型的探测器会在探测器或者静态几何体被编辑的时候进行更新。所以,让我们坚持使用烘焙类型的探测器,并且确保我们的建筑是静态的。

    对象实际上不需要是完全静态的。你可以为各种子系统的使用将物体标记为静态。在这种情况下,相关的设置为“反射探测器静态“。启用这个设置以后,对象将渲染到烘焙类型的反射探测器里面。你可以在运行的时候移动它们,但它们的反射仍然是冻结状态的。

    Unity 渲染教程(八):反射

    “反射探测器静态“

    在建筑物被标记为静态之后,反射探测器将被更新。它会显示黑色一会儿,然后反射就会出现。反射球不是反射本身的一部分,所以保持动态。

    Unity 渲染教程(八):反射

    反射出来的几何体


    不完美的反射

    只有完美光滑的表面才会产生完美的锐利反射。表面越粗糙的话,反射扩散的越广。比较钝的镜子会产生模糊的反射。我们该如何模糊反射?

    纹理可以具有mipmap,这是原始图像的下采样版本。当以全尺寸进行观察的时候,较高的mipmap因此会产生模糊的图像。这些会是块状的图像,但是Unity使用不同的算法来生成环境贴图的mipmap。这些卷积图表示从锐利到模糊的良好过渡。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    Mipmap等级从0到5的对比图



    粗糙的镜子

    我们可以使用UNITY_SAMPLE_TEXCUBE_LOD宏在特定的mipmap等级对立方体贴图进行采样。环境立方体贴图使用三线性过滤,因此我们可以在相邻的级别之间进行混合。这允许我们根据材质的平滑度来选择mipmap。材质越粗糙,我们应该使用更高的mipmap等级。

    因为粗糙度从0变化到1的,我们必须通过我们使用的mipmap的范围来缩放这个值。Unity使用UNITY_SPECCUBE_LOD_STEPS宏来确定此范围,因此我们也使用它。

    1
    2
    3
    4
    float roughness = 1 - _Smoothness;
    float4 envSample = UNITY_SAMPLE_TEXCUBE_LOD(
        unity_SpecCube0, reflectionDir, roughness * UNITY_SPECCUBE_LOD_STEPS
    );

     

    UNITY_SPECCUBE_LOD_STEPS在哪里定义?

    UnityShaderVariables将UNITY_SPECCUBE_LOD_STEPS定义为6,除非它之前已在其他位置定义过了。所以你可以在自己的着色器中定义它,在导入其他文件之前。Unity的着色器不在其他任何地方定义它,所以他们总是使用6这个值。环境贴图的实际大小没有考虑在内。

    Unity 渲染教程(八):反射

    光滑度为0.5时候的效果
    实际上,粗糙度和mipmap等级之间的关系不是线性的。Unity使用的转换公式是Unity 渲染教程(八):反射,其中r是原始的粗糙度。

    Unity 渲染教程(八):反射

    1
    2
    3
    4
    5
    float roughness = 1 - _Smoothness;
    roughness *= 1.7 - 0.7 * roughness;
    float4 envSample = UNITY_SAMPLE_TEXCUBE_LOD(
        unity_SpecCube0, reflectionDir, roughness * UNITY_SPECCUBE_LOD_STEPS
    );


    Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    光滑度为0.5、0.75和0.95时候的效果

    UnityStandardBRDF导入文件包含了Unity_GlossyEnvironment函数。它包含了所有用于转换粗糙度的代码,对立方体贴图进行采样,并从高动态光照渲染进行转换。所以让我们使用这个函数代替我们自己的代码。

    要将立方体贴图作为参数传递,我们必须使用UNITY_PASS_TEXCUBE macrp。这需要处理类型差异。 此外,粗糙度和反射方向必须被封装在Unity_GlossyEnvironmentData结构之中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
            indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
            float3 reflectionDir = reflect(-viewDir, i.normal);
    //      float roughness = 1 - _Smoothness;
    //      roughness *= 1.7 - 0.7 * roughness;
    //      float4 envSample = UNITY_SAMPLE_TEXCUBE_LOD(
    //          unity_SpecCube0, reflectionDir, roughness * UNITY_SPECCUBE_LOD_STEPS
    //      );
    //      indirectLight.specular = DecodeHDR(envSample, unity_SpecCube0_HDR);
            Unity_GlossyEnvironmentData envData;
            envData.roughness = 1 - _Smoothness;
            envData.reflUVW = reflectionDir;
            indirectLight.specular = Unity_GlossyEnvironment(
                UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData
            );



    Unity_GlossyEnvironment做了什么不同的事情吗?

    它执行与我们做的相同的操作,但它有一些基于目标平台和一些其他设置的变化。此外,它还包含了一些注释和禁用代码,这触及了mipmaps如何创建的细节。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    half3 Unity_GlossyEnvironment (
        UNITY_ARGS_TEXCUBE(tex), half4 hdr, Unity_GlossyEnvironmentData glossIn
    ) {
    #if UNITY_GLOSS_MATCHES_MARMOSET_TOOLBAG2 && (SHADER_TARGET >= 30)
        // TODO: remove pow, store cubemap mips differently
        half roughness = pow(glossIn.roughness, 3.0 / 4.0);
    #else
        // MM: switched to this
        half roughness = glossIn.roughness;
    #endif
        // spec power to the square root of real roughness
        //roughness = sqrt(sqrt(2/(64.0+2)));
     
    #if 0
        // m is the real roughness parameter
        float m = roughness*roughness;
        // smallest such that 1.0+FLT_EPSILON != 1.0
        // (+1e-4h is NOT good here. is visibly very wrong)
        const float fEps = 1.192092896e-07F;
        // remap to spec power. See eq. 21 in -->
        float n =  (2.0 / max(fEps, m * m)) - 2.0;
        // remap from n_dot_h formulatino to n_dot_r.
        // See section "Pre-convolved Cube Maps vs Path Tracers" --> https://
        // s3.amazonaws.com/docs.knaldtech.com/knald/1.0.0/lys_power_drops.html
        n /= 4;
        // remap back to square root of real roughness
        roughness = pow( 2 / (n + 2), 0.25);
    #else
        // MM: came up with a surprisingly close approximation
        // to what the #if 0'ed out code above does.
        roughness = roughness * (1.7 - 0.7 * roughness);
    #endif
     
    #if UNITY_OPTIMIZE_TEXCUBELOD
        half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, glossIn.reflUVW, 4);
        if(roughness > 0.5)
            rgbm = lerp(rgbm, UNITY_SAMPLE_TEXCUBE_LOD(tex, glossIn.reflUVW, 8),
                2 * roughness - 1);
        else
            rgbm = lerp(UNITY_SAMPLE_TEXCUBE(tex, glossIn.reflUVW), rgbm,
                2 * roughness);
    #else
        half mip = roughness * UNITY_SPECCUBE_LOD_STEPS;
        half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, glossIn.reflUVW, mip);
    #endif
     
        return DecodeHDR_NoLinearSupportInSM2 (rgbm, hdr);
    }

    最后的优化部分是用于PVR类型的图形处理器,以避免相关的纹理读取。 为了使它能够正常工作,它需要反射向量来传递给内插值器。

    DecodeHDR_NoLinearSupportInSM2函数只是负责转发到DecodeHDR,但是针对Shader Model 2.0优化过。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // Decodes HDR textures
    // handles dLDR, RGBM formats
    // Modified version of DecodeHDR from UnityCG.cginc
    inline half3 DecodeHDR_NoLinearSupportInSM2 (
        half4 data, half4 decodeInstructions
    ) {
        // If Linear mode is not supported we can skip exponent part
     
        // In Standard shader SM2.0 and SM3.0 paths are always using different
        // shader variations
        // SM2.0: hardware does not support Linear, we can skip exponent part
        #if defined(UNITY_NO_LINEAR_COLORSPACE) && (SHADER_TARGET < 30)
            return (data.a * decodeInstructions.x) * data.rgb;
        #else
            return DecodeHDR(data, decodeInstructions);
        #endif
    }

     

    凹凸不平的镜子

    除了使用平滑度来表示粗糙的镜子,你当然可以使用法线贴图来添加更大的变形。因为我们使用扰动的法线来确定反射方向,使用法线贴图来添加更大的变形是没问题的。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    光滑度分别为0.5、 0.75和0.95的凹凸不平的镜子的对比图



    金属与非金属的对比

    金属和非金属表面可以产生清晰的反射,它们看起来不同。镜面反射可以在发光的电介质材质上表现得很好,但它们不会支配它们的外观。仍然有大量的漫反射可以看到。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    光滑度为0.5、0.75和0.95的非金属的对比图


    回想一下,金属会它的镜面反射发生带上颜色,而非金属不会。这对于镜面高光,以及镜面的环境反射也是如此。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    红色的金属和非金属


    镜子和阴影

    正如我们前面所看到的那样,间接反射与表面的直接光照无关。这对于其他阴影区域最为明显。在非金属的情况下,这仅仅导致视觉上更明亮的表面。你仍然可以看到直接光照投射的阴影。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    光滑度为0.5、0.75和1的非金属的对比图

    相同的规则适用于金属,但间接反射占据了主导地位。因此,直接光照和阴影随着光泽增加而消失。完美的镜子上没有阴影。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    光滑度为0.5、0.75和1的金属的对比图


    虽然这在物理上是正确的,但现实生活很少是完美的。举个简单的例子来说,你可以看到直接光照和粘在一个完美的镜子上的污垢和灰尘产生的阴影。并且有许多材质是金属和电介质组分的混合物。你可以通过将金属滑块设置在0和1之间的某个位置来模拟此问题。

    Unity 渲染教程(八):反射

    金属和电介质组分为0.75的金属材质,一个有一点脏的镜子


    对盒子的投影

    我们目前有一个反射球和一个反射探测器。两者都悬停在我们建筑的中心。让我们添加一些球体,将它们放置在内部正方形区域的边缘附近。但是让我们坚持只使用一个探测器,位于建筑的中心。

    Unity 渲染教程(八):反射

    所有的反射都是相同的

    这里面的反射有些问题。他们都看起来一样。视角稍有不同,但所有球体都反射了环境,就好像它们位于建筑物的中心。他们其实不是位于建筑物的中心,但反射探测器是位于建筑物的中心!

    如果我们想要更逼真的反射,那么我们必须为每个球体创建一个探测器,将它们放置在适当的位置。这样,每个球体从其自己的角度获得环境地图。

    Unity 渲染教程(八):反射

    每个球体一个探测器,带有不同的反射内容

    虽然这个效果更好,但它仍然不是完全真实的反射。为了实现完全真实的反射的效果,我们必须对我们渲染的每个片段使用一个反射探测器。这将需要位于球体表面上的许多反射探测器。幸运的是,近似对球体来说效果不是太坏。但是如果是平面镜的话效果是怎么样的?

    首先,去除中心反射探测器以外的所有部件。然后创建一个四边形并将其定位,使其覆盖建筑物的内部并接触支柱的中点。然后把它变成镜子,观察反射。

    Unity 渲染教程(八):反射

    地板反射不正确

    反射的内容根本不匹配!方向显示是正确的,但大小和位置都是错误的。如果我们给每个片段使用一个反射探测器,反射效果会很好。但是我们只有一个反射探测器。这种近似对于实际是无限远的东西是足够的,就好比说天空盒。但它不适合对附近的事物进行反射。

    当一个环境无限远的时候,我们在确定它的反射的时候不需要关心视角的位置。但是当大多数环境就在附近的时候,我们需要小心视角的位置。假设我们在一个空的立方体房间的中间有一个反射探测器。它的环境贴图包含这个房间的墙壁、地板和天花板。如果立方体贴图和房间对齐的话,那么立方体贴图的每个面都与墙壁、地板或天花板中的一个完全对应。

    接下来,假设我们在这个房间的任何地方有一个表面位置和一个反射方向。向量将最终与立方体的边缘相交。我们可以用一点数学来计算这个交点。然后我们可以从房间的中心到这一点构造一个向量。使用这个向量,我们可以对立方体贴图进行采样,并以正确的反射结束。

    Unity 渲染教程(八):反射

    投影来找到采样的方向

    房间不必是一个立方体,也能正常工作。任何矩形形状都可以,像是我们建筑物的内部。但是,房间和立方体贴图必须对齐。


    反射探测器的盒子.

    反射探测器具有大小和探测器起点的位置,这些信息相对于其位置在世界空间中限定了一个立方区域。这个立方区域总是轴对齐的,这意味着这个立方区域忽略所有的旋转。同时它也忽略缩放。

    这个立方区域用于两个目的。首先,Unity使用这些区域来决定在渲染对象时使用哪个探测器。第二,这个区域用于盒子的投影,这是我们要做的。

    选择探测器后,可以在场景视图中显示盒子。在反射探测器的检查器的顶部是Probe场景编辑模式切换。点击左边的按钮可以打开盒子投影边界的小控件。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    盒子的投影边界

    你可以使用边界面中心的黄点来调整它们。你也可以通过在检查器中编辑大小和探测器的原点矢量来调整它们。通过调整探测器的原点位置,可以相对于采样点来移动盒子。你也可以在场景中使用其他编辑模式来调整它,但是它很精巧,目前不能很好地与撤消一起工作。

    调整盒子,使这个盒子覆盖建筑物的内部,触摸柱子,并达到最高点。我把这个盒子做的稍微大一点,以防止由于与场景视图中的gizmo出现Z冲突而发生闪烁。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    调整后的边界

    当我们在这个位置的时候,启用Box Projection,因为这是我们想做的。



    调整采样的方向

    为了计算对盒子的投影,我们需要初始反射方向、我们采样的位置、立方体贴图的位置以及盒子的边界。为我们的着色器添加一个函数,在CreateIndirectLight上面的位置。

    1
    2
    3
    4
    5
    6
    float3 BoxProjection (
        float3 direction, float3 position,
        float3 cubemapPosition, float3 boxMin, float3 boxMax
    ) {
        return direction;
    }

    首先,调整盒子的边界,使盒子的边界相对于表面位置。

    1
    2
    3
    boxMin -= position;
    boxMax -= position;
    return direction; 

     

    接下来,我们必须对方向向量进行放缩,使其从它的位置指向所需的交叉点。让我们先考虑X方向。如果方向的X分量为正,则它指向最大的边界。否则,它指向最小边界。用方向的X分量划分适当的界限给了我们需要的标量。当方向为负的时候,这也是有效的,因为最小边界也是负的,在除法之后会产生一个为正的结果。

    1
    2
    3
    boxMin -= position;
    boxMax -= position;
    float x = (direction.x > 0 ? boxMax.x : boxMin.x) / direction.x;

    对于Y方向和Z方向也是如此。

    1
    2
    3
    float x = (direction.x > 0 ? boxMax.x : boxMin.x) / direction.x;
    float y = (direction.y > 0 ? boxMax.y : boxMin.y) / direction.y;
    float z = (direction.z > 0 ? boxMax.z : boxMin.z) / direction.z;

    我们现在有三个标量,但那个是正确的那个标量?这取决于哪个标量最小。哪个标量最小表示着哪个边界面最近。

    Unity 渲染教程(八):反射

    选择最小的因子

    1
    2
    float z = (direction.z > 0 ? boxMax.z : boxMin.z) / direction.z;
    float scalar = min(min(x, y), z);

     

    当其中一个分数为零的时候会发生什么?

    可能的结果是,方向矢量分量中的一个或两个是零。这将产生无效的结果,这不会使它超过选择的最小值。

    现在我们可以通过将缩放后的方向添加到位置中来找到交点。通过从那里减去立方体贴图的位置,我们得到新的投影采样方向。

    Unity 渲染教程(八):反射

    找到新的投影方向

    1
    2
    float scalar = min(min(x, y), z);
    return direction * scalar + (position - cubemapPosition);


    新的方向是否必须规一化?

    可以使用任何非零矢量对立方体贴图进行采样。硬件的立方体贴图的采样基本上与我们刚刚做的一样。它要确定向量指向哪个面,然后执行除法以找到与立方体贴图面的交点。它使用该点的适当坐标来对立方体贴图面的纹理进行采样。

    我们可以通过在单个float3表达式中组合三个候选因子来简化这个代码,直到选择适当的界限之后再做减法和除法。

    1
    2
    3
    4
    5
    6
    7
    8
    float3 BoxProjection (
        float3 direction, float3 position,
        float3 cubemapPosition, float3 boxMin, float3 boxMax
    ) {
        float3 factors = ((direction > 0 ? boxMax : boxMin) - position) / direction;
        float scalar = min(min(factors.x, factors.y), factors.z);
        return direction * scalar + (position - cubemapPosition);
    }

     

    现在使用我们在CreateIndirectLight中的新函数来修改环境的采样向量。

    1
    2
    3
    4
    5
    envData.reflUVW = BoxProjection(
        reflectionDir, i.worldPos,
        unity_SpecCube0_ProbePosition,
        unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax
    );


    Unity 渲染教程(八):反射

    投影之后的反射

    我们的平面镜调整后的反射看起来好多了。但重要的是,反射表面不会延伸超出探测器的界限。缩放镜像,使其与边界完全匹配,并触及柱子。

    Unity 渲染教程(八):反射

    调整后的地板镜像

    柱子的反射现在完全匹配真实的情况了。至少它的位置是正好在镜子的边缘和探测器的边界。距离更远的一切都是不对齐的,因为这些点的投影是错误的。这是我们用单个反射探测器所能做的最好的结果。

    另一件明显错误的事情是,我们看到地板上的镜子反射了部分地板。这是因为环境地图是从底部的镜子上方的角度进行渲染的。这可以通过将探测器的原点降低到仅稍微高于镜子来修正,同时保持它们的边界还是正确的。

    Unity 渲染教程(八):反射

    探测器的中心被降低以后的效果图

    虽然这样一个比较低的采样点对于地板上的镜子是更好的,但是对于浮动的球体来说就不是那么好。所以,让我们再次移动探测器,并看看球体的反射效果。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    一个投影后的探测器与九个未投影的探测器的对比效果图

    事实证明,一个单箱投影的探测器产生的反射非常类似于九个单独的探测器所产生的效果!所以盒体投影是一个非常方便的技巧,尽管它不是完美的。



    可选的投影

    是否使用盒体投影会根据每个探测器的情况来确定不同,这由盒体投影的切换开关控制。Unity将此信息存储在立方贴图所在位置的第四个组件之中。如果这个分量大于零的话,则探测器应使用盒体投影。让我们使用if语句来处理这个问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    float3 BoxProjection (
        float3 direction, float3 position,
        float4 cubemapPosition, float3 boxMin, float3 boxMax
    ) {
        if (cubemapPosition.w > 0) {
            float3 factors =
                ((direction > 0 ? boxMax : boxMin) - position) / direction;
            float scalar = min(min(factors.x, factors.y), factors.z);
            direction = direction * scalar + (position - cubemapPosition);
        }
        return direction;
    }

     

    即使我们使用了if语句,这并不意味着编译后的代码也会包含一个分支。举个简单的例子来说,OpenGLCore用一个条件赋值结尾,但是它不是一个分支。

    Unity 渲染教程(八):反射

    Direct3D11也是如此。

    Unity 渲染教程(八):反射

     

    我们可以通过在我们自己的分支之前插入UNITY_BRANCH宏来请求实际会有一个分支。尽管在着色器中应该尽量避免分支,但在这种情况下有分支并不是那么糟糕。这是因为条件是均匀的。对象的所有片段使用的是相同的探测器设置,因此最终采取相同的分支。

    1
    2
    3
    4
    UNITY_BRANCH
    if (cubemapPosition.w > 0) {
        
    }


    OpenGL核心现在包含了一个明显的分支。

     Unity 渲染教程(八):反射


    Direct3D11也是如此。

    Unity 渲染教程(八):反射

     

     

    是不是有一个Unity函数用于盒体投影?

    是有这么一个函数。UnityStandardUtils里面包含了BoxProjectedCubemapDirection函数。它做的就是我们刚才所做的同样的事情,包括分支。但它也对反射方向参数进行了归一化,这是不必要的。这就是为什么我们不使用它的原因。


    都反射探测器进行混合

    现在我们在建筑物内部有了很好的反射,但是建筑物外面怎么样的?一旦你移动一个球体超出探测器的范围之外,它会将反射的内容切换到天空盒。

    Unity 渲染教程(八):反射

    位于探测器盒体内部和外部的球体

    探测器和天空盒之间的切换是非常突然的。我们可以增加探测器的盒体,这样它也可以覆盖建筑物外面的空间。然后,我们可以移动一个球体进出建筑物,它的反射内容将逐渐改变。然而,探测器的位置位于建筑物的内部。如果要在建筑物的外面使用它会产生非常奇怪的反射效果。

    Unity 渲染教程(八):反射

    探测器的盒体变大以后的效果

    为了在建筑物的内部和外部都得到合适的反射,我们必须使用多个反射探测器。

    Unity 渲染教程(八):反射

    第二反射探测器

    这些反射是有意义的,但在两个不同的探测器之间的区域仍然会有突然和非常清楚的过渡。



    在探测器之间进行插值

    Unity为着色器提供了两个反射探测器的数据,因此我们可以在它们之间进行混合。第二个反射探测器是unity_SpecCube1。我们可以对环境贴图和内插值进行采样,这取决于哪个反射探测器起到更主要的效果。Unity为我们计算了这个值,并将插值器的结果存储在unity_SpecCube0_BoxMin的第四个坐标中。当仅使用第一个反射探测器的时候,它会被设置为1,如果有混合的话,则设置为较低的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    envData.reflUVW = BoxProjection(
        reflectionDir, i.worldPos,
        unity_SpecCube0_ProbePosition,
        unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax
    );
    float3 probe0 = Unity_GlossyEnvironment(
        UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData
    );
    envData.reflUVW = BoxProjection(
        reflectionDir, i.worldPos,
        unity_SpecCube1_ProbePosition,
        unity_SpecCube1_BoxMin, unity_SpecCube1_BoxMax
    );
    float3 probe1 = Unity_GlossyEnvironment(
        UNITY_PASS_TEXCUBE(unity_SpecCube1), unity_SpecCube0_HDR, envData
    );
    indirectLight.specular = lerp(probe1, probe0, unity_SpecCube0_BoxMin.w);


    上面的代码很可能会产生一个编译错误。显然,samplerunity_SpecCube1变量是不存在的。这是因为访问纹理的话需要纹理资源和采样器,第二个反射探测器没有任何的纹理资源和采样器。相反,它依赖于第一个反射探测器的采样器。

    使用UNITY_PASS_TEXCUBE_SAMPLER宏将第二个反射探测器针的纹理与我们唯一的采样器相结合。这样我们就摆脱了错误。

    1
    2
    3
    4
    float3 probe1 = Unity_GlossyEnvironment(
        UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1, unity_SpecCube0),
        unity_SpecCube0_HDR, envData
    );

     

    Unity 渲染教程(八):反射

    仍然没有发生混合


    重叠探测器的盒体

    为了让混合起作用,多个反射探测器的边界必须重叠。所以调整第二个反射探测器的边界,以便这个反射探测器的区域延伸到建筑物上。重叠区域中的球体应该获得混合反射。网格渲染器组件的检查器还会显示正在使用的反射探测器及其权重。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    开启了混合功能的发生重叠的反射探测器的盒体

    如果这个过渡不够顺利的话,你可以在其他两个反射探测器之间添加第三个反射探测器。这个反射探测器的盒体要与另外两个反射探测器的盒体重叠。所以当球体移动到建筑物的外面的时候,你首先要混合内部反射探测器和中间的反射探测器,以及在中间的反射探测器和外部的反射探测器之间做混合。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    三个反射探测器

    我们也可以在反射探测器和天空盒之间进行混合。你必须将物体的模式从“反射探测器”模式更改为“反射探测器和天空盒相结合”的模式。 当物体的包围盒部分地位于探测器的边界之外的时候,将会发生混合。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    对一个反射探测器和天空盒进行混合


    其他反射探测器的模式是怎么样的?

     “关闭“表示物体根本不会使用探测器。它总是使用天空盒。

    “简单“则是禁用混合。它总是使用最重要的探测器,或是天空盒。


    优化

    对两个探测器进行采样需要做很多的工作。只有当有东西要混合的时候,我们才应该这样做。因此,需要添加一个基于插值器的分支。Unity也在标准着色器中做到了这一点。再次强调下,它是一个概率均匀的分支。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    float interpolator = unity_SpecCube0_BoxMin.w;
    UNITY_BRANCH
    if (interpolator < 0.99999) {
        float3 probe1 = Unity_GlossyEnvironment(
            UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1, unity_SpecCube0),
            unity_SpecCube0_HDR, envData
        );
        indirectLight.specular = lerp(probe1, probe0, interpolator);
    }
    else {
        indirectLight.specular = probe0;
    }


    当目标平台被认为不能处理它的时候,Unity的着色器也会禁用混合。这由UNITY_SPECCUBE_BLENDING进行控制,在可以进行混合的时候这个值会定义为1,否则的话为0。我们可以通过使用预处理器条件块仅在需要时包括对应的代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #if UNITY_SPECCUBE_BLENDING
        float interpolator = unity_SpecCube0_BoxMin.w;
        UNITY_BRANCH
        if (interpolator < 0.99999) {
            float3 probe1 = Unity_GlossyEnvironment(
                UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1, unity_SpecCube0),
                unity_SpecCube0_HDR, envData
            );
            indirectLight.specular = lerp(probe1, probe0, interpolator);
        }
        else {
            indirectLight.specular = probe0;
        }
    #else
        indirectLight.specular = probe0;
    #endif


     

    我们不应该使用#ifdefinedUNITY_SPECCUBE_BLENDING)吗?

    不应该,因为UNITY_SPECCUBE_BLENDING是始终定义的。在这种情况下,实际的定义很重要。A 1表示真,而a 0表示假。

    对于盒体投影存在一个类似的优化,这是基于UNITY_SPECCUBE_BOX_PROJECTION的定义来决定的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    float3 BoxProjection (
        float3 direction, float3 position,
        float4 cubemapPosition, float3 boxMin, float3 boxMax
    ) {
        #if UNITY_SPECCUBE_BOX_PROJECTION
            UNITY_BRANCH
            if (cubemapPosition.w > 0) {
                float3 factors =
                    ((direction > 0 ? boxMax : boxMin) - position) / direction;
                float scalar = min(min(factors.x, factors.y), factors.z);
                direction = direction * scalar + (position - cubemapPosition);
            }
        #endif
        return direction;
    } 

     

    这两个值在哪里定义?

    它们由编辑器基于目标平台进行定义。此外,当面向3.0以下的着色器模型的时候,UnityStandardConfig将它们设置为0。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // "platform caps" defines that were moved to editor,
    // so they are set automatically when compiling shader
    // UNITY_SPECCUBE_BOX_PROJECTION
    // UNITY_SPECCUBE_BLENDING
     
    // still add safe net for low shader models,
    // otherwise we might end up with shaders failing to compile
    #if SHADER_TARGET < 30
        #undef UNITY_SPECCUBE_BOX_PROJECTION
        #define UNITY_SPECCUBE_BOX_PROJECTION 0
        #undef UNITY_SPECCUBE_BLENDING
        #define UNITY_SPECCUBE_BLENDING 0
    #endif

     

     

    多次反射

    当你有两个镜子彼此面对的时候,你最终会看到看起来无尽的嵌套反射。我们可以在Unity中得到相同的结果吗?

    Unity 渲染教程(八):反射

    没有多次反射的效果

    我们的镜子不包括在反射本身,因为它们不是静态的。所以让我们把底部的镜子设为静态。而球应该保持动态,否则的话探测器不能再忽视它们,会产生奇怪的反射。

    Unity 渲染教程(八):反射

    地板镜是静态的,会产生黑色的反光

    镜子现在显示在我们的单个反射探测器里面,但它看起来全部是黑的。这是因为在渲染反射探测器的时候,镜子的环境贴图不存在。它试图反射自己,然后失败了!

    默认情况下,Unity不包括环境贴图中的反射。但这可以通过光照设置进行更改。环境设置部分包含反射反弹的滑块,默认情况下设置为1。让我们将它设置为2。

    Unity 渲染教程(八):反射Unity 渲染教程(八):反射

    发生了两次反射以后的效果

    当设置为两次反射的时候,Unity首先正常渲染每个反射探测器。然后,使用现有的反射数据再次渲染它们。因此,来自地面镜的初始反射现在包括在环境贴图之中。

    Unity支持支持最多五次反弹。而这需要大量的渲染,所以你绝对不想在运行的时候使用这个!如果要在实际环境中看到个五次反弹的效果,可以复制地面镜,把它变成一个天花板上的镜子。

    Unity 渲染教程(八):反射

    同时有地面镜和天花板上的镜子的环境里面发生五次反弹的效果

    所以在Unity中可以得到嵌套的反射,但是它们是有限的。此外,投影是错误的,因为反射探测器的边界没有延伸到镜子之外的虚拟空间之中。



    有了所有这些限制,反射这个功能还有用吗?

    我们在这一节教程中专注于反射本身,所以我们看到了反射里面包含的所有的缺陷。通过反射功能造出一个完美的镜子不太实际,但有了这些微妙的反射就可以让镜子很逼真。知道了它们的极限所在,你就可以找出有效地使用它们的时间和地点。

    反射探测器是向场景中添加反射的默认方法,也是最方便的方法,但它不是唯一的方法。如果你需要强大的平面镜,一种与反射探测器不同的方法是从虚拟观察者的角度渲染场景,并将其用作镜子的纹理。另一种方法是对场景几何做镜像。你可以通过这种方式获得很好的结果,但是这些方法有很多的限制,不像反射探测器那样通用。然后还有屏幕空间反射。我们将在添加对延迟渲染的支持之后讲解屏幕空间反射。


    这个系列的下一篇教程是:复杂的材质。


    展开全文
  • 私有(private)属性及方法可以通过反射访问,那么private的意义是什么? 在一个类中,为了不让外界访问到某些属性和方法,通常将其设置为private,用正常的方式(对象名.属性名,对象名.方法名)将无法访问此属性与...
  • Swift反射API及其用法

    千次阅读 2015-11-23 09:49:39
    猛戳查看最终版@SwiftGG尽管 Swift 一直在强调强类型、编译时安全和静态调度,但它的标准库仍然提供了反射机制。可能你已经在很多博客文章或者类似Tuples、Midi Packets 和 Core Data 的项目中见过它。也许你刚好对...
  • java反射实战

    千次阅读 2016-12-01 22:57:39
    《Java反射实战》—— Ira R.Forman 、Nate Forman第一章 一些基本知识 反射基础 类基础 使用方法我们经常碰到一些问题,而这些问题可以使用反射来简单并优雅的解决掉。如果没有反射,我们的解决方法散乱的,累赘和...
  • C++反射机制的实现

    万次阅读 多人点赞 2016-06-20 10:03:26
    Java中有天然的反射机制,因为Java本身就是半编译语言,很多东西可以在运行时来做,但是c++就不同了。要建立c++的反射机制,就需要登记每个类名与对象的对应关系。 1.前言 在实际的项目中,听到师兄说C++中用到了...
  • 我会持续更新,欢迎star!...2. 反射原理大解析2.1 反射的常用类和函数2.2 Class 类包含的方法2.3 反射的主要方法2.3.1 得到构造器的方法2.3.2 获得字段信息的方法2.3.3 获得方法信息的方法2.4 反射实战的基.
  • 反射机制常用方法与示例

    千次阅读 2019-12-15 01:50:11
    反射机制如果只是针对于普通开发者而言,意义不大,一般都是作为一些系统的架构设计去使用的,基本所有开源框架,几乎都是反射机制。 反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释...
  • 理解C#反射机制

    万次阅读 多人点赞 2016-11-01 13:12:32
    反射是.NET中的重要机制,通过反射可以得到*.exe或*.dll等程序集内部的接口、类、方法、字段、属性、特性等信息,还可以动态创建出类型实例并执行其中的方法。
  • 反射效果,是一个渲染中很重要的一个效果。在表现光滑表面(金属,光滑地面),水面(湖面,地面积水)等材质的时候,加上反射,都可以画面效果有很大的提升。来看几张图: 先来张最近比较火爆的国产大作《逆水寒...
  • C#反射学习总结

    千次阅读 2016-12-13 14:56:29
    详解C#中的反射 反射(Reflection) 两个现实中的例子: 1、B超:大家体检的时候大概都做过B超吧,B超可以透过肚皮探测到你内脏的生理情况。这是如何做到 的呢?B超是B型超声波,它可以透过肚皮通过向你体内...
  • UE4反射系统简析(含实例过程分析)

    万次阅读 多人点赞 2016-10-23 15:44:25
    一、UE4中的反射系统 1.简述: 1.1 什么是UE4反射 在UE4里面,你无时无刻都会看到类似UFUNCTION()这样的宏。官方文档告诉你,只要在一个函数的前面加上这个宏,然后在括号里面加上BlueprintCallable就可以在编辑器...
  • java反射原理

    千次阅读 多人点赞 2011-03-03 10:52:00
    java反射原理 知道了java反射实现,了解了java反射性能,现在发掘一下java反射原理
  • Java反射机制的原理和用途

    万次阅读 多人点赞 2017-09-07 11:19:14
    看了好多关于Java反射机制的文章,大多都太过官方,消化起来比较稍显费劲,本篇,我会依据自己的理解去阐述什么是Java的反射机制,反射用在什么地方,以及怎么来使用? 开篇前,我们还是要了解一下,什么是Java的...
  • .Net反射

    千次阅读 2007-12-04 15:17:00
    以下转载自:http://www.cnblogs.com/zhouyinhui/archive/2007/08/17/859152.html反射之反思 Mike Repasshttp://msdn.microsoft.com/msdnmag/issues/07/06/CLRInsideOut/default.aspx?loc=en清 晰的组件化目标是否...
  • opengl镜面反射

    千次阅读 2010-07-25 18:05:00
    但做镜面的反射不是让opengl自己去计算一个镜面的反射画面,而是把物件和反射面画出来后,再在反射面的那块区域把物件的倒影画出来。听起来不算简单,但难点却不是怎么画倒影,而是怎么把倒影限定在某块区域。要实现...
  • 这篇博文是对magazine文章: 《Towards Smart and Reconfigurable Environment: Intelligent Reflecting Surface Aided Wireless Network》的读后感记录,也借此理解智能反射面这个新兴的topic。 文章目录智能反射面...
  • 其一是集合框架,刚开始学集合框架总是理解集合框架究竟有什么用,此外集合框架之间复杂的关系确实让我头大了好几天。除了集合框架,另一个感到压力的地方就是反射了。与集合框架不同,反射的类要简单的多,不需要...
  • 反射机制对于普通开发者而言,意义不大,一般都是作为一些系统的架构设计去使用的,包括以后学习的开源框架,几乎都使用了反射机制。 反射概念 反射之后的“反”是针对与“正”来的。那什么是正?就是使用一个类构造...
  • java反射机制及在Abdroid的应用

    千次阅读 2016-08-16 18:00:52
    想写一篇比较短的博客,总结一下java反射机制以及Android中的应用,因为博客太长很多人看着会头疼,而且多去重复那些反射机制的具体实现方式也没啥意义,所以想写的简单易懂些,别过段时间自己看着都头疼。...
  • 我们继续C#基础知识的学习,这篇文章主要要讲的是我们C#程序员迈向高级C#程序员...当然可以,但是用与不用肯定是不一样的,任何复杂抽象的分层架构或者说是复杂的设计模式均是建立在这些基础之上的,比如我们要进
  • GoLang反射的规则

    千次阅读 2014-02-25 20:12:13
    GoLang反射的规则 时间 2012-04-01 11:58:25 随... 第一次知道反射的时候还是许多年前在学校里玩 C# 的时候。那时总是弄不清楚这个复杂的玩意能有什么实际用途……然后发现 Java 有这个,后来发现 PHP 也有了,
  • Java高级特性—反射

    千次阅读 2020-01-23 17:22:11
    Java高级特性——反射 原文链接 定义 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 36,511
精华内容 14,604
关键字:

复杂反射的意义