GLSL Depth-RayMarching(一) 拓展片段丢弃与深度计算

版权声明:本文为博主原创文章,博客地址:http://blog.csdn.net/qq_26821643,未经同意不得转载。

以下内容需要读者理解熟悉Opengl的矩阵运算以及掌握Shader编程基础。

RayMarching(光影投射)

      什么是RayMarching? 简而言之,它是一种基于着色器语言的体渲染技术。它能够突破三角面片渲染规则,渲染出更多的表面细节,例如结合一些噪点函数和RayMarching技术可以创建“自然”,“无限”的高画质地貌奇观。

      

       以上高质量的地貌渲染样例来自ShaderToy,一个创建和分享着色器代码的神奇网站。

技术概要

RayMarching用于片元着色器。在着色器代码中需要一个构建几何体的距离函数,对于每个像素,通常步骤如下:

  1. 通过 gl_FragCoord.xy 计算三维空间的视点起点和射线方向。

  2. 使用距离函数在射线方向上逐步逼近,直到距离小于设定的精度,获得逼近几何体表面的坐标

  3. 根据逼近坐标计算Normal。

  4. 计算光照模型和其他光影效果(诸如:镜面,阴影 会使用到步骤2),输出gl_FragColor

关于距离函数和RayMarching过程的理论参考链接:理论详解

现实问题

       在ShaderToy上可以找到很多RayMarching 的样例。它们能够产生很多惊艳的渲染效果,但是比较局限的是ShaderToy都是全窗口渲染,在openGL概念下可看作成在一个填充窗口的图元上使用片元着色器,这就导致在一般的以面片模型为主的三维场景中很难应用。本文章的重点以GLSL为例构建一个在一般三维场景有较好的渲染效果的 RayMarching 增强方案 。为了表述之便,将其称之为 Depth-RayMarching。

实验环境

  1.       推荐使用  freeglut  glew 搭建最简单的 C++ OpenGL实验项目 。
  2.       确保实验过程中使用独立显卡渲染

从一个球开始

  • 计算射线方向: 使用 gl_ModelViewMatrixInverse(模型矩阵的逆),gl_ProjectionMatrixInverse(投影矩阵的逆) 这两个Build-in Uniform。由此计算出的所有空间数据都是基于本地坐标系的。(请务必理解本地,世界,相机坐标体系概念)。
#version 130
uniform vec2      viewport;//窗口分辨率

//坐标归一到[-1,1]
vec2 coord2uv(vec2 coord){
    return 2.0*coord/viewport.xy-1.0;
}

//通过uv计算本地坐标系下的射线方向
vec3 uv2ray(vec2 uv){
     vec4 camdir = gl_ProjectionMatrixInverse*vec4(uv,1,1);
     camdir = camdir/camdir.w;//W归一后 得出相机坐标体系下的点
     vec3 dir = mat3(gl_ModelViewMatrixInverse)*vec3(camdir);
     return normalize(dir);
}

//本地坐标系下的视点坐标
vec3 eyePos(){
    return vec3(gl_ModelViewMatrixInverse*vec4(0,0,0,1));
}

//计算本地坐标系下的灯光方向(平行光)
vec3 lightPos(){
	return normalize(mat3(gl_ModelViewMatrixInverse)*gl_LightSource[0].position.xyz);
}

//本地坐标系下点光源坐标
vec3 spotLightPos(){    
    return vec3(gl_ModelViewMatrixInverse*gl_LightSource[0].position);
}
  • 距离逼近计算(Ray Marching) :在常规的算法过程的基础上添加片段丢弃和深度计算
#define MAXSTEP 40 //最大逼近步数
#define TOLERANCE 0.0001 // 距离需要减小到该值以下 


float sdsphere(vec3 pos,float r){
    return length(pos)-r;
}

vec2 map(vec3 pos){
    return vec2(sdsphere(pos,1),0.5);
}

//输入:视点坐标,射线方向;
//输出:最近点和对应材质系数。
bool marching(vec3 pos,vec3 ray,out vec3 closeing,out float material){
    int step =0;
    float mindist = 1;
    vec2 res;
    do{
        res = map(pos);
        if(res.x<mindist){
            mindist = res.x;
            material = res.y;
            closeing = pos;
        }
        
        pos+= ray*res.x;       
        step++;
    }while(res.x>TOLERANCE&&step<=MAXSTEP);

    
    return step<=MAXSTEP;
}

float procDepth(vec3 localPos){
    vec4 frag = gl_ModelViewProjectionMatrix*vec4(localPos,1);
    frag/=frag.w;
    return (frag.z+1)/2;
}

void main(){
    vec2 uv = coord2uv(gl_FragCoord.xy);
    vec3 ro = eyePos();
    vec3 rd = uv2ray(uv);

    vec3 nearest;
    float material;
      
    if(marching(ro,rd,nearest,material)){
        gl_FragDepth = procDepth(nearest);
        gl_FragColor = vec4(vec3(material),1);
    }else{
       discard;
    }
}






添加光照模型与色彩,使用ADS光照模型(ambient,diffuse,specular) 进行渲染。

#define NORMALESP 0.001
vec3 procNormal(vec3 marchpt)
{
    float dist = map(marchpt).x;

    return normalize(vec3(
        dist-map(marchpt-vec3(NORMALESP,0,0)).x,
        dist-map(marchpt-vec3(0,NORMALESP,0)).x,
        dist-map(marchpt-vec3(0,0,NORMALESP)).x
    ));
}

