• OpenGL进阶(十三)


    提要

           在上一篇文章中,我们介绍了简单的Shading,同时提出了一个光照模型,模拟了一个点光源,但是,关于光的故事还没有结束... 

           今天要学习的是方向光源(Directional Light),聚光灯,per pixel shading,halfway vector。

          关于光源的原理及数学描述,请参考:光线追踪(RayTracing)算法理论与实践(三)光照


    方向光源

          方向光源就两个参数,方向和强度。

          还是简单的 ambient + diffuse + spec 光照模型。先看shader的代码。

    basic.vert

    #version 400
    layout (location = 0) in vec3 VertexPosition;  
    layout (location = 1) in vec3 VertexNormal;  
    
    out vec3 LightIntensity;
    
    struct LightInfo{
    	vec4 Direction;
    	vec3 Intensity;
    };
    
    struct MaterialInfo{
    	vec3 Ka;
    	vec3 Kd;
    	vec3 Ks;
    	float Shininess;
    };
    
    uniform LightInfo Light;
    uniform	MaterialInfo Material;
    
    uniform mat4 ModelViewMatrix;
    uniform mat3 NormalMatrix;
    uniform mat4 ProjectionMatrix;
    uniform mat4 MVP;
    
    
    void getEyeSpace(out vec3 norm, out vec4 position)
    {
    	norm =  normalize(NormalMatrix * VertexNormal);
    	position = ModelViewMatrix * vec4(VertexPosition, 1.0);
    }
    
    
    
    vec3 ads(vec4 position, vec3 norm)
    {
    	vec3 s;
    	if(Light.Direction.w == 0.0)
    		s = normalize(vec3(Light.Direction));
    	else
    		s = normalize(vec3(Light.Direction - position));
    	vec3 v = normalize(vec3(-position));
    	vec3 r = reflect(-s, norm);
    
    	return Light.Intensity * (Material.Ka + Material.Kd*max(dot(s,norm), 0.0) + 
    	       Material.Ks * pow(max(dot(r,v),0.0), Material.Shininess));
    }
    
    void main()
    {
    	vec3 eyeNorm;
    	vec4 eyePosition;
    	getEyeSpace(eyeNorm, eyePosition);
    	LightIntensity = ads(eyePosition, eyeNorm);
    	
    	gl_Position = MVP * vec4( VertexPosition, 1.0);
    }


    在ads函数中,首先通过nomal矩阵将顶点法向量变换到视口坐标下,(nomal矩阵其实就是model-view矩阵的左上3x3的矩阵)然后通过model-view矩阵将顶点坐标转化为视口坐标系(eye coordinates)下。

    接下来的ads用来计算光照模型下顶点的颜色,分别计算三个分量,然后相加。


    basic.frag

    #version 400
    
    in vec3 LightIntensity;
    
    void main(void)
    {
    	gl_FragColor = vec4(LightIntensity, 1.0);
    	//gl_FragColor = vec4(1.0,1.0,0.1, 1.0);
    }
    这个就是将根据顶点shader传来的颜色对片段进行赋值。

    在cgl.cpp的setUniform函数中对Uniform变量进行赋值。
    void CGL::setUniform()
    {
        mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
        mat4 mv = view * model;
    
        prog.setUniform("Material.Kd", 0.9f, 0.5f, 0.3f);
        prog.setUniform("Material.Ka", 0.9f, 0.5f, 0.3f);
        prog.setUniform("Material.Ks", 0.8f, 0.8f, 0.8f);
        prog.setUniform("Material.Shininess", 100.0f);
        prog.setUniform("Light.Direction", vec4(1.0f, 0.0f, 0.0f, 0.0f));
        prog.setUniform("Light.Intensity", 1.0f, 1.0f, 1.0f);
    
        prog.setUniform("ModelViewMatrix", mv);
        prog.setUniform("NormalMatrix",mat3( vec3(mv[0]), vec3(mv[1]), vec3(mv[2]) ));
        prog.setUniform("MVP", projection * mv);
    
    }

    渲染的效果如下:



    可以很明显的感觉模型旋转时到表面光照的变化。


    halfway vector 性能优化

            在前面的光照模型中,用于计算specular分量的公式如下:


    其中 是反射光线的方向向量,v是往视口方向的向量,其中 r 的计算:


    这个计算过程会非常耗时,我们可以用一个trick来改善一下。

    定义一个 h (halfway vector)向量:


    下图是 和其他向量的位置关系。


    specular分量的计算就可以转化成:


             相比于计算 r ,的计算相对比较简单,而 h 和 n 之间的夹角与 和 r 之间的夹角大小几乎相同!那就意味着我们可以用 h.n 来代替 r.v 从而可以带利用 halfway vector 来获得性能上的一些提升。虽然效果上会有那么小小的不同。

             后面的灯光的计算都会用到这个优化。

    聚光灯 Spotlight

            这里的采用一个最简单的聚光灯模型:



           灯光的属性有:位置,强度,方向,衰减(exponent),裁剪角度。

           实现起来也比较简单,在投射角内的物体,渲染方式和点光源的计算一样,投射角之外的顶点,着色的时候只有ambient。

            还是采用我们比较熟悉的per vertex shading 方式。在vert中定义聚光灯:

    //baisc.vert
    #version 400
    layout (location = 0) in vec3 VertexPosition;  
    layout (location = 1) in vec3 VertexNormal;  
    
    out vec3 LightIntensity;
    
    struct SpotLightInfo{
    	vec4 position;
    	vec3 direction;
    	vec3 intensity;
    	float exponent;
    	float cutoff;
    };
    
    struct MaterialInfo{
    	vec3 Ka;
    	vec3 Kd;
    	vec3 Ks;
    	float Shininess;
    };
    
    uniform SpotLightInfo Spot;
    uniform	MaterialInfo Material;
    
    uniform mat4 ModelViewMatrix;
    uniform mat3 NormalMatrix;
    uniform mat4 ProjectionMatrix;
    uniform mat4 MVP;
    
    
    void getEyeSpace(out vec3 norm, out vec4 position)
    {
    	norm =  normalize(NormalMatrix * VertexNormal);
    	position = ModelViewMatrix * vec4(VertexPosition, 1.0);
    }
    
    
    
    vec3 adsSpotLight(vec4 position, vec3 norm)
    {
    	vec3 s = normalize(vec3(Spot.position - position));
    	float angle = acos(dot(-s, normalize(Spot.direction)));
    	float cutoff = radians(clamp(Spot.cutoff, 0.0, 90.0));
    	vec3 ambient = Spot.intensity * Material.Ka;
    	
    	if(angle < cutoff){
    		float spotFactor = pow(dot(-s, normalize(Spot.direction)), Spot.exponent);
    		vec3 v = normalize(vec3(-position));
    		vec3 h = normalize(v + s);
    		return ambient + spotFactor * Spot.intensity * (Material.Kd * max(dot(s, norm),0.0)
    		      + Material.Ks * pow(max(dot(h,norm), 0.0),Material.Shininess)); 
    	}
    	else
    	{
    		return ambient; 
    	}
    	       
    }
    
    void main()
    {
    	vec3 eyeNorm;
    	vec4 eyePosition;
    	getEyeSpace(eyeNorm, eyePosition);
    	
    	LightIntensity = adsSpotLight(eyePosition, eyeNorm);
    	
    	gl_Position = MVP * vec4( VertexPosition, 1.0);
    }


    几个GLSL中的内置函数在这里说明一下。

    genType clamp( genType x, genType minVal, genType maxVal);

    获取三个数中第二大的数。

    genType radians(genType degrees);

    将角度转换成弧度。

    adsSpotLight是主要的函数,先计算顶点和光源方向之间的夹角,判断顶点是否在照射的区域,然后分别求得最终的颜色。


    片段shader还是那样:

    #version 400
    
    in vec3 LightIntensity;
    
    void main(void)
    {
    	gl_FragColor = vec4(LightIntensity, 1.0);
    }


    uniform变量的赋值:

    void CGL::setUniform()
    {
        //model = glm::rotate(this->model, 10.0f, vec3(0.0f,1.0f,0.0f));
        mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
        mat4 mv = view * model;
        mat3 normalMatrix = mat3( vec3(view[0]), vec3(view[1]), vec3(view[2]) );
    
        prog.setUniform("Material.Kd", 0.9f, 0.5f, 0.3f);
        prog.setUniform("Material.Ka", 0.9f, 0.5f, 0.3f);
        prog.setUniform("Material.Ks", 0.8f, 0.8f, 0.8f);
        prog.setUniform("Material.Shininess", 100.0f);
    
        vec4 lightPos = vec4(1.0f, 5.0f, 20.0f, 1.0f);
       // prog.setUniform("Spot.position", lightPos);
        prog.setUniform("Spot.position", view * lightPos);
        prog.setUniform("Spot.direction", normalMatrix * vec3(-10.0,0.0,-40.0) );
        //prog.setUniform("Spot.direction", vec3(10.9f,10.9f,10.9f)  );
        prog.setUniform("Spot.intensity", vec3(0.9f,0.9f,0.9f) );
        prog.setUniform("Spot.exponent", 30.0f );
        prog.setUniform("Spot.cutoff", 15.0f );
    
        prog.setUniform("ModelViewMatrix", mv);
        prog.setUniform("NormalMatrix",normalMatrix);
        prog.setUniform("MVP", projection * mv);
    
    }
    


           在这里给Spot.position赋值的时候不是 prog.setUniform("Spot.position", lightPos); 而是prog.setUniform("Spot.position", view * lightPos),因为在shader中的计算都是在视口坐标下进行的,这样做是为了统一坐标。Spot.direction的赋值也是一样。也可以把坐标转换这一步放到shader中去做。

    最终效果如下:



    逐像素着色 per pixel shading

            相对与前面将主要计算工作放在顶点shader中的 per vertex shading ,per pixel shading 指的是将计算放到片段shader中,这样可以带来更加真实可感的渲染效果。

    basic2.vert

    #version 400
    layout (location = 0) in vec3 VertexPosition;  
    layout (location = 1) in vec3 VertexNormal;  
    
    out vec4 Position;
    out vec3 Normal;
    
    
    uniform mat4 ModelViewMatrix;
    uniform mat3 NormalMatrix;
    uniform mat4 ProjectionMatrix;
    uniform mat4 MVP;
    
    
    void getEyeSpace(out vec3 norm, out vec4 position)
    {
    	norm =  normalize(NormalMatrix * VertexNormal);
    	position = ModelViewMatrix * vec4(VertexPosition, 1.0);
    }
    
    
    void main()
    {
    	getEyeSpace(Normal, Position);
    	gl_Position = MVP * vec4( VertexPosition, 1.0);
    }


    basic2.frag 

    #version 400
    
    in vec4 Position;
    in vec3 Normal;
    
    struct SpotLightInfo{
    	vec4 position;
    	vec3 direction;
    	vec3 intensity;
    	float exponent;
    	float cutoff;
    };
    
    struct MaterialInfo{
    	vec3 Ka;
    	vec3 Kd;
    	vec3 Ks;
    	float Shininess;
    };
    
    uniform SpotLightInfo Spot;
    uniform	MaterialInfo Material;
    
    vec3 adsSpotLight(vec4 position, vec3 norm)
    {
    	vec3 s = normalize(vec3(Spot.position - position));
    	float angle = acos(dot(-s, normalize(Spot.direction)));
    	float cutoff = radians(clamp(Spot.cutoff, 0.0, 90.0));
    	vec3 ambient = Spot.intensity * Material.Ka;
    	
    	if(angle < cutoff){
    		float spotFactor = pow(dot(-s, normalize(Spot.direction)), Spot.exponent);
    		vec3 v = normalize(vec3(-position));
    		vec3 h = normalize(v + s);
    		return ambient + spotFactor * Spot.intensity * (Material.Kd * max(dot(s, norm),0.0)
    		      + Material.Ks * pow(max(dot(h,norm), 0.0),Material.Shininess)); 
    	}
    	else
    	{
    		return ambient; 
    	}
    	       
    }
    
    void main(void)
    {
    	gl_FragColor = vec4(adsSpotLight(Position, Normal), 1.0);
    	//gl_FragColor = vec4(1.0,1.0,0.5, 1.0);
    }


    看一下渲染效果:


    最终的效果还是有一些差别,特别是光线交界的地方。


    代码下载

    OpenGLPro13


    参考

    OpenGL 4.0 Shading Language Cookbook

     





  • 相关阅读:
    HDU6060 RXD and dividing
    Knapsack in a Globalized World --多重完全背包
    hdu 6058 Kanade's sum
    矩形面积 HDU
    Bridge Across Islands POJ
    Manors HDU
    Harry Potter and J.K.Rowling HDU
    Polygons HDU
    Jungle Outpost HDU
    CRB and Farm HDU
  • 原文地址:https://www.cnblogs.com/bbsno1/p/3279753.html
Copyright © 2020-2023  润新知