shader中的光照和阴影实现方法

发布于 2023-04-04 23:41:30

背景

上次我们利用光线追踪做了一个球体,但是球体似乎看起来不像球体,更像一个圆,估计这会让你觉得有点low,我们这篇文章来一起探讨下光照和阴影,让我们场景更加接近现实。想要实现这个目标,我们仍然需要基于光线追踪理论。

光照

想想现实中,光线照过的地方一定比没有照过的地方要亮很多。比如下面这种风景图。
光照实物.png

我们把现实中的景象抽象成模型,光照强度或光线的方向会影响物体表面状态。图中的黑色箭头表示球体的几个表面法线。如果表面法线指向光源,则球体上的该点看起来比球体的其余部分更亮。如果表面法线点完全远离光源,则球体的该部分将显得更暗。
光照示意.png

图形学中有多种算法来模拟光照,后面我们会给大家逐一聊。这次我们主要和大家按照我们最常用的朗伯反射,它是属于漫反射的一种。其具体的公式如下所示:

vec3 diffuseColor = dot(normal, lightDirection) * lightColor ; //法线和光线方向

其中lightColor一般都是已知的,lightDirection,一般我们通过光线起点减去几何顶点或者像素坐标即可得到,其中normal是我们接下来探讨的重点。

法线==梯度

曲面在某一点处的法线等于曲面上某一点处曲面的梯度,所以我们求法线就转换为求梯度。有多种方法可以计算这种梯度。很多数学相关的书中对于梯度的描述非常抽象,让人不容易抓取到重点。可以看看我在上篇文章中写的定义。由上篇文章我们得到求得发现的代码如下所示:

vec3 calcNormal(vec3 p) {
  float e = 0.0001; 
  float r = 1.; 
  return normalize(vec3(
    sdfSphere(vec3(p.x + e, p.y, p.z), r) - sdfSphere(vec3(p.x - e, p.y, p.z), r),
    sdfSphere(vec3(p.x, p.y + e, p.z), r) - sdfSphere(vec3(p.x, p.y - e, p.z), r),
    sdfSphere(vec3(p.x, p.y, p.z  + e), r) - sdfSphere(vec3(p.x, p.y, p.z - e), r)
  ));
}

光照与法线角度

  • 光照位置
vec3 lightPosition = vec3(0, 2, 4);

此外我们通过旋转矩阵控制光照位置变化,关于矩阵变换内容,我们在shader中级课程做详细讨论。

mat2 r2d(float a) {
    float c = cos(a), s = sin(a);
    return mat2(
        c, s, // column 1
        -s, c // column 2
    );
}
  • 光照方向

这里用到了已知两个点求两个点之间的向量的大小和方向,我们使用光照的位置减去经过raymaching完成之后的p点的位置,其意义是光照始终只向的是球体。

vec3 lightDirection = normalize(lightPosition - p);
  • 求光照与法线夹角

    我们是应用点积求得两个向量之间的夹角,夹角范围[-1,1],结果存在小于0的时候,为了避免此情况我们采用max,用结果和0做比较,一直返回的是大于0的值。

float dif = dot(normal, lightDirection); 
dif = max(dot(normal, lightDirection),0.); 

阴影

阴影生成原理也比较简单,我们假设阴影部分存在一点P,点P是光线追踪完成后落在地面上的一个点,连接点P到lightPosition的向量是一个指向光源的射线。接下来是核心,计算从点P到球表面的距离如果小于点PlightPosition,那么说明肯定中间有物体挡着。那么我们就判断此时P,就在阴影里面。
阴影.png

// Author: ice
// Title: 光线追踪

#ifdef GL_ES
precision mediump float;
#endif
#define MAX_ITERATIO_NNUMBER 255
#define MIN_DISTANCE 0.001
#define START_POSITION 0.
#define END_POSITION 100.
uniform vec2 u_resolution;
uniform float u_time;
mat2 r2d(float a) {
    float c = cos(a), s = sin(a);
    return mat2(
        c, s, // column 1
        -s, c // column 2
    );
}
float sdfSphere(vec3 p, float r){
    vec3 sphere = vec3(0.,2.,-5.);
    float plane = p.y-0.;
    return min(length(p-sphere)-r,plane) ;
}

vec3 getNormal(vec3 p,float r){
    float e = 0.0001;
    
    vec3 n = normalize(
        vec3(
        sdfSphere(p+vec3(e,0,0),r)-sdfSphere(p-vec3(e,0,0),r),
        sdfSphere(p+vec3(0,e,0),r)-sdfSphere(p-vec3(0,e,0),r),
        sdfSphere(p+vec3(0,0,e),r)-sdfSphere(p-vec3(0,0,e),r)
            
            ));
    return n;
}

float rayMaching(vec3 eyedrection,vec3 eyePosition){
    float d = START_POSITION;
    
    for(int i = 0;i<MAX_ITERATIO_NNUMBER;i++){
        vec3 p =eyePosition+ d* eyedrection;
      
        float newd  = sdfSphere(p ,2.);
        d+=newd;
        if(newd<MIN_DISTANCE||d>END_POSITION){
            break;
        }
    }
     return d ;
}


void main() {
    vec2 uv = (gl_FragCoord.xy*2.-u_resolution.xy)/min(u_resolution.x,u_resolution.y);
    vec3 col = vec3(0.);
    vec3 eyeposition  = vec3(0, 5,1.);
    vec3 eyedrection  =normalize( vec3(uv,-1.));
    float d = rayMaching(eyedrection,eyeposition);
    

    //光线检测后的坐标p
    vec3  p = eyeposition+eyedrection*d;
    vec3 lightPosition = vec3(0, 5, 6);
    //围绕着xz轴旋转
    lightPosition.xz *=r2d(u_time*0.5) ;
    vec3 lightDirection = normalize(lightPosition-p);
    vec3 n = getNormal(p,2.);
    float diff =clamp(dot(n, lightDirection), 0., 1.);
    
    //创造阴影
    float shdowd = rayMaching(lightDirection,p+n*0.01);
    if(shdowd<length(lightPosition-p)){diff *= 0.1;} 
    col = vec3(diff );
    col = pow(col, vec3(3.));    
    gl_FragColor = vec4(col,1.0);
}

这就是我们关于光照和阴影的讨论,感兴趣朋友可以实践一下,下期节目我们再见!

0 条评论

发布
问题