vec3 lightModel(vec3 eye,vec3 normal, vec3 light,float material){
	float NL = max(dot(normal,light),0);
	float RL = max(dot(eye,reflect(light,normal)),0);
	return vec3(material*0.2)+vec3(material)*NL+ vec3(material)*pow(RL,5);
}

//

void main(){
    vec2 uv = coord2uv(gl_FragCoord.xy);
    vec3 ro = eyePos();
    vec3 rd = uv2ray(uv);

    vec3 nearest;
    float material;
      
    if(marching(ro,rd,nearest,material)){
		vec3 normal = procNormal(nearest);
        gl_FragDepth = procDepth(nearest);
        gl_FragColor = vec4(lightModel(rd,normal,lightPos(),material)*normal,1);
    }else{
       discard;
    }
}

基于以上代码着色器,渲染的伪代码如下所示

//伪代码描述
Shader sphereShd;
Mesh cube;


void init(){
    //构建一个-1到-1的正方体    
    cube.buildCube(-1,1);
    
    //加载frag文件生成Shader
    sphereShd.loadFrag("sphere_frag.glsl"); 
    sphereShd.link();
}

float offsetx = -0.8;
float offsety = -0.8;

void render(){
    glPushMatrix();

    glUseProgram(sphereShd.id);
    cube.draw();
    glUseProgram(0);

    gltranslatef(offsetx ,offsety ,0);
    cube.draw();

    glPopMatrix();
}

调整offset 渲染效果如何所示,可以观察到两物体相交时深度测试符合预期

 

实际应用

 1.渲染多个模型

   不同的半径和不同的位置:设置不同的translate rotation scale 多次渲染 伪代码如下

//伪代码描述
Shader sphereShd;
Mesh cube;


void init(){
    //构建一个-1到1的正方体    
    cube.buildCube(-1,1);
    
    //加载frag文件生成Shader
    sphereShd.loadFrag("sphere_frag.glsl"); 
    sphereShd.link();
}

float offset = 1.0f;

void render(){
    

    glUseProgram(sphereShd.id);

    for(int i=1;i<=5;i++){
       glPushMatrix();
       glTranslatef(0,(i-1)*5,0);
       glScalef(i,i,i);
       cube.draw();
       glPopMatrix();
    }
    
    glUseProgram(0);

}

       每次渲染cube之前设置位置和缩放本质上都在改变Shader代码中的gl_ModelViewMatrixInverse,从而产生正确位置大小的球。

       使用raymarch shader的多对象渲染流程和普通的片面模型的流程是一致的,不同之处在于raymarch不需要外部顶点数据传入, 与面片模型渲染相比较,优点是更加适用于曲面渲染以及更加灵活、细致的表面着色,缺点是没有类似autodesk的建模工具,要求设计师拥有扎实的数学建模功底。

       需要注意的是该体系下不建议使用 XYZ不一致的缩放 比如glScalef(1,1,0.5)。不一致的缩放会产生不合适的光照颜色,原因在于本地坐标体系下的Normal计算,详细请搜索“gl_NormalMatrix”。

2.使用其他距离函数绘制复杂的模型

       更改模型的同时需要调整渲染载体(指上文代码中的 Mesh cube)。渲染载体是一个封闭的面片模型对象,要求在几何意义上包含绘制模型,为了避免渲染不完整;要求体积尽可能小,减少需要计算并丢弃的像素个数。通常做法将 cube 设置成渲染模型的 Border Box。   以下代码来自ShaderToy  渲染一个卡通车。

float sdBox(vec3 p, vec3 radius)
{
  vec3 dist = abs(p) - radius;
  return min(max(dist.x, max(dist.y, dist.z)), 0.0) + length(max(dist, 0.0));
}

float cylCap(vec3 p, float r, float lenRad)
{
    float a = length(p.xy) - r;
    a = max(a, abs(p.z) - lenRad);
    return a;
}

// k should be negative. -4.0 works nicely.
// smooth blending function
float smin(float a, float b, float k)
{
	return log2(exp2(k*a)+exp2(k*b))/k;
}

float Repeat(float a, float len)
{
    return mod(a, len) - 0.5 * len;
}

vec2 matmin(vec2 v1,vec2 v2){
	return v1.x>v2.x?v2:v1;
}

vec2 Car(vec3 baseCenter)
{
    // bottom box
    float car = sdBox(baseCenter + vec3(0.0,0.01,-0.08), vec3(0.1,0.275,0.0225));
    // top box smooth blended
    car = smin(car, sdBox(baseCenter + vec3(0.0, 0.08,-0.16), vec3(0.05, 0.1, 0.005)), -16.0);
    // mirror the z axis to duplicate the cylinders for wheels
    vec3 wMirror = baseCenter + vec3(0.0, 0.0,-0.05);
    wMirror.y = abs(wMirror.y)-0.2;
    float wheels = cylCap((wMirror).yzx, 0.04, 0.135);
    // Set materials
    vec2 distAndMat = vec2(wheels, 0.1);	// car wheels
    // Car material is some big number that's unique to each car
    // so I can have each car be a different color
    distAndMat = matmin(distAndMat, vec2(car,0.5));	// car
    return distAndMat;
}


vec2 map(vec3 pos){
    return Car(pos);
}

总结

         Depth-RayMarching方案到目前为止比较理想,并且存在较广的应用空间。但细心的读者可能发现了 图片中模型内部,模型与其他模型,模型与环境,这些关系中的锯齿问题比较突出。并且这个问题是由片元着色器所产生的,所以 glEnable(GL_MULTISAMPLE) 并不有效。抗锯齿的实现将会在下一篇博客中阐述。(博主还在研究中... 需要等待)

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值