渲染4——第一盏灯

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wodownload2/article/details/82192619

原文网址:https://catlikecoding.com/unity/tutorials/rendering/part-4/

将法线从本地空间转换到世界空间。
使用一盏平行光。
计算漫反射和镜面反射。
保持能量守恒。
使用金属材质。
利用unity的PBS算法。

本节为渲染得第四节课程。前一小节介绍的是如何使用贴图。本节将学习如何计算光照。

本节课程使用unity 5.4.0b17(我使用的是2017.1.3.f1)。

这里写图片描述

1法线
我之所以能看到物体,是因为我们的眼睛可以侦测到电磁辐射。通常讲的光子实际是光线的一个一个量子。我们可以看到有限的电磁光谱,其他的光谱是看不见的,看到的光谱就是可见光了。

全部的光谱包括如下几个:无线电波,微波,红外线,可见光,紫外线,x射线,gamma射线。

由光源发出的光,一部分照射到物体,一部分照射到物体之后又反弹出去。如果反弹出去的光线最终射到我们的眼睛或者是摄像机的镜头,那么此时我们就看到了物体。

为了解决这个问题,我们必须知道物体的表面。而目前我们只知道物体的位置,但是不知道朝向。所以,需要知道物体表面的法线。

1.1 使用网格法线
首先要准备一些立方体和球体,每个物体有不同的旋转、缩放。
项目的下载地址为:
https://pan.baidu.com/s/1HsUmGgZ72eRwRAqwpSIoog

初始的时候自然是删除shader的内容,然后一步一步往里面加东西。首先是编写一个空的shader,如下:
这里写图片描述

扫描二维码关注公众号,回复: 2960777 查看本文章

传入顶点着色器的数据结构定义:

struct VertexData
{
    float4 position:POSITION;
    float3 normal:NORMAL;
    float2 uv:TEXCOORD0;
};

它包含了本地位置信息、本地法线信息,完整代码如下:

Shader "Custom/My First Lighting Shader" 
{
    SubShader 
    {
        Pass 
        {
            CGPROGRAM

            #pragma vertex MyVertexProgram
            #pragma fragment MyFragmentProgram

            #include "UnityCG.cginc"

            struct VertexData 
            {
                float4 position : POSITION;
                float3 normal : NORMAL;
            };

            struct Interpolators 
            {
                float4 position : SV_POSITION;
                float3 normal : TEXCOORD0;
            };

            Interpolators MyVertexProgram (VertexData v) 
            {
                Interpolators i;
                i.position = UnityObjectToClipPos(v.position);
                i.normal = v.normal;
                return i;
            }

            float4 MyFragmentProgram (Interpolators i) : SV_TARGET 
            {
                return float4(i.normal * 0.5 + 0.5, 1);
            }
            ENDCG
        }
    }
}

效果如下:
这里写图片描述

1.2 动态批处理
首先要知道动态合批在哪里设置。在File->Build Settings->Player Settings->Other Settings->Dynamic Batching
这里写图片描述

勾选动态合批的Frame Debug查看结果:
这里写图片描述
此时物体渲染效果为:
这里写图片描述
不勾选动态合批的Frame Debug:
这里写图片描述
此时物体渲染效果为:
这里写图片描述
红色圈住的物体略微颜色有点颜色的不同。

其原因是,在动态合批处理的时候,散落的物体的法线会被统一转换到世界坐标系,这样法线会有些变化,导致我们的颜色也发生相应的变化。

1.3 世界坐标系下的法线求法
第一种做法是:使用local转world的矩阵乘以法线,得到世界坐标系的法线,具体如下:

Interpolators MyVertexProgram(VertexData v)
{
        Interpolators i;
        i.position = UnityObjectToClipPos(v.position);
        i.normal = mul(unity_ObjectToWorld, float4(v.normal, 0));
        return i;
}

其渲染结果如下:

可以看到法线转到世界坐标系之后,物体的颜色有些变化了。
这里写图片描述
然后我们要再做的就是把世界坐标系的法线规格化之后,看其效果:

Interpolators MyVertexProgram(VertexData v)
{
        Interpolators i;
        i.position = UnityObjectToClipPos(v.position);
        i.normal = mul(unity_ObjectToWorld, float4(v.normal, 0));
        i.normal = normalize(i.normal);
        return i;
}

这里写图片描述

紧接着我们还要注意一个问题,就是对于没有按照一比一缩放的物体,其法线转到世界坐标系之后,物体所对应的颜色有点奇怪,比如sphere球体的颜色。

于是我们可以参考:https://blog.csdn.net/pizi0475/article/details/7932913
介绍如何求得世界法线。

