* render Q3 shader based skyboxes

logic: load 6 skybox textures when shader gets used by scene, don't unload dynamically, just on 'flush'
texture browser only uses normal preview image and doesn't trigger potentially heavy box loading
also fix R_ResampleTexture for [2+x upscaling
This commit is contained in:
Garux 2022-07-18 10:05:19 +03:00
parent 22377bb255
commit d3e48d8c31
11 changed files with 219 additions and 21 deletions

View File

@ -82,6 +82,7 @@ public:
GLint m_texture5;
GLint m_texture6;
GLint m_texture7;
GLint m_textureSkyBox;
Vector4 m_colour;
GLenum m_blend_src, m_blend_dst;
GLenum m_depthfunc;

View File

@ -98,6 +98,7 @@ public:
virtual void DecRef() = 0;
// get/set the qtexture_t* Radiant uses to represent this shader object
virtual qtexture_t* getTexture() const = 0;
virtual qtexture_t* getSkyBox() = 0;
virtual qtexture_t* getDiffuse() const = 0;
virtual qtexture_t* getBump() const = 0;
virtual qtexture_t* getSpecular() const = 0;

View File

@ -32,8 +32,9 @@ class LoadImageCallback
public:
void* m_environment;
LoadFunc m_func;
bool m_skybox;
LoadImageCallback( void* environment, LoadFunc func ) : m_environment( environment ), m_func( func ){
LoadImageCallback( void* environment, LoadFunc func, bool skybox = false ) : m_environment( environment ), m_func( func ), m_skybox( skybox ){
}
Image* loadImage( const char* name ) const {
return m_func( m_environment, name );

View File

@ -279,6 +279,7 @@ public:
ShaderParameters m_params;
TextureExpression m_textureName;
TextureExpression m_skyBox;
TextureExpression m_diffuse;
TextureExpression m_bump;
ShaderValue m_heightmapScale;
@ -838,6 +839,7 @@ class CShader : public IShader
CopiedString m_Name;
qtexture_t* m_pTexture;
qtexture_t* m_pSkyBox;
qtexture_t* m_notfound;
qtexture_t* m_pDiffuse;
float m_heightmapScale;
@ -860,6 +862,7 @@ public:
m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ),
m_bInUse( false ){
m_pTexture = 0;
m_pSkyBox = 0;
m_pDiffuse = 0;
m_pBump = 0;
m_pSpecular = 0;
@ -893,6 +896,13 @@ public:
qtexture_t* getTexture() const {
return m_pTexture;
}
qtexture_t* getSkyBox() override {
/* load skybox if only used */
if( m_pSkyBox == nullptr && !m_template.m_skyBox.empty() )
m_pSkyBox = GlobalTexturesCache().capture( LoadImageCallback( 0, GlobalTexturesCache().defaultLoader().m_func, true ), m_template.m_skyBox.c_str() );
return m_pSkyBox;
}
qtexture_t* getDiffuse() const {
return m_pDiffuse;
}
@ -966,6 +976,10 @@ public:
GlobalTexturesCache().release( m_notfound );
}
if ( m_pSkyBox != 0 ) {
GlobalTexturesCache().release( m_pSkyBox );
}
unrealiseLighting();
}
@ -1220,6 +1234,18 @@ bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) );
}
else if ( string_equal_nocase( token, "skyparms" ) ) {
const char* sky = tokeniser.getToken();
if ( sky == 0 ) {
Tokeniser_unexpectedError( tokeniser, sky, "#skyparms" );
return false;
}
if( !string_equal( sky, "-" ) ){
m_skyBox = sky;
}
}
else if ( string_equal_nocase( token, "cull" ) ) {
const char* cull = tokeniser.getToken();

View File

@ -2091,7 +2091,8 @@ void CamWnd::Cam_Draw(){
| RENDER_LIGHTING
| RENDER_TEXTURE
| RENDER_SMOOTH
| RENDER_SCALED;
| RENDER_SCALED
| RENDER_PROGRAM;
break;
case cd_lighting:
globalstate |= RENDER_FILL

View File

@ -365,6 +365,69 @@ public:
GLSLDepthFillProgram g_depthFillGLSL;
class GLSLSkyboxProgram : public GLProgram
{
public:
GLhandleARB m_program;
GLint u_view_origin;
GLSLSkyboxProgram() : m_program( 0 ){
}
void create(){
// create program
m_program = glCreateProgramObjectARB();
// create shader
{
StringOutputStream filename( 256 );
createShader( m_program, filename( GlobalRadiant().getAppPath(), "gl/skybox_vp.glsl" ), GL_VERTEX_SHADER_ARB );
createShader( m_program, filename( GlobalRadiant().getAppPath(), "gl/skybox_fp.glsl" ), GL_FRAGMENT_SHADER_ARB );
}
GLSLProgram_link( m_program );
GLSLProgram_validate( m_program );
glUseProgramObjectARB( m_program );
u_view_origin = glGetUniformLocationARB( m_program, "u_view_origin" );
glUseProgramObjectARB( 0 );
GlobalOpenGL_debugAssertNoErrors();
}
void destroy(){
glDeleteObjectARB( m_program );
m_program = 0;
}
void enable(){
glUseProgramObjectARB( m_program );
GlobalOpenGL_debugAssertNoErrors();
debug_string( "enable skybox" );
}
void disable(){
glUseProgramObjectARB( 0 );
GlobalOpenGL_debugAssertNoErrors();
debug_string( "disable skybox" );
}
void setParameters( const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light ){
glUniform3fARB( u_view_origin, viewer.x(), viewer.y(), viewer.z() );
GlobalOpenGL_debugAssertNoErrors();
}
};
GLSLSkyboxProgram g_skyboxGLSL;
// ARB path
void createProgram( const char* filename, GLenum type ){
@ -790,6 +853,9 @@ inline bool OpenGLState_less( const OpenGLState& self, const OpenGLState& other
if ( self.m_texture7 != other.m_texture7 ) {
return self.m_texture7 < other.m_texture7;
}
if ( self.m_textureSkyBox != other.m_textureSkyBox ) {
return self.m_textureSkyBox < other.m_textureSkyBox;
}
//! Sort by state bit-vector.
if ( self.m_state != other.m_state ) {
return self.m_state < other.m_state;
@ -809,6 +875,7 @@ void OpenGLState_constructDefault( OpenGLState& state ){
state.m_texture5 = 0;
state.m_texture6 = 0;
state.m_texture7 = 0;
state.m_textureSkyBox = 0;
state.m_colour[0] = 1;
state.m_colour[1] = 1;
@ -1158,7 +1225,7 @@ class OpenGLShaderCache final : public ShaderCache, public TexturesCacheObserver
bool m_lightingEnabled;
bool m_lightingSupported;
bool m_useShaderLanguage;
const bool m_useShaderLanguage;
public:
OpenGLShaderCache() :
@ -1302,8 +1369,9 @@ public:
}
debug_string( "end rendering" );
OpenGLState reset = current; /* popmatrix after RENDER_TEXT */
reset.m_state = current.m_state & ~RENDER_TEXT;
OpenGLState reset = current; /* reset some states */
reset.m_state = current.m_state & ~RENDER_TEXT; /* popmatrix after RENDER_TEXT */
reset.m_program = nullptr; /* disable shader */
OpenGLState_apply( reset, current, globalstate );
}
void realise(){
@ -1320,6 +1388,9 @@ public:
}
}
if( lightingSupported() )
g_skyboxGLSL.create();
for ( Shaders::iterator i = m_shaders.begin(); i != m_shaders.end(); ++i )
{
if ( !( *i ).value.empty() ) {
@ -1347,6 +1418,8 @@ public:
g_depthFillARB.destroy();
}
}
if( GlobalOpenGL().contextValid && lightingSupported() )
g_skyboxGLSL.destroy();
}
}
bool realised(){
@ -1645,7 +1718,7 @@ void OpenGLState_apply( const OpenGLState& self, OpenGLState& current, unsigned
if ( program != current.m_program ) {
if ( current.m_program != 0 ) {
current.m_program->disable();
glColor4fv( vector4_to_array( current.m_colour ) );
//why? glColor4fv( vector4_to_array( current.m_colour ) );
debug_colour( "cleaning program" );
}
@ -1888,6 +1961,16 @@ void OpenGLState_apply( const OpenGLState& self, OpenGLState& current, unsigned
}
if( current.m_textureSkyBox != self.m_textureSkyBox ){
if ( GlobalOpenGL().GL_1_3() ) {
glActiveTexture( GL_TEXTURE0 );
glClientActiveTexture( GL_TEXTURE0 );
}
glBindTexture( GL_TEXTURE_CUBE_MAP, self.m_textureSkyBox );
GlobalOpenGL_debugAssertNoErrors();
current.m_textureSkyBox = self.m_textureSkyBox;
}
if ( state & RENDER_TEXTURE && self.m_colour[3] != current.m_colour[3] ) {
debug_colour( "setting alpha" );
glColor4f( 1,1,1,self.m_colour[3] );
@ -1932,6 +2015,11 @@ void OpenGLState_apply( const OpenGLState& self, OpenGLState& current, unsigned
void Renderables_flush( OpenGLStateBucket::Renderables& renderables, OpenGLState& current, unsigned int globalstate, const Vector3& viewer ){
const Matrix4* transform = 0;
glPushMatrix();
if ( current.m_program != 0 && current.m_textureSkyBox != 0 && globalstate & RENDER_PROGRAM ) {
current.m_program->setParameters( viewer, g_matrix4_identity, g_vector3_identity, g_vector3_identity, g_matrix4_identity );
}
for ( OpenGLStateBucket::Renderables::const_iterator i = renderables.begin(); i != renderables.end(); ++i )
{
//qglLoadMatrixf(i->m_transform);
@ -2421,6 +2509,19 @@ void OpenGLShader::construct( const char* name ){
bumpPass.m_blend_src = GL_ONE;
bumpPass.m_blend_dst = GL_ONE;
}
// g_ShaderCache->lightingSupported() as in GLSL is available
else if( m_shader->getSkyBox() != nullptr && m_shader->getSkyBox()->texture_number != 0 && g_ShaderCache->lightingSupported() )
{
state.m_texture = m_shader->getTexture()->texture_number;
state.m_textureSkyBox = m_shader->getSkyBox()->texture_number;
state.m_state = RENDER_FILL | RENDER_CULLFACE | RENDER_TEXTURE | RENDER_DEPTHTEST | RENDER_DEPTHWRITE | RENDER_COLOURWRITE | RENDER_PROGRAM;
state.m_colour.vec3() = m_shader->getTexture()->color;
state.m_colour[3] = 1.0f;
state.m_sort = OpenGLState::eSortFullbright;
state.m_program = &g_skyboxGLSL;
}
else
{
state.m_texture = m_shader->getTexture()->texture_number;
@ -2461,7 +2562,7 @@ void OpenGLShader::construct( const char* name ){
break;
}
}
reinterpret_cast<Vector3&>( state.m_colour ) = m_shader->getTexture()->color;
state.m_colour.vec3() = m_shader->getTexture()->color;
state.m_colour[3] = 1.0f;
if ( ( m_shader->getFlags() & QER_TRANS ) != 0 ) {

View File

@ -209,6 +209,7 @@ void R_ResampleTexture( const void *indata, int inwidth, int inheight, void *out
oldy = yi;
}
memcpy( out, row1, outwidth4 );
out += outwidth4;
}
}
}
@ -297,6 +298,7 @@ void R_ResampleTexture( const void *indata, int inwidth, int inheight, void *out
oldy = yi;
}
memcpy( out, row1, outwidth3 );
out += outwidth3;
}
}
}

View File

@ -33,6 +33,7 @@
#include "container/hashfunc.h"
#include "container/cache.h"
#include "generic/callback.h"
#include "stream/stringstream.h"
#include "stringio.h"
#include "image.h"
@ -334,6 +335,7 @@ typedef std::pair<LoadImageCallback, CopiedString> TextureKey;
void qtexture_realise( qtexture_t& texture, const TextureKey& key ){
texture.texture_number = 0;
if ( !string_empty( key.second.c_str() ) ) {
if( !key.first.m_skybox ){
Image* image = key.first.loadImage( key.second.c_str() );
if ( image != 0 ) {
LoadTextureRGBA( &texture, image->getRGBAPixels(), image->getWidth(), image->getHeight() );
@ -349,6 +351,53 @@ void qtexture_realise( qtexture_t& texture, const TextureKey& key ){
globalErrorStream() << "Texture load failed: \"" << key.second << "\"\n";
}
}
else {
Image *images[6]{};
/* load in order, so that Q3 cubemap is seamless in openGL, but rotated & flipped; fix misorientation in shader later */
const char *suffixes[] = { "_ft", "_bk", "_up", "_dn", "_rt", "_lf" };
for( int i = 0; i < 6; ++i ){
images[i] = key.first.loadImage( StringOutputStream( 64 )( key.second, suffixes[i] ) );
}
if( std::all_of( images, images + std::size( images ), []( const Image *img ){ return img != nullptr; } ) ){
glGenTextures( 1, &texture.texture_number );
glBindTexture( GL_TEXTURE_CUBE_MAP, texture.texture_number );
glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_FALSE );
glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0 );
glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, 0); //this or mipmaps are required for samplerCube to work
// fix non quadratic, varying sizes; GL_TEXTURE_CUBE_MAP requires this
unsigned int size = 0;
for( const auto img : images )
size = std::max( { size, img->getWidth(), img->getHeight() } );
for( int i = 0; i < 6; ++i ){
const Image& img = *images[i];
byte *pix = img.getRGBAPixels();
if( img.getWidth() != size || img.getHeight() != size ){
pix = static_cast<byte*>( malloc( size * size * 4 ) );
R_ResampleTexture( img.getRGBAPixels(), img.getWidth(), img.getHeight(), pix, size, size, 4 );
}
glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, g_texture_globals.texture_components, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, pix );
if( pix != img.getRGBAPixels() )
free( pix );
}
glBindTexture( GL_TEXTURE_CUBE_MAP, 0 );
globalOutputStream() << "Loaded Skybox: \"" << key.second << "\"\n";
GlobalOpenGL_debugAssertNoErrors();
}
else
{
globalErrorStream() << "Skybox load failed: \"" << key.second << "\"\n";
}
std::for_each_n( images, std::size( images ), []( Image *img ){ if( img != nullptr ) img->release(); } );
}
}
}
void qtexture_unrealise( qtexture_t& texture ){

View File

@ -1313,8 +1313,8 @@ void BackgroundImage::render( const VIEWTYPE viewtype ){
glDisable( GL_DEPTH_TEST );
glBindTexture( GL_TEXTURE_2D, _tex );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glBegin( GL_QUADS );

View File

@ -0,0 +1,8 @@
uniform samplerCube skybox;
void main()
{
//doing rotation/flip to fix skybox orientation
gl_FragColor = textureCube( skybox, vec3( -gl_TexCoord[0].y, gl_TexCoord[0].z, gl_TexCoord[0].x ) );
}

View File

@ -0,0 +1,8 @@
uniform vec3 u_view_origin;
void main()
{
gl_TexCoord[0] = vec4( ( gl_Vertex.xyz - u_view_origin ), 1 );
gl_Position = ftransform();
}