Urho3D Wiki

Urho quad texture.jpg

This method means having four textures in one image like this:

Blender bricks quad.jpg

Diffuse, normal, specular and height map (the height map is not used in this material). A similar method could be used to pack three textures (which would be better in this case as only three are used) or even more materials in one image file.

This has two advantages:

There is an issue with the mipmaps then using such an image with four textures like displayed above, it results in ugly stripes like these:

Urho quad stripes.jpg

To fix this the shader code needs to be a bit different and the image needs to be like this:

Blender bricks quad-0.jpg

Each of the four tiles is still used as a square which means that half of the texture (the left part of each rectangle) isn't even used. But it's correcting the mipmaps to not create the black stripes seen above. The material file:

<technique name="Techniques/quad.xml"/>
<texture name="Textures/blender_bricks_quad.jpg" unit="diffuse"/>
<parameter name="MatDiffColor" value="1.0 1.0 1.0 1"/>
<parameter name="MatSpecColor" value="2.5 2.5 2.5 30"/>

unit="diffuse" has no real meaning here as this image is not only used for diffuse. This just says that this texture is number 0 which is important for the shader as it expects the one texture at "place" 0.

The technique: "Techniques/quad.xml"

<technique vs="quad" ps="quad">
<pass name="base" />
<pass name="litbase" psdefines="AMBIENT" />
<pass name="light" depthtest="equal" depthwrite="false" blend="add" />
<pass name="prepass" psdefines="PREPASS" />
<pass name="material" psdefines="MATERIAL" depthtest="equal" depthwrite="false" />
<pass name="deferred" psdefines="DEFERRED" />
<pass name="depth" vs="Depth" ps="Depth" />
<pass name="shadow" vs="Shadow" ps="Shadow" />

The GLSL shader (based on the terrain shader and parts from LitBase): "Shaders/quad.glsl"

#include "Uniforms.glsl"
#include "Samplers.glsl"
#include "Transform.glsl"
#include "ScreenPos.glsl"
#include "Lighting.glsl"
#include "Fog.glsl"

varying vec4 vTexCoord;

varying vec3 vNormal;
varying vec4 vTangent;
varying vec4 vWorldPos;
#ifdef SHADOW
varying vec4 vShadowPos[NUMCASCADES];
varying vec4 vSpotPos;
varying vec3 vCubeMaskVec;
varying vec3 vVertexLight;
varying vec4 vScreenPos;
varying vec3 vReflectionVec;
#if defined(LIGHTMAP) || defined(AO)
varying vec2 vTexCoord2;

uniform sampler2D sTexture0;

void VS()
mat4 modelMatrix = iModelMatrix;
vec3 worldPos = GetWorldPos(modelMatrix);
gl_Position = GetClipPos(worldPos);
vNormal = GetWorldNormal(modelMatrix);
vWorldPos = vec4(worldPos, GetDepth(gl_Position));
vec3 tangent = GetWorldTangent(modelMatrix);
vec3 bitangent = cross(tangent, vNormal) * iTangent.w;
vTexCoord = vec4(GetTexCoord(iTexCoord), bitangent.xy);
vTangent = vec4(tangent, bitangent.z);
// Per-pixel forward lighting
vec4 projWorldPos = vec4(worldPos, 1.0);

#ifdef SHADOW
// Shadow projection: transform from world space to shadow space
for (int i = 0; i < NUMCASCADES; i++)
vShadowPos[i] = GetShadowPos(i, projWorldPos);
// Spotlight projection: transform from world space to projector texture coordinates
vSpotPos = projWorldPos * cLightMatrices[0];

vCubeMaskVec = (worldPos - cLightPos.xyz) * mat3(cLightMatrices[0][0].xyz, cLightMatrices[0][1].xyz, cLightMatrices[0][2].xyz);
// Ambient & per-vertex lighting
#if defined(LIGHTMAP) || defined(AO)
// If using lightmap, disregard zone ambient light
// If using AO, calculate ambient in the PS
vVertexLight = vec3(0.0, 0.0, 0.0);
vTexCoord2 = iTexCoord2;
vVertexLight = GetAmbient(GetZonePos(worldPos));
for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
vVertexLight += GetVertexLight(i, worldPos, vNormal) * cVertexLights[i * 3].rgb;
vScreenPos = GetScreenPos(gl_Position);
vReflectionVec = worldPos - cCameraPos;
#endif #endif