其正确的求法,是利用本地到世界的矩阵的逆矩阵的转置矩阵左乘以局部法线得到世界法线。
于是我们修正我们的shader如下:

Interpolators MyVertexProgram(VertexData v)
{
        Interpolators i;
        i.position = UnityObjectToClipPos(v.position);
        i.normal = mul(
        transpose((float3x3)unity_WorldToObject),v.normal);
        i.normal = normalize(i.normal);
        return i;
}

其渲染结果为:
这里写图片描述
看到不规则的物体的颜色也正常了。

我们可以选择在vertex shader中对法线进行规格化,也可以选择在fragment shader中进行规格化,前者效率更高一点。

2 漫反射
我们之所以能看到物体,是因为反射了光线,而漫反射是光线射到物体表面,然后渗透到物体里面去,进行物理的碰撞,最后部分光线又再次窜出表面最终到达我们的眼睛。而反射多少光呢?这与如入射光的角度有关系,根据Lamber定律,当入射光的角度与法线的越小,那么反射就越大,具体我们可以使用入射光向量点乘以法线向量,如果值越大则反射越强。这样我们就得到一个系数。
此时我们需要知道入射光的方向向量,主要我们要求点乘,这个入射光的方向存储在unity自己提供的变量中,_WorldSpaceLightPos0。这个变量不仅仅表示光源的位置,而且还能表示光源的方向,当且仅当时平行光的时候,这个变量就表示了平行光的方向。然后我们已经知道物体表面的法线了,于是可以求得一个点乘系数:
float factor = dot(_WorldSpaceLightPos0.xyz, i.normal);
有了这个系数,我们还要知道光源的颜色,然后用这个颜色乘以这个系数就得到了最终的结果:

Shader "Custom/My First Lighting Shader"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM

            #pragma vertex MyVertexProgram
            #pragma fragment MyFragmentProgram

            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"


            struct VertexData
            {
                float4 position : POSITION;
                float3 normal : NORMAL;
            };

            struct Interpolators
            {
                float4 position : SV_POSITION;
                float3 normal : TEXCOORD0;
            };

            Interpolators MyVertexProgram(VertexData v)
            {
                Interpolators i;
                i.position = UnityObjectToClipPos(v.position);
                i.normal = mul(transpose((float3x3)unity_WorldToObject), v.normal);
                i.normal = normalize(i.normal);
                return i;
            }

            float4 MyFragmentProgram(Interpolators i) : SV_TARGET
            {
                float diffuseFactor = dot(_WorldSpaceLightPos0.xyz, i.normal);
                float3 diffuse = _LightColor0.rgb * diffuseFactor;
                return float4(diffuse, 1);
            }

            ENDCG
        }
    }
}

此时我们的效果为:
这里写图片描述

此时我们要指定我们的pass的light mode为forwardbase。以及摄像机的渲染模式:
首先在上面的shader中加入:
这里写图片描述

其次是更改camera的rendering path为forward。
这里写图片描述

这样更改之后,我们的物体的渲染效果为:
这里写图片描述

2.5 反射率
我们会一直混淆反射率以及漫反射,反射率albedo,是一个材质对光的反射情况。它是一个拉丁文,是白色。用它来表面对红绿蓝三个通道的反射情况。在unity中通常使用一张贴图+一个tint颜色来表示它,同时也就是漫反射的颜色。

所以下面要加两个属性一个是贴图,一个是tint颜色。

Shader "Custom/My First Lighting Shader" 
{
    Properties 
    {
        _Tint ("Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Texture", 2D) = "white"
    }

    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}

            CGPROGRAM

            #pragma vertex MyVertexProgram
            #pragma fragment MyFragmentProgram

            #include "UnityCG.cginc"
            #include "UnityStandardBRDF.cginc"

            float4 _Tint;
            sampler2D _MainTex;
            float4 _MainTex_ST;

            struct VertexData 
            {
                float4 position : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct Interpolators 
            {
                float4 position : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : TEXCOORD1;
            };

            Interpolators MyVertexProgram (VertexData v) 
            {
                Interpolators i;
                i.position = UnityObjectToClipPos(v.position);
                i.normal = UnityObjectToWorldNormal(v.normal);
                i.normal = normalize(i.normal);
                i.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return i;
            }

            float4 MyFragmentProgram (Interpolators i) : SV_TARGET 
            {
                float3 albedo = tex2D(_MainTex, i.uv).rgb * _Tint.rgb;
                float3 diffuse = albedo * _LightColor0.rgb * dot(_WorldSpaceLightPos0.xyz, i.normal);
                return float4(diffuse, 1);
            }

            ENDCG
        }
    }
}

这里写图片描述

这里写图片描述

猜你喜欢

转载自blog.csdn.net/wodownload2/article/details/82192619