基于物理的渲染(3):OSG中PBR实践
1.1 单光源直接光照
void main()
{
//创建小球几何
osg::ref_ptr<osg::ShapeDrawable> sphere =new osg::ShapeDrawable;
sphere->setShape(new osg::Sphere(osg::Vec3(0,0,0),radius));
sphere->setColor(osg::Vec4(col));
//创建小球节点
osg::ref_ptr<osg::Geode> pbrGeode =new osg::Geode;
pbrGeode->addChild(sphere);
//创建纹理
QVector<QString> paths={
"height.png",
"metakkic.png",
"normal.png",
"roughness.png",
"albedo.png",
"ao.png"};
for(int i=0;i<paths.size();i++)
{
osg::ref_ptr<osg::Texture2D> texture =new osg::Texture2D;
osg::ref_ptr<osg::Image> img =nullptr;
texture->setImage(img );
pbrGeode->getOrcreateStateSet()->setTextureAttributeAndModes(i,texture);
auto num=("tex"+QString::number(i)).toStdString();
pbrGeode->getOrcreateStateSet()->addUniform(new osg::Uniform(num.data(),(int)i));
}
//顶点着色器
static const char* vertCode="#version 330\n"
"layout(location =0) in vec3 Position;\n"
"layout(location =2) in vec3 Normal;\n"
"layout(location =8) in vec3 TexCoord;\n"
"uniform mat4 osg_ModelViewProjectionMatrix;\n"
"uniform mat4 osg_ModelViewMatrix;\n"
"uniform mat4 osg_NormalMatrix;\n"
"out vec3 vNormal;\n"
"out vec3 vCameraPos;\n"
"out vec3 vWorldPos;\n"
"out vec2 texCoord;\n"
"void main()\n"
"{"
" texCoord=TexCoord;\n"
" vNormal=normalize(osg_NormalMatrix*vec4(Position,1.0));\n"
" gl_position=osg_ModelViewProjectionMatrix*vec4(Position,1.0);\n"
//实时计算相机世界坐标
" vCameraPos=inverse(osg_ModelViewMatrix)*vec4(0.0,0.0,0.0,1.0);\n"
" vWorldPos=Position;\n"
"}"
//片段着色器
static const char* fragCode="#version 330\n"
"uniform sampler2D tex0;\n"
"uniform sampler2D tex1;\n"
"uniform sampler2D tex2;\n"
"uniform sampler2D tex3;\n"
"uniform sampler2D tex4;\n"
"uniform sampler2D tex5;\n"
"uniform vec3 lightPos=vec3(100.0,0.0,100.0);\n"
"uniform vec3 lightCol=vec3(150.0,150.0,150.0);\n"
"in vec3 vNormal;\n"
"in vec3 vCameraPos;\n"
"in vec3 vWorldPos;\n"
"in vec2 texCoord;\n"
"out vec4 fragColor;\n"
//菲涅尔方程
"vec3 fresnelSchlick(float cosTheta, vec3 F0)\n"
"{\n"
" return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);\n"
"}\n"
//法线分布函数
"float distributionGGX(float NdotV, float roughness)\n"
"{\n"
" float a4 = roughness*roughness*roughness*roughness;\n"
" float NdotH = max(dot(N, H), 0.0);\n"
" float NdotH2 = NdotH*NdotH;\n"
" float denom = (NdotH2 * (a4 - 1.0) + 1.0);\n"
" denom = 3.1415 * denom * denom;\n"
" return a4 / denom;\n"
"}\n"
//观察方向几何遮蔽函数
"float geometrySchlickGGX(float NdotV, float roughness)\n"
"{\n"
" float k = ((roughness + 1.0)*(roughness + 1.0)) / 8.0;\n"
" float denom = NdotV * (1.0 - k) + k;\n"
" return NdotV / denom;\n"
"}\n"
//光线方向几何阴影函数
"float geometrySmith(float NdotV,float NdotL, float roughness)\n"
"{\n"
" float ggx2 = GeometrySchlickGGX(NdotV, roughness);\n"
" float ggx1 = GeometrySchlickGGX(NdotL, roughness);\n"
" return ggx1 * ggx2;\n"
"}\n"
//计算切线空间到世界空间坐标转换矩阵
"vec3 tangToWorldMat()\n"
"{\n"
" vec3 tangentNormal = texture(tex2, texCoord).xyz * 2.0 - 1.0;\n"
" vec3 Q1 = dFdx(WorldPos);\n"
" vec3 Q2 = dFdy(WorldPos);\n"
" vec2 st1 = dFdx(texCoord);\n"
" vec2 st2 = dFdy(texCoord);\n"
" vec3 N = normalize(Normal);\n"
" vec3 T = normalize(Q1*st2.t - Q2*st1.t);\n"
" vec3 B = -normalize(cross(N, T));\n"
" mat3 TBN = mat3(T, B, N);\n"
" return normalize(TBN * tangentNormal);\n"
"}\n"
"void main()\n"
"{"
//获取纹理参数
" vec3 albdo=texture(tex4,texCoord).rgb;\n"
" float metalic=texture(tex1,texCoord).r;\n"
" float rough=texture(tex3,texCoord).r;\n"
" float ao=texture(tex5,texCoord).r;\n"
//计算世界坐标系下法线
" vec3 N=tangToWorldMat();\n"
" V=normailze(vCameraPos-vWorldPos);\n"
//计算F0
" vec3 F0=vec3(0.4);\n"
" F0=mix(F0,albdo,metallic);\n"
" vec3 L=normailze(lightPos-vWorldPos);\n"
" vec3 H=normailze(V+L);\n"
" float distan=length(lightPos-vWorldPos);\n"
" float atten=200.0/(distan*distan);\n"
" vec3 radiance=lightCol*atten;\n"
" float NdotV=max(dot(N,V),0.0);\n"
" float NdotL=max(dot(N,L),0.0);\n"
" float NdotH=max(dot(N,H),0.0);\n"
" float HdotV=max(dot(H,V),0.0);\n"
//计算BRDF
" float D=distributionGGX(NdotH,rough);\n"
" float G=geometrySmith(NdotV,NdotL,rough);\n"
" vec3 F=fresnelSchlick(HdotV,F0);\n"
" vec3 spec=D*F*G/(4.0*NdotV*NdotL+0.00001);\n"
" kD=(vec3(1.0)-F)*(1.0-metalic);\n"
" Lo=(kD*albdo/3.1415+spec)*radiance*NdotL;\n"
" vec3 ambient=vec3(1.0)*albdo*ao;\n"
" vec3 color=ambient+Lo;\n"
" fragColor=vec4(color,1.0);\n"
"}";
//编译shader
osg::ref_ptr<osg::Shader> vertShader=new osg::Shader(osg::Shader::VERT,vertCode);
osg::ref_ptr<osg::Shader> fragShader=new osg::Shader(osg::Shader::FRAG,fragCode);
osg::ref_ptr<osg::Program> program=new osg::Program;
program->addShader(vertShader);
program->addShader(fragShader);
pbrGeode->getOrCreateStateSet()->setAttributeAndModes(program,OVERRIDE_ON);
return pbrGeode;
}
1.2 PBR结合视差贴图
要达到模型表面凹凸不平的效果,可以使用高度贴图对几何顶点做偏移,但是这要求模型顶点足够多,以呈现较好的视觉效果,此外也可以使用视差贴图技术实现相同效果。
视差贴图原理:如上图所示,红线为高度贴图上顶点高度偏移值,黄线为视点和像素点连线,A点为实际纹理坐标,这样不用A点纹理坐标取得的高度,而是用B点高度,也就是说将A点纹理坐标加上一个偏移量得到修正后纹理坐标,来采样得到修正高度。视差贴图需要在切线空间计算,以便在不同视角上观察都可以得到正常的视觉效果。
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
float height = texture(depthMap, texCoords).r;
vec2 p = clamp(viewDir.xy / (viewDir.z=0.0001),-1.0,1.0);
return p* (height * height_scale);
}