基于物理的渲染(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);    
}
posted @ 2024-01-20 22:22  王小于的啦  阅读(219)  评论(0编辑  收藏  举报