void PS()
{ vec2 tex_coord_diff=vec2(mod(vTexCoord.x/4.0,0.25),vTexCoord.y);
vec4 diffColor=cMatDiffColor.rgba*texture2D(sTexture0,tex_coord_diff);

// Get material specular albedo
vec3 specColor = cMatSpecColor.rgb*texture2D(sTexture0,vec2(tex_coord_diff.x+0.5,tex_coord_diff.y)).rgb;

// Get normal
mat3 tbn = mat3(vTangent.xyz, vec3(vTexCoord.zw, vTangent.w), vNormal);
vec3 normal = normalize(tbn * DecodeNormal(texture2D(sTexture0,vec2(tex_coord_diff.x+0.25,tex_coord_diff.y))));

// Get fog factor
float fogFactor = GetHeightFogFactor(vWorldPos.w, vWorldPos.y);
float fogFactor = GetFogFactor(vWorldPos.w);

#if defined(PERPIXEL)
// Per-pixel forward lighting
vec3 lightColor;
vec3 lightDir;
vec3 finalColor;
float diff = GetDiffuse(normal, vWorldPos.xyz, lightDir);
#ifdef SHADOW
diff *= GetShadow(vShadowPos, vWorldPos.w);
#if defined(SPOTLIGHT)
lightColor = vSpotPos.w > 0.0 ? texture2DProj(sLightSpotMap, vSpotPos).rgb * cLightColor.rgb : vec3(0.0, 0.0, 0.0);
#elif defined(CUBEMASK)
lightColor = textureCube(sLightCubeMap, vCubeMaskVec).rgb * cLightColor.rgb;
lightColor = cLightColor.rgb;

float spec = GetSpecular(normal, cCameraPosPS - vWorldPos.xyz, lightDir, cMatSpecColor.a);
finalColor = diff * lightColor * (diffColor.rgb + spec * specColor * cLightColor.a);
finalColor = diff * lightColor * diffColor.rgb;

#ifdef AMBIENT
finalColor += cAmbientColor * diffColor.rgb;
finalColor += cMatEmissiveColor;
gl_FragColor = vec4(GetFog(finalColor, fogFactor), diffColor.a);
gl_FragColor = vec4(GetLitFog(finalColor, fogFactor), diffColor.a);
#elif defined(PREPASS)
// Fill light pre-pass G-Buffer
float specPower = cMatSpecColor.a / 255.0;

gl_FragData[0] = vec4(normal * 0.5 + 0.5, specPower);
gl_FragData[1] = vec4(EncodeDepth(vWorldPos.w), 0.0);
#elif defined(DEFERRED)
// Fill deferred G-buffer
float specIntensity = specColor.g;
float specPower = cMatSpecColor.a / 255.0;

gl_FragData[0] = vec4(GetFog(vVertexLight * diffColor.rgb, fogFactor), 1.0);
gl_FragData[1] = fogFactor * vec4(diffColor.rgb, specIntensity);
gl_FragData[2] = vec4(normal * 0.5 + 0.5, specPower);
gl_FragData[3] = vec4(EncodeDepth(vWorldPos.w), 0.0);
// Ambient & per-vertex lighting
vec3 finalColor = vVertexLight * diffColor.rgb;

// Add light pre-pass accumulation result
// Lights are accumulated at half intensity. Bring back to full intensity now
vec4 lightInput = 2.0 * texture2DProj(sLightBuffer, vScreenPos);
vec3 lightSpecColor = lightInput.a * lightInput.rgb / max(GetIntensity(lightInput.rgb), 0.001);

finalColor += lightInput.rgb * diffColor.rgb + lightSpecColor * specColor;
gl_FragColor = vec4(GetFog(finalColor, fogFactor), diffColor.a);

See also Terrain Shader with normal, specular and height mapping.

Another note on the texture sampling issue: Theoretically this can be also fixed by doing manual texture sampling in the shader by using texelFetch instead of texture2D, but it was super slow in my tests. My FPS in FullHD dropped from >200 to ~